diff options
Diffstat (limited to 'src/os')
30 files changed, 19588 insertions, 0 deletions
diff --git a/src/os/darwin_usb.c b/src/os/darwin_usb.c new file mode 100644 index 0000000..903422c --- /dev/null +++ b/src/os/darwin_usb.c @@ -0,0 +1,2611 @@ +/* -*- Mode: C; indent-tabs-mode:nil -*- */ +/* + * darwin backend for libusb 1.0 + * Copyright © 2008-2021 Nathan Hjelm <hjelmn@cs.unm.edu> + * Copyright © 2019-2021 Google LLC. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> +#include <assert.h> +#include <time.h> +#include <ctype.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/sysctl.h> + +#include <mach/clock.h> +#include <mach/clock_types.h> +#include <mach/mach_host.h> +#include <mach/mach_port.h> + +/* Suppress warnings about the use of the deprecated objc_registerThreadWithCollector + * function. Its use is also conditionalized to only older deployment targets. */ +#define OBJC_SILENCE_GC_DEPRECATIONS 1 + +/* Default timeout to 10s for reenumerate. This is needed because USBDeviceReEnumerate + * does not return error status on macOS. */ +#define DARWIN_REENUMERATE_TIMEOUT_US 10000000 + +#include <AvailabilityMacros.h> +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200 + #include <objc/objc-auto.h> +#endif + +#include "darwin_usb.h" + +static int init_count = 0; + +/* Both kIOMasterPortDefault or kIOMainPortDefault are synonyms for 0. */ +static const mach_port_t darwin_default_master_port = 0; + +/* async event thread */ +static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER; + +#if !defined(HAVE_CLOCK_GETTIME) +static clock_serv_t clock_realtime; +static clock_serv_t clock_monotonic; +#endif + +#define LIBUSB_DARWIN_STARTUP_FAILURE ((CFRunLoopRef) -1) + +static CFRunLoopRef libusb_darwin_acfl = NULL; /* event cf loop */ +static CFRunLoopSourceRef libusb_darwin_acfls = NULL; /* shutdown signal for event cf loop */ + +static usbi_mutex_t darwin_cached_devices_lock = PTHREAD_MUTEX_INITIALIZER; +static struct list_head darwin_cached_devices; +static const char *darwin_device_class = "IOUSBDevice"; + +#define DARWIN_CACHED_DEVICE(a) (((struct darwin_device_priv *)usbi_get_device_priv((a)))->dev) + +/* async event thread */ +static pthread_t libusb_darwin_at; + +static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len); +static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface); +static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface); +static int darwin_reenumerate_device(struct libusb_device_handle *dev_handle, bool capture); +static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int darwin_reset_device(struct libusb_device_handle *dev_handle); +static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0); + +static enum libusb_error darwin_scan_devices(struct libusb_context *ctx); +static enum libusb_error process_new_device (struct libusb_context *ctx, struct darwin_cached_device *cached_device, + UInt64 old_session_id); + +static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out, + UInt64 *old_session_id); + +#if defined(ENABLE_LOGGING) +static const char *darwin_error_str (IOReturn result) { + static char string_buffer[50]; + switch (result) { + case kIOReturnSuccess: + return "no error"; + case kIOReturnNotOpen: + return "device not opened for exclusive access"; + case kIOReturnNoDevice: + return "no connection to an IOService"; + case kIOUSBNoAsyncPortErr: + return "no async port has been opened for interface"; + case kIOReturnExclusiveAccess: + return "another process has device opened for exclusive access"; + case kIOUSBPipeStalled: +#if defined(kUSBHostReturnPipeStalled) + case kUSBHostReturnPipeStalled: +#endif + return "pipe is stalled"; + case kIOReturnError: + return "could not establish a connection to the Darwin kernel"; + case kIOUSBTransactionTimeout: + return "transaction timed out"; + case kIOReturnBadArgument: + return "invalid argument"; + case kIOReturnAborted: + return "transaction aborted"; + case kIOReturnNotResponding: + return "device not responding"; + case kIOReturnOverrun: + return "data overrun"; + case kIOReturnCannotWire: + return "physical memory can not be wired down"; + case kIOReturnNoResources: + return "out of resources"; + case kIOUSBHighSpeedSplitError: + return "high speed split error"; + case kIOUSBUnknownPipeErr: + return "pipe ref not recognized"; + default: + snprintf(string_buffer, sizeof(string_buffer), "unknown error (0x%x)", result); + return string_buffer; + } +} +#endif + +static enum libusb_error darwin_to_libusb (IOReturn result) { + switch (result) { + case kIOReturnUnderrun: + case kIOReturnSuccess: + return LIBUSB_SUCCESS; + case kIOReturnNotOpen: + case kIOReturnNoDevice: + return LIBUSB_ERROR_NO_DEVICE; + case kIOReturnExclusiveAccess: + return LIBUSB_ERROR_ACCESS; + case kIOUSBPipeStalled: +#if defined(kUSBHostReturnPipeStalled) + case kUSBHostReturnPipeStalled: +#endif + return LIBUSB_ERROR_PIPE; + case kIOReturnBadArgument: + return LIBUSB_ERROR_INVALID_PARAM; + case kIOUSBTransactionTimeout: + return LIBUSB_ERROR_TIMEOUT; + case kIOUSBUnknownPipeErr: + return LIBUSB_ERROR_NOT_FOUND; + case kIOReturnNotResponding: + case kIOReturnAborted: + case kIOReturnError: + case kIOUSBNoAsyncPortErr: + default: + return LIBUSB_ERROR_OTHER; + } +} + +/* this function must be called with the darwin_cached_devices_lock held */ +static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) { + cached_dev->refcount--; + /* free the device and remove it from the cache */ + if (0 == cached_dev->refcount) { + list_del(&cached_dev->list); + + if (cached_dev->device) { + (*(cached_dev->device))->Release(cached_dev->device); + cached_dev->device = NULL; + } + IOObjectRelease (cached_dev->service); + free (cached_dev); + } +} + +static void darwin_ref_cached_device(struct darwin_cached_device *cached_dev) { + cached_dev->refcount++; +} + +static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, uint8_t *pipep, uint8_t *ifcp, struct darwin_interface **interface_out) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + + /* current interface */ + struct darwin_interface *cInterface; + + uint8_t i, iface; + + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + + usbi_dbg (ctx, "converting ep address 0x%02x to pipeRef and interface", ep); + + for (iface = 0 ; iface < USB_MAXINTERFACES ; iface++) { + cInterface = &priv->interfaces[iface]; + + if (dev_handle->claimed_interfaces & (1U << iface)) { + for (i = 0 ; i < cInterface->num_endpoints ; i++) { + if (cInterface->endpoint_addrs[i] == ep) { + *pipep = i + 1; + + if (ifcp) + *ifcp = iface; + + if (interface_out) + *interface_out = cInterface; + + usbi_dbg (ctx, "pipe %d on interface %d matches", *pipep, iface); + return LIBUSB_SUCCESS; + } + } + } + } + + /* No pipe found with the correct endpoint address */ + usbi_warn (HANDLE_CTX(dev_handle), "no pipeRef found with endpoint address 0x%02x.", ep); + + return LIBUSB_ERROR_NOT_FOUND; +} + +static IOReturn usb_setup_device_iterator (io_iterator_t *deviceIterator, UInt32 location) { + CFMutableDictionaryRef matchingDict = IOServiceMatching(darwin_device_class); + + if (!matchingDict) + return kIOReturnError; + + if (location) { + CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + /* there are no unsigned CFNumber types so treat the value as signed. the OS seems to do this + internally (CFNumberType of locationID is kCFNumberSInt32Type) */ + CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location); + + if (propertyMatchDict && locationCF) { + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + } + /* else we can still proceed as long as the caller accounts for the possibility of other devices in the iterator */ + + /* release our references as per the Create Rule */ + if (propertyMatchDict) + CFRelease (propertyMatchDict); + if (locationCF) + CFRelease (locationCF); + } + + return IOServiceGetMatchingServices(darwin_default_master_port, matchingDict, deviceIterator); +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_ioregistry_value_number (io_service_t service, CFStringRef property, CFNumberType type, void *p) { + CFTypeRef cfNumber = IORegistryEntryCreateCFProperty (service, property, kCFAllocatorDefault, 0); + Boolean success = 0; + + if (cfNumber) { + if (CFGetTypeID(cfNumber) == CFNumberGetTypeID()) { + success = CFNumberGetValue(cfNumber, type, p); + } + + CFRelease (cfNumber); + } + + return (success != 0); +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_ioregistry_value_data (io_service_t service, CFStringRef property, ssize_t size, void *p) { + CFTypeRef cfData = IORegistryEntryCreateCFProperty (service, property, kCFAllocatorDefault, 0); + bool success = false; + + if (cfData) { + if (CFGetTypeID (cfData) == CFDataGetTypeID ()) { + CFIndex length = CFDataGetLength (cfData); + if (length < size) { + size = length; + } + + CFDataGetBytes (cfData, CFRangeMake(0, size), p); + success = true; + } + + CFRelease (cfData); + } + + return success; +} + +static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io_service_t service) +{ + io_cf_plugin_ref_t *plugInInterface = NULL; + usb_device_t **device; + IOReturn kresult; + SInt32 score; + const int max_retries = 5; + + /* The IOCreatePlugInInterfaceForService function might consistently return + an "out of resources" error with certain USB devices the first time we run + it. The reason is still unclear, but retrying fixes the problem */ + for (int count = 0; count < max_retries; count++) { + kresult = IOCreatePlugInInterfaceForService(service, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &plugInInterface, + &score); + if (kIOReturnSuccess == kresult && plugInInterface) { + break; + } + + usbi_dbg (ctx, "set up plugin for service retry: %s", darwin_error_str (kresult)); + + /* sleep for a little while before trying again */ + nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 1000}, NULL); + } + + if (kIOReturnSuccess != kresult || !plugInInterface) { + usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult)); + return NULL; + } + + (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(DeviceInterfaceID), + (LPVOID)&device); + /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ + (*plugInInterface)->Release (plugInInterface); + + return device; +} + +static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { + UNUSED(ptr); + struct darwin_cached_device *cached_device; + UInt64 old_session_id; + struct libusb_context *ctx; + io_service_t service; + int ret; + + usbi_mutex_lock(&active_contexts_lock); + + while ((service = IOIteratorNext(add_devices))) { + ret = darwin_get_cached_device (NULL, service, &cached_device, &old_session_id); + if (ret < 0 || !cached_device->can_enumerate) { + continue; + } + + /* add this device to each active context's device list */ + for_each_context(ctx) { + process_new_device (ctx, cached_device, old_session_id); + } + + if (cached_device->in_reenumerate) { + usbi_dbg (NULL, "cached device in reset state. reset complete..."); + cached_device->in_reenumerate = false; + } + + IOObjectRelease(service); + } + + usbi_mutex_unlock(&active_contexts_lock); +} + +static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { + UNUSED(ptr); + struct libusb_device *dev = NULL; + struct libusb_context *ctx; + struct darwin_cached_device *old_device; + + io_service_t device; + UInt64 session, locationID; + int ret; + + usbi_mutex_lock(&active_contexts_lock); + + while ((device = IOIteratorNext (rem_devices)) != 0) { + bool is_reenumerating = false; + + /* get the location from the i/o registry */ + ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session); + (void) get_ioregistry_value_number (device, CFSTR("locationID"), kCFNumberSInt32Type, &locationID); + IOObjectRelease (device); + if (!ret) + continue; + + /* we need to match darwin_ref_cached_device call made in darwin_get_cached_device function + otherwise no cached device will ever get freed */ + usbi_mutex_lock(&darwin_cached_devices_lock); + list_for_each_entry(old_device, &darwin_cached_devices, list, struct darwin_cached_device) { + if (old_device->session == session) { + if (old_device->in_reenumerate) { + /* device is re-enumerating. do not dereference the device at this time. libusb_reset_device() + * will deref if needed. */ + usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 ", locationID: 0x%" PRIx64, + session, locationID); + + /* the device object is no longer usable so go ahead and release it */ + if (old_device->device) { + (*(old_device->device))->Release(old_device->device); + old_device->device = NULL; + } + + is_reenumerating = true; + } else { + darwin_deref_cached_device (old_device); + } + + break; + } + } + + usbi_mutex_unlock(&darwin_cached_devices_lock); + if (is_reenumerating) { + continue; + } + + for_each_context(ctx) { + usbi_dbg (ctx, "notifying context %p of device disconnect", ctx); + + dev = usbi_get_device_by_session_id(ctx, (unsigned long) session); + if (dev) { + /* signal the core that this device has been disconnected. the core will tear down this device + when the reference count reaches 0 */ + usbi_disconnect_device(dev); + libusb_unref_device(dev); + } + } + } + + usbi_mutex_unlock(&active_contexts_lock); +} + +static void darwin_hotplug_poll (void) +{ + /* not sure if 1 ms will be too long/short but it should work ok */ + mach_timespec_t timeout = {.tv_sec = 0, .tv_nsec = 1000000ul}; + + /* since a kernel thread may notify the IOIterators used for + * hotplug notification we can't just clear the iterators. + * instead just wait until all IOService providers are quiet */ + (void) IOKitWaitQuiet (darwin_default_master_port, &timeout); +} + +static void darwin_clear_iterator (io_iterator_t iter) { + io_service_t device; + + while ((device = IOIteratorNext (iter)) != 0) + IOObjectRelease (device); +} + +static void darwin_fail_startup(void) { + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfl = LIBUSB_DARWIN_STARTUP_FAILURE; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + pthread_exit (NULL); +} + +static void *darwin_event_thread_main (void *arg0) { + IOReturn kresult; + struct libusb_context *ctx = (struct libusb_context *)arg0; + CFRunLoopRef runloop; + CFRunLoopSourceRef libusb_shutdown_cfsource; + CFRunLoopSourceContext libusb_shutdown_cfsourcectx; + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + /* Set this thread's name, so it can be seen in the debugger + and crash reports. */ + pthread_setname_np ("org.libusb.device-hotplug"); +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200 + /* Tell the Objective-C garbage collector about this thread. + This is required because, unlike NSThreads, pthreads are + not automatically registered. Although we don't use + Objective-C, we use CoreFoundation, which does. + Garbage collection support was entirely removed in 10.12, + so don't bother there. */ + objc_registerThreadWithCollector(); +#endif + + /* hotplug (device arrival/removal) sources */ + CFRunLoopSourceRef libusb_notification_cfsource; + io_notification_port_t libusb_notification_port; + io_iterator_t libusb_rem_device_iterator; + io_iterator_t libusb_add_device_iterator; + + usbi_dbg (ctx, "creating hotplug event source"); + + runloop = CFRunLoopGetCurrent (); + CFRetain (runloop); + + /* add the shutdown cfsource to the run loop */ + memset(&libusb_shutdown_cfsourcectx, 0, sizeof(libusb_shutdown_cfsourcectx)); + libusb_shutdown_cfsourcectx.info = runloop; + libusb_shutdown_cfsourcectx.perform = (void (*)(void *))CFRunLoopStop; + libusb_shutdown_cfsource = CFRunLoopSourceCreate(NULL, 0, &libusb_shutdown_cfsourcectx); + CFRunLoopAddSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode); + + /* add the notification port to the run loop */ + libusb_notification_port = IONotificationPortCreate (darwin_default_master_port); + libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port); + CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); + + /* create notifications for removed devices */ + kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification, + IOServiceMatching(darwin_device_class), + darwin_devices_detached, + ctx, &libusb_rem_device_iterator); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); + CFRelease (libusb_shutdown_cfsource); + CFRelease (runloop); + darwin_fail_startup (); + } + + /* create notifications for attached devices */ + kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification, + IOServiceMatching(darwin_device_class), + darwin_devices_attached, + ctx, &libusb_add_device_iterator); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); + CFRelease (libusb_shutdown_cfsource); + CFRelease (runloop); + darwin_fail_startup (); + } + + /* arm notifiers */ + darwin_clear_iterator (libusb_rem_device_iterator); + darwin_clear_iterator (libusb_add_device_iterator); + + usbi_dbg (ctx, "darwin event thread ready to receive events"); + + /* signal the main thread that the hotplug runloop has been created. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfl = runloop; + libusb_darwin_acfls = libusb_shutdown_cfsource; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + /* run the runloop */ + CFRunLoopRun(); + + usbi_dbg (ctx, "darwin event thread exiting"); + + /* signal the main thread that the hotplug runloop has finished. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfls = NULL; + libusb_darwin_acfl = NULL; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + /* remove the notification cfsource */ + CFRunLoopRemoveSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); + + /* remove the shutdown cfsource */ + CFRunLoopRemoveSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode); + + /* delete notification port */ + IONotificationPortDestroy (libusb_notification_port); + + /* delete iterators */ + IOObjectRelease (libusb_rem_device_iterator); + IOObjectRelease (libusb_add_device_iterator); + + CFRelease (libusb_shutdown_cfsource); + CFRelease (runloop); + + pthread_exit (NULL); +} + +/* cleanup function to destroy cached devices */ +static void darwin_cleanup_devices(void) { + struct darwin_cached_device *dev, *next; + + list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) { + darwin_deref_cached_device(dev); + } +} + +static int darwin_init(struct libusb_context *ctx) { + bool first_init; + int rc; + + first_init = (1 == ++init_count); + + do { + if (first_init) { + if (NULL == darwin_cached_devices.next) { + list_init (&darwin_cached_devices); + } + assert(list_empty(&darwin_cached_devices)); +#if !defined(HAVE_CLOCK_GETTIME) + /* create the clocks that will be used if clock_gettime() is not available */ + host_name_port_t host_self; + + host_self = mach_host_self(); + host_get_clock_service(host_self, CALENDAR_CLOCK, &clock_realtime); + host_get_clock_service(host_self, SYSTEM_CLOCK, &clock_monotonic); + mach_port_deallocate(mach_task_self(), host_self); +#endif + } + + rc = darwin_scan_devices (ctx); + if (LIBUSB_SUCCESS != rc) + break; + + if (first_init) { + rc = pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, ctx); + if (0 != rc) { + usbi_err (ctx, "could not create event thread, error %d", rc); + rc = LIBUSB_ERROR_OTHER; + break; + } + + pthread_mutex_lock (&libusb_darwin_at_mutex); + while (!libusb_darwin_acfl) + pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + if (libusb_darwin_acfl == LIBUSB_DARWIN_STARTUP_FAILURE) { + libusb_darwin_acfl = NULL; + rc = LIBUSB_ERROR_OTHER; + } + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + if (0 != rc) + pthread_join (libusb_darwin_at, NULL); + } + } while (0); + + if (LIBUSB_SUCCESS != rc) { + if (first_init) { + darwin_cleanup_devices (); +#if !defined(HAVE_CLOCK_GETTIME) + mach_port_deallocate(mach_task_self(), clock_realtime); + mach_port_deallocate(mach_task_self(), clock_monotonic); +#endif + } + --init_count; + } + + return rc; +} + +static void darwin_exit (struct libusb_context *ctx) { + UNUSED(ctx); + + if (0 == --init_count) { + /* stop the event runloop and wait for the thread to terminate. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + CFRunLoopSourceSignal (libusb_darwin_acfls); + CFRunLoopWakeUp (libusb_darwin_acfl); + while (libusb_darwin_acfl) + pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + pthread_join (libusb_darwin_at, NULL); + + darwin_cleanup_devices (); + +#if !defined(HAVE_CLOCK_GETTIME) + mach_port_deallocate(mach_task_self(), clock_realtime); + mach_port_deallocate(mach_task_self(), clock_monotonic); +#endif + } +} + +static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) { + struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev); + UInt8 i, numConfig; + IOUSBConfigurationDescriptorPtr desc; + IOReturn kresult; + + /* is there a simpler way to determine the index? */ + kresult = (*(priv->device))->GetNumberOfConfigurations (priv->device, &numConfig); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + for (i = 0 ; i < numConfig ; i++) { + (*(priv->device))->GetConfigurationDescriptorPtr (priv->device, i, &desc); + + if (desc->bConfigurationValue == config_value) + return i; + } + + /* configuration not found */ + return LIBUSB_ERROR_NOT_FOUND; +} + +static int darwin_get_active_config_descriptor(struct libusb_device *dev, void *buffer, size_t len) { + struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev); + int config_index; + + if (0 == priv->active_config) + return LIBUSB_ERROR_NOT_FOUND; + + config_index = get_configuration_index (dev, priv->active_config); + if (config_index < 0) + return config_index; + + assert(config_index >= 0 && config_index <= UINT8_MAX); + return darwin_get_config_descriptor (dev, (UInt8)config_index, buffer, len); +} + +static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len) { + struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev); + IOUSBConfigurationDescriptorPtr desc; + IOReturn kresult; + int ret; + + if (!priv || !priv->device) + return LIBUSB_ERROR_OTHER; + + kresult = (*priv->device)->GetConfigurationDescriptorPtr (priv->device, config_index, &desc); + if (kresult == kIOReturnSuccess) { + /* copy descriptor */ + if (libusb_le16_to_cpu(desc->wTotalLength) < len) + len = libusb_le16_to_cpu(desc->wTotalLength); + + memmove (buffer, desc, len); + } + + ret = darwin_to_libusb (kresult); + if (ret != LIBUSB_SUCCESS) + return ret; + + return (int) len; +} + +/* check whether the os has configured the device */ +static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, struct darwin_cached_device *dev) { + usb_device_t **darwin_device = dev->device; + + IOUSBConfigurationDescriptorPtr configDesc; + IOUSBFindInterfaceRequest request; + IOReturn kresult; + io_iterator_t interface_iterator; + io_service_t firstInterface; + + if (dev->dev_descriptor.bNumConfigurations < 1) { + usbi_err (ctx, "device has no configurations"); + return LIBUSB_ERROR_OTHER; /* no configurations at this speed so we can't use it */ + } + + /* checking the configuration of a root hub simulation takes ~1 s in 10.11. the device is + not usable anyway */ + if (0x05ac == libusb_le16_to_cpu (dev->dev_descriptor.idVendor) && + 0x8005 == libusb_le16_to_cpu (dev->dev_descriptor.idProduct)) { + usbi_dbg (ctx, "ignoring configuration on root hub simulation"); + dev->active_config = 0; + return LIBUSB_SUCCESS; + } + + /* find the first configuration */ + kresult = (*darwin_device)->GetConfigurationDescriptorPtr (darwin_device, 0, &configDesc); + dev->first_config = (kIOReturnSuccess == kresult) ? configDesc->bConfigurationValue : 1; + + /* check if the device is already configured. there is probably a better way than iterating over the + to accomplish this (the trick is we need to avoid a call to GetConfigurations since buggy devices + might lock up on the device request) */ + + /* Setup the Interface Request */ + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* iterate once */ + firstInterface = IOIteratorNext(interface_iterator); + + /* done with the interface iterator */ + IOObjectRelease(interface_iterator); + + if (firstInterface) { + IOObjectRelease (firstInterface); + + /* device is configured */ + if (dev->dev_descriptor.bNumConfigurations == 1) + /* to avoid problems with some devices get the configurations value from the configuration descriptor */ + dev->active_config = dev->first_config; + else + /* devices with more than one configuration should work with GetConfiguration */ + (*darwin_device)->GetConfiguration (darwin_device, &dev->active_config); + } else + /* not configured */ + dev->active_config = 0; + + usbi_dbg (ctx, "active config: %u, first config: %u", dev->active_config, dev->first_config); + + return LIBUSB_SUCCESS; +} + +static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) { + IOUSBDevRequestTO req; + + assert(buffer_size <= UINT16_MAX); + + memset (buffer, 0, buffer_size); + + /* Set up request for descriptor/ */ + req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); + req.bRequest = kUSBRqGetDescriptor; + req.wValue = (UInt16)(desc << 8); + req.wIndex = desc_index; + req.wLength = (UInt16)buffer_size; + req.pData = buffer; + req.noDataTimeout = 20; + req.completionTimeout = 100; + + return (*device)->DeviceRequestTO (device, &req); +} + +static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *ctx, struct darwin_cached_device *dev) { + usb_device_t **device = dev->device; + int retries = 1; + long delay = 30000; // microseconds + int unsuspended = 0, try_unsuspend = 1, try_reconfigure = 1; + int is_open = 0; + IOReturn ret = 0, ret2; + UInt8 bDeviceClass; + UInt16 idProduct, idVendor; + + dev->can_enumerate = 0; + + (*device)->GetDeviceClass (device, &bDeviceClass); + (*device)->GetDeviceProduct (device, &idProduct); + (*device)->GetDeviceVendor (device, &idVendor); + + /* According to Apple's documentation the device must be open for DeviceRequest but we may not be able to open some + * devices and Apple's USB Prober doesn't bother to open the device before issuing a descriptor request. Still, + * to follow the spec as closely as possible, try opening the device */ + is_open = ((*device)->USBDeviceOpenSeize(device) == kIOReturnSuccess); + + do { + /**** retrieve device descriptor ****/ + ret = darwin_request_descriptor (device, kUSBDeviceDesc, 0, &dev->dev_descriptor, sizeof(dev->dev_descriptor)); + + if (kIOReturnOverrun == ret && kUSBDeviceDesc == dev->dev_descriptor.bDescriptorType) + /* received an overrun error but we still received a device descriptor */ + ret = kIOReturnSuccess; + + if (kIOUSBVendorIDAppleComputer == idVendor) { + /* NTH: don't bother retrying or unsuspending Apple devices */ + break; + } + + if (kIOReturnSuccess == ret && (0 == dev->dev_descriptor.bNumConfigurations || + 0 == dev->dev_descriptor.bcdUSB)) { + /* work around for incorrectly configured devices */ + if (try_reconfigure && is_open) { + usbi_dbg(ctx, "descriptor appears to be invalid. resetting configuration before trying again..."); + + /* set the first configuration */ + (*device)->SetConfiguration(device, 1); + + /* don't try to reconfigure again */ + try_reconfigure = 0; + } + + ret = kIOUSBPipeStalled; + } + + if (kIOReturnSuccess != ret && is_open && try_unsuspend) { + /* device may be suspended. unsuspend it and try again */ +#if DeviceVersion >= 320 + UInt32 info = 0; + + /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */ + (void)(*device)->GetUSBDeviceInformation (device, &info); + + /* note that the device was suspended */ + if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info) + try_unsuspend = 1; +#endif + + if (try_unsuspend) { + /* try to unsuspend the device */ + ret2 = (*device)->USBDeviceSuspend (device, 0); + if (kIOReturnSuccess != ret2) { + /* prevent log spew from poorly behaving devices. this indicates the + os actually had trouble communicating with the device */ + usbi_dbg(ctx, "could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); + } else + unsuspended = 1; + + try_unsuspend = 0; + } + } + + if (kIOReturnSuccess != ret) { + usbi_dbg(ctx, "kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000); + /* sleep for a little while before trying again */ + nanosleep(&(struct timespec){delay / 1000000, (delay * 1000) % 1000000000}, NULL); + } + } while (kIOReturnSuccess != ret && retries--); + + if (unsuspended) + /* resuspend the device */ + (void)(*device)->USBDeviceSuspend (device, 1); + + if (is_open) + (void) (*device)->USBDeviceClose (device); + + if (ret != kIOReturnSuccess) { + /* a debug message was already printed out for this error */ + if (LIBUSB_CLASS_HUB == bDeviceClass) + usbi_dbg (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + idVendor, idProduct, darwin_error_str (ret), ret); + else + usbi_warn (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + idVendor, idProduct, darwin_error_str (ret), ret); + return darwin_to_libusb (ret); + } + + /* catch buggy hubs (which appear to be virtual). Apple's own USB prober has problems with these devices. */ + if (libusb_le16_to_cpu (dev->dev_descriptor.idProduct) != idProduct) { + /* not a valid device */ + usbi_warn (NULL, "idProduct from iokit (%04x) does not match idProduct in descriptor (%04x). skipping device", + idProduct, libusb_le16_to_cpu (dev->dev_descriptor.idProduct)); + return LIBUSB_ERROR_NO_DEVICE; + } + + usbi_dbg (ctx, "cached device descriptor:"); + usbi_dbg (ctx, " bDescriptorType: 0x%02x", dev->dev_descriptor.bDescriptorType); + usbi_dbg (ctx, " bcdUSB: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB)); + usbi_dbg (ctx, " bDeviceClass: 0x%02x", dev->dev_descriptor.bDeviceClass); + usbi_dbg (ctx, " bDeviceSubClass: 0x%02x", dev->dev_descriptor.bDeviceSubClass); + usbi_dbg (ctx, " bDeviceProtocol: 0x%02x", dev->dev_descriptor.bDeviceProtocol); + usbi_dbg (ctx, " bMaxPacketSize0: 0x%02x", dev->dev_descriptor.bMaxPacketSize0); + usbi_dbg (ctx, " idVendor: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor)); + usbi_dbg (ctx, " idProduct: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct)); + usbi_dbg (ctx, " bcdDevice: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice)); + usbi_dbg (ctx, " iManufacturer: 0x%02x", dev->dev_descriptor.iManufacturer); + usbi_dbg (ctx, " iProduct: 0x%02x", dev->dev_descriptor.iProduct); + usbi_dbg (ctx, " iSerialNumber: 0x%02x", dev->dev_descriptor.iSerialNumber); + usbi_dbg (ctx, " bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations); + + dev->can_enumerate = 1; + + return LIBUSB_SUCCESS; +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_device_port (io_service_t service, UInt8 *port) { + IOReturn kresult; + io_service_t parent; + bool ret = false; + + if (get_ioregistry_value_number (service, CFSTR("PortNum"), kCFNumberSInt8Type, port)) { + return true; + } + + kresult = IORegistryEntryGetParentEntry (service, kIOServicePlane, &parent); + if (kIOReturnSuccess == kresult) { + ret = get_ioregistry_value_data (parent, CFSTR("port"), 1, port); + IOObjectRelease (parent); + } + + return ret; +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_device_parent_sessionID(io_service_t service, UInt64 *parent_sessionID) { + IOReturn kresult; + io_service_t parent; + + /* Walk up the tree in the IOService plane until we find a parent that has a sessionID */ + parent = service; + while((kresult = IORegistryEntryGetParentEntry (parent, kIOUSBPlane, &parent)) == kIOReturnSuccess) { + if (get_ioregistry_value_number (parent, CFSTR("sessionID"), kCFNumberSInt64Type, parent_sessionID)) { + /* Success */ + return true; + } + } + + /* We ran out of parents */ + return false; +} + +static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out, + UInt64 *old_session_id) { + struct darwin_cached_device *new_device; + UInt64 sessionID = 0, parent_sessionID = 0; + UInt32 locationID = 0; + enum libusb_error ret = LIBUSB_SUCCESS; + usb_device_t **device; + UInt8 port = 0; + + /* assuming sessionID != 0 normally (never seen it be 0) */ + *old_session_id = 0; + *cached_out = NULL; + + /* get some info from the io registry */ + (void) get_ioregistry_value_number (service, CFSTR("sessionID"), kCFNumberSInt64Type, &sessionID); + (void) get_ioregistry_value_number (service, CFSTR("locationID"), kCFNumberSInt32Type, &locationID); + if (!get_device_port (service, &port)) { + usbi_dbg(ctx, "could not get connected port number"); + } + + usbi_dbg(ctx, "finding cached device for sessionID 0x%" PRIx64, sessionID); + + if (get_device_parent_sessionID(service, &parent_sessionID)) { + usbi_dbg(ctx, "parent sessionID: 0x%" PRIx64, parent_sessionID); + } + + usbi_mutex_lock(&darwin_cached_devices_lock); + do { + list_for_each_entry(new_device, &darwin_cached_devices, list, struct darwin_cached_device) { + usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x", + sessionID, locationID, new_device->session, new_device->location); + if (new_device->location == locationID && new_device->in_reenumerate) { + usbi_dbg (ctx, "found cached device with matching location that is being re-enumerated"); + *old_session_id = new_device->session; + break; + } + + if (new_device->session == sessionID) { + usbi_dbg(ctx, "using cached device for device"); + *cached_out = new_device; + break; + } + } + + if (*cached_out) + break; + + usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID); + + device = darwin_device_from_service (ctx, service); + if (!device) { + ret = LIBUSB_ERROR_NO_DEVICE; + break; + } + + if (!(*old_session_id)) { + new_device = calloc (1, sizeof (*new_device)); + if (!new_device) { + ret = LIBUSB_ERROR_NO_MEM; + break; + } + + /* add this device to the cached device list */ + list_add(&new_device->list, &darwin_cached_devices); + + (*device)->GetDeviceAddress (device, (USBDeviceAddress *)&new_device->address); + + /* keep a reference to this device */ + darwin_ref_cached_device(new_device); + + (*device)->GetLocationID (device, &new_device->location); + new_device->port = port; + new_device->parent_session = parent_sessionID; + } else { + /* release the ref to old device's service */ + IOObjectRelease (new_device->service); + } + + /* keep track of devices regardless of if we successfully enumerate them to + prevent them from being enumerated multiple times */ + *cached_out = new_device; + + new_device->session = sessionID; + new_device->device = device; + new_device->service = service; + + /* retain the service */ + IOObjectRetain (service); + + /* cache the device descriptor */ + ret = darwin_cache_device_descriptor(ctx, new_device); + if (ret) + break; + + if (new_device->can_enumerate) { + snprintf(new_device->sys_path, 20, "%03i-%04x-%04x-%02x-%02x", new_device->address, + libusb_le16_to_cpu (new_device->dev_descriptor.idVendor), + libusb_le16_to_cpu (new_device->dev_descriptor.idProduct), + new_device->dev_descriptor.bDeviceClass, new_device->dev_descriptor.bDeviceSubClass); + } + } while (0); + + usbi_mutex_unlock(&darwin_cached_devices_lock); + + return ret; +} + +static enum libusb_error process_new_device (struct libusb_context *ctx, struct darwin_cached_device *cached_device, + UInt64 old_session_id) { + struct darwin_device_priv *priv; + struct libusb_device *dev = NULL; + UInt8 devSpeed; + enum libusb_error ret = LIBUSB_SUCCESS; + + do { + /* check current active configuration (and cache the first configuration value-- + which may be used by claim_interface) */ + ret = darwin_check_configuration (ctx, cached_device); + if (ret) + break; + + if (0 != old_session_id) { + usbi_dbg (ctx, "re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64, + ctx, old_session_id, cached_device->session); + /* save the libusb device before the session id is updated */ + dev = usbi_get_device_by_session_id (ctx, (unsigned long) old_session_id); + } + + if (!dev) { + usbi_dbg (ctx, "allocating new device in context %p for with session 0x%" PRIx64, + ctx, cached_device->session); + + dev = usbi_alloc_device(ctx, (unsigned long) cached_device->session); + if (!dev) { + return LIBUSB_ERROR_NO_MEM; + } + + priv = usbi_get_device_priv(dev); + + priv->dev = cached_device; + darwin_ref_cached_device (priv->dev); + dev->port_number = cached_device->port; + /* the location ID encodes the path to the device. the top byte of the location ID contains the bus number + (numbered from 0). the remaining bytes can be used to construct the device tree for that bus. */ + dev->bus_number = cached_device->location >> 24; + assert(cached_device->address <= UINT8_MAX); + dev->device_address = (uint8_t)cached_device->address; + } else { + priv = usbi_get_device_priv(dev); + } + + static_assert(sizeof(dev->device_descriptor) == sizeof(cached_device->dev_descriptor), + "mismatch between libusb and IOKit device descriptor sizes"); + memcpy(&dev->device_descriptor, &cached_device->dev_descriptor, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + dev->session_data = cached_device->session; + + if (NULL != dev->parent_dev) { + libusb_unref_device(dev->parent_dev); + dev->parent_dev = NULL; + } + + if (cached_device->parent_session > 0) { + dev->parent_dev = usbi_get_device_by_session_id (ctx, (unsigned long) cached_device->parent_session); + } + + (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed); + + switch (devSpeed) { + case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break; + case kUSBDeviceSpeedFull: dev->speed = LIBUSB_SPEED_FULL; break; + case kUSBDeviceSpeedHigh: dev->speed = LIBUSB_SPEED_HIGH; break; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 + case kUSBDeviceSpeedSuper: dev->speed = LIBUSB_SPEED_SUPER; break; +#endif +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 + case kUSBDeviceSpeedSuperPlus: dev->speed = LIBUSB_SPEED_SUPER_PLUS; break; +#endif + default: + usbi_warn (ctx, "Got unknown device speed %d", devSpeed); + } + + ret = usbi_sanitize_device (dev); + if (ret < 0) + break; + + usbi_dbg (ctx, "found device with address %d port = %d parent = %p at %p", dev->device_address, + dev->port_number, (void *) dev->parent_dev, priv->dev->sys_path); + + } while (0); + + if (!cached_device->in_reenumerate && 0 == ret) { + usbi_connect_device (dev); + } else { + libusb_unref_device (dev); + } + + return ret; +} + +static enum libusb_error darwin_scan_devices(struct libusb_context *ctx) { + struct darwin_cached_device *cached_device; + UInt64 old_session_id; + io_iterator_t deviceIterator; + io_service_t service; + IOReturn kresult; + int ret; + + kresult = usb_setup_device_iterator (&deviceIterator, 0); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + while ((service = IOIteratorNext (deviceIterator))) { + ret = darwin_get_cached_device (ctx, service, &cached_device, &old_session_id); + if (ret < 0 || !cached_device->can_enumerate) { + continue; + } + + (void) process_new_device (ctx, cached_device, old_session_id); + + IOObjectRelease(service); + } + + IOObjectRelease(deviceIterator); + + return LIBUSB_SUCCESS; +} + +static int darwin_open (struct libusb_device_handle *dev_handle) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + + if (0 == dpriv->open_count) { + /* try to open the device */ + kresult = (*(dpriv->device))->USBDeviceOpenSeize (dpriv->device); + if (kresult != kIOReturnSuccess) { + usbi_warn (HANDLE_CTX (dev_handle), "USBDeviceOpen: %s", darwin_error_str(kresult)); + + if (kIOReturnExclusiveAccess != kresult) { + return darwin_to_libusb (kresult); + } + + /* it is possible to perform some actions on a device that is not open so do not return an error */ + priv->is_open = false; + } else { + priv->is_open = true; + } + + /* create async event source */ + kresult = (*(dpriv->device))->CreateDeviceAsyncEventSource (dpriv->device, &priv->cfSource); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "CreateDeviceAsyncEventSource: %s", darwin_error_str(kresult)); + + if (priv->is_open) { + (*(dpriv->device))->USBDeviceClose (dpriv->device); + } + + priv->is_open = false; + + return darwin_to_libusb (kresult); + } + + CFRetain (libusb_darwin_acfl); + + /* add the cfSource to the async run loop */ + CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes); + } + + /* device opened successfully */ + dpriv->open_count++; + + usbi_dbg (HANDLE_CTX(dev_handle), "device open for access"); + + return 0; +} + +static void darwin_close (struct libusb_device_handle *dev_handle) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + int i; + + if (dpriv->open_count == 0) { + /* something is probably very wrong if this is the case */ + usbi_err (HANDLE_CTX (dev_handle), "Close called on a device that was not open!"); + return; + } + + dpriv->open_count--; + if (NULL == dpriv->device) { + usbi_warn (HANDLE_CTX (dev_handle), "darwin_close device missing IOService"); + return; + } + + /* make sure all interfaces are released */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1U << i)) + libusb_release_interface (dev_handle, i); + + if (0 == dpriv->open_count) { + /* delete the device's async event source */ + if (priv->cfSource) { + CFRunLoopRemoveSource (libusb_darwin_acfl, priv->cfSource, kCFRunLoopDefaultMode); + CFRelease (priv->cfSource); + priv->cfSource = NULL; + CFRelease (libusb_darwin_acfl); + } + + if (priv->is_open) { + /* close the device */ + kresult = (*(dpriv->device))->USBDeviceClose(dpriv->device); + if (kresult != kIOReturnSuccess) { + /* Log the fact that we had a problem closing the file, however failing a + * close isn't really an error, so return success anyway */ + usbi_warn (HANDLE_CTX (dev_handle), "USBDeviceClose: %s", darwin_error_str(kresult)); + } + } + } +} + +static int darwin_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + *config = dpriv->active_config; + + return LIBUSB_SUCCESS; +} + +static enum libusb_error darwin_set_configuration(struct libusb_device_handle *dev_handle, int config) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + uint8_t i; + + if (config == -1) + config = 0; + + /* Setting configuration will invalidate the interface, so we need + to reclaim it. First, dispose of existing interfaces, if any. */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1U << i)) + darwin_release_interface (dev_handle, i); + + kresult = (*(dpriv->device))->SetConfiguration (dpriv->device, (UInt8)config); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* Reclaim any interfaces. */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1U << i)) + darwin_claim_interface (dev_handle, i); + + dpriv->active_config = (UInt8)config; + + return LIBUSB_SUCCESS; +} + +static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_service_t *usbInterfacep) { + IOUSBFindInterfaceRequest request; + IOReturn kresult; + io_iterator_t interface_iterator; + UInt8 bInterfaceNumber; + bool ret; + + *usbInterfacep = IO_OBJECT_NULL; + + /* Setup the Interface Request */ + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + if (kresult != kIOReturnSuccess) + return kresult; + + while ((*usbInterfacep = IOIteratorNext(interface_iterator))) { + /* find the interface number */ + ret = get_ioregistry_value_number (*usbInterfacep, CFSTR("bInterfaceNumber"), kCFNumberSInt8Type, + &bInterfaceNumber); + + if (ret && bInterfaceNumber == ifc) { + break; + } + + (void) IOObjectRelease (*usbInterfacep); + } + + /* done with the interface iterator */ + IOObjectRelease(interface_iterator); + + return kIOReturnSuccess; +} + +static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, uint8_t iface) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + IOReturn kresult; + + UInt8 numep, direction, number; + UInt8 dont_care1, dont_care3; + UInt16 dont_care2; + int rc; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + + usbi_dbg (ctx, "building table of endpoints."); + + /* retrieve the total number of endpoints on this interface */ + kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* iterate through pipe references */ + for (UInt8 i = 1 ; i <= numep ; i++) { + kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1, + &dont_care2, &dont_care3); + + if (kresult != kIOReturnSuccess) { + /* probably a buggy device. try to get the endpoint address from the descriptors */ + struct libusb_config_descriptor *config; + const struct libusb_endpoint_descriptor *endpoint_desc; + UInt8 alt_setting; + + kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &alt_setting); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "can't get alternate setting for interface"); + return darwin_to_libusb (kresult); + } + + rc = libusb_get_active_config_descriptor (dev_handle->dev, &config); + if (LIBUSB_SUCCESS != rc) { + return rc; + } + + endpoint_desc = config->interface[iface].altsetting[alt_setting].endpoint + i - 1; + + cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress; + } else { + cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK)); + } + + usbi_dbg (ctx, "interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift, + cInterface->endpoint_addrs[i - 1] & LIBUSB_ENDPOINT_ADDRESS_MASK); + } + + cInterface->num_endpoints = numep; + + return LIBUSB_SUCCESS; +} + +static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + io_service_t usbInterface = IO_OBJECT_NULL; + IOReturn kresult; + enum libusb_error ret; + IOCFPlugInInterface **plugInInterface = NULL; + SInt32 score; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* make sure we have an interface */ + if (!usbInterface && dpriv->first_config != 0) { + usbi_info (ctx, "no interface found; setting configuration: %d", dpriv->first_config); + + /* set the configuration */ + ret = darwin_set_configuration (dev_handle, (int) dpriv->first_config); + if (ret != LIBUSB_SUCCESS) { + usbi_err (ctx, "could not set configuration"); + return ret; + } + + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "darwin_get_interface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + } + + if (!usbInterface) { + usbi_info (ctx, "interface not found"); + return LIBUSB_ERROR_NOT_FOUND; + } + + /* get an interface to the device's interface */ + kresult = IOCreatePlugInInterfaceForService (usbInterface, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &plugInInterface, &score); + + /* ignore release error */ + (void)IOObjectRelease (usbInterface); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + if (!plugInInterface) { + usbi_err (ctx, "plugin interface not found"); + return LIBUSB_ERROR_NOT_FOUND; + } + + /* Do the actual claim */ + kresult = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(InterfaceInterfaceID), + (LPVOID)&cInterface->interface); + /* We no longer need the intermediate plug-in */ + /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ + (*plugInInterface)->Release (plugInInterface); + if (kresult != kIOReturnSuccess || !cInterface->interface) { + usbi_err (ctx, "QueryInterface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* claim the interface */ + kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface); + if (kresult != kIOReturnSuccess) { + usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* update list of endpoints */ + ret = get_endpoints (dev_handle, iface); + if (ret) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (ctx, "could not build endpoint table"); + return ret; + } + + cInterface->cfSource = NULL; + + /* create async event source */ + kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not create async event source"); + + /* can't continue without an async event source */ + (void)darwin_release_interface (dev_handle, iface); + + return darwin_to_libusb (kresult); + } + + /* add the cfSource to the async thread's run loop */ + CFRunLoopAddSource(libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); + + usbi_dbg (ctx, "interface opened"); + + return LIBUSB_SUCCESS; +} + +static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + IOReturn kresult; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + /* Check to see if an interface is open */ + if (!cInterface->interface) + return LIBUSB_SUCCESS; + + /* clean up endpoint data */ + cInterface->num_endpoints = 0; + + /* delete the interface's async event source */ + if (cInterface->cfSource) { + CFRunLoopRemoveSource (libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); + CFRelease (cInterface->cfSource); + cInterface->cfSource = NULL; + } + + kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface); + if (kresult != kIOReturnSuccess) + usbi_warn (HANDLE_CTX (dev_handle), "USBInterfaceClose: %s", darwin_error_str(kresult)); + + kresult = (*(cInterface->interface))->Release(cInterface->interface); + if (kresult != kIOReturnSuccess) + usbi_warn (HANDLE_CTX (dev_handle), "Release: %s", darwin_error_str(kresult)); + + cInterface->interface = (usb_interface_t **) IO_OBJECT_NULL; + + return darwin_to_libusb (kresult); +} + +static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_handle, uint8_t altsetting, struct darwin_interface *cInterface) { + enum libusb_error ret; + IOReturn kresult; + uint8_t current_alt_setting; + + kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, ¤t_alt_setting); + if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) { + return LIBUSB_ERROR_PIPE; + } + + for (int i = 0 ; i < cInterface->num_endpoints ; i++) { + ret = darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]); + if (LIBUSB_SUCCESS != ret) { + usbi_warn(HANDLE_CTX (dev_handle), "error clearing pipe halt for endpoint %d", i); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* may need to re-open the interface */ + return ret; + } + } + } + + return LIBUSB_SUCCESS; +} + +static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + IOReturn kresult; + enum libusb_error ret; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + if (!cInterface->interface) + return LIBUSB_ERROR_NO_DEVICE; + + kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting); + if (kresult == kIOReturnSuccess) { + /* update the list of endpoints */ + ret = get_endpoints (dev_handle, iface); + if (ret) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + } + return ret; + } + + usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult)); + + ret = darwin_to_libusb(kresult); + if (ret != LIBUSB_ERROR_PIPE) { + return ret; + } + + /* If a device only supports a default setting for the specified interface, then a STALL + (kIOUSBPipeStalled) may be returned. Ref: USB 2.0 specs 9.4.10. + Mimic the behaviour in e.g. the Linux kernel: in such case, reset all endpoints + of the interface (as would have been done per 9.1.1.5) and return success. */ + + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* For some reason we need to reclaim the interface after the pipe error with some versions of macOS */ + ret = darwin_claim_interface (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface: %s", darwin_error_str(kresult)); + } + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + } + + return ret; +} + +static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) { + /* current interface */ + struct darwin_interface *cInterface; + IOReturn kresult; + uint8_t pipeRef; + + /* determine the interface/endpoint to use */ + if (ep_to_pipeRef (dev_handle, endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (HANDLE_CTX (dev_handle), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + /* newer versions of darwin support clearing additional bits on the device's endpoint */ + kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); + if (kresult != kIOReturnSuccess) + usbi_warn (HANDLE_CTX (dev_handle), "ClearPipeStall: %s", darwin_error_str (kresult)); + + return darwin_to_libusb (kresult); +} + +static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t active_config, + unsigned long claimed_interfaces) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + int open_count = dpriv->open_count; + int ret; + + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + /* clear claimed interfaces temporarily */ + dev_handle->claimed_interfaces = 0; + + /* close and re-open the device */ + priv->is_open = false; + dpriv->open_count = 1; + + /* clean up open interfaces */ + (void) darwin_close (dev_handle); + + /* re-open the device */ + ret = darwin_open (dev_handle); + dpriv->open_count = open_count; + if (LIBUSB_SUCCESS != ret) { + /* could not restore configuration */ + return LIBUSB_ERROR_NOT_FOUND; + } + + if (dpriv->active_config != active_config) { + usbi_dbg (ctx, "darwin/restore_state: restoring configuration %d...", active_config); + + ret = darwin_set_configuration (dev_handle, active_config); + if (LIBUSB_SUCCESS != ret) { + usbi_dbg (ctx, "darwin/restore_state: could not restore configuration"); + return LIBUSB_ERROR_NOT_FOUND; + } + } + + usbi_dbg (ctx, "darwin/restore_state: reclaiming interfaces"); + + if (claimed_interfaces) { + for (uint8_t iface = 0 ; iface < USB_MAXINTERFACES ; ++iface) { + if (!(claimed_interfaces & (1U << iface))) { + continue; + } + + usbi_dbg (ctx, "darwin/restore_state: re-claiming interface %u", iface); + + ret = darwin_claim_interface (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + usbi_dbg (ctx, "darwin/restore_state: could not claim interface %u", iface); + return LIBUSB_ERROR_NOT_FOUND; + } + + dev_handle->claimed_interfaces |= 1U << iface; + } + } + + usbi_dbg (ctx, "darwin/restore_state: device state restored"); + + return LIBUSB_SUCCESS; +} + +static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, bool capture) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + unsigned long claimed_interfaces = dev_handle->claimed_interfaces; + int8_t active_config = dpriv->active_config; + UInt32 options = 0; + IOUSBDeviceDescriptor descriptor; + IOUSBConfigurationDescriptorPtr cached_configuration; + IOUSBConfigurationDescriptor *cached_configurations; + IOReturn kresult; + UInt8 i; + + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + if (dpriv->in_reenumerate) { + /* ack, two (or more) threads are trying to reset the device! abort! */ + return LIBUSB_ERROR_NOT_FOUND; + } + + dpriv->in_reenumerate = true; + + /* store copies of descriptors so they can be compared after the reset */ + memcpy (&descriptor, &dpriv->dev_descriptor, sizeof (descriptor)); + cached_configurations = alloca (sizeof (*cached_configurations) * descriptor.bNumConfigurations); + + for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { + (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i])); + } + + /* if we need to release capture */ + if (HAS_CAPTURE_DEVICE()) { + if (capture) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + options |= kUSBReEnumerateCaptureDeviceMask; +#endif + } + } else { + capture = false; + } + + /* from macOS 10.11 ResetDevice no longer does anything so just use USBDeviceReEnumerate */ + kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); + dpriv->in_reenumerate = false; + return darwin_to_libusb (kresult); + } + + /* capture mode does not re-enumerate but it does require re-open */ + if (capture) { + usbi_dbg (ctx, "darwin/reenumerate_device: restoring state..."); + dpriv->in_reenumerate = false; + return darwin_restore_state (dev_handle, active_config, claimed_interfaces); + } + + usbi_dbg (ctx, "darwin/reenumerate_device: waiting for re-enumeration to complete..."); + + struct timespec start; + clock_gettime(CLOCK_MONOTONIC, &start); + + while (dpriv->in_reenumerate) { + struct timespec delay = {.tv_sec = 0, .tv_nsec = 1000}; + nanosleep (&delay, NULL); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + UInt32 elapsed = (now.tv_sec - start.tv_sec) * 1000000 + (now.tv_nsec - start.tv_nsec) / 1000; + + if (elapsed >= DARWIN_REENUMERATE_TIMEOUT_US) { + usbi_err (ctx, "darwin/reenumerate_device: timeout waiting for reenumerate"); + dpriv->in_reenumerate = false; + return LIBUSB_ERROR_TIMEOUT; + } + } + + /* compare descriptors */ + usbi_dbg (ctx, "darwin/reenumerate_device: checking whether descriptors changed"); + + if (memcmp (&descriptor, &dpriv->dev_descriptor, sizeof (descriptor))) { + /* device descriptor changed. need to return not found. */ + usbi_dbg (ctx, "darwin/reenumerate_device: device descriptor changed"); + return LIBUSB_ERROR_NOT_FOUND; + } + + for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { + (void) (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + if (memcmp (cached_configuration, cached_configurations + i, sizeof (cached_configurations[i]))) { + usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i); + return LIBUSB_ERROR_NOT_FOUND; + } + } + + usbi_dbg (ctx, "darwin/reenumerate_device: device reset complete. restoring state..."); + + return darwin_restore_state (dev_handle, active_config, claimed_interfaces); +} + +static int darwin_reset_device (struct libusb_device_handle *dev_handle) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + + if (dpriv->capture_count > 0) { + /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */ + kresult = (*(dpriv->device))->ResetDevice (dpriv->device); + return darwin_to_libusb (kresult); + } else { + return darwin_reenumerate_device (dev_handle, false); + } +} + +static io_service_t usb_find_interface_matching_location (const io_name_t class_name, UInt8 interface_number, UInt32 location) { + CFMutableDictionaryRef matchingDict = IOServiceMatching (class_name); + CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location); + CFTypeRef interfaceCF = CFNumberCreate (NULL, kCFNumberSInt8Type, &interface_number); + + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBHostMatchingPropertyInterfaceNumber), interfaceCF); + + CFRelease (interfaceCF); + CFRelease (locationCF); + CFRelease (propertyMatchDict); + + return IOServiceGetMatchingService (darwin_default_master_port, matchingDict); +} + +static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + io_service_t usb_interface, child = IO_OBJECT_NULL; + + /* locate the IO registry entry for this interface */ + usb_interface = usb_find_interface_matching_location (kIOUSBHostInterfaceClassName, interface, dpriv->location); + if (0 == usb_interface) { + /* check for the legacy class entry */ + usb_interface = usb_find_interface_matching_location (kIOUSBInterfaceClassName, interface, dpriv->location); + if (0 == usb_interface) { + return LIBUSB_ERROR_NOT_FOUND; + } + } + + /* if the IO object has a child entry in the IO Registry it has a kernel driver attached */ + (void) IORegistryEntryGetChildEntry (usb_interface, kIOServicePlane, &child); + IOObjectRelease (usb_interface); + if (IO_OBJECT_NULL != child) { + IOObjectRelease (child); + return 1; + } + + /* no driver */ + return 0; +} + +static void darwin_destroy_device(struct libusb_device *dev) { + struct darwin_device_priv *dpriv = usbi_get_device_priv(dev); + + if (dpriv->dev) { + /* need to hold the lock in case this is the last reference to the device */ + usbi_mutex_lock(&darwin_cached_devices_lock); + darwin_deref_cached_device (dpriv->dev); + dpriv->dev = NULL; + usbi_mutex_unlock(&darwin_cached_devices_lock); + } +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + IOReturn ret; + uint8_t transferType; + uint8_t pipeRef; + uint16_t maxPacketSize; + + struct darwin_interface *cInterface; +#if InterfaceVersion >= 550 + IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; +#else + /* None of the values below are used in libusb for bulk transfers */ + uint8_t direction, number, interval; +#endif + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + +#if InterfaceVersion >= 550 + ret = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties); + + transferType = pipeProperties.bTransferType; + maxPacketSize = pipeProperties.wMaxPacketSize; +#else + ret = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); +#endif + + if (ret) { + usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + return darwin_to_libusb (ret); + } + + if (0 != (transfer->length % maxPacketSize)) { + /* do not need a zero packet */ + transfer->flags &= ~LIBUSB_TRANSFER_ADD_ZERO_PACKET; + } + + /* submit the request */ + /* timeouts are unavailable on interrupt endpoints */ + if (transferType == kUSBInterrupt) { + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadPipeAsync(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WritePipeAsync(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, darwin_async_io_callback, itransfer); + } else { + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadPipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WritePipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, itransfer); + } + + if (ret) + usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + + return darwin_to_libusb (ret); +} + +#if InterfaceVersion >= 550 +static int submit_stream_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_interface *cInterface; + uint8_t pipeRef; + IOReturn ret; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id, + transfer->buffer, (UInt32)transfer->length, transfer->timeout, + transfer->timeout, darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WriteStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id, + transfer->buffer, (UInt32)transfer->length, transfer->timeout, + transfer->timeout, darwin_async_io_callback, itransfer); + + if (ret) + usbi_err (TRANSFER_CTX (transfer), "bulk stream transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + + return darwin_to_libusb (ret); +} +#endif + +static int submit_iso_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + IOReturn kresult; + uint8_t direction, number, interval, pipeRef, transferType; + uint16_t maxPacketSize; + UInt64 frame; + AbsoluteTime atTime; + int i; + + struct darwin_interface *cInterface; + + /* construct an array of IOUSBIsocFrames, reuse the old one if the sizes are the same */ + if (tpriv->num_iso_packets != transfer->num_iso_packets) { + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } + + if (!tpriv->isoc_framelist) { + tpriv->num_iso_packets = transfer->num_iso_packets; + tpriv->isoc_framelist = (IOUSBIsocFrame*) calloc ((size_t)transfer->num_iso_packets, sizeof(IOUSBIsocFrame)); + if (!tpriv->isoc_framelist) + return LIBUSB_ERROR_NO_MEM; + } + + /* copy the frame list from the libusb descriptor (the structures differ only is member order) */ + for (i = 0 ; i < transfer->num_iso_packets ; i++) { + unsigned int length = transfer->iso_packet_desc[i].length; + assert(length <= UINT16_MAX); + tpriv->isoc_framelist[i].frReqCount = (UInt16)length; + } + + /* determine the interface/endpoint to use */ + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + /* determine the properties of this endpoint and the speed of the device */ + (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); + + /* Last but not least we need the bus frame number */ + kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime); + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "failed to get bus frame number: %d", kresult); + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + + return darwin_to_libusb (kresult); + } + + (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); + + /* schedule for a frame a little in the future */ + frame += 4; + + if (cInterface->frames[transfer->endpoint] && frame < cInterface->frames[transfer->endpoint]) + frame = cInterface->frames[transfer->endpoint]; + + /* submit the request */ + if (IS_XFERIN(transfer)) + kresult = (*(cInterface->interface))->ReadIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, + (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); + else + kresult = (*(cInterface->interface))->WriteIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, + (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); + + if (LIBUSB_SPEED_FULL == transfer->dev_handle->dev->speed) + /* Full speed */ + cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)); + else + /* High/super speed */ + cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)) / 8; + + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "isochronous transfer failed (dir: %s): %s", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(kresult)); + free (tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } + + return darwin_to_libusb (kresult); +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_control_setup *setup = (struct libusb_control_setup *) transfer->buffer; + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(transfer->dev_handle->dev); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + IOReturn kresult; + + memset(&tpriv->req, 0, sizeof(tpriv->req)); + + /* IOUSBDeviceInterface expects the request in cpu endianness */ + tpriv->req.bmRequestType = setup->bmRequestType; + tpriv->req.bRequest = setup->bRequest; + /* these values should be in bus order from libusb_fill_control_setup */ + tpriv->req.wValue = OSSwapLittleToHostInt16 (setup->wValue); + tpriv->req.wIndex = OSSwapLittleToHostInt16 (setup->wIndex); + tpriv->req.wLength = OSSwapLittleToHostInt16 (setup->wLength); + /* data is stored after the libusb control block */ + tpriv->req.pData = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + tpriv->req.completionTimeout = transfer->timeout; + tpriv->req.noDataTimeout = transfer->timeout; + + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + /* all transfers in libusb-1.0 are async */ + + if (transfer->endpoint) { + struct darwin_interface *cInterface; + uint8_t pipeRef; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + kresult = (*(cInterface->interface))->ControlRequestAsyncTO (cInterface->interface, pipeRef, &(tpriv->req), darwin_async_io_callback, itransfer); + } else + /* control request on endpoint 0 */ + kresult = (*(dpriv->device))->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer); + + if (kresult != kIOReturnSuccess) + usbi_err (TRANSFER_CTX (transfer), "control request failed: %s", darwin_error_str(kresult)); + + return darwin_to_libusb (kresult); +} + +static int darwin_submit_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: +#if InterfaceVersion >= 550 + return submit_stream_transfer(itransfer); +#else + usbi_err (TRANSFER_CTX(transfer), "IOUSBFamily version does not support bulk stream transfers"); + return LIBUSB_ERROR_NOT_SUPPORTED; +#endif + default: + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int cancel_control_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(transfer->dev_handle->dev); + IOReturn kresult; + + usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions control pipe"); + + if (!dpriv->device) + return LIBUSB_ERROR_NO_DEVICE; + + kresult = (*(dpriv->device))->USBDeviceAbortPipeZero (dpriv->device); + + return darwin_to_libusb (kresult); +} + +static int darwin_abort_transfers (struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(transfer->dev_handle->dev); + struct darwin_interface *cInterface; + uint8_t pipeRef, iface; + IOReturn kresult; + + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface, &cInterface) != 0) { + usbi_err (ctx, "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + if (!dpriv->device) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef); + + /* abort transactions */ +#if InterfaceVersion >= 550 + if (LIBUSB_TRANSFER_TYPE_BULK_STREAM == transfer->type) + (*(cInterface->interface))->AbortStreamsPipe (cInterface->interface, pipeRef, itransfer->stream_id); + else +#endif + (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef); + + usbi_dbg (ctx, "calling clear pipe stall to clear the data toggle bit"); + + /* newer versions of darwin support clearing additional bits on the device's endpoint */ + kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); + + return darwin_to_libusb (kresult); +} + +static int darwin_cancel_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return cancel_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return darwin_abort_transfers (itransfer); + default: + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0) { + struct usbi_transfer *itransfer = (struct usbi_transfer *)refcon; + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + usbi_dbg (TRANSFER_CTX(transfer), "an async io operation has completed"); + + /* if requested write a zero packet */ + if (kIOReturnSuccess == result && IS_XFEROUT(transfer) && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + struct darwin_interface *cInterface; + uint8_t pipeRef; + + (void) ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface); + + (*(cInterface->interface))->WritePipe (cInterface->interface, pipeRef, transfer->buffer, 0); + } + + tpriv->result = result; + tpriv->size = (UInt32) (uintptr_t) arg0; + + /* signal the core that this transfer is complete */ + usbi_signal_transfer_completion(itransfer); +} + +static enum libusb_transfer_status darwin_transfer_status (struct usbi_transfer *itransfer, IOReturn result) { + if (itransfer->timeout_flags & USBI_TRANSFER_TIMED_OUT) + result = kIOUSBTransactionTimeout; + + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + + switch (result) { + case kIOReturnUnderrun: + case kIOReturnSuccess: + return LIBUSB_TRANSFER_COMPLETED; + case kIOReturnAborted: + return LIBUSB_TRANSFER_CANCELLED; + case kIOUSBPipeStalled: + usbi_dbg (ctx, "transfer error: pipe is stalled"); + return LIBUSB_TRANSFER_STALL; + case kIOReturnOverrun: + usbi_warn (ctx, "transfer error: data overrun"); + return LIBUSB_TRANSFER_OVERFLOW; + case kIOUSBTransactionTimeout: + usbi_warn (ctx, "transfer error: timed out"); + itransfer->timeout_flags |= USBI_TRANSFER_TIMED_OUT; + return LIBUSB_TRANSFER_TIMED_OUT; + default: + usbi_warn (ctx, "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); + return LIBUSB_TRANSFER_ERROR; + } +} + +static int darwin_handle_transfer_completion (struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + const unsigned char max_transfer_type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; + const char *transfer_types[] = {"control", "isoc", "bulk", "interrupt", "bulk-stream", NULL}; + bool is_isoc = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type; + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + + if (transfer->type > max_transfer_type) { + usbi_err (ctx, "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (NULL == tpriv) { + usbi_err (ctx, "malformed request is missing transfer priv"); + return LIBUSB_ERROR_INVALID_PARAM; + } + + usbi_dbg (ctx, "handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result); + + if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result || kIOUSBTransactionTimeout == tpriv->result) { + if (is_isoc && tpriv->isoc_framelist) { + /* copy isochronous results back */ + + for (int i = 0; i < transfer->num_iso_packets ; i++) { + struct libusb_iso_packet_descriptor *lib_desc = &transfer->iso_packet_desc[i]; + lib_desc->status = darwin_transfer_status (itransfer, tpriv->isoc_framelist[i].frStatus); + lib_desc->actual_length = tpriv->isoc_framelist[i].frActCount; + } + } else if (!is_isoc) { + itransfer->transferred += tpriv->size; + } + } + + /* it is ok to handle cancelled transfers without calling usbi_handle_transfer_cancellation (we catch timeout transfers) */ + return usbi_handle_transfer_completion (itransfer, darwin_transfer_status (itransfer, tpriv->result)); +} + +#if !defined(HAVE_CLOCK_GETTIME) +void usbi_get_monotonic_time(struct timespec *tp) { + mach_timespec_t sys_time; + + /* use system boot time as reference for the monotonic clock */ + clock_get_time (clock_monotonic, &sys_time); + + tp->tv_sec = sys_time.tv_sec; + tp->tv_nsec = sys_time.tv_nsec; +} + +void usbi_get_real_time(struct timespec *tp) { + mach_timespec_t sys_time; + + /* CLOCK_REALTIME represents time since the epoch */ + clock_get_time (clock_realtime, &sys_time); + + tp->tv_sec = sys_time.tv_sec; + tp->tv_nsec = sys_time.tv_nsec; +} +#endif + +#if InterfaceVersion >= 550 +static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32_t num_streams, unsigned char *endpoints, + int num_endpoints) { + struct darwin_interface *cInterface; + UInt32 supportsStreams; + uint8_t pipeRef; + int rc, i; + + /* find the minimum number of supported streams on the endpoint list */ + for (i = 0 ; i < num_endpoints ; ++i) { + if (0 != (rc = ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface))) { + return rc; + } + + (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams); + if (num_streams > supportsStreams) + num_streams = supportsStreams; + } + + /* it is an error if any endpoint in endpoints does not support streams */ + if (0 == num_streams) + return LIBUSB_ERROR_INVALID_PARAM; + + /* create the streams */ + for (i = 0 ; i < num_endpoints ; ++i) { + (void) ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface); + + rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, num_streams); + if (kIOReturnSuccess != rc) + return darwin_to_libusb(rc); + } + + assert(num_streams <= INT_MAX); + return (int)num_streams; +} + +static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigned char *endpoints, int num_endpoints) { + struct darwin_interface *cInterface; + UInt32 supportsStreams; + uint8_t pipeRef; + int rc; + + for (int i = 0 ; i < num_endpoints ; ++i) { + if (0 != (rc = ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface))) + return rc; + + (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams); + if (0 == supportsStreams) + return LIBUSB_ERROR_INVALID_PARAM; + + rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, 0); + if (kIOReturnSuccess != rc) + return darwin_to_libusb(rc); + } + + return LIBUSB_SUCCESS; +} +#endif + +#if InterfaceVersion >= 700 + +/* macOS APIs for getting entitlement values */ + +#if TARGET_OS_OSX +#include <Security/Security.h> +#else +typedef struct __SecTask *SecTaskRef; +extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator); +extern CFTypeRef SecTaskCopyValueForEntitlement(SecTaskRef task, CFStringRef entitlement, CFErrorRef *error); +#endif + +static bool darwin_has_capture_entitlements (void) { + SecTaskRef task; + CFTypeRef value; + bool entitled; + + task = SecTaskCreateFromSelf (kCFAllocatorDefault); + if (task == NULL) { + return false; + } + value = SecTaskCopyValueForEntitlement(task, CFSTR("com.apple.vm.device-access"), NULL); + CFRelease (task); + entitled = value && (CFGetTypeID (value) == CFBooleanGetTypeID ()) && CFBooleanGetValue (value); + if (value) { + CFRelease (value); + } + return entitled; +} + +static int darwin_reload_device (struct libusb_device_handle *dev_handle) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + enum libusb_error err; + + usbi_mutex_lock(&darwin_cached_devices_lock); + (*(dpriv->device))->Release(dpriv->device); + dpriv->device = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service); + if (!dpriv->device) { + err = LIBUSB_ERROR_NO_DEVICE; + } else { + err = LIBUSB_SUCCESS; + } + usbi_mutex_unlock(&darwin_cached_devices_lock); + + return err; +} + +/* On macOS, we capture an entire device at once, not individual interfaces. */ + +static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) { + UNUSED(interface); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + enum libusb_error err; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + if (HAS_CAPTURE_DEVICE()) { + } else { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + if (dpriv->capture_count == 0) { + usbi_dbg (ctx, "attempting to detach kernel driver from device"); + + if (darwin_has_capture_entitlements ()) { + /* request authorization */ + kresult = IOServiceAuthorize (dpriv->service, kIOServiceInteractionAllowed); + if (kresult != kIOReturnSuccess) { + usbi_warn (ctx, "IOServiceAuthorize: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* we need start() to be called again for authorization status to refresh */ + err = darwin_reload_device (dev_handle); + if (err != LIBUSB_SUCCESS) { + return err; + } + } else { + usbi_info (ctx, "no capture entitlements. may not be able to detach the kernel driver for this device"); + if (0 != geteuid()) { + usbi_warn (ctx, "USB device capture requires either an entitlement (com.apple.vm.device-access) or root privilege"); + return LIBUSB_ERROR_ACCESS; + } + } + + /* reset device to release existing drivers */ + err = darwin_reenumerate_device (dev_handle, true); + if (err != LIBUSB_SUCCESS) { + return err; + } + } + dpriv->capture_count++; + return LIBUSB_SUCCESS; +} + + +static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) { + UNUSED(interface); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + if (HAS_CAPTURE_DEVICE()) { + } else { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + dpriv->capture_count--; + if (dpriv->capture_count > 0) { + return LIBUSB_SUCCESS; + } + + usbi_dbg (HANDLE_CTX (dev_handle), "reenumerating device for kernel driver attach"); + + /* reset device to attach kernel drivers */ + return darwin_reenumerate_device (dev_handle, false); +} + +static int darwin_capture_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + enum libusb_error ret; + if (dev_handle->auto_detach_kernel_driver && darwin_kernel_driver_active(dev_handle, iface)) { + ret = darwin_detach_kernel_driver (dev_handle, iface); + if (ret != LIBUSB_SUCCESS) { + usbi_info (HANDLE_CTX (dev_handle), "failed to auto-detach the kernel driver for this device, ret=%d", ret); + } + } + + return darwin_claim_interface (dev_handle, iface); +} + +static int darwin_capture_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + enum libusb_error ret; + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + ret = darwin_release_interface (dev_handle, iface); + if (ret != LIBUSB_SUCCESS) { + return ret; + } + + if (dev_handle->auto_detach_kernel_driver && dpriv->capture_count > 0) { + ret = darwin_attach_kernel_driver (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + usbi_info (HANDLE_CTX (dev_handle), "on attempt to reattach the kernel driver got ret=%d", ret); + } + /* ignore the error as the interface was successfully released */ + } + + return LIBUSB_SUCCESS; +} + +#endif + +const struct usbi_os_backend usbi_backend = { + .name = "Darwin", + .caps = USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, + .init = darwin_init, + .exit = darwin_exit, + .get_active_config_descriptor = darwin_get_active_config_descriptor, + .get_config_descriptor = darwin_get_config_descriptor, + .hotplug_poll = darwin_hotplug_poll, + + .open = darwin_open, + .close = darwin_close, + .get_configuration = darwin_get_configuration, + .set_configuration = darwin_set_configuration, + + .set_interface_altsetting = darwin_set_interface_altsetting, + .clear_halt = darwin_clear_halt, + .reset_device = darwin_reset_device, + +#if InterfaceVersion >= 550 + .alloc_streams = darwin_alloc_streams, + .free_streams = darwin_free_streams, +#endif + + .kernel_driver_active = darwin_kernel_driver_active, + +#if InterfaceVersion >= 700 + .detach_kernel_driver = darwin_detach_kernel_driver, + .attach_kernel_driver = darwin_attach_kernel_driver, + .claim_interface = darwin_capture_claim_interface, + .release_interface = darwin_capture_release_interface, +#else + .claim_interface = darwin_claim_interface, + .release_interface = darwin_release_interface, +#endif + + .destroy_device = darwin_destroy_device, + + .submit_transfer = darwin_submit_transfer, + .cancel_transfer = darwin_cancel_transfer, + + .handle_transfer_completion = darwin_handle_transfer_completion, + + .device_priv_size = sizeof(struct darwin_device_priv), + .device_handle_priv_size = sizeof(struct darwin_device_handle_priv), + .transfer_priv_size = sizeof(struct darwin_transfer_priv), +}; diff --git a/src/os/darwin_usb.h b/src/os/darwin_usb.h new file mode 100644 index 0000000..7b72fff --- /dev/null +++ b/src/os/darwin_usb.h @@ -0,0 +1,227 @@ +/* + * darwin backend for libusb 1.0 + * Copyright © 2008-2019 Nathan Hjelm <hjelmn@users.sourceforge.net> + * Copyright © 2019 Google LLC. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#if !defined(LIBUSB_DARWIN_H) +#define LIBUSB_DARWIN_H + +#include <stdbool.h> + +#include "libusbi.h" + +#include <IOKit/IOTypes.h> +#include <IOKit/IOCFBundle.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOCFPlugIn.h> + +#if defined(HAVE_IOKIT_USB_IOUSBHOSTFAMILYDEFINITIONS_H) +#include <IOKit/usb/IOUSBHostFamilyDefinitions.h> +#endif + +/* IOUSBInterfaceInferface */ + +/* New in OS 10.12.0. */ +#if defined (kIOUSBInterfaceInterfaceID800) + +#define usb_interface_t IOUSBInterfaceInterface800 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID800 +#define InterfaceVersion 800 + +/* New in OS 10.10.0. */ +#elif defined (kIOUSBInterfaceInterfaceID700) + +#define usb_interface_t IOUSBInterfaceInterface700 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700 +#define InterfaceVersion 700 + +/* New in OS 10.9.0. */ +#elif defined (kIOUSBInterfaceInterfaceID650) + +#define usb_interface_t IOUSBInterfaceInterface650 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID650 +#define InterfaceVersion 650 + +/* New in OS 10.8.2 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBInterfaceInterfaceID550) + +#define usb_interface_t IOUSBInterfaceInterface550 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID550 +#define InterfaceVersion 550 + +/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBInterfaceInterfaceID500) + +#define usb_interface_t IOUSBInterfaceInterface500 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500 +#define InterfaceVersion 500 + +/* New in OS 10.5.0. */ +#elif defined (kIOUSBInterfaceInterfaceID300) + +#define usb_interface_t IOUSBInterfaceInterface300 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300 +#define InterfaceVersion 300 + +/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBInterfaceInterfaceID245) + +#define usb_interface_t IOUSBInterfaceInterface245 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245 +#define InterfaceVersion 245 + +/* New in OS 10.4.0. */ +#elif defined (kIOUSBInterfaceInterfaceID220) + +#define usb_interface_t IOUSBInterfaceInterface220 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220 +#define InterfaceVersion 220 + +#else + +#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target" + +#endif + +/* IOUSBDeviceInterface */ + +/* New in OS 10.9.0. */ +#if defined (kIOUSBDeviceInterfaceID650) + +#define usb_device_t IOUSBDeviceInterface650 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID650 +#define DeviceVersion 650 + +/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID500) + +#define usb_device_t IOUSBDeviceInterface500 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID500 +#define DeviceVersion 500 + +/* New in OS 10.5.4 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID320) + +#define usb_device_t IOUSBDeviceInterface320 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID320 +#define DeviceVersion 320 + +/* New in OS 10.5.0. */ +#elif defined (kIOUSBDeviceInterfaceID300) + +#define usb_device_t IOUSBDeviceInterface300 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID300 +#define DeviceVersion 300 + +/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID245) + +#define usb_device_t IOUSBDeviceInterface245 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID245 +#define DeviceVersion 245 + +/* New in OS 10.2.3 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID197) + +#define usb_device_t IOUSBDeviceInterface197 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID197 +#define DeviceVersion 197 + +#else + +#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target" + +#endif + +#if !defined(kIOUSBHostInterfaceClassName) +#define kIOUSBHostInterfaceClassName "IOUSBHostInterface" +#endif + +#if !defined(kUSBHostMatchingPropertyInterfaceNumber) +#define kUSBHostMatchingPropertyInterfaceNumber "bInterfaceNumber" +#endif + +#if !defined(IO_OBJECT_NULL) +#define IO_OBJECT_NULL ((io_object_t) 0) +#endif + +/* Testing availability */ +#ifndef __has_builtin + #define __has_builtin(x) 0 // Compatibility with non-clang compilers. +#endif +#if __has_builtin(__builtin_available) + #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *) +#else + #define HAS_CAPTURE_DEVICE() 0 +#endif + +typedef IOCFPlugInInterface *io_cf_plugin_ref_t; +typedef IONotificationPortRef io_notification_port_t; + +/* private structures */ +struct darwin_cached_device { + struct list_head list; + IOUSBDeviceDescriptor dev_descriptor; + UInt32 location; + UInt64 parent_session; + UInt64 session; + USBDeviceAddress address; + char sys_path[21]; + usb_device_t **device; + io_service_t service; + int open_count; + UInt8 first_config, active_config, port; + int can_enumerate; + int refcount; + bool in_reenumerate; + int capture_count; +}; + +struct darwin_device_priv { + struct darwin_cached_device *dev; +}; + +struct darwin_device_handle_priv { + bool is_open; + CFRunLoopSourceRef cfSource; + + struct darwin_interface { + usb_interface_t **interface; + uint8_t num_endpoints; + CFRunLoopSourceRef cfSource; + uint64_t frames[256]; + uint8_t endpoint_addrs[USB_MAXENDPOINTS]; + } interfaces[USB_MAXINTERFACES]; +}; + +struct darwin_transfer_priv { + /* Isoc */ + IOUSBIsocFrame *isoc_framelist; + int num_iso_packets; + + /* Control */ + IOUSBDevRequestTO req; + + /* Bulk */ + + /* Completion status */ + IOReturn result; + UInt32 size; +}; + +#endif diff --git a/src/os/events_posix.c b/src/os/events_posix.c new file mode 100644 index 0000000..715a2d5 --- /dev/null +++ b/src/os/events_posix.c @@ -0,0 +1,300 @@ +/* + * libusb event abstraction on POSIX platforms + * + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" + +#include <errno.h> +#include <fcntl.h> +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif +#ifdef HAVE_TIMERFD +#include <sys/timerfd.h> +#endif +#include <unistd.h> + +#ifdef HAVE_EVENTFD +#define EVENT_READ_FD(e) ((e)->eventfd) +#define EVENT_WRITE_FD(e) ((e)->eventfd) +#else +#define EVENT_READ_FD(e) ((e)->pipefd[0]) +#define EVENT_WRITE_FD(e) ((e)->pipefd[1]) +#endif + +#ifdef HAVE_NFDS_T +typedef nfds_t usbi_nfds_t; +#else +typedef unsigned int usbi_nfds_t; +#endif + +int usbi_create_event(usbi_event_t *event) +{ +#ifdef HAVE_EVENTFD + event->eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (event->eventfd == -1) { + usbi_err(NULL, "failed to create eventfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +#else +#if defined(HAVE_PIPE2) + int ret = pipe2(event->pipefd, O_CLOEXEC); +#else + int ret = pipe(event->pipefd); +#endif + + if (ret != 0) { + usbi_err(NULL, "failed to create pipe, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + +#if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC) + ret = fcntl(event->pipefd[0], F_GETFD); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + + ret = fcntl(event->pipefd[1], F_GETFD); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } +#endif + + ret = fcntl(event->pipefd[1], F_GETFL); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno); + goto err_close_pipe; + } + + return 0; + +err_close_pipe: + close(event->pipefd[1]); + close(event->pipefd[0]); + return LIBUSB_ERROR_OTHER; +#endif +} + +void usbi_destroy_event(usbi_event_t *event) +{ +#ifdef HAVE_EVENTFD + if (close(event->eventfd) == -1) + usbi_warn(NULL, "failed to close eventfd, errno=%d", errno); +#else + if (close(event->pipefd[1]) == -1) + usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno); + if (close(event->pipefd[0]) == -1) + usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno); +#endif +} + +void usbi_signal_event(usbi_event_t *event) +{ + uint64_t dummy = 1; + ssize_t r; + + r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy)); + if (r != sizeof(dummy)) + usbi_warn(NULL, "event write failed"); +} + +void usbi_clear_event(usbi_event_t *event) +{ + uint64_t dummy; + ssize_t r; + + r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy)); + if (r != sizeof(dummy)) + usbi_warn(NULL, "event read failed"); +} + +#ifdef HAVE_TIMERFD +int usbi_create_timer(usbi_timer_t *timer) +{ + timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (timer->timerfd == -1) { + usbi_warn(NULL, "failed to create timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +void usbi_destroy_timer(usbi_timer_t *timer) +{ + if (close(timer->timerfd) == -1) + usbi_warn(NULL, "failed to close timerfd, errno=%d", errno); +} + +int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout) +{ + const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } }; + + if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) { + usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +int usbi_disarm_timer(usbi_timer_t *timer) +{ + const struct itimerspec it = { { 0, 0 }, { 0, 0 } }; + + if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) { + usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} +#endif + +int usbi_alloc_event_data(struct libusb_context *ctx) +{ + struct usbi_event_source *ievent_source; + struct pollfd *fds; + size_t i = 0; + + if (ctx->event_data) { + free(ctx->event_data); + ctx->event_data = NULL; + } + + ctx->event_data_cnt = 0; + for_each_event_source(ctx, ievent_source) + ctx->event_data_cnt++; + + fds = calloc(ctx->event_data_cnt, sizeof(*fds)); + if (!fds) + return LIBUSB_ERROR_NO_MEM; + + for_each_event_source(ctx, ievent_source) { + fds[i].fd = ievent_source->data.os_handle; + fds[i].events = ievent_source->data.poll_events; + i++; + } + + ctx->event_data = fds; + return 0; +} + +int usbi_wait_for_events(struct libusb_context *ctx, + struct usbi_reported_events *reported_events, int timeout_ms) +{ + struct pollfd *fds = ctx->event_data; + usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt; + int internal_fds, num_ready; + + usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); + num_ready = poll(fds, nfds, timeout_ms); + usbi_dbg(ctx, "poll() returned %d", num_ready); + if (num_ready == 0) { + if (usbi_using_timer(ctx)) + goto done; + return LIBUSB_ERROR_TIMEOUT; + } else if (num_ready == -1) { + if (errno == EINTR) + return LIBUSB_ERROR_INTERRUPTED; + usbi_err(ctx, "poll() failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + /* fds[0] is always the internal signalling event */ + if (fds[0].revents) { + reported_events->event_triggered = 1; + num_ready--; + } else { + reported_events->event_triggered = 0; + } + +#ifdef HAVE_OS_TIMER + /* on timer configurations, fds[1] is the timer */ + if (usbi_using_timer(ctx) && fds[1].revents) { + reported_events->timer_triggered = 1; + num_ready--; + } else { + reported_events->timer_triggered = 0; + } +#endif + + if (!num_ready) + goto done; + + /* the backend will never need to attempt to handle events on the + * library's internal file descriptors, so we determine how many are + * in use internally for this context and skip these when passing any + * remaining pollfds to the backend. */ + internal_fds = usbi_using_timer(ctx) ? 2 : 1; + fds += internal_fds; + nfds -= internal_fds; + + usbi_mutex_lock(&ctx->event_data_lock); + if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) { + struct usbi_event_source *ievent_source; + + for_each_removed_event_source(ctx, ievent_source) { + usbi_nfds_t n; + + for (n = 0; n < nfds; n++) { + if (ievent_source->data.os_handle != fds[n].fd) + continue; + if (!fds[n].revents) + continue; + /* pollfd was removed between the creation of the fds array and + * here. remove triggered revent as it is no longer relevant. */ + usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd); + fds[n].revents = 0; + num_ready--; + break; + } + } + } + usbi_mutex_unlock(&ctx->event_data_lock); + + if (num_ready) { + assert(num_ready > 0); + reported_events->event_data = fds; + reported_events->event_data_count = (unsigned int)nfds; + } + +done: + reported_events->num_ready = num_ready; + return LIBUSB_SUCCESS; +} diff --git a/src/os/events_posix.h b/src/os/events_posix.h new file mode 100644 index 0000000..d81b5c4 --- /dev/null +++ b/src/os/events_posix.h @@ -0,0 +1,59 @@ +/* + * libusb event abstraction on POSIX platforms + * + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_EVENTS_POSIX_H +#define LIBUSB_EVENTS_POSIX_H + +#include <poll.h> + +typedef int usbi_os_handle_t; +#define USBI_OS_HANDLE_FORMAT_STRING "fd %d" + +#ifdef HAVE_EVENTFD +typedef struct usbi_event { + int eventfd; +} usbi_event_t; +#define USBI_EVENT_OS_HANDLE(e) ((e)->eventfd) +#define USBI_EVENT_POLL_EVENTS POLLIN +#define USBI_INVALID_EVENT { -1 } +#else +typedef struct usbi_event { + int pipefd[2]; +} usbi_event_t; +#define USBI_EVENT_OS_HANDLE(e) ((e)->pipefd[0]) +#define USBI_EVENT_POLL_EVENTS POLLIN +#define USBI_INVALID_EVENT { { -1, -1 } } +#endif + +#ifdef HAVE_TIMERFD +#define HAVE_OS_TIMER 1 +typedef struct usbi_timer { + int timerfd; +} usbi_timer_t; +#define USBI_TIMER_OS_HANDLE(t) ((t)->timerfd) +#define USBI_TIMER_POLL_EVENTS POLLIN + +static inline int usbi_timer_valid(usbi_timer_t *timer) +{ + return timer->timerfd >= 0; +} +#endif + +#endif diff --git a/src/os/events_windows.c b/src/os/events_windows.c new file mode 100644 index 0000000..f22bebc --- /dev/null +++ b/src/os/events_windows.c @@ -0,0 +1,214 @@ +/* + * libusb event abstraction on Microsoft Windows + * + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include "libusbi.h" +#include "windows_common.h" + +int usbi_create_event(usbi_event_t *event) +{ + event->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (event->hEvent == NULL) { + usbi_err(NULL, "CreateEvent failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +void usbi_destroy_event(usbi_event_t *event) +{ + if (!CloseHandle(event->hEvent)) + usbi_warn(NULL, "CloseHandle failed: %s", windows_error_str(0)); +} + +void usbi_signal_event(usbi_event_t *event) +{ + if (!SetEvent(event->hEvent)) + usbi_warn(NULL, "SetEvent failed: %s", windows_error_str(0)); +} + +void usbi_clear_event(usbi_event_t *event) +{ + if (!ResetEvent(event->hEvent)) + usbi_warn(NULL, "ResetEvent failed: %s", windows_error_str(0)); +} + +#ifdef HAVE_OS_TIMER +int usbi_create_timer(usbi_timer_t *timer) +{ + timer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL); + if (timer->hTimer == NULL) { + usbi_warn(NULL, "CreateWaitableTimer failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +void usbi_destroy_timer(usbi_timer_t *timer) +{ + if (!CloseHandle(timer->hTimer)) + usbi_warn(NULL, "CloseHandle failed: %s", windows_error_str(0)); +} + +int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout) +{ + struct timespec systime, remaining; + FILETIME filetime; + LARGE_INTEGER dueTime; + + /* Transfer timeouts are based on the monotonic clock and the waitable + * timers on the system clock. This requires a conversion between the + * two, so we calculate the remaining time relative to the monotonic + * clock and calculate an absolute system time for the timer expiration. + * Note that if the timeout has already passed, the remaining time will + * be negative and thus an absolute system time in the past will be set. + * This works just as intended because the timer becomes signalled + * immediately. */ + usbi_get_monotonic_time(&systime); + + TIMESPEC_SUB(timeout, &systime, &remaining); + + GetSystemTimeAsFileTime(&filetime); + dueTime.LowPart = filetime.dwLowDateTime; + dueTime.HighPart = filetime.dwHighDateTime; + dueTime.QuadPart += (remaining.tv_sec * 10000000LL) + (remaining.tv_nsec / 100LL); + + if (!SetWaitableTimer(timer->hTimer, &dueTime, 0, NULL, NULL, FALSE)) { + usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +int usbi_disarm_timer(usbi_timer_t *timer) +{ + LARGE_INTEGER dueTime; + + /* A manual-reset waitable timer will stay in the signalled state until + * another call to SetWaitableTimer() is made. It is possible that the + * timer has already expired by the time we come in to disarm it, so to + * be entirely sure the timer is disarmed and not in the signalled state, + * we will set it with an impossibly large expiration and immediately + * cancel. */ + dueTime.QuadPart = LLONG_MAX; + if (!SetWaitableTimer(timer->hTimer, &dueTime, 0, NULL, NULL, FALSE)) { + usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + if (!CancelWaitableTimer(timer->hTimer)) { + usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} +#endif + +int usbi_alloc_event_data(struct libusb_context *ctx) +{ + struct usbi_event_source *ievent_source; + HANDLE *handles; + size_t i = 0; + + /* Event sources are only added during usbi_io_init(). We should not + * be running this function again if the event data has already been + * allocated. */ + if (ctx->event_data) { + usbi_warn(ctx, "program assertion failed - event data already allocated"); + return LIBUSB_ERROR_OTHER; + } + + ctx->event_data_cnt = 0; + for_each_event_source(ctx, ievent_source) + ctx->event_data_cnt++; + + /* We only expect up to two HANDLEs to wait on, one for the internal + * signalling event and the other for the timer. */ + if (ctx->event_data_cnt != 1 && ctx->event_data_cnt != 2) { + usbi_err(ctx, "program assertion failed - expected exactly 1 or 2 HANDLEs"); + return LIBUSB_ERROR_OTHER; + } + + handles = calloc(ctx->event_data_cnt, sizeof(HANDLE)); + if (!handles) + return LIBUSB_ERROR_NO_MEM; + + for_each_event_source(ctx, ievent_source) { + handles[i] = ievent_source->data.os_handle; + i++; + } + + ctx->event_data = handles; + return 0; +} + +int usbi_wait_for_events(struct libusb_context *ctx, + struct usbi_reported_events *reported_events, int timeout_ms) +{ + HANDLE *handles = ctx->event_data; + DWORD num_handles = (DWORD)ctx->event_data_cnt; + DWORD result; + + usbi_dbg(ctx, "WaitForMultipleObjects() for %lu HANDLEs with timeout in %dms", ULONG_CAST(num_handles), timeout_ms); + result = WaitForMultipleObjects(num_handles, handles, FALSE, (DWORD)timeout_ms); + usbi_dbg(ctx, "WaitForMultipleObjects() returned %lu", ULONG_CAST(result)); + if (result == WAIT_TIMEOUT) { + if (usbi_using_timer(ctx)) + goto done; + return LIBUSB_ERROR_TIMEOUT; + } else if (result == WAIT_FAILED) { + usbi_err(ctx, "WaitForMultipleObjects() failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + result -= WAIT_OBJECT_0; + + /* handles[0] is always the internal signalling event */ + if (result == 0) + reported_events->event_triggered = 1; + else + reported_events->event_triggered = 0; + +#ifdef HAVE_OS_TIMER + /* on timer configurations, handles[1] is the timer */ + if (usbi_using_timer(ctx)) { + /* The WaitForMultipleObjects() function reports the index of + * the first object that became signalled. If the internal + * signalling event was reported, we need to also check and + * report whether the timer is in the signalled state. */ + if (result == 1 || WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) + reported_events->timer_triggered = 1; + else + reported_events->timer_triggered = 0; + } else { + reported_events->timer_triggered = 0; + } +#endif + +done: + /* no events are ever reported to the backend */ + reported_events->num_ready = 0; + return LIBUSB_SUCCESS; +} diff --git a/src/os/events_windows.h b/src/os/events_windows.h new file mode 100644 index 0000000..0c5e0b0 --- /dev/null +++ b/src/os/events_windows.h @@ -0,0 +1,46 @@ +/* + * libusb event abstraction on Microsoft Windows + * + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_EVENTS_WINDOWS_H +#define LIBUSB_EVENTS_WINDOWS_H + +typedef HANDLE usbi_os_handle_t; +#define USBI_OS_HANDLE_FORMAT_STRING "HANDLE %p" + +typedef struct usbi_event { + HANDLE hEvent; +} usbi_event_t; +#define USBI_EVENT_OS_HANDLE(e) ((e)->hEvent) +#define USBI_EVENT_POLL_EVENTS 0 +#define USBI_INVALID_EVENT { INVALID_HANDLE_VALUE } + +#define HAVE_OS_TIMER 1 +typedef struct usbi_timer { + HANDLE hTimer; +} usbi_timer_t; +#define USBI_TIMER_OS_HANDLE(t) ((t)->hTimer) +#define USBI_TIMER_POLL_EVENTS 0 + +static inline int usbi_timer_valid(usbi_timer_t *timer) +{ + return timer->hTimer != NULL; +} + +#endif diff --git a/src/os/haiku_pollfs.cpp b/src/os/haiku_pollfs.cpp new file mode 100644 index 0000000..b85edf7 --- /dev/null +++ b/src/os/haiku_pollfs.cpp @@ -0,0 +1,372 @@ +/* + * Copyright 2007-2008, Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Michael Lotz <mmlr@mlotz.ch> + */ + +#include "haiku_usb.h" +#include <cstdio> +#include <Directory.h> +#include <Entry.h> +#include <Looper.h> +#include <Messenger.h> +#include <Node.h> +#include <NodeMonitor.h> +#include <Path.h> +#include <cstring> + +class WatchedEntry { +public: + WatchedEntry(BMessenger *, entry_ref *); + ~WatchedEntry(); + bool EntryCreated(entry_ref *ref); + bool EntryRemoved(ino_t node); + bool InitCheck(); + +private: + BMessenger* fMessenger; + node_ref fNode; + bool fIsDirectory; + USBDevice* fDevice; + WatchedEntry* fEntries; + WatchedEntry* fLink; + bool fInitCheck; +}; + + +class RosterLooper : public BLooper { +public: + RosterLooper(USBRoster *); + void Stop(); + virtual void MessageReceived(BMessage *); + bool InitCheck(); + +private: + USBRoster* fRoster; + WatchedEntry* fRoot; + BMessenger* fMessenger; + bool fInitCheck; +}; + + +WatchedEntry::WatchedEntry(BMessenger *messenger, entry_ref *ref) + : fMessenger(messenger), + fIsDirectory(false), + fDevice(NULL), + fEntries(NULL), + fLink(NULL), + fInitCheck(false) +{ + BEntry entry(ref); + entry.GetNodeRef(&fNode); + + BDirectory directory; + if (entry.IsDirectory() && directory.SetTo(ref) >= B_OK) { + fIsDirectory = true; + + while (directory.GetNextEntry(&entry) >= B_OK) { + if (entry.GetRef(ref) < B_OK) + continue; + + WatchedEntry *child = new(std::nothrow) WatchedEntry(fMessenger, ref); + if (child == NULL) + continue; + if (child->InitCheck() == false) { + delete child; + continue; + } + + child->fLink = fEntries; + fEntries = child; + } + + watch_node(&fNode, B_WATCH_DIRECTORY, *fMessenger); + } + else { + if (strncmp(ref->name, "raw", 3) == 0) + return; + + BPath path, parent_path; + entry.GetPath(&path); + fDevice = new(std::nothrow) USBDevice(path.Path()); + if (fDevice != NULL && fDevice->InitCheck() == true) { + // Add this new device to each active context's device list + struct libusb_context *ctx; + unsigned long session_id = (unsigned long)&fDevice; + + usbi_mutex_lock(&active_contexts_lock); + for_each_context(ctx) { + struct libusb_device *dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + usbi_dbg(NULL, "using previously allocated device with location %lu", session_id); + libusb_unref_device(dev); + continue; + } + usbi_dbg(NULL, "allocating new device with location %lu", session_id); + dev = usbi_alloc_device(ctx, session_id); + if (!dev) { + usbi_dbg(NULL, "device allocation failed"); + continue; + } + *((USBDevice **)usbi_get_device_priv(dev)) = fDevice; + + // Calculate pseudo-device-address + int addr, tmp; + if (strcmp(path.Leaf(), "hub") == 0) + tmp = 100; //Random Number + else + sscanf(path.Leaf(), "%d", &tmp); + addr = tmp + 1; + path.GetParent(&parent_path); + while (strcmp(parent_path.Leaf(), "usb") != 0) { + sscanf(parent_path.Leaf(), "%d", &tmp); + addr += tmp + 1; + parent_path.GetParent(&parent_path); + } + sscanf(path.Path(), "/dev/bus/usb/%hhu", &dev->bus_number); + dev->device_address = addr - (dev->bus_number + 1); + + static_assert(sizeof(dev->device_descriptor) == sizeof(usb_device_descriptor), + "mismatch between libusb and OS device descriptor sizes"); + memcpy(&dev->device_descriptor, fDevice->Descriptor(), LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + + if (usbi_sanitize_device(dev) < 0) { + usbi_dbg(NULL, "device sanitization failed"); + libusb_unref_device(dev); + continue; + } + usbi_connect_device(dev); + } + usbi_mutex_unlock(&active_contexts_lock); + } + else if (fDevice) { + delete fDevice; + fDevice = NULL; + return; + } + } + fInitCheck = true; +} + + +WatchedEntry::~WatchedEntry() +{ + if (fIsDirectory) { + watch_node(&fNode, B_STOP_WATCHING, *fMessenger); + + WatchedEntry *child = fEntries; + while (child) { + WatchedEntry *next = child->fLink; + delete child; + child = next; + } + } + + if (fDevice) { + // Remove this device from each active context's device list + struct libusb_context *ctx; + struct libusb_device *dev; + unsigned long session_id = (unsigned long)&fDevice; + + usbi_mutex_lock(&active_contexts_lock); + for_each_context(ctx) { + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev != NULL) { + usbi_disconnect_device(dev); + libusb_unref_device(dev); + } else { + usbi_dbg(ctx, "device with location %lu not found", session_id); + } + } + usbi_mutex_static_unlock(&active_contexts_lock); + delete fDevice; + } +} + + +bool +WatchedEntry::EntryCreated(entry_ref *ref) +{ + if (!fIsDirectory) + return false; + + if (ref->directory != fNode.node) { + WatchedEntry *child = fEntries; + while (child) { + if (child->EntryCreated(ref)) + return true; + child = child->fLink; + } + return false; + } + + WatchedEntry *child = new(std::nothrow) WatchedEntry(fMessenger, ref); + if (child == NULL) + return false; + child->fLink = fEntries; + fEntries = child; + return true; +} + + +bool +WatchedEntry::EntryRemoved(ino_t node) +{ + if (!fIsDirectory) + return false; + + WatchedEntry *child = fEntries; + WatchedEntry *lastChild = NULL; + while (child) { + if (child->fNode.node == node) { + if (lastChild) + lastChild->fLink = child->fLink; + else + fEntries = child->fLink; + delete child; + return true; + } + + if (child->EntryRemoved(node)) + return true; + + lastChild = child; + child = child->fLink; + } + return false; +} + + +bool +WatchedEntry::InitCheck() +{ + return fInitCheck; +} + + +RosterLooper::RosterLooper(USBRoster *roster) + : BLooper("LibusbRoster Looper"), + fRoster(roster), + fRoot(NULL), + fMessenger(NULL), + fInitCheck(false) +{ + BEntry entry("/dev/bus/usb"); + if (!entry.Exists()) { + usbi_err(NULL, "usb_raw not published"); + return; + } + + Run(); + fMessenger = new(std::nothrow) BMessenger(this); + if (fMessenger == NULL) { + usbi_err(NULL, "error creating BMessenger object"); + return; + } + + if (Lock()) { + entry_ref ref; + entry.GetRef(&ref); + fRoot = new(std::nothrow) WatchedEntry(fMessenger, &ref); + Unlock(); + if (fRoot == NULL) + return; + if (fRoot->InitCheck() == false) { + delete fRoot; + fRoot = NULL; + return; + } + } + fInitCheck = true; +} + + +void +RosterLooper::Stop() +{ + Lock(); + delete fRoot; + delete fMessenger; + Quit(); +} + + +void +RosterLooper::MessageReceived(BMessage *message) +{ + int32 opcode; + if (message->FindInt32("opcode", &opcode) < B_OK) + return; + + switch (opcode) { + case B_ENTRY_CREATED: + { + dev_t device; + ino_t directory; + const char *name; + if (message->FindInt32("device", &device) < B_OK || + message->FindInt64("directory", &directory) < B_OK || + message->FindString("name", &name) < B_OK) + break; + + entry_ref ref(device, directory, name); + fRoot->EntryCreated(&ref); + break; + } + case B_ENTRY_REMOVED: + { + ino_t node; + if (message->FindInt64("node", &node) < B_OK) + break; + fRoot->EntryRemoved(node); + break; + } + } +} + + +bool +RosterLooper::InitCheck() +{ + return fInitCheck; +} + + +USBRoster::USBRoster() + : fLooper(NULL) +{ +} + + +USBRoster::~USBRoster() +{ + Stop(); +} + + +int +USBRoster::Start() +{ + if (fLooper == NULL) { + fLooper = new(std::nothrow) RosterLooper(this); + if (fLooper == NULL || ((RosterLooper *)fLooper)->InitCheck() == false) { + if (fLooper) + fLooper = NULL; + return LIBUSB_ERROR_OTHER; + } + } + return LIBUSB_SUCCESS; +} + + +void +USBRoster::Stop() +{ + if (fLooper) { + ((RosterLooper *)fLooper)->Stop(); + fLooper = NULL; + } +} diff --git a/src/os/haiku_usb.h b/src/os/haiku_usb.h new file mode 100644 index 0000000..2dd5177 --- /dev/null +++ b/src/os/haiku_usb.h @@ -0,0 +1,113 @@ +/* + * Haiku Backend for libusb + * Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <List.h> +#include <Locker.h> +#include <Autolock.h> +#include <USBKit.h> +#include <map> +#include "libusbi.h" +#include "haiku_usb_raw.h" + +using namespace std; + +class USBDevice; +class USBDeviceHandle; +class USBTransfer; + +class USBDevice { +public: + USBDevice(const char *); + virtual ~USBDevice(); + const char* Location() const; + uint8 CountConfigurations() const; + const usb_device_descriptor* Descriptor() const; + const usb_configuration_descriptor* ConfigurationDescriptor(uint8) const; + const usb_configuration_descriptor* ActiveConfiguration() const; + uint8 EndpointToIndex(uint8) const; + uint8 EndpointToInterface(uint8) const; + int ClaimInterface(uint8); + int ReleaseInterface(uint8); + int CheckInterfacesFree(uint8); + void SetActiveConfiguration(uint8); + uint8 ActiveConfigurationIndex() const; + bool InitCheck(); +private: + int Initialise(); + unsigned int fClaimedInterfaces; // Max Interfaces can be 32. Using a bitmask + usb_device_descriptor fDeviceDescriptor; + unsigned char** fConfigurationDescriptors; + uint8 fActiveConfiguration; + char* fPath; + map<uint8,uint8> fConfigToIndex; + map<uint8,uint8>* fEndpointToIndex; + map<uint8,uint8>* fEndpointToInterface; + bool fInitCheck; +}; + +class USBDeviceHandle { +public: + USBDeviceHandle(USBDevice *dev); + virtual ~USBDeviceHandle(); + int ClaimInterface(uint8); + int ReleaseInterface(uint8); + int SetConfiguration(uint8); + int SetAltSetting(uint8, uint8); + int ClearHalt(uint8); + status_t SubmitTransfer(struct usbi_transfer *); + status_t CancelTransfer(USBTransfer *); + bool InitCheck(); +private: + int fRawFD; + static status_t TransfersThread(void *); + void TransfersWorker(); + USBDevice* fUSBDevice; + unsigned int fClaimedInterfaces; + BList fTransfers; + BLocker fTransfersLock; + sem_id fTransfersSem; + thread_id fTransfersThread; + bool fInitCheck; +}; + +class USBTransfer { +public: + USBTransfer(struct usbi_transfer *, USBDevice *); + virtual ~USBTransfer(); + void Do(int); + struct usbi_transfer* UsbiTransfer(); + void SetCancelled(); + bool IsCancelled(); +private: + struct usbi_transfer* fUsbiTransfer; + struct libusb_transfer* fLibusbTransfer; + USBDevice* fUSBDevice; + BLocker fStatusLock; + bool fCancelled; +}; + +class USBRoster { +public: + USBRoster(); + virtual ~USBRoster(); + int Start(); + void Stop(); +private: + void* fLooper; +}; diff --git a/src/os/haiku_usb_backend.cpp b/src/os/haiku_usb_backend.cpp new file mode 100644 index 0000000..2fcefdd --- /dev/null +++ b/src/os/haiku_usb_backend.cpp @@ -0,0 +1,532 @@ +/* + * Haiku Backend for libusb + * Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + + +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <new> +#include <vector> + +#include "haiku_usb.h" + +static int _errno_to_libusb(int status) +{ + return status; +} + +USBTransfer::USBTransfer(struct usbi_transfer *itransfer, USBDevice *device) +{ + fUsbiTransfer = itransfer; + fLibusbTransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + fUSBDevice = device; + fCancelled = false; +} + +USBTransfer::~USBTransfer() +{ +} + +struct usbi_transfer * +USBTransfer::UsbiTransfer() +{ + return fUsbiTransfer; +} + +void +USBTransfer::SetCancelled() +{ + fCancelled = true; +} + +bool +USBTransfer::IsCancelled() +{ + return fCancelled; +} + +void +USBTransfer::Do(int fRawFD) +{ + switch (fLibusbTransfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + { + struct libusb_control_setup *setup = (struct libusb_control_setup *)fLibusbTransfer->buffer; + usb_raw_command command; + command.control.request_type = setup->bmRequestType; + command.control.request = setup->bRequest; + command.control.value = setup->wValue; + command.control.index = setup->wIndex; + command.control.length = setup->wLength; + command.control.data = fLibusbTransfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + if (fCancelled) + break; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_CONTROL_TRANSFER, &command, sizeof(command)) || + command.control.status != B_USB_RAW_STATUS_SUCCESS) { + fUsbiTransfer->transferred = -1; + usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed control transfer"); + break; + } + fUsbiTransfer->transferred = command.control.length; + } + break; + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + { + usb_raw_command command; + command.transfer.interface = fUSBDevice->EndpointToInterface(fLibusbTransfer->endpoint); + command.transfer.endpoint = fUSBDevice->EndpointToIndex(fLibusbTransfer->endpoint); + command.transfer.data = fLibusbTransfer->buffer; + command.transfer.length = fLibusbTransfer->length; + if (fCancelled) + break; + if (fLibusbTransfer->type == LIBUSB_TRANSFER_TYPE_BULK) { + if (ioctl(fRawFD, B_USB_RAW_COMMAND_BULK_TRANSFER, &command, sizeof(command)) || + command.transfer.status != B_USB_RAW_STATUS_SUCCESS) { + fUsbiTransfer->transferred = -1; + usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed bulk transfer"); + break; + } + } + else { + if (ioctl(fRawFD, B_USB_RAW_COMMAND_INTERRUPT_TRANSFER, &command, sizeof(command)) || + command.transfer.status != B_USB_RAW_STATUS_SUCCESS) { + fUsbiTransfer->transferred = -1; + usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed interrupt transfer"); + break; + } + } + fUsbiTransfer->transferred = command.transfer.length; + } + break; + // IsochronousTransfers not tested + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + { + usb_raw_command command; + command.isochronous.interface = fUSBDevice->EndpointToInterface(fLibusbTransfer->endpoint); + command.isochronous.endpoint = fUSBDevice->EndpointToIndex(fLibusbTransfer->endpoint); + command.isochronous.data = fLibusbTransfer->buffer; + command.isochronous.length = fLibusbTransfer->length; + command.isochronous.packet_count = fLibusbTransfer->num_iso_packets; + int i; + usb_iso_packet_descriptor *packetDescriptors = new usb_iso_packet_descriptor[fLibusbTransfer->num_iso_packets]; + for (i = 0; i < fLibusbTransfer->num_iso_packets; i++) { + if ((fLibusbTransfer->iso_packet_desc[i]).length > (unsigned int)INT16_MAX) { + fUsbiTransfer->transferred = -1; + usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed isochronous transfer"); + break; + } + packetDescriptors[i].request_length = (int16)(fLibusbTransfer->iso_packet_desc[i]).length; + } + if (i < fLibusbTransfer->num_iso_packets) + break; // TODO Handle this error + command.isochronous.packet_descriptors = packetDescriptors; + if (fCancelled) + break; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_ISOCHRONOUS_TRANSFER, &command, sizeof(command)) || + command.isochronous.status != B_USB_RAW_STATUS_SUCCESS) { + fUsbiTransfer->transferred = -1; + usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed isochronous transfer"); + break; + } + for (i = 0; i < fLibusbTransfer->num_iso_packets; i++) { + (fLibusbTransfer->iso_packet_desc[i]).actual_length = packetDescriptors[i].actual_length; + switch (packetDescriptors[i].status) { + case B_OK: + (fLibusbTransfer->iso_packet_desc[i]).status = LIBUSB_TRANSFER_COMPLETED; + break; + default: + (fLibusbTransfer->iso_packet_desc[i]).status = LIBUSB_TRANSFER_ERROR; + break; + } + } + delete[] packetDescriptors; + // Do we put the length of transfer here, for isochronous transfers? + fUsbiTransfer->transferred = command.transfer.length; + } + break; + default: + usbi_err(TRANSFER_CTX(fLibusbTransfer), "Unknown type of transfer"); + } +} + +bool +USBDeviceHandle::InitCheck() +{ + return fInitCheck; +} + +status_t +USBDeviceHandle::TransfersThread(void *self) +{ + USBDeviceHandle *handle = (USBDeviceHandle *)self; + handle->TransfersWorker(); + return B_OK; +} + +void +USBDeviceHandle::TransfersWorker() +{ + while (true) { + status_t status = acquire_sem(fTransfersSem); + if (status == B_BAD_SEM_ID) + break; + if (status == B_INTERRUPTED) + continue; + fTransfersLock.Lock(); + USBTransfer *fPendingTransfer = (USBTransfer *) fTransfers.RemoveItem((int32)0); + fTransfersLock.Unlock(); + fPendingTransfer->Do(fRawFD); + usbi_signal_transfer_completion(fPendingTransfer->UsbiTransfer()); + } +} + +status_t +USBDeviceHandle::SubmitTransfer(struct usbi_transfer *itransfer) +{ + USBTransfer *transfer = new USBTransfer(itransfer, fUSBDevice); + *((USBTransfer **)usbi_get_transfer_priv(itransfer)) = transfer; + BAutolock locker(fTransfersLock); + fTransfers.AddItem(transfer); + release_sem(fTransfersSem); + return LIBUSB_SUCCESS; +} + +status_t +USBDeviceHandle::CancelTransfer(USBTransfer *transfer) +{ + transfer->SetCancelled(); + fTransfersLock.Lock(); + bool removed = fTransfers.RemoveItem(transfer); + fTransfersLock.Unlock(); + if (removed) + usbi_signal_transfer_completion(transfer->UsbiTransfer()); + return LIBUSB_SUCCESS; +} + +USBDeviceHandle::USBDeviceHandle(USBDevice *dev) + : + fUSBDevice(dev), + fClaimedInterfaces(0), + fTransfersThread(-1), + fInitCheck(false) +{ + fRawFD = open(dev->Location(), O_RDWR | O_CLOEXEC); + if (fRawFD < 0) { + usbi_err(NULL,"failed to open device"); + return; + } + fTransfersSem = create_sem(0, "Transfers Queue Sem"); + fTransfersThread = spawn_thread(TransfersThread, "Transfer Worker", B_NORMAL_PRIORITY, this); + resume_thread(fTransfersThread); + fInitCheck = true; +} + +USBDeviceHandle::~USBDeviceHandle() +{ + if (fRawFD > 0) + close(fRawFD); + for (int i = 0; i < 32; i++) { + if (fClaimedInterfaces & (1U << i)) + ReleaseInterface(i); + } + delete_sem(fTransfersSem); + if (fTransfersThread > 0) + wait_for_thread(fTransfersThread, NULL); +} + +int +USBDeviceHandle::ClaimInterface(uint8 inumber) +{ + int status = fUSBDevice->ClaimInterface(inumber); + if (status == LIBUSB_SUCCESS) + fClaimedInterfaces |= (1U << inumber); + return status; +} + +int +USBDeviceHandle::ReleaseInterface(uint8 inumber) +{ + fUSBDevice->ReleaseInterface(inumber); + fClaimedInterfaces &= ~(1U << inumber); + return LIBUSB_SUCCESS; +} + +int +USBDeviceHandle::SetConfiguration(uint8 config) +{ + int config_index = fUSBDevice->CheckInterfacesFree(config); + if (config_index == LIBUSB_ERROR_BUSY || config_index == LIBUSB_ERROR_NOT_FOUND) + return config_index; + usb_raw_command command; + command.config.config_index = config_index; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_SET_CONFIGURATION, &command, sizeof(command)) || + command.config.status != B_USB_RAW_STATUS_SUCCESS) { + return _errno_to_libusb(command.config.status); + } + fUSBDevice->SetActiveConfiguration((uint8)config_index); + return LIBUSB_SUCCESS; +} + +int +USBDeviceHandle::SetAltSetting(uint8 inumber, uint8 alt) +{ + usb_raw_command command; + command.alternate.config_index = fUSBDevice->ActiveConfigurationIndex(); + command.alternate.interface_index = inumber; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_ACTIVE_ALT_INTERFACE_INDEX, &command, sizeof(command)) || + command.alternate.status != B_USB_RAW_STATUS_SUCCESS) { + usbi_err(NULL, "Error retrieving active alternate interface"); + return _errno_to_libusb(command.alternate.status); + } + if (command.alternate.alternate_info == (uint32)alt) { + usbi_dbg(NULL, "Setting alternate interface successful"); + return LIBUSB_SUCCESS; + } + command.alternate.alternate_info = alt; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_SET_ALT_INTERFACE, &command, sizeof(command)) || + command.alternate.status != B_USB_RAW_STATUS_SUCCESS) { //IF IOCTL FAILS DEVICE DISCONNECTED PROBABLY + usbi_err(NULL, "Error setting alternate interface"); + return _errno_to_libusb(command.alternate.status); + } + usbi_dbg(NULL, "Setting alternate interface successful"); + return LIBUSB_SUCCESS; +} + +int +USBDeviceHandle::ClearHalt(uint8 endpoint) +{ + usb_raw_command command; + command.control.request_type = USB_REQTYPE_ENDPOINT_OUT; + command.control.request = USB_REQUEST_CLEAR_FEATURE; + command.control.value = USB_FEATURE_ENDPOINT_HALT; + command.control.index = endpoint; + command.control.length = 0; + + if (ioctl(fRawFD, B_USB_RAW_COMMAND_CONTROL_TRANSFER, &command, sizeof(command)) || + command.control.status != B_USB_RAW_STATUS_SUCCESS) { + return _errno_to_libusb(command.control.status); + } + return LIBUSB_SUCCESS; +} + + +USBDevice::USBDevice(const char *path) + : + fClaimedInterfaces(0), + fConfigurationDescriptors(NULL), + fActiveConfiguration(0), //0? + fPath(NULL), + fEndpointToIndex(NULL), + fEndpointToInterface(NULL), + fInitCheck(false) +{ + fPath=strdup(path); + Initialise(); +} + +USBDevice::~USBDevice() +{ + free(fPath); + if (fConfigurationDescriptors) { + for (uint8 i = 0; i < fDeviceDescriptor.num_configurations; i++) { + if (fConfigurationDescriptors[i]) + delete fConfigurationDescriptors[i]; + } + delete[] fConfigurationDescriptors; + } + if (fEndpointToIndex) + delete[] fEndpointToIndex; + if (fEndpointToInterface) + delete[] fEndpointToInterface; +} + +bool +USBDevice::InitCheck() +{ + return fInitCheck; +} + +const char * +USBDevice::Location() const +{ + return fPath; +} + +uint8 +USBDevice::CountConfigurations() const +{ + return fDeviceDescriptor.num_configurations; +} + +const usb_device_descriptor * +USBDevice::Descriptor() const +{ + return &fDeviceDescriptor; +} + +const usb_configuration_descriptor * +USBDevice::ConfigurationDescriptor(uint8 index) const +{ + if (index > CountConfigurations()) + return NULL; + return (usb_configuration_descriptor *) fConfigurationDescriptors[index]; +} + +const usb_configuration_descriptor * +USBDevice::ActiveConfiguration() const +{ + return (usb_configuration_descriptor *) fConfigurationDescriptors[fActiveConfiguration]; +} + +uint8 +USBDevice::ActiveConfigurationIndex() const +{ + return fActiveConfiguration; +} + +int USBDevice::ClaimInterface(uint8 interface) +{ + if (interface > ActiveConfiguration()->number_interfaces) + return LIBUSB_ERROR_NOT_FOUND; + if (fClaimedInterfaces & (1U << interface)) + return LIBUSB_ERROR_BUSY; + fClaimedInterfaces |= (1U << interface); + return LIBUSB_SUCCESS; +} + +int USBDevice::ReleaseInterface(uint8 interface) +{ + fClaimedInterfaces &= ~(1U << interface); + return LIBUSB_SUCCESS; +} + +int +USBDevice::CheckInterfacesFree(uint8 config) +{ + if (fConfigToIndex.count(config) == 0) + return LIBUSB_ERROR_NOT_FOUND; + if (fClaimedInterfaces == 0) + return fConfigToIndex[config]; + return LIBUSB_ERROR_BUSY; +} + +void +USBDevice::SetActiveConfiguration(uint8 config_index) +{ + fActiveConfiguration = config_index; +} + +uint8 +USBDevice::EndpointToIndex(uint8 address) const +{ + return fEndpointToIndex[fActiveConfiguration][address]; +} + +uint8 +USBDevice::EndpointToInterface(uint8 address) const +{ + return fEndpointToInterface[fActiveConfiguration][address]; +} + +int +USBDevice::Initialise() //Do we need more error checking, etc? How to report? +{ + int fRawFD = open(fPath, O_RDWR | O_CLOEXEC); + if (fRawFD < 0) + return B_ERROR; + usb_raw_command command; + command.device.descriptor = &fDeviceDescriptor; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_DEVICE_DESCRIPTOR, &command, sizeof(command)) || + command.device.status != B_USB_RAW_STATUS_SUCCESS) { + close(fRawFD); + return B_ERROR; + } + + fConfigurationDescriptors = new(std::nothrow) unsigned char *[fDeviceDescriptor.num_configurations]; + fEndpointToIndex = new(std::nothrow) map<uint8,uint8> [fDeviceDescriptor.num_configurations]; + fEndpointToInterface = new(std::nothrow) map<uint8,uint8> [fDeviceDescriptor.num_configurations]; + for (uint8 i = 0; i < fDeviceDescriptor.num_configurations; i++) { + usb_configuration_descriptor tmp_config; + command.config.descriptor = &tmp_config; + command.config.config_index = i; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR, &command, sizeof(command)) || + command.config.status != B_USB_RAW_STATUS_SUCCESS) { + usbi_err(NULL, "failed retrieving configuration descriptor"); + close(fRawFD); + return B_ERROR; + } + fConfigToIndex[tmp_config.configuration_value] = i; + fConfigurationDescriptors[i] = new(std::nothrow) unsigned char[tmp_config.total_length]; + + command.config_etc.descriptor = (usb_configuration_descriptor*)fConfigurationDescriptors[i]; + command.config_etc.length = tmp_config.total_length; + command.config_etc.config_index = i; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR_ETC, &command, sizeof(command)) || + command.config_etc.status != B_USB_RAW_STATUS_SUCCESS) { + usbi_err(NULL, "failed retrieving full configuration descriptor"); + close(fRawFD); + return B_ERROR; + } + + for (uint8 j = 0; j < tmp_config.number_interfaces; j++) { + command.alternate.config_index = i; + command.alternate.interface_index = j; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_ALT_INTERFACE_COUNT, &command, sizeof(command)) || + command.config.status != B_USB_RAW_STATUS_SUCCESS) { + usbi_err(NULL, "failed retrieving number of alternate interfaces"); + close(fRawFD); + return B_ERROR; + } + uint8 num_alternate = (uint8)command.alternate.alternate_info; + for (uint8 k = 0; k < num_alternate; k++) { + usb_interface_descriptor tmp_interface; + command.interface_etc.config_index = i; + command.interface_etc.interface_index = j; + command.interface_etc.alternate_index = k; + command.interface_etc.descriptor = &tmp_interface; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR_ETC, &command, sizeof(command)) || + command.config.status != B_USB_RAW_STATUS_SUCCESS) { + usbi_err(NULL, "failed retrieving interface descriptor"); + close(fRawFD); + return B_ERROR; + } + for (uint8 l = 0; l < tmp_interface.num_endpoints; l++) { + usb_endpoint_descriptor tmp_endpoint; + command.endpoint_etc.config_index = i; + command.endpoint_etc.interface_index = j; + command.endpoint_etc.alternate_index = k; + command.endpoint_etc.endpoint_index = l; + command.endpoint_etc.descriptor = &tmp_endpoint; + if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR_ETC, &command, sizeof(command)) || + command.config.status != B_USB_RAW_STATUS_SUCCESS) { + usbi_err(NULL, "failed retrieving endpoint descriptor"); + close(fRawFD); + return B_ERROR; + } + fEndpointToIndex[i][tmp_endpoint.endpoint_address] = l; + fEndpointToInterface[i][tmp_endpoint.endpoint_address] = j; + } + } + } + } + close(fRawFD); + fInitCheck = true; + return B_OK; +} diff --git a/src/os/haiku_usb_raw.cpp b/src/os/haiku_usb_raw.cpp new file mode 100644 index 0000000..bce706c --- /dev/null +++ b/src/os/haiku_usb_raw.cpp @@ -0,0 +1,231 @@ +/* + * Haiku Backend for libusb + * Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + + +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <new> +#include <vector> + +#include "haiku_usb.h" + +USBRoster gUsbRoster; +int32 gInitCount = 0; + +static int haiku_get_config_descriptor(struct libusb_device *, uint8_t, + void *, size_t); + +static int +haiku_init(struct libusb_context *ctx) +{ + UNUSED(ctx); + if (atomic_add(&gInitCount, 1) == 0) + return gUsbRoster.Start(); + return LIBUSB_SUCCESS; +} + +static void +haiku_exit(struct libusb_context *ctx) +{ + UNUSED(ctx); + if (atomic_add(&gInitCount, -1) == 1) + gUsbRoster.Stop(); +} + +static int +haiku_open(struct libusb_device_handle *dev_handle) +{ + USBDevice *dev = *((USBDevice **)usbi_get_device_priv(dev_handle->dev)); + USBDeviceHandle *handle = new(std::nothrow) USBDeviceHandle(dev); + if (handle == NULL) + return LIBUSB_ERROR_NO_MEM; + if (handle->InitCheck() == false) { + delete handle; + return LIBUSB_ERROR_NO_DEVICE; + } + *((USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle)) = handle; + return LIBUSB_SUCCESS; +} + +static void +haiku_close(struct libusb_device_handle *dev_handle) +{ + USBDeviceHandle **pHandle = (USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle); + USBDeviceHandle *handle = *pHandle; + if (handle == NULL) + return; + delete handle; + *pHandle = NULL; +} + +static int +haiku_get_active_config_descriptor(struct libusb_device *device, void *buffer, size_t len) +{ + USBDevice *dev = *((USBDevice **)usbi_get_device_priv(device)); + return haiku_get_config_descriptor(device, dev->ActiveConfigurationIndex(), buffer, len); +} + +static int +haiku_get_config_descriptor(struct libusb_device *device, uint8_t config_index, void *buffer, size_t len) +{ + USBDevice *dev = *((USBDevice **)usbi_get_device_priv(device)); + const usb_configuration_descriptor *config = dev->ConfigurationDescriptor(config_index); + if (config == NULL) { + usbi_err(DEVICE_CTX(device), "failed getting configuration descriptor"); + return LIBUSB_ERROR_IO; + } + if (len > config->total_length) { + len = config->total_length; + } + memcpy(buffer, config, len); + return len; +} + +static int +haiku_set_configuration(struct libusb_device_handle *dev_handle, int config) +{ + USBDeviceHandle *handle= *((USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle)); + if (config <= 0) + return LIBUSB_ERROR_NOT_SUPPORTED; // cannot unconfigure + return handle->SetConfiguration((uint8)config); +} + +static int +haiku_claim_interface(struct libusb_device_handle *dev_handle, uint8_t interface_number) +{ + USBDeviceHandle *handle = *((USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle)); + return handle->ClaimInterface(interface_number); +} + +static int +haiku_set_altsetting(struct libusb_device_handle *dev_handle, uint8_t interface_number, uint8_t altsetting) +{ + USBDeviceHandle *handle = *((USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle)); + return handle->SetAltSetting(interface_number, altsetting); +} + +static int +haiku_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + USBDeviceHandle *handle = *((USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle)); + return handle->ClearHalt(endpoint); +} + +static int +haiku_release_interface(struct libusb_device_handle *dev_handle, uint8_t interface_number) +{ + USBDeviceHandle *handle = *((USBDeviceHandle **)usbi_get_device_handle_priv(dev_handle)); + haiku_set_altsetting(dev_handle, interface_number, 0); + return handle->ReleaseInterface(interface_number); +} + +static int +haiku_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *fLibusbTransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + USBDeviceHandle *fDeviceHandle = *((USBDeviceHandle **)usbi_get_device_handle_priv(fLibusbTransfer->dev_handle)); + return fDeviceHandle->SubmitTransfer(itransfer); +} + +static int +haiku_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *fLibusbTransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + USBDeviceHandle *fDeviceHandle = *((USBDeviceHandle **)usbi_get_device_handle_priv(fLibusbTransfer->dev_handle)); + return fDeviceHandle->CancelTransfer(*((USBTransfer **)usbi_get_transfer_priv(itransfer))); +} + +static int +haiku_handle_transfer_completion(struct usbi_transfer *itransfer) +{ + USBTransfer **pTransfer = (USBTransfer **)usbi_get_transfer_priv(itransfer); + USBTransfer *transfer = *pTransfer; + + usbi_mutex_lock(&itransfer->lock); + if (transfer->IsCancelled()) { + delete transfer; + *pTransfer = NULL; + usbi_mutex_unlock(&itransfer->lock); + if (itransfer->transferred < 0) + itransfer->transferred = 0; + return usbi_handle_transfer_cancellation(itransfer); + } + libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; + if (itransfer->transferred < 0) { + usbi_err(ITRANSFER_CTX(itransfer), "error in transfer"); + status = LIBUSB_TRANSFER_ERROR; + itransfer->transferred = 0; + } + delete transfer; + *pTransfer = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); +} + +const struct usbi_os_backend usbi_backend = { + /*.name =*/ "Haiku usbfs", + /*.caps =*/ 0, + /*.init =*/ haiku_init, + /*.exit =*/ haiku_exit, + /*.set_option =*/ NULL, + /*.get_device_list =*/ NULL, + /*.hotplug_poll =*/ NULL, + /*.wrap_sys_device =*/ NULL, + /*.open =*/ haiku_open, + /*.close =*/ haiku_close, + + /*.get_active_config_descriptor =*/ haiku_get_active_config_descriptor, + /*.get_config_descriptor =*/ haiku_get_config_descriptor, + /*.get_config_descriptor_by_value =*/ NULL, + + /*.get_configuration =*/ NULL, + /*.set_configuration =*/ haiku_set_configuration, + + /*.claim_interface =*/ haiku_claim_interface, + /*.release_interface =*/ haiku_release_interface, + /*.set_interface_altsetting =*/ haiku_set_altsetting, + + /*.clear_halt =*/ haiku_clear_halt, + /*.reset_device =*/ NULL, + + /*.alloc_streams =*/ NULL, + /*.free_streams =*/ NULL, + + /*.dev_mem_alloc =*/ NULL, + /*.dev_mem_free =*/ NULL, + + /*.kernel_driver_active =*/ NULL, + /*.detach_kernel_driver =*/ NULL, + /*.attach_kernel_driver =*/ NULL, + + /*.destroy_device =*/ NULL, + + /*.submit_transfer =*/ haiku_submit_transfer, + /*.cancel_transfer =*/ haiku_cancel_transfer, + /*.clear_transfer_priv =*/ NULL, + + /*.handle_events =*/ NULL, + /*.handle_transfer_completion =*/ haiku_handle_transfer_completion, + + /*.context_priv_size =*/ 0, + /*.device_priv_size =*/ sizeof(USBDevice *), + /*.device_handle_priv_size =*/ sizeof(USBDeviceHandle *), + /*.transfer_priv_size =*/ sizeof(USBTransfer *), +}; diff --git a/src/os/haiku_usb_raw.h b/src/os/haiku_usb_raw.h new file mode 100644 index 0000000..d371f01 --- /dev/null +++ b/src/os/haiku_usb_raw.h @@ -0,0 +1,188 @@ +/* + * Copyright 2006-2008, Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ + +#ifndef _USB_RAW_H_ +#define _USB_RAW_H_ + +#include <USB3.h> + +#define B_USB_RAW_PROTOCOL_VERSION 0x0015 +#define B_USB_RAW_ACTIVE_ALTERNATE 0xffffffff + +typedef enum { + B_USB_RAW_COMMAND_GET_VERSION = 0x1000, + + B_USB_RAW_COMMAND_GET_DEVICE_DESCRIPTOR = 0x2000, + B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR, + B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR, + B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR, + B_USB_RAW_COMMAND_GET_STRING_DESCRIPTOR, + B_USB_RAW_COMMAND_GET_GENERIC_DESCRIPTOR, + B_USB_RAW_COMMAND_GET_ALT_INTERFACE_COUNT, + B_USB_RAW_COMMAND_GET_ACTIVE_ALT_INTERFACE_INDEX, + B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR_ETC, + B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR_ETC, + B_USB_RAW_COMMAND_GET_GENERIC_DESCRIPTOR_ETC, + B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR_ETC, + + B_USB_RAW_COMMAND_SET_CONFIGURATION = 0x3000, + B_USB_RAW_COMMAND_SET_FEATURE, + B_USB_RAW_COMMAND_CLEAR_FEATURE, + B_USB_RAW_COMMAND_GET_STATUS, + B_USB_RAW_COMMAND_GET_DESCRIPTOR, + B_USB_RAW_COMMAND_SET_ALT_INTERFACE, + + B_USB_RAW_COMMAND_CONTROL_TRANSFER = 0x4000, + B_USB_RAW_COMMAND_INTERRUPT_TRANSFER, + B_USB_RAW_COMMAND_BULK_TRANSFER, + B_USB_RAW_COMMAND_ISOCHRONOUS_TRANSFER +} usb_raw_command_id; + + +typedef enum { + B_USB_RAW_STATUS_SUCCESS = 0, + + B_USB_RAW_STATUS_FAILED, + B_USB_RAW_STATUS_ABORTED, + B_USB_RAW_STATUS_STALLED, + B_USB_RAW_STATUS_CRC_ERROR, + B_USB_RAW_STATUS_TIMEOUT, + + B_USB_RAW_STATUS_INVALID_CONFIGURATION, + B_USB_RAW_STATUS_INVALID_INTERFACE, + B_USB_RAW_STATUS_INVALID_ENDPOINT, + B_USB_RAW_STATUS_INVALID_STRING, + + B_USB_RAW_STATUS_NO_MEMORY +} usb_raw_command_status; + + +typedef union { + struct { + status_t status; + } version; + + struct { + status_t status; + usb_device_descriptor *descriptor; + } device; + + struct { + status_t status; + usb_configuration_descriptor *descriptor; + uint32 config_index; + } config; + + struct { + status_t status; + usb_configuration_descriptor *descriptor; + uint32 config_index; + size_t length; + } config_etc; + + struct { + status_t status; + uint32 alternate_info; + uint32 config_index; + uint32 interface_index; + } alternate; + + struct { + status_t status; + usb_interface_descriptor *descriptor; + uint32 config_index; + uint32 interface_index; + } interface; + + struct { + status_t status; + usb_interface_descriptor *descriptor; + uint32 config_index; + uint32 interface_index; + uint32 alternate_index; + } interface_etc; + + struct { + status_t status; + usb_endpoint_descriptor *descriptor; + uint32 config_index; + uint32 interface_index; + uint32 endpoint_index; + } endpoint; + + struct { + status_t status; + usb_endpoint_descriptor *descriptor; + uint32 config_index; + uint32 interface_index; + uint32 alternate_index; + uint32 endpoint_index; + } endpoint_etc; + + struct { + status_t status; + usb_descriptor *descriptor; + uint32 config_index; + uint32 interface_index; + uint32 generic_index; + size_t length; + } generic; + + struct { + status_t status; + usb_descriptor *descriptor; + uint32 config_index; + uint32 interface_index; + uint32 alternate_index; + uint32 generic_index; + size_t length; + } generic_etc; + + struct { + status_t status; + usb_string_descriptor *descriptor; + uint32 string_index; + size_t length; + } string; + + struct { + status_t status; + uint8 type; + uint8 index; + uint16 language_id; + void *data; + size_t length; + } descriptor; + + struct { + status_t status; + uint8 request_type; + uint8 request; + uint16 value; + uint16 index; + uint16 length; + void *data; + } control; + + struct { + status_t status; + uint32 interface; + uint32 endpoint; + void *data; + size_t length; + } transfer; + + struct { + status_t status; + uint32 interface; + uint32 endpoint; + void *data; + size_t length; + usb_iso_packet_descriptor *packet_descriptors; + uint32 packet_count; + } isochronous; +} usb_raw_command; + +#endif // _USB_RAW_H_ diff --git a/src/os/linux_netlink.c b/src/os/linux_netlink.c new file mode 100644 index 0000000..899084f --- /dev/null +++ b/src/os/linux_netlink.c @@ -0,0 +1,401 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright (c) 2013 Nathan Hjelm <hjelmn@mac.com> + * Copyright (c) 2016 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" +#include "linux_usbfs.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_ASM_TYPES_H +#include <asm/types.h> +#endif +#include <sys/socket.h> +#include <linux/netlink.h> + +#define NL_GROUP_KERNEL 1 + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif + +#ifndef SOCK_NONBLOCK +#define SOCK_NONBLOCK 0 +#endif + +static int linux_netlink_socket = -1; +static usbi_event_t netlink_control_event = USBI_INVALID_EVENT; +static pthread_t libusb_linux_event_thread; + +static void *linux_netlink_event_thread_main(void *arg); + +static int set_fd_cloexec_nb(int fd, int socktype) +{ + int flags; + +#if defined(FD_CLOEXEC) + /* Make sure the netlink socket file descriptor is marked as CLOEXEC */ + if (!(socktype & SOCK_CLOEXEC)) { + flags = fcntl(fd, F_GETFD); + if (flags == -1) { + usbi_err(NULL, "failed to get netlink fd flags, errno=%d", errno); + return -1; + } + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + usbi_err(NULL, "failed to set netlink fd flags, errno=%d", errno); + return -1; + } + } +#endif + + /* Make sure the netlink socket is non-blocking */ + if (!(socktype & SOCK_NONBLOCK)) { + flags = fcntl(fd, F_GETFL); + if (flags == -1) { + usbi_err(NULL, "failed to get netlink fd status flags, errno=%d", errno); + return -1; + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + usbi_err(NULL, "failed to set netlink fd status flags, errno=%d", errno); + return -1; + } + } + + return 0; +} + +int linux_netlink_start_event_monitor(void) +{ + struct sockaddr_nl sa_nl = { .nl_family = AF_NETLINK, .nl_groups = NL_GROUP_KERNEL }; + int socktype = SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC; + int opt = 1; + int ret; + + linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT); + if (linux_netlink_socket == -1 && errno == EINVAL) { + usbi_dbg(NULL, "failed to create netlink socket of type %d, attempting SOCK_RAW", socktype); + socktype = SOCK_RAW; + linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT); + } + + if (linux_netlink_socket == -1) { + usbi_err(NULL, "failed to create netlink socket, errno=%d", errno); + goto err; + } + + ret = set_fd_cloexec_nb(linux_netlink_socket, socktype); + if (ret == -1) + goto err_close_socket; + + ret = bind(linux_netlink_socket, (struct sockaddr *)&sa_nl, sizeof(sa_nl)); + if (ret == -1) { + usbi_err(NULL, "failed to bind netlink socket, errno=%d", errno); + goto err_close_socket; + } + + ret = setsockopt(linux_netlink_socket, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt)); + if (ret == -1) { + usbi_err(NULL, "failed to set netlink socket SO_PASSCRED option, errno=%d", errno); + goto err_close_socket; + } + + ret = usbi_create_event(&netlink_control_event); + if (ret) { + usbi_err(NULL, "failed to create netlink control event"); + goto err_close_socket; + } + + ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL); + if (ret != 0) { + usbi_err(NULL, "failed to create netlink event thread (%d)", ret); + goto err_destroy_event; + } + + return LIBUSB_SUCCESS; + +err_destroy_event: + usbi_destroy_event(&netlink_control_event); + netlink_control_event = (usbi_event_t)USBI_INVALID_EVENT; +err_close_socket: + close(linux_netlink_socket); + linux_netlink_socket = -1; +err: + return LIBUSB_ERROR_OTHER; +} + +int linux_netlink_stop_event_monitor(void) +{ + int ret; + + assert(linux_netlink_socket != -1); + + /* Signal the control event and wait for the thread to exit */ + usbi_signal_event(&netlink_control_event); + + ret = pthread_join(libusb_linux_event_thread, NULL); + if (ret) + usbi_warn(NULL, "failed to join netlink event thread (%d)", ret); + + usbi_destroy_event(&netlink_control_event); + netlink_control_event = (usbi_event_t)USBI_INVALID_EVENT; + + close(linux_netlink_socket); + linux_netlink_socket = -1; + + return LIBUSB_SUCCESS; +} + +static const char *netlink_message_parse(const char *buffer, size_t len, const char *key) +{ + const char *end = buffer + len; + size_t keylen = strlen(key); + + while (buffer < end && *buffer) { + if (strncmp(buffer, key, keylen) == 0 && buffer[keylen] == '=') + return buffer + keylen + 1; + buffer += strlen(buffer) + 1; + } + + return NULL; +} + +/* parse parts of netlink message common to both libudev and the kernel */ +static int linux_netlink_parse(const char *buffer, size_t len, int *detached, + const char **sys_name, uint8_t *busnum, uint8_t *devaddr) +{ + const char *tmp, *slash; + + errno = 0; + + *sys_name = NULL; + *detached = 0; + *busnum = 0; + *devaddr = 0; + + tmp = netlink_message_parse(buffer, len, "ACTION"); + if (!tmp) { + return -1; + } else if (strcmp(tmp, "remove") == 0) { + *detached = 1; + } else if (strcmp(tmp, "add") != 0) { + usbi_dbg(NULL, "unknown device action %s", tmp); + return -1; + } + + /* check that this is a usb message */ + tmp = netlink_message_parse(buffer, len, "SUBSYSTEM"); + if (!tmp || strcmp(tmp, "usb") != 0) { + /* not usb. ignore */ + return -1; + } + + /* check that this is an actual usb device */ + tmp = netlink_message_parse(buffer, len, "DEVTYPE"); + if (!tmp || strcmp(tmp, "usb_device") != 0) { + /* not usb. ignore */ + return -1; + } + + tmp = netlink_message_parse(buffer, len, "BUSNUM"); + if (tmp) { + *busnum = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff); + if (errno) { + errno = 0; + return -1; + } + + tmp = netlink_message_parse(buffer, len, "DEVNUM"); + if (NULL == tmp) + return -1; + + *devaddr = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff); + if (errno) { + errno = 0; + return -1; + } + } else { + /* no bus number. try "DEVICE" */ + tmp = netlink_message_parse(buffer, len, "DEVICE"); + if (!tmp) { + /* not usb. ignore */ + return -1; + } + + /* Parse a device path such as /dev/bus/usb/003/004 */ + slash = strrchr(tmp, '/'); + if (!slash) + return -1; + + *busnum = (uint8_t)(strtoul(slash - 3, NULL, 10) & 0xff); + if (errno) { + errno = 0; + return -1; + } + + *devaddr = (uint8_t)(strtoul(slash + 1, NULL, 10) & 0xff); + if (errno) { + errno = 0; + return -1; + } + + return 0; + } + + tmp = netlink_message_parse(buffer, len, "DEVPATH"); + if (!tmp) + return -1; + + slash = strrchr(tmp, '/'); + if (slash) + *sys_name = slash + 1; + + /* found a usb device */ + return 0; +} + +static int linux_netlink_read_message(void) +{ + char cred_buffer[CMSG_SPACE(sizeof(struct ucred))]; + char msg_buffer[2048]; + const char *sys_name = NULL; + uint8_t busnum, devaddr; + int detached, r; + ssize_t len; + struct cmsghdr *cmsg; + struct ucred *cred; + struct sockaddr_nl sa_nl; + struct iovec iov = { .iov_base = msg_buffer, .iov_len = sizeof(msg_buffer) }; + struct msghdr msg = { + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cred_buffer, .msg_controllen = sizeof(cred_buffer), + .msg_name = &sa_nl, .msg_namelen = sizeof(sa_nl) + }; + + /* read netlink message */ + len = recvmsg(linux_netlink_socket, &msg, 0); + if (len == -1) { + if (errno != EAGAIN && errno != EINTR) + usbi_err(NULL, "error receiving message from netlink, errno=%d", errno); + return -1; + } + + if (len < 32 || (msg.msg_flags & MSG_TRUNC)) { + usbi_err(NULL, "invalid netlink message length"); + return -1; + } + + if (sa_nl.nl_groups != NL_GROUP_KERNEL || sa_nl.nl_pid != 0) { + usbi_dbg(NULL, "ignoring netlink message from unknown group/PID (%u/%u)", + (unsigned int)sa_nl.nl_groups, (unsigned int)sa_nl.nl_pid); + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) { + usbi_dbg(NULL, "ignoring netlink message with no sender credentials"); + return -1; + } + + cred = (struct ucred *)CMSG_DATA(cmsg); + if (cred->uid != 0) { + usbi_dbg(NULL, "ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid); + return -1; + } + + r = linux_netlink_parse(msg_buffer, (size_t)len, &detached, &sys_name, &busnum, &devaddr); + if (r) + return r; + + usbi_dbg(NULL, "netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s", + busnum, devaddr, sys_name, detached ? "yes" : "no"); + + /* signal device is available (or not) to all contexts */ + if (detached) + linux_device_disconnected(busnum, devaddr); + else + linux_hotplug_enumerate(busnum, devaddr, sys_name); + + return 0; +} + +static void *linux_netlink_event_thread_main(void *arg) +{ + struct pollfd fds[] = { + { .fd = USBI_EVENT_OS_HANDLE(&netlink_control_event), + .events = USBI_EVENT_POLL_EVENTS }, + { .fd = linux_netlink_socket, + .events = POLLIN }, + }; + int r; + + UNUSED(arg); + +#if defined(HAVE_PTHREAD_SETNAME_NP) + r = pthread_setname_np(pthread_self(), "libusb_event"); + if (r) + usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r); +#endif + + usbi_dbg(NULL, "netlink event thread entering"); + + while (1) { + r = poll(fds, 2, -1); + if (r == -1) { + /* check for temporary failure */ + if (errno == EINTR) + continue; + usbi_err(NULL, "poll() failed, errno=%d", errno); + break; + } + if (fds[0].revents) { + /* activity on control event, exit */ + break; + } + if (fds[1].revents) { + usbi_mutex_static_lock(&linux_hotplug_lock); + linux_netlink_read_message(); + usbi_mutex_static_unlock(&linux_hotplug_lock); + } + } + + usbi_dbg(NULL, "netlink event thread exiting"); + + return NULL; +} + +void linux_netlink_hotplug_poll(void) +{ + int r; + + usbi_mutex_static_lock(&linux_hotplug_lock); + do { + r = linux_netlink_read_message(); + } while (r == 0); + usbi_mutex_static_unlock(&linux_hotplug_lock); +} diff --git a/src/os/linux_udev.c b/src/os/linux_udev.c new file mode 100644 index 0000000..9ec9eb1 --- /dev/null +++ b/src/os/linux_udev.c @@ -0,0 +1,321 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright (c) 2012-2013 Nathan Hjelm <hjelmn@mac.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" +#include "linux_usbfs.h" + +#include <errno.h> +#include <fcntl.h> +#include <libudev.h> +#include <poll.h> +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +/* udev context */ +static struct udev *udev_ctx = NULL; +static int udev_monitor_fd = -1; +static usbi_event_t udev_control_event = USBI_INVALID_EVENT; +static struct udev_monitor *udev_monitor = NULL; +static pthread_t linux_event_thread; + +static void udev_hotplug_event(struct udev_device *udev_dev); +static void *linux_udev_event_thread_main(void *arg); + +int linux_udev_start_event_monitor(void) +{ + int r; + + assert(udev_ctx == NULL); + udev_ctx = udev_new(); + if (!udev_ctx) { + usbi_err(NULL, "could not create udev context"); + goto err; + } + + udev_monitor = udev_monitor_new_from_netlink(udev_ctx, "udev"); + if (!udev_monitor) { + usbi_err(NULL, "could not initialize udev monitor"); + goto err_free_ctx; + } + + r = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", "usb_device"); + if (r) { + usbi_err(NULL, "could not initialize udev monitor filter for \"usb\" subsystem"); + goto err_free_monitor; + } + + if (udev_monitor_enable_receiving(udev_monitor)) { + usbi_err(NULL, "failed to enable the udev monitor"); + goto err_free_monitor; + } + + udev_monitor_fd = udev_monitor_get_fd(udev_monitor); + +#if defined(FD_CLOEXEC) + /* Make sure the udev file descriptor is marked as CLOEXEC */ + r = fcntl(udev_monitor_fd, F_GETFD); + if (r == -1) { + usbi_err(NULL, "failed to get udev monitor fd flags, errno=%d", errno); + goto err_free_monitor; + } + if (!(r & FD_CLOEXEC)) { + if (fcntl(udev_monitor_fd, F_SETFD, r | FD_CLOEXEC) == -1) { + usbi_err(NULL, "failed to set udev monitor fd flags, errno=%d", errno); + goto err_free_monitor; + } + } +#endif + + /* Some older versions of udev are not non-blocking by default, + * so make sure this is set */ + r = fcntl(udev_monitor_fd, F_GETFL); + if (r == -1) { + usbi_err(NULL, "failed to get udev monitor fd status flags, errno=%d", errno); + goto err_free_monitor; + } + if (!(r & O_NONBLOCK)) { + if (fcntl(udev_monitor_fd, F_SETFL, r | O_NONBLOCK) == -1) { + usbi_err(NULL, "failed to set udev monitor fd status flags, errno=%d", errno); + goto err_free_monitor; + } + } + + r = usbi_create_event(&udev_control_event); + if (r) { + usbi_err(NULL, "failed to create udev control event"); + goto err_free_monitor; + } + + r = pthread_create(&linux_event_thread, NULL, linux_udev_event_thread_main, NULL); + if (r) { + usbi_err(NULL, "failed to create hotplug event thread (%d)", r); + goto err_destroy_event; + } + + return LIBUSB_SUCCESS; + +err_destroy_event: + usbi_destroy_event(&udev_control_event); + udev_control_event = (usbi_event_t)USBI_INVALID_EVENT; +err_free_monitor: + udev_monitor_unref(udev_monitor); + udev_monitor = NULL; + udev_monitor_fd = -1; +err_free_ctx: + udev_unref(udev_ctx); +err: + udev_ctx = NULL; + return LIBUSB_ERROR_OTHER; +} + +int linux_udev_stop_event_monitor(void) +{ + int r; + + assert(udev_ctx != NULL); + assert(udev_monitor != NULL); + assert(udev_monitor_fd != -1); + + /* Signal the control event and wait for the thread to exit */ + usbi_signal_event(&udev_control_event); + + r = pthread_join(linux_event_thread, NULL); + if (r) + usbi_warn(NULL, "failed to join hotplug event thread (%d)", r); + + usbi_destroy_event(&udev_control_event); + udev_control_event = (usbi_event_t)USBI_INVALID_EVENT; + + /* Release the udev monitor */ + udev_monitor_unref(udev_monitor); + udev_monitor = NULL; + udev_monitor_fd = -1; + + /* Clean up the udev context */ + udev_unref(udev_ctx); + udev_ctx = NULL; + + return LIBUSB_SUCCESS; +} + +static void *linux_udev_event_thread_main(void *arg) +{ + struct pollfd fds[] = { + { .fd = USBI_EVENT_OS_HANDLE(&udev_control_event), + .events = USBI_EVENT_POLL_EVENTS }, + { .fd = udev_monitor_fd, + .events = POLLIN }, + }; + struct udev_device *udev_dev; + int r; + + UNUSED(arg); + +#if defined(HAVE_PTHREAD_SETNAME_NP) + r = pthread_setname_np(pthread_self(), "libusb_event"); + if (r) + usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r); +#endif + + usbi_dbg(NULL, "udev event thread entering"); + + while (1) { + r = poll(fds, 2, -1); + if (r == -1) { + /* check for temporary failure */ + if (errno == EINTR) + continue; + usbi_err(NULL, "poll() failed, errno=%d", errno); + break; + } + if (fds[0].revents) { + /* activity on control event, exit */ + break; + } + if (fds[1].revents) { + usbi_mutex_static_lock(&linux_hotplug_lock); + udev_dev = udev_monitor_receive_device(udev_monitor); + if (udev_dev) + udev_hotplug_event(udev_dev); + usbi_mutex_static_unlock(&linux_hotplug_lock); + } + } + + usbi_dbg(NULL, "udev event thread exiting"); + + return NULL; +} + +static int udev_device_info(struct libusb_context *ctx, int detached, + struct udev_device *udev_dev, uint8_t *busnum, + uint8_t *devaddr, const char **sys_name) { + const char *dev_node; + + dev_node = udev_device_get_devnode(udev_dev); + if (!dev_node) { + return LIBUSB_ERROR_OTHER; + } + + *sys_name = udev_device_get_sysname(udev_dev); + if (!*sys_name) { + return LIBUSB_ERROR_OTHER; + } + + return linux_get_device_address(ctx, detached, busnum, devaddr, + dev_node, *sys_name, -1); +} + +static void udev_hotplug_event(struct udev_device *udev_dev) +{ + const char *udev_action; + const char *sys_name = NULL; + uint8_t busnum = 0, devaddr = 0; + int detached; + int r; + + do { + udev_action = udev_device_get_action(udev_dev); + if (!udev_action) { + break; + } + + detached = !strncmp(udev_action, "remove", 6); + + r = udev_device_info(NULL, detached, udev_dev, &busnum, &devaddr, &sys_name); + if (LIBUSB_SUCCESS != r) { + break; + } + + usbi_dbg(NULL, "udev hotplug event. action: %s.", udev_action); + + if (strncmp(udev_action, "add", 3) == 0) { + linux_hotplug_enumerate(busnum, devaddr, sys_name); + } else if (detached) { + linux_device_disconnected(busnum, devaddr); + } else if (strncmp(udev_action, "bind", 4) == 0) { + /* silently ignore "known unhandled" action */ + } else { + usbi_err(NULL, "ignoring udev action %s", udev_action); + } + } while (0); + + udev_device_unref(udev_dev); +} + +int linux_udev_scan_devices(struct libusb_context *ctx) +{ + struct udev_enumerate *enumerator; + struct udev_list_entry *devices, *entry; + struct udev_device *udev_dev; + const char *sys_name; + int r; + + assert(udev_ctx != NULL); + + enumerator = udev_enumerate_new(udev_ctx); + if (NULL == enumerator) { + usbi_err(ctx, "error creating udev enumerator"); + return LIBUSB_ERROR_OTHER; + } + + udev_enumerate_add_match_subsystem(enumerator, "usb"); + udev_enumerate_add_match_property(enumerator, "DEVTYPE", "usb_device"); + udev_enumerate_scan_devices(enumerator); + devices = udev_enumerate_get_list_entry(enumerator); + + entry = NULL; + udev_list_entry_foreach(entry, devices) { + const char *path = udev_list_entry_get_name(entry); + uint8_t busnum = 0, devaddr = 0; + + udev_dev = udev_device_new_from_syspath(udev_ctx, path); + + r = udev_device_info(ctx, 0, udev_dev, &busnum, &devaddr, &sys_name); + if (r) { + udev_device_unref(udev_dev); + continue; + } + + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + udev_device_unref(udev_dev); + } + + udev_enumerate_unref(enumerator); + + return LIBUSB_SUCCESS; +} + +void linux_udev_hotplug_poll(void) +{ + struct udev_device *udev_dev; + + usbi_mutex_static_lock(&linux_hotplug_lock); + do { + udev_dev = udev_monitor_receive_device(udev_monitor); + if (udev_dev) { + usbi_dbg(NULL, "Handling hotplug event from hotplug_poll"); + udev_hotplug_event(udev_dev); + } + } while (udev_dev); + usbi_mutex_static_unlock(&linux_hotplug_lock); +} diff --git a/src/os/linux_usbfs.c b/src/os/linux_usbfs.c new file mode 100644 index 0000000..c300675 --- /dev/null +++ b/src/os/linux_usbfs.c @@ -0,0 +1,2807 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright © 2013 Nathan Hjelm <hjelmn@mac.com> + * Copyright © 2012-2013 Hans de Goede <hdegoede@redhat.com> + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" +#include "linux_usbfs.h" + +#include <alloca.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/utsname.h> +#include <sys/vfs.h> +#include <unistd.h> + +/* sysfs vs usbfs: + * opening a usbfs node causes the device to be resumed, so we attempt to + * avoid this during enumeration. + * + * sysfs allows us to read the kernel's in-memory copies of device descriptors + * and so forth, avoiding the need to open the device: + * - The binary "descriptors" file contains all config descriptors since + * 2.6.26, commit 217a9081d8e69026186067711131b77f0ce219ed + * - The binary "descriptors" file was added in 2.6.23, commit + * 69d42a78f935d19384d1f6e4f94b65bb162b36df, but it only contains the + * active config descriptors + * - The "busnum" file was added in 2.6.22, commit + * 83f7d958eab2fbc6b159ee92bf1493924e1d0f72 + * - The "devnum" file has been present since pre-2.6.18 + * - the "bConfigurationValue" file has been present since pre-2.6.18 + * + * If we have bConfigurationValue, busnum, and devnum, then we can determine + * the active configuration without having to open the usbfs node in RDWR mode. + * The busnum file is important as that is the only way we can relate sysfs + * devices to usbfs nodes. + * + * If we also have all descriptors, we can obtain the device descriptor and + * configuration without touching usbfs at all. + */ + +/* endianness for multi-byte fields: + * + * Descriptors exposed by usbfs have the multi-byte fields in the device + * descriptor as host endian. Multi-byte fields in the other descriptors are + * bus-endian. The kernel documentation says otherwise, but it is wrong. + * + * In sysfs all descriptors are bus-endian. + */ + +#define USBDEV_PATH "/dev" +#define USB_DEVTMPFS_PATH "/dev/bus/usb" + +/* use usbdev*.* device names in /dev instead of the usbfs bus directories */ +static int usbdev_names = 0; + +/* Linux has changed the maximum length of an individual isochronous packet + * over time. Initially this limit was 1,023 bytes, but Linux 2.6.18 + * (commit 3612242e527eb47ee4756b5350f8bdf791aa5ede) increased this value to + * 8,192 bytes to support higher bandwidth devices. Linux 3.10 + * (commit e2e2f0ea1c935edcf53feb4c4c8fdb4f86d57dd9) further increased this + * value to 49,152 bytes to support super speed devices. Linux 5.2 + * (commit 8a1dbc8d91d3d1602282c7e6b4222c7759c916fa) even further increased + * this value to 98,304 bytes to support super speed plus devices. + */ +static unsigned int max_iso_packet_len = 0; + +/* is sysfs available (mounted) ? */ +static int sysfs_available = -1; + +/* how many times have we initted (and not exited) ? */ +static int init_count = 0; + +/* have no authority to operate usb device directly */ +static int no_enumeration = 0; + +/* Serialize scan-devices, event-thread, and poll */ +usbi_mutex_static_t linux_hotplug_lock = USBI_MUTEX_INITIALIZER; + +static int linux_scan_devices(struct libusb_context *ctx); +static int detach_kernel_driver_and_claim(struct libusb_device_handle *, uint8_t); + +#if !defined(HAVE_LIBUDEV) +static int linux_default_scan_devices(struct libusb_context *ctx); +#endif + +struct kernel_version { + int major; + int minor; + int sublevel; +}; + +struct config_descriptor { + struct usbi_configuration_descriptor *desc; + size_t actual_len; +}; + +struct linux_device_priv { + char *sysfs_dir; + void *descriptors; + size_t descriptors_len; + struct config_descriptor *config_descriptors; + int active_config; /* cache val for !sysfs_available */ +}; + +struct linux_device_handle_priv { + int fd; + int fd_removed; + int fd_keep; + uint32_t caps; +}; + +enum reap_action { + NORMAL = 0, + /* submission failed after the first URB, so await cancellation/completion + * of all the others */ + SUBMIT_FAILED, + + /* cancelled by user or timeout */ + CANCELLED, + + /* completed multi-URB transfer in non-final URB */ + COMPLETED_EARLY, + + /* one or more urbs encountered a low-level error */ + ERROR, +}; + +struct linux_transfer_priv { + union { + struct usbfs_urb *urbs; + struct usbfs_urb **iso_urbs; + }; + + enum reap_action reap_action; + int num_urbs; + int num_retired; + enum libusb_transfer_status reap_status; + + /* next iso packet in user-supplied transfer to be populated */ + int iso_packet_offset; +}; + +static int dev_has_config0(struct libusb_device *dev) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + uint8_t idx; + + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + config = &priv->config_descriptors[idx]; + if (config->desc->bConfigurationValue == 0) + return 1; + } + + return 0; +} + +static int get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + char path[24]; + int fd; + + if (usbdev_names) + sprintf(path, USBDEV_PATH "/usbdev%u.%u", + dev->bus_number, dev->device_address); + else + sprintf(path, USB_DEVTMPFS_PATH "/%03u/%03u", + dev->bus_number, dev->device_address); + + fd = open(path, mode | O_CLOEXEC); + if (fd != -1) + return fd; /* Success */ + + if (errno == ENOENT) { + const long delay_ms = 10L; + const struct timespec delay_ts = { 0L, delay_ms * 1000L * 1000L }; + + if (!silent) + usbi_err(ctx, "File doesn't exist, wait %ld ms and try again", delay_ms); + + /* Wait 10ms for USB device path creation.*/ + nanosleep(&delay_ts, NULL); + + fd = open(path, mode | O_CLOEXEC); + if (fd != -1) + return fd; /* Success */ + } + + if (!silent) { + usbi_err(ctx, "libusb couldn't open USB device %s, errno=%d", path, errno); + if (errno == EACCES && mode == O_RDWR) + usbi_err(ctx, "libusb requires write access to USB device nodes"); + } + + if (errno == EACCES) + return LIBUSB_ERROR_ACCESS; + if (errno == ENOENT) + return LIBUSB_ERROR_NO_DEVICE; + return LIBUSB_ERROR_IO; +} + +/* check dirent for a /dev/usbdev%d.%d name + * optionally return bus/device on success */ +static int is_usbdev_entry(const char *name, uint8_t *bus_p, uint8_t *dev_p) +{ + int busnum, devnum; + + if (sscanf(name, "usbdev%d.%d", &busnum, &devnum) != 2) + return 0; + if (busnum < 0 || busnum > UINT8_MAX || devnum < 0 || devnum > UINT8_MAX) { + usbi_dbg(NULL, "invalid usbdev format '%s'", name); + return 0; + } + + usbi_dbg(NULL, "found: %s", name); + if (bus_p) + *bus_p = (uint8_t)busnum; + if (dev_p) + *dev_p = (uint8_t)devnum; + return 1; +} + +static const char *find_usbfs_path(void) +{ + const char *path; + DIR *dir; + struct dirent *entry; + + path = USB_DEVTMPFS_PATH; + dir = opendir(path); + if (dir) { + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + /* We assume if we find any files that it must be the right place */ + break; + } + + closedir(dir); + + if (entry) + return path; + } + + /* look for /dev/usbdev*.* if the normal place fails */ + path = USBDEV_PATH; + dir = opendir(path); + if (dir) { + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + if (is_usbdev_entry(entry->d_name, NULL, NULL)) { + /* found one; that's enough */ + break; + } + } + + closedir(dir); + + if (entry) { + usbdev_names = 1; + return path; + } + } + +/* On udev based systems without any usb-devices /dev/bus/usb will not + * exist. So if we've not found anything and we're using udev for hotplug + * simply assume /dev/bus/usb rather then making libusb_init fail. + * Make the same assumption for Android where SELinux policies might block us + * from reading /dev on newer devices. */ +#if defined(HAVE_LIBUDEV) || defined(__ANDROID__) + return USB_DEVTMPFS_PATH; +#else + return NULL; +#endif +} + +static int get_kernel_version(struct libusb_context *ctx, + struct kernel_version *ver) +{ + struct utsname uts; + int atoms; + + if (uname(&uts) < 0) { + usbi_err(ctx, "uname failed, errno=%d", errno); + return -1; + } + + atoms = sscanf(uts.release, "%d.%d.%d", &ver->major, &ver->minor, &ver->sublevel); + if (atoms < 2) { + usbi_err(ctx, "failed to parse uname release '%s'", uts.release); + return -1; + } + + if (atoms < 3) + ver->sublevel = -1; + + usbi_dbg(ctx, "reported kernel version is %s", uts.release); + + return 0; +} + +static int kernel_version_ge(const struct kernel_version *ver, + int major, int minor, int sublevel) +{ + if (ver->major > major) + return 1; + else if (ver->major < major) + return 0; + + /* kmajor == major */ + if (ver->minor > minor) + return 1; + else if (ver->minor < minor) + return 0; + + /* kminor == minor */ + if (ver->sublevel == -1) + return sublevel == 0; + + return ver->sublevel >= sublevel; +} + +static int op_init(struct libusb_context *ctx) +{ + struct kernel_version kversion; + const char *usbfs_path; + int r; + + if (get_kernel_version(ctx, &kversion) < 0) + return LIBUSB_ERROR_OTHER; + + if (!kernel_version_ge(&kversion, 2, 6, 32)) { + usbi_err(ctx, "kernel version is too old (reported as %d.%d.%d)", + kversion.major, kversion.minor, + kversion.sublevel != -1 ? kversion.sublevel : 0); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + usbfs_path = find_usbfs_path(); + if (!usbfs_path) { + usbi_err(ctx, "could not find usbfs"); + return LIBUSB_ERROR_OTHER; + } + + usbi_dbg(ctx, "found usbfs at %s", usbfs_path); + + if (!max_iso_packet_len) { + if (kernel_version_ge(&kversion, 5, 2, 0)) + max_iso_packet_len = 98304; + else if (kernel_version_ge(&kversion, 3, 10, 0)) + max_iso_packet_len = 49152; + else + max_iso_packet_len = 8192; + } + + usbi_dbg(ctx, "max iso packet length is (likely) %u bytes", max_iso_packet_len); + + if (sysfs_available == -1) { + struct statfs statfsbuf; + + r = statfs(SYSFS_MOUNT_PATH, &statfsbuf); + if (r == 0 && statfsbuf.f_type == SYSFS_MAGIC) { + usbi_dbg(ctx, "sysfs is available"); + sysfs_available = 1; + } else { + usbi_warn(ctx, "sysfs not mounted"); + sysfs_available = 0; + } + } + + if (no_enumeration) { + return LIBUSB_SUCCESS; + } + + r = LIBUSB_SUCCESS; + if (init_count == 0) { + /* start up hotplug event handler */ + r = linux_start_event_monitor(); + } + if (r == LIBUSB_SUCCESS) { + r = linux_scan_devices(ctx); + if (r == LIBUSB_SUCCESS) + init_count++; + else if (init_count == 0) + linux_stop_event_monitor(); + } else { + usbi_err(ctx, "error starting hotplug event monitor"); + } + + return r; +} + +static void op_exit(struct libusb_context *ctx) +{ + UNUSED(ctx); + + if (no_enumeration) { + return; + } + + assert(init_count != 0); + if (!--init_count) { + /* tear down event handler */ + linux_stop_event_monitor(); + } +} + +static int op_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) +{ + UNUSED(ctx); + UNUSED(ap); + + if (option == LIBUSB_OPTION_NO_DEVICE_DISCOVERY) { + usbi_dbg(ctx, "no enumeration will be performed"); + no_enumeration = 1; + return LIBUSB_SUCCESS; + } + + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int linux_scan_devices(struct libusb_context *ctx) +{ + int ret; + + usbi_mutex_static_lock(&linux_hotplug_lock); + +#if defined(HAVE_LIBUDEV) + ret = linux_udev_scan_devices(ctx); +#else + ret = linux_default_scan_devices(ctx); +#endif + + usbi_mutex_static_unlock(&linux_hotplug_lock); + + return ret; +} + +static void op_hotplug_poll(void) +{ + linux_hotplug_poll(); +} + +static int open_sysfs_attr(struct libusb_context *ctx, + const char *sysfs_dir, const char *attr) +{ + char filename[256]; + int fd; + + snprintf(filename, sizeof(filename), SYSFS_DEVICE_PATH "/%s/%s", sysfs_dir, attr); + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + if (errno == ENOENT) { + /* File doesn't exist. Assume the device has been + disconnected (see trac ticket #70). */ + return LIBUSB_ERROR_NO_DEVICE; + } + usbi_err(ctx, "open %s failed, errno=%d", filename, errno); + return LIBUSB_ERROR_IO; + } + + return fd; +} + +/* Note only suitable for attributes which always read >= 0, < 0 is error */ +static int read_sysfs_attr(struct libusb_context *ctx, + const char *sysfs_dir, const char *attr, int max_value, int *value_p) +{ + char buf[20], *endptr; + long value; + ssize_t r; + int fd; + + fd = open_sysfs_attr(ctx, sysfs_dir, attr); + if (fd < 0) + return fd; + + r = read(fd, buf, sizeof(buf) - 1); + if (r < 0) { + r = errno; + close(fd); + if (r == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + usbi_err(ctx, "attribute %s read failed, errno=%zd", attr, r); + return LIBUSB_ERROR_IO; + } + close(fd); + + if (r == 0) { + /* Certain attributes (e.g. bConfigurationValue) are not + * populated if the device is not configured. */ + *value_p = -1; + return 0; + } + + /* The kernel does *not* NUL-terminate the string, but every attribute + * should be terminated with a newline character. */ + if (!isdigit(buf[0])) { + usbi_err(ctx, "attribute %s doesn't have numeric value?", attr); + return LIBUSB_ERROR_IO; + } else if (buf[r - 1] != '\n') { + usbi_warn(ctx, "attribute %s doesn't end with newline?", attr); + } else { + /* Remove the terminating newline character */ + r--; + } + buf[r] = '\0'; + + errno = 0; + value = strtol(buf, &endptr, 10); + if (value < 0 || value > (long)max_value || errno) { + usbi_err(ctx, "attribute %s contains an invalid value: '%s'", attr, buf); + return LIBUSB_ERROR_INVALID_PARAM; + } else if (*endptr != '\0') { + /* Consider the value to be valid if the remainder is a '.' + * character followed by numbers. This occurs, for example, + * when reading the "speed" attribute for a low-speed device + * (e.g. "1.5") */ + if (*endptr == '.' && isdigit(*(endptr + 1))) { + endptr++; + while (isdigit(*endptr)) + endptr++; + } + if (*endptr != '\0') { + usbi_err(ctx, "attribute %s contains an invalid value: '%s'", attr, buf); + return LIBUSB_ERROR_INVALID_PARAM; + } + } + + *value_p = (int)value; + return 0; +} + +static int sysfs_scan_device(struct libusb_context *ctx, const char *devname) +{ + uint8_t busnum, devaddr; + int ret; + + ret = linux_get_device_address(ctx, 0, &busnum, &devaddr, NULL, devname, -1); + if (ret != LIBUSB_SUCCESS) + return ret; + + return linux_enumerate_device(ctx, busnum, devaddr, devname); +} + +/* read the bConfigurationValue for a device */ +static int sysfs_get_active_config(struct libusb_device *dev, int *config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + + return read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue", + UINT8_MAX, config); +} + +int linux_get_device_address(struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, const char *dev_node, + const char *sys_name, int fd) +{ + int sysfs_val; + int r; + + usbi_dbg(ctx, "getting address for device: %s detached: %d", sys_name, detached); + /* can't use sysfs to read the bus and device number if the + * device has been detached */ + if (!sysfs_available || detached || !sys_name) { + if (!dev_node && fd >= 0) { + char *fd_path = alloca(PATH_MAX); + char proc_path[32]; + + /* try to retrieve the device node from fd */ + sprintf(proc_path, "/proc/self/fd/%d", fd); + r = readlink(proc_path, fd_path, PATH_MAX - 1); + if (r > 0) { + fd_path[r] = '\0'; + dev_node = fd_path; + } + } + + if (!dev_node) + return LIBUSB_ERROR_OTHER; + + /* will this work with all supported kernel versions? */ + if (!strncmp(dev_node, "/dev/bus/usb", 12)) + sscanf(dev_node, "/dev/bus/usb/%hhu/%hhu", busnum, devaddr); + else + return LIBUSB_ERROR_OTHER; + + return LIBUSB_SUCCESS; + } + + usbi_dbg(ctx, "scan %s", sys_name); + + r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val); + if (r < 0) + return r; + *busnum = (uint8_t)sysfs_val; + + r = read_sysfs_attr(ctx, sys_name, "devnum", UINT8_MAX, &sysfs_val); + if (r < 0) + return r; + *devaddr = (uint8_t)sysfs_val; + + usbi_dbg(ctx, "bus=%u dev=%u", *busnum, *devaddr); + + return LIBUSB_SUCCESS; +} + +/* Return offset of the next config descriptor */ +static int seek_to_next_config(struct libusb_context *ctx, + uint8_t *buffer, size_t len) +{ + struct usbi_descriptor_header *header; + int offset; + + /* Start seeking past the config descriptor */ + offset = LIBUSB_DT_CONFIG_SIZE; + buffer += LIBUSB_DT_CONFIG_SIZE; + len -= LIBUSB_DT_CONFIG_SIZE; + + while (len > 0) { + if (len < 2) { + usbi_err(ctx, "short descriptor read %zu/2", len); + return LIBUSB_ERROR_IO; + } + + header = (struct usbi_descriptor_header *)buffer; + if (header->bDescriptorType == LIBUSB_DT_CONFIG) + return offset; + + if (len < header->bLength) { + usbi_err(ctx, "bLength overflow by %zu bytes", + (size_t)header->bLength - len); + return LIBUSB_ERROR_IO; + } + + offset += header->bLength; + buffer += header->bLength; + len -= header->bLength; + } + + usbi_err(ctx, "config descriptor not found"); + return LIBUSB_ERROR_IO; +} + +static int parse_config_descriptors(struct libusb_device *dev) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct usbi_device_descriptor *device_desc; + uint8_t idx, num_configs; + uint8_t *buffer; + size_t remaining; + + device_desc = priv->descriptors; + num_configs = device_desc->bNumConfigurations; + + if (num_configs == 0) + return 0; /* no configurations? */ + + priv->config_descriptors = malloc(num_configs * sizeof(priv->config_descriptors[0])); + if (!priv->config_descriptors) + return LIBUSB_ERROR_NO_MEM; + + buffer = (uint8_t *)priv->descriptors + LIBUSB_DT_DEVICE_SIZE; + remaining = priv->descriptors_len - LIBUSB_DT_DEVICE_SIZE; + + for (idx = 0; idx < num_configs; idx++) { + struct usbi_configuration_descriptor *config_desc; + uint16_t config_len; + + if (remaining < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "short descriptor read %zu/%d", + remaining, LIBUSB_DT_CONFIG_SIZE); + return LIBUSB_ERROR_IO; + } + + config_desc = (struct usbi_configuration_descriptor *)buffer; + if (config_desc->bDescriptorType != LIBUSB_DT_CONFIG) { + usbi_err(ctx, "descriptor is not a config desc (type 0x%02x)", + config_desc->bDescriptorType); + return LIBUSB_ERROR_IO; + } else if (config_desc->bLength < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid descriptor bLength %u", + config_desc->bLength); + return LIBUSB_ERROR_IO; + } + + config_len = libusb_le16_to_cpu(config_desc->wTotalLength); + if (config_len < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid wTotalLength %u", config_len); + return LIBUSB_ERROR_IO; + } + + if (priv->sysfs_dir) { + /* + * In sysfs wTotalLength is ignored, instead the kernel returns a + * config descriptor with verified bLength fields, with descriptors + * with an invalid bLength removed. + */ + uint16_t sysfs_config_len; + int offset; + + if (num_configs > 1 && idx < num_configs - 1) { + offset = seek_to_next_config(ctx, buffer, remaining); + if (offset < 0) + return offset; + sysfs_config_len = (uint16_t)offset; + } else { + sysfs_config_len = (uint16_t)remaining; + } + + if (config_len != sysfs_config_len) { + usbi_warn(ctx, "config length mismatch wTotalLength %u real %u", + config_len, sysfs_config_len); + config_len = sysfs_config_len; + } + } else { + /* + * In usbfs the config descriptors are wTotalLength bytes apart, + * with any short reads from the device appearing as holes in the file. + */ + if (config_len > remaining) { + usbi_warn(ctx, "short descriptor read %zu/%u", remaining, config_len); + config_len = (uint16_t)remaining; + } + } + + if (config_desc->bConfigurationValue == 0) + usbi_warn(ctx, "device has configuration 0"); + + priv->config_descriptors[idx].desc = config_desc; + priv->config_descriptors[idx].actual_len = config_len; + + buffer += config_len; + remaining -= config_len; + } + + return LIBUSB_SUCCESS; +} + +static int op_get_config_descriptor_by_value(struct libusb_device *dev, + uint8_t value, void **buffer) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + uint8_t idx; + + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + config = &priv->config_descriptors[idx]; + if (config->desc->bConfigurationValue == value) { + *buffer = config->desc; + return (int)config->actual_len; + } + } + + return LIBUSB_ERROR_NOT_FOUND; +} + +static int op_get_active_config_descriptor(struct libusb_device *dev, + void *buffer, size_t len) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + void *config_desc; + int active_config; + int r; + + if (priv->sysfs_dir) { + r = sysfs_get_active_config(dev, &active_config); + if (r < 0) + return r; + } else { + /* Use cached bConfigurationValue */ + active_config = priv->active_config; + } + + if (active_config == -1) { + usbi_err(DEVICE_CTX(dev), "device unconfigured"); + return LIBUSB_ERROR_NOT_FOUND; + } + + r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc); + if (r < 0) + return r; + + len = MIN(len, (size_t)r); + memcpy(buffer, config_desc, len); + return len; +} + +static int op_get_config_descriptor(struct libusb_device *dev, + uint8_t config_index, void *buffer, size_t len) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + + if (config_index >= dev->device_descriptor.bNumConfigurations) + return LIBUSB_ERROR_NOT_FOUND; + + config = &priv->config_descriptors[config_index]; + len = MIN(len, config->actual_len); + memcpy(buffer, config->desc, len); + return len; +} + +/* send a control message to retrieve active configuration */ +static int usbfs_get_active_config(struct libusb_device *dev, int fd) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + uint8_t active_config = 0; + int r; + + struct usbfs_ctrltransfer ctrl = { + .bmRequestType = LIBUSB_ENDPOINT_IN, + .bRequest = LIBUSB_REQUEST_GET_CONFIGURATION, + .wValue = 0, + .wIndex = 0, + .wLength = 1, + .timeout = 1000, + .data = &active_config + }; + + r = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl); + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + /* we hit this error path frequently with buggy devices :( */ + usbi_warn(DEVICE_CTX(dev), "get configuration failed, errno=%d", errno); + + /* assume the current configuration is the first one if we have + * the configuration descriptors, otherwise treat the device + * as unconfigured. */ + if (priv->config_descriptors) + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; + else + priv->active_config = -1; + } else if (active_config == 0) { + if (dev_has_config0(dev)) { + /* some buggy devices have a configuration 0, but we're + * reaching into the corner of a corner case here. */ + priv->active_config = 0; + } else { + priv->active_config = -1; + } + } else { + priv->active_config = (int)active_config; + } + + return LIBUSB_SUCCESS; +} + +static enum libusb_speed usbfs_get_speed(struct libusb_context *ctx, int fd) +{ + int r; + + r = ioctl(fd, IOCTL_USBFS_GET_SPEED, NULL); + switch (r) { + case USBFS_SPEED_UNKNOWN: return LIBUSB_SPEED_UNKNOWN; + case USBFS_SPEED_LOW: return LIBUSB_SPEED_LOW; + case USBFS_SPEED_FULL: return LIBUSB_SPEED_FULL; + case USBFS_SPEED_HIGH: return LIBUSB_SPEED_HIGH; + case USBFS_SPEED_WIRELESS: return LIBUSB_SPEED_HIGH; + case USBFS_SPEED_SUPER: return LIBUSB_SPEED_SUPER; + case USBFS_SPEED_SUPER_PLUS: return LIBUSB_SPEED_SUPER_PLUS; + default: + usbi_warn(ctx, "Error getting device speed: %d", r); + } + + return LIBUSB_SPEED_UNKNOWN; +} + +static int initialize_device(struct libusb_device *dev, uint8_t busnum, + uint8_t devaddr, const char *sysfs_dir, int wrapped_fd) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct libusb_context *ctx = DEVICE_CTX(dev); + size_t alloc_len; + int fd, speed, r; + ssize_t nb; + + dev->bus_number = busnum; + dev->device_address = devaddr; + + if (sysfs_dir) { + priv->sysfs_dir = strdup(sysfs_dir); + if (!priv->sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + + /* Note speed can contain 1.5, in this case read_sysfs_attr() + will stop parsing at the '.' and return 1 */ + if (read_sysfs_attr(ctx, sysfs_dir, "speed", INT_MAX, &speed) == 0) { + switch (speed) { + case 1: dev->speed = LIBUSB_SPEED_LOW; break; + case 12: dev->speed = LIBUSB_SPEED_FULL; break; + case 480: dev->speed = LIBUSB_SPEED_HIGH; break; + case 5000: dev->speed = LIBUSB_SPEED_SUPER; break; + case 10000: dev->speed = LIBUSB_SPEED_SUPER_PLUS; break; + default: + usbi_warn(ctx, "unknown device speed: %d Mbps", speed); + } + } + } else if (wrapped_fd >= 0) { + dev->speed = usbfs_get_speed(ctx, wrapped_fd); + } + + /* cache descriptors in memory */ + if (sysfs_dir) { + fd = open_sysfs_attr(ctx, sysfs_dir, "descriptors"); + } else if (wrapped_fd < 0) { + fd = get_usbfs_fd(dev, O_RDONLY, 0); + } else { + fd = wrapped_fd; + r = lseek(fd, 0, SEEK_SET); + if (r < 0) { + usbi_err(ctx, "lseek failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + } + if (fd < 0) + return fd; + + alloc_len = 0; + do { + const size_t desc_read_length = 256; + uint8_t *read_ptr; + + alloc_len += desc_read_length; + priv->descriptors = usbi_reallocf(priv->descriptors, alloc_len); + if (!priv->descriptors) { + if (fd != wrapped_fd) + close(fd); + return LIBUSB_ERROR_NO_MEM; + } + read_ptr = (uint8_t *)priv->descriptors + priv->descriptors_len; + /* usbfs has holes in the file */ + if (!sysfs_dir) + memset(read_ptr, 0, desc_read_length); + nb = read(fd, read_ptr, desc_read_length); + if (nb < 0) { + usbi_err(ctx, "read descriptor failed, errno=%d", errno); + if (fd != wrapped_fd) + close(fd); + return LIBUSB_ERROR_IO; + } + priv->descriptors_len += (size_t)nb; + } while (priv->descriptors_len == alloc_len); + + if (fd != wrapped_fd) + close(fd); + + if (priv->descriptors_len < LIBUSB_DT_DEVICE_SIZE) { + usbi_err(ctx, "short descriptor read (%zu)", priv->descriptors_len); + return LIBUSB_ERROR_IO; + } + + r = parse_config_descriptors(dev); + if (r < 0) + return r; + + memcpy(&dev->device_descriptor, priv->descriptors, LIBUSB_DT_DEVICE_SIZE); + + if (sysfs_dir) { + /* sysfs descriptors are in bus-endian format */ + usbi_localize_device_descriptor(&dev->device_descriptor); + return LIBUSB_SUCCESS; + } + + /* cache active config */ + if (wrapped_fd < 0) + fd = get_usbfs_fd(dev, O_RDWR, 1); + else + fd = wrapped_fd; + if (fd < 0) { + /* cannot send a control message to determine the active + * config. just assume the first one is active. */ + usbi_warn(ctx, "Missing rw usbfs access; cannot determine " + "active configuration descriptor"); + if (priv->config_descriptors) + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; + else + priv->active_config = -1; /* No config dt */ + + return LIBUSB_SUCCESS; + } + + r = usbfs_get_active_config(dev, fd); + if (fd != wrapped_fd) + close(fd); + + return r; +} + +static int linux_get_parent_info(struct libusb_device *dev, const char *sysfs_dir) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_device *it; + char *parent_sysfs_dir, *tmp; + int ret, add_parent = 1; + + /* XXX -- can we figure out the topology when using usbfs? */ + if (!sysfs_dir || !strncmp(sysfs_dir, "usb", 3)) { + /* either using usbfs or finding the parent of a root hub */ + return LIBUSB_SUCCESS; + } + + parent_sysfs_dir = strdup(sysfs_dir); + if (!parent_sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + + if ((tmp = strrchr(parent_sysfs_dir, '.')) || + (tmp = strrchr(parent_sysfs_dir, '-'))) { + dev->port_number = atoi(tmp + 1); + *tmp = '\0'; + } else { + usbi_warn(ctx, "Can not parse sysfs_dir: %s, no parent info", + parent_sysfs_dir); + free(parent_sysfs_dir); + return LIBUSB_SUCCESS; + } + + /* is the parent a root hub? */ + if (!strchr(parent_sysfs_dir, '-')) { + tmp = parent_sysfs_dir; + ret = asprintf(&parent_sysfs_dir, "usb%s", tmp); + free(tmp); + if (ret < 0) + return LIBUSB_ERROR_NO_MEM; + } + +retry: + /* find the parent in the context */ + usbi_mutex_lock(&ctx->usb_devs_lock); + for_each_device(ctx, it) { + struct linux_device_priv *priv = usbi_get_device_priv(it); + + if (priv->sysfs_dir) { + if (!strcmp(priv->sysfs_dir, parent_sysfs_dir)) { + dev->parent_dev = libusb_ref_device(it); + break; + } + } + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + + if (!dev->parent_dev && add_parent) { + usbi_dbg(ctx, "parent_dev %s not enumerated yet, enumerating now", + parent_sysfs_dir); + sysfs_scan_device(ctx, parent_sysfs_dir); + add_parent = 0; + goto retry; + } + + usbi_dbg(ctx, "dev %p (%s) has parent %p (%s) port %u", dev, sysfs_dir, + dev->parent_dev, parent_sysfs_dir, dev->port_number); + + free(parent_sysfs_dir); + + return LIBUSB_SUCCESS; +} + +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, const char *sysfs_dir) +{ + unsigned long session_id; + struct libusb_device *dev; + int r; + + /* FIXME: session ID is not guaranteed unique as addresses can wrap and + * will be reused. instead we should add a simple sysfs attribute with + * a session ID. */ + session_id = busnum << 8 | devaddr; + usbi_dbg(ctx, "busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id); + + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + /* device already exists in the context */ + usbi_dbg(ctx, "session_id %lu already exists", session_id); + libusb_unref_device(dev); + return LIBUSB_SUCCESS; + } + + usbi_dbg(ctx, "allocating new device for %u/%u (session %lu)", + busnum, devaddr, session_id); + dev = usbi_alloc_device(ctx, session_id); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, sysfs_dir, -1); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; + + r = linux_get_parent_info(dev, sysfs_dir); + if (r < 0) + goto out; +out: + if (r < 0) + libusb_unref_device(dev); + else + usbi_connect_device(dev); + + return r; +} + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name) +{ + struct libusb_context *ctx; + + usbi_mutex_static_lock(&active_contexts_lock); + for_each_context(ctx) { + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +void linux_device_disconnected(uint8_t busnum, uint8_t devaddr) +{ + struct libusb_context *ctx; + struct libusb_device *dev; + unsigned long session_id = busnum << 8 | devaddr; + + usbi_mutex_static_lock(&active_contexts_lock); + for_each_context(ctx) { + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + usbi_disconnect_device(dev); + libusb_unref_device(dev); + } else { + usbi_dbg(ctx, "device not found for session %lx", session_id); + } + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +#if !defined(HAVE_LIBUDEV) +static int parse_u8(const char *str, uint8_t *val_p) +{ + char *endptr; + long num; + + errno = 0; + num = strtol(str, &endptr, 10); + if (num < 0 || num > UINT8_MAX || errno) + return 0; + if (endptr == str || *endptr != '\0') + return 0; + + *val_p = (uint8_t)num; + return 1; +} + +/* open a bus directory and adds all discovered devices to the context */ +static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum) +{ + DIR *dir; + char dirpath[20]; + struct dirent *entry; + int r = LIBUSB_ERROR_IO; + + sprintf(dirpath, USB_DEVTMPFS_PATH "/%03u", busnum); + usbi_dbg(ctx, "%s", dirpath); + dir = opendir(dirpath); + if (!dir) { + usbi_err(ctx, "opendir '%s' failed, errno=%d", dirpath, errno); + /* FIXME: should handle valid race conditions like hub unplugged + * during directory iteration - this is not an error */ + return r; + } + + while ((entry = readdir(dir))) { + uint8_t devaddr; + + if (entry->d_name[0] == '.') + continue; + + if (!parse_u8(entry->d_name, &devaddr)) { + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); + continue; + } + + if (linux_enumerate_device(ctx, busnum, devaddr, NULL)) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + + r = 0; + } + + closedir(dir); + return r; +} + +static int usbfs_get_device_list(struct libusb_context *ctx) +{ + struct dirent *entry; + DIR *buses; + uint8_t busnum, devaddr; + int r = 0; + + if (usbdev_names) + buses = opendir(USBDEV_PATH); + else + buses = opendir(USB_DEVTMPFS_PATH); + + if (!buses) { + usbi_err(ctx, "opendir buses failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(buses))) { + if (entry->d_name[0] == '.') + continue; + + if (usbdev_names) { + if (!is_usbdev_entry(entry->d_name, &busnum, &devaddr)) + continue; + + r = linux_enumerate_device(ctx, busnum, devaddr, NULL); + if (r < 0) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + } else { + if (!parse_u8(entry->d_name, &busnum)) { + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); + continue; + } + + r = usbfs_scan_busdir(ctx, busnum); + if (r < 0) + break; + } + } + + closedir(buses); + return r; + +} + +static int sysfs_get_device_list(struct libusb_context *ctx) +{ + DIR *devices = opendir(SYSFS_DEVICE_PATH); + struct dirent *entry; + int num_devices = 0; + int num_enumerated = 0; + + if (!devices) { + usbi_err(ctx, "opendir devices failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(devices))) { + if ((!isdigit(entry->d_name[0]) && strncmp(entry->d_name, "usb", 3)) + || strchr(entry->d_name, ':')) + continue; + + num_devices++; + + if (sysfs_scan_device(ctx, entry->d_name)) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + + num_enumerated++; + } + + closedir(devices); + + /* successful if at least one device was enumerated or no devices were found */ + if (num_enumerated || !num_devices) + return LIBUSB_SUCCESS; + else + return LIBUSB_ERROR_IO; +} + +static int linux_default_scan_devices(struct libusb_context *ctx) +{ + /* we can retrieve device list and descriptors from sysfs or usbfs. + * sysfs is preferable, because if we use usbfs we end up resuming + * any autosuspended USB devices. however, sysfs is not available + * everywhere, so we need a usbfs fallback too. + */ + if (sysfs_available) + return sysfs_get_device_list(ctx); + else + return usbfs_get_device_list(ctx); +} +#endif + +static int initialize_handle(struct libusb_device_handle *handle, int fd) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r; + + hpriv->fd = fd; + + r = ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps); + if (r < 0) { + if (errno == ENOTTY) + usbi_dbg(HANDLE_CTX(handle), "getcap not available"); + else + usbi_err(HANDLE_CTX(handle), "getcap failed, errno=%d", errno); + hpriv->caps = USBFS_CAP_BULK_CONTINUATION; + } + + return usbi_add_event_source(HANDLE_CTX(handle), hpriv->fd, POLLOUT); +} + +static int op_wrap_sys_device(struct libusb_context *ctx, + struct libusb_device_handle *handle, intptr_t sys_dev) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = (int)sys_dev; + uint8_t busnum, devaddr; + struct usbfs_connectinfo ci; + struct libusb_device *dev; + int r; + + r = linux_get_device_address(ctx, 1, &busnum, &devaddr, NULL, NULL, fd); + if (r < 0) { + r = ioctl(fd, IOCTL_USBFS_CONNECTINFO, &ci); + if (r < 0) { + usbi_err(ctx, "connectinfo failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + /* There is no ioctl to get the bus number. We choose 0 here + * as linux starts numbering buses from 1. */ + busnum = 0; + devaddr = ci.devnum; + } + + /* Session id is unused as we do not add the device to the list of + * connected devices. */ + usbi_dbg(ctx, "allocating new device for fd %d", fd); + dev = usbi_alloc_device(ctx, 0); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, NULL, fd); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; + /* Consider the device as connected, but do not add it to the managed + * device list. */ + usbi_atomic_store(&dev->attached, 1); + handle->dev = dev; + + r = initialize_handle(handle, fd); + hpriv->fd_keep = 1; + +out: + if (r < 0) + libusb_unref_device(dev); + return r; +} + +static int op_open(struct libusb_device_handle *handle) +{ + int fd, r; + + fd = get_usbfs_fd(handle->dev, O_RDWR, 0); + if (fd < 0) { + if (fd == LIBUSB_ERROR_NO_DEVICE) { + /* device will still be marked as attached if hotplug monitor thread + * hasn't processed remove event yet */ + usbi_mutex_static_lock(&linux_hotplug_lock); + if (usbi_atomic_load(&handle->dev->attached)) { + usbi_dbg(HANDLE_CTX(handle), "open failed with no device, but device still attached"); + linux_device_disconnected(handle->dev->bus_number, + handle->dev->device_address); + } + usbi_mutex_static_unlock(&linux_hotplug_lock); + } + return fd; + } + + r = initialize_handle(handle, fd); + if (r < 0) + close(fd); + + return r; +} + +static void op_close(struct libusb_device_handle *dev_handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(dev_handle); + + /* fd may have already been removed by POLLERR condition in op_handle_events() */ + if (!hpriv->fd_removed) + usbi_remove_event_source(HANDLE_CTX(dev_handle), hpriv->fd); + if (!hpriv->fd_keep) + close(hpriv->fd); +} + +static int op_get_configuration(struct libusb_device_handle *handle, + uint8_t *config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(handle->dev); + int active_config; + int r; + + if (priv->sysfs_dir) { + r = sysfs_get_active_config(handle->dev, &active_config); + } else { + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + + r = usbfs_get_active_config(handle->dev, hpriv->fd); + if (r == LIBUSB_SUCCESS) + active_config = priv->active_config; + } + if (r < 0) + return r; + + if (active_config == -1) { + usbi_warn(HANDLE_CTX(handle), "device unconfigured"); + active_config = 0; + } + + *config = (uint8_t)active_config; + + return 0; +} + +static int op_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(handle->dev); + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_SETCONFIGURATION, &config); + + if (r < 0) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "set configuration failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + /* if necessary, update our cached active config descriptor */ + if (!priv->sysfs_dir) { + if (config == 0 && !dev_has_config0(handle->dev)) + config = -1; + + priv->active_config = config; + } + + return LIBUSB_SUCCESS; +} + +static int claim_interface(struct libusb_device_handle *handle, unsigned int iface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_CLAIMINTERFACE, &iface); + + if (r < 0) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "claim interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int release_interface(struct libusb_device_handle *handle, unsigned int iface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_RELEASEINTERFACE, &iface); + + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "release interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int op_set_interface(struct libusb_device_handle *handle, uint8_t interface, + uint8_t altsetting) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_setinterface setintf; + int r; + + setintf.interface = interface; + setintf.altsetting = altsetting; + r = ioctl(fd, IOCTL_USBFS_SETINTERFACE, &setintf); + if (r < 0) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "set interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_clear_halt(struct libusb_device_handle *handle, + unsigned char endpoint) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + unsigned int _endpoint = endpoint; + int r = ioctl(fd, IOCTL_USBFS_CLEAR_HALT, &_endpoint); + + if (r < 0) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "clear halt failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_reset_device(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r, ret = 0; + uint8_t i; + + /* Doing a device reset will cause the usbfs driver to get unbound + * from any interfaces it is bound to. By voluntarily unbinding + * the usbfs driver ourself, we stop the kernel from rebinding + * the interface after reset (which would end up with the interface + * getting bound to the in kernel driver if any). */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (handle->claimed_interfaces & (1UL << i)) + release_interface(handle, i); + } + + usbi_mutex_lock(&handle->lock); + r = ioctl(fd, IOCTL_USBFS_RESET, NULL); + if (r < 0) { + if (errno == ENODEV) { + ret = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + usbi_err(HANDLE_CTX(handle), "reset failed, errno=%d", errno); + ret = LIBUSB_ERROR_OTHER; + goto out; + } + + /* And re-claim any interfaces which were claimed before the reset */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (!(handle->claimed_interfaces & (1UL << i))) + continue; + /* + * A driver may have completed modprobing during + * IOCTL_USBFS_RESET, and bound itself as soon as + * IOCTL_USBFS_RESET released the device lock + */ + r = detach_kernel_driver_and_claim(handle, i); + if (r) { + usbi_warn(HANDLE_CTX(handle), "failed to re-claim interface %u after reset: %s", + i, libusb_error_name(r)); + handle->claimed_interfaces &= ~(1UL << i); + ret = LIBUSB_ERROR_NOT_FOUND; + } + } +out: + usbi_mutex_unlock(&handle->lock); + return ret; +} + +static int do_streams_ioctl(struct libusb_device_handle *handle, long req, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r, fd = hpriv->fd; + struct usbfs_streams *streams; + + if (num_endpoints > 30) /* Max 15 in + 15 out eps */ + return LIBUSB_ERROR_INVALID_PARAM; + + streams = malloc(sizeof(*streams) + num_endpoints); + if (!streams) + return LIBUSB_ERROR_NO_MEM; + + streams->num_streams = num_streams; + streams->num_eps = num_endpoints; + memcpy(streams->eps, endpoints, num_endpoints); + + r = ioctl(fd, req, streams); + + free(streams); + + if (r < 0) { + if (errno == ENOTTY) + return LIBUSB_ERROR_NOT_SUPPORTED; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "streams-ioctl failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return r; +} + +static int op_alloc_streams(struct libusb_device_handle *handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + return do_streams_ioctl(handle, IOCTL_USBFS_ALLOC_STREAMS, + num_streams, endpoints, num_endpoints); +} + +static int op_free_streams(struct libusb_device_handle *handle, + unsigned char *endpoints, int num_endpoints) +{ + return do_streams_ioctl(handle, IOCTL_USBFS_FREE_STREAMS, 0, + endpoints, num_endpoints); +} + +static void *op_dev_mem_alloc(struct libusb_device_handle *handle, size_t len) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + void *buffer; + + buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, hpriv->fd, 0); + if (buffer == MAP_FAILED) { + usbi_err(HANDLE_CTX(handle), "alloc dev mem failed, errno=%d", errno); + return NULL; + } + return buffer; +} + +static int op_dev_mem_free(struct libusb_device_handle *handle, void *buffer, + size_t len) +{ + if (munmap(buffer, len) != 0) { + usbi_err(HANDLE_CTX(handle), "free dev mem failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } else { + return LIBUSB_SUCCESS; + } +} + +static int op_kernel_driver_active(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_getdriver getdrv; + int r; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r < 0) { + if (errno == ENODATA) + return 0; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "get driver failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return strcmp(getdrv.driver, "usbfs") != 0; +} + +static int op_detach_kernel_driver(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_ioctl command; + struct usbfs_getdriver getdrv; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_DISCONNECT; + command.data = NULL; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r == 0 && !strcmp(getdrv.driver, "usbfs")) + return LIBUSB_ERROR_NOT_FOUND; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "detach failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_attach_kernel_driver(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_ioctl command; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_CONNECT; + command.data = NULL; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + + usbi_err(HANDLE_CTX(handle), "attach failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } else if (r == 0) { + return LIBUSB_ERROR_NOT_FOUND; + } + + return 0; +} + +static int detach_kernel_driver_and_claim(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + struct usbfs_disconnect_claim dc; + int r, fd = hpriv->fd; + + dc.interface = interface; + strcpy(dc.driver, "usbfs"); + dc.flags = USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER; + r = ioctl(fd, IOCTL_USBFS_DISCONNECT_CLAIM, &dc); + if (r == 0) + return 0; + switch (errno) { + case ENOTTY: + break; + case EBUSY: + return LIBUSB_ERROR_BUSY; + case EINVAL: + return LIBUSB_ERROR_INVALID_PARAM; + case ENODEV: + return LIBUSB_ERROR_NO_DEVICE; + default: + usbi_err(HANDLE_CTX(handle), "disconnect-and-claim failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + /* Fallback code for kernels which don't support the + disconnect-and-claim ioctl */ + r = op_detach_kernel_driver(handle, interface); + if (r != 0 && r != LIBUSB_ERROR_NOT_FOUND) + return r; + + return claim_interface(handle, interface); +} + +static int op_claim_interface(struct libusb_device_handle *handle, uint8_t interface) +{ + if (handle->auto_detach_kernel_driver) + return detach_kernel_driver_and_claim(handle, interface); + else + return claim_interface(handle, interface); +} + +static int op_release_interface(struct libusb_device_handle *handle, uint8_t interface) +{ + int r; + + r = release_interface(handle, interface); + if (r) + return r; + + if (handle->auto_detach_kernel_driver) + op_attach_kernel_driver(handle, interface); + + return 0; +} + +static void op_destroy_device(struct libusb_device *dev) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + + free(priv->config_descriptors); + free(priv->descriptors); + free(priv->sysfs_dir); +} + +/* URBs are discarded in reverse order of submission to avoid races. */ +static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plus_one) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + int i, ret = 0; + struct usbfs_urb *urb; + + for (i = last_plus_one - 1; i >= first; i--) { + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + urb = tpriv->iso_urbs[i]; + else + urb = &tpriv->urbs[i]; + + if (ioctl(hpriv->fd, IOCTL_USBFS_DISCARDURB, urb) == 0) + continue; + + if (errno == EINVAL) { + usbi_dbg(TRANSFER_CTX(transfer), "URB not found --> assuming ready to be reaped"); + if (i == (last_plus_one - 1)) + ret = LIBUSB_ERROR_NOT_FOUND; + } else if (errno == ENODEV) { + usbi_dbg(TRANSFER_CTX(transfer), "Device not found for URB --> assuming ready to be reaped"); + ret = LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_warn(TRANSFER_CTX(transfer), "unrecognised discard errno %d", errno); + ret = LIBUSB_ERROR_OTHER; + } + } + return ret; +} + +static void free_iso_urbs(struct linux_transfer_priv *tpriv) +{ + int i; + + for (i = 0; i < tpriv->num_urbs; i++) { + struct usbfs_urb *urb = tpriv->iso_urbs[i]; + + if (!urb) + break; + free(urb); + } + + free(tpriv->iso_urbs); + tpriv->iso_urbs = NULL; +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urbs; + int is_out = IS_XFEROUT(transfer); + int bulk_buffer_len, use_bulk_continuation; + int num_urbs; + int last_urb_partial = 0; + int r; + int i; + + /* + * Older versions of usbfs place a 16kb limit on bulk URBs. We work + * around this by splitting large transfers into 16k blocks, and then + * submit all urbs at once. it would be simpler to submit one urb at + * a time, but there is a big performance gain doing it this way. + * + * Newer versions lift the 16k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbitrary large transfers can still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + * + * The kernel solves this problem by splitting the transfer into + * blocks itself when the host-controller is scatter-gather capable + * (USBFS_CAP_BULK_SCATTER_GATHER), which most controllers are. + * + * Last, there is the issue of short-transfers when splitting, for + * short split-transfers to work reliable USBFS_CAP_BULK_CONTINUATION + * is needed, but this is not always available. + */ + if (hpriv->caps & USBFS_CAP_BULK_SCATTER_GATHER) { + /* Good! Just submit everything in one go */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else if (hpriv->caps & USBFS_CAP_BULK_CONTINUATION) { + /* Split the transfers and use bulk-continuation to + avoid issues with short-transfers */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 1; + } else if (hpriv->caps & USBFS_CAP_NO_PACKET_SIZE_LIM) { + /* Don't split, assume the kernel can alloc the buffer + (otherwise the submit will fail with -ENOMEM) */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else { + /* Bad, splitting without bulk-continuation, short transfers + which end before the last urb will not work reliable! */ + /* Note we don't warn here as this is "normal" on kernels < + 2.6.32 and not a problem for most applications */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 0; + } + + num_urbs = transfer->length / bulk_buffer_len; + + if (transfer->length == 0) { + num_urbs = 1; + } else if ((transfer->length % bulk_buffer_len) > 0) { + last_urb_partial = 1; + num_urbs++; + } + usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length); + urbs = calloc(num_urbs, sizeof(*urbs)); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + tpriv->urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->reap_status = LIBUSB_TRANSFER_COMPLETED; + + for (i = 0; i < num_urbs; i++) { + struct usbfs_urb *urb = &urbs[i]; + + urb->usercontext = itransfer; + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + urb->type = USBFS_URB_TYPE_BULK; + urb->stream_id = 0; + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + urb->type = USBFS_URB_TYPE_BULK; + urb->stream_id = itransfer->stream_id; + break; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + urb->type = USBFS_URB_TYPE_INTERRUPT; + break; + } + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer + (i * bulk_buffer_len); + + /* don't set the short not ok flag for the last URB */ + if (use_bulk_continuation && !is_out && (i < num_urbs - 1)) + urb->flags = USBFS_URB_SHORT_NOT_OK; + + if (i == num_urbs - 1 && last_urb_partial) + urb->buffer_length = transfer->length % bulk_buffer_len; + else if (transfer->length == 0) + urb->buffer_length = 0; + else + urb->buffer_length = bulk_buffer_len; + + if (i > 0 && use_bulk_continuation) + urb->flags |= USBFS_URB_BULK_CONTINUATION; + + /* we have already checked that the flag is supported */ + if (is_out && i == num_urbs - 1 && + (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)) + urb->flags |= USBFS_URB_ZERO_PACKET; + + r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r == 0) + continue; + + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else if (errno == ENOMEM) { + r = LIBUSB_ERROR_NO_MEM; + } else { + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); + free(urbs); + tpriv->urbs = NULL; + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we may need to discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * - this URB failing may be no error; EREMOTEIO means that + * this transfer simply didn't need all the URBs we submitted + * so, we report that the transfer was submitted successfully and + * in case of error we discard all previous URBs. later when + * the final reap completes we can report error to the user, + * or success if an earlier URB was completed successfully. + */ + tpriv->reap_action = errno == EREMOTEIO ? COMPLETED_EARLY : SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired += num_urbs - i; + + /* If we completed short then don't try to discard. */ + if (tpriv->reap_action == COMPLETED_EARLY) + return 0; + + discard_urbs(itransfer, 0, i); + + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + + return 0; +} + +static int submit_iso_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb **urbs; + int num_packets = transfer->num_iso_packets; + int num_packets_remaining; + int i, j; + int num_urbs; + unsigned int packet_len; + unsigned int total_len = 0; + unsigned char *urb_buffer = transfer->buffer; + + if (num_packets < 1) + return LIBUSB_ERROR_INVALID_PARAM; + + /* usbfs places arbitrary limits on iso URBs. this limit has changed + * at least three times, but we attempt to detect this limit during + * init and check it here. if the kernel rejects the request due to + * its size, we return an error indicating such to the user. + */ + for (i = 0; i < num_packets; i++) { + packet_len = transfer->iso_packet_desc[i].length; + + if (packet_len > max_iso_packet_len) { + usbi_warn(TRANSFER_CTX(transfer), + "iso packet length of %u bytes exceeds maximum of %u bytes", + packet_len, max_iso_packet_len); + return LIBUSB_ERROR_INVALID_PARAM; + } + + total_len += packet_len; + } + + if (transfer->length < (int)total_len) + return LIBUSB_ERROR_INVALID_PARAM; + + /* usbfs limits the number of iso packets per URB */ + num_urbs = (num_packets + (MAX_ISO_PACKETS_PER_URB - 1)) / MAX_ISO_PACKETS_PER_URB; + + usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length); + + urbs = calloc(num_urbs, sizeof(*urbs)); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + + tpriv->iso_urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->iso_packet_offset = 0; + + /* allocate + initialize each URB with the correct number of packets */ + num_packets_remaining = num_packets; + for (i = 0, j = 0; i < num_urbs; i++) { + int num_packets_in_urb = MIN(num_packets_remaining, MAX_ISO_PACKETS_PER_URB); + struct usbfs_urb *urb; + size_t alloc_size; + int k; + + alloc_size = sizeof(*urb) + + (num_packets_in_urb * sizeof(struct usbfs_iso_packet_desc)); + urb = calloc(1, alloc_size); + if (!urb) { + free_iso_urbs(tpriv); + return LIBUSB_ERROR_NO_MEM; + } + urbs[i] = urb; + + /* populate packet lengths */ + for (k = 0; k < num_packets_in_urb; j++, k++) { + packet_len = transfer->iso_packet_desc[j].length; + urb->buffer_length += packet_len; + urb->iso_frame_desc[k].length = packet_len; + } + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_ISO; + /* FIXME: interface for non-ASAP data? */ + urb->flags = USBFS_URB_ISO_ASAP; + urb->endpoint = transfer->endpoint; + urb->number_of_packets = num_packets_in_urb; + urb->buffer = urb_buffer; + + urb_buffer += urb->buffer_length; + num_packets_remaining -= num_packets_in_urb; + } + + /* submit URBs */ + for (i = 0; i < num_urbs; i++) { + int r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]); + + if (r == 0) + continue; + + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else if (errno == EINVAL) { + usbi_warn(TRANSFER_CTX(transfer), "submiturb failed, transfer too large"); + r = LIBUSB_ERROR_INVALID_PARAM; + } else if (errno == EMSGSIZE) { + usbi_warn(TRANSFER_CTX(transfer), "submiturb failed, iso packet length too large"); + r = LIBUSB_ERROR_INVALID_PARAM; + } else { + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); + free_iso_urbs(tpriv); + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we must discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * so, in this case we discard all the previous URBs BUT we report + * that the transfer was submitted successfully. then later when + * the final discard completes we can report error to the user. + */ + tpriv->reap_action = SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired = num_urbs - i; + discard_urbs(itransfer, 0, i); + + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + + return 0; +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urb; + int r; + + if (transfer->length - LIBUSB_CONTROL_SETUP_SIZE > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + urb = calloc(1, sizeof(*urb)); + if (!urb) + return LIBUSB_ERROR_NO_MEM; + tpriv->urbs = urb; + tpriv->num_urbs = 1; + tpriv->reap_action = NORMAL; + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_CONTROL; + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer; + urb->buffer_length = transfer->length; + + r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0) { + free(urb); + tpriv->urbs = NULL; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + return 0; +} + +static int op_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + default: + usbi_err(TRANSFER_CTX(transfer), "unknown transfer type %u", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int op_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int r; + + if (!tpriv->urbs) + return LIBUSB_ERROR_NOT_FOUND; + + r = discard_urbs(itransfer, 0, tpriv->num_urbs); + if (r != 0) + return r; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + if (tpriv->reap_action == ERROR) + break; + /* else, fall through */ + default: + tpriv->reap_action = CANCELLED; + } + + return 0; +} + +static void op_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (tpriv->urbs) { + free(tpriv->urbs); + tpriv->urbs = NULL; + } + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + if (tpriv->iso_urbs) { + free_iso_urbs(tpriv); + tpriv->iso_urbs = NULL; + } + break; + default: + usbi_err(TRANSFER_CTX(transfer), "unknown transfer type %u", transfer->type); + } +} + +static int handle_bulk_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int urb_idx = urb - tpriv->urbs; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of bulk urb %d/%d", urb->status, + urb_idx + 1, tpriv->num_urbs); + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { + /* cancelled, submit_fail, or completed early */ + usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: urb status %d", urb->status); + + /* even though we're in the process of cancelling, it's possible that + * we may receive some data in these URBs that we don't want to lose. + * examples: + * 1. while the kernel is cancelling all the packets that make up an + * URB, a few of them might complete. so we get back a successful + * cancellation *and* some data. + * 2. we receive a short URB which marks the early completion condition, + * so we start cancelling the remaining URBs. however, we're too + * slow and another URB completes (or at least completes partially). + * (this can't happen since we always use BULK_CONTINUATION.) + * + * When this happens, our objectives are not to lose any "surplus" data, + * and also to stick it at the end of the previously-received data + * (closing any holes), so that libusb reports the total amount of + * transferred data and presents it in a contiguous chunk. + */ + if (urb->actual_length > 0) { + unsigned char *target = transfer->buffer + itransfer->transferred; + + usbi_dbg(TRANSFER_CTX(transfer), "received %d bytes of surplus data", urb->actual_length); + if (urb->buffer != target) { + usbi_dbg(TRANSFER_CTX(transfer), "moving surplus data from offset %zu to offset %zu", + (unsigned char *)urb->buffer - transfer->buffer, + target - transfer->buffer); + memmove(target, urb->buffer, urb->actual_length); + } + itransfer->transferred += urb->actual_length; + } + + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: last URB handled, reporting"); + if (tpriv->reap_action != COMPLETED_EARLY && + tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + goto completed; + } + goto out_unlock; + } + + itransfer->transferred += urb->actual_length; + + /* Many of these errors can occur on *any* urb of a multi-urb + * transfer. When they do, we tear down the rest of the transfer. + */ + switch (urb->status) { + case 0: + break; + case -EREMOTEIO: /* short transfer */ + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); + tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE; + goto cancel_remaining; + case -EPIPE: + usbi_dbg(TRANSFER_CTX(transfer), "detected endpoint stall"); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_STALL; + goto cancel_remaining; + case -EOVERFLOW: + /* overflow can only ever occur in the last urb */ + usbi_dbg(TRANSFER_CTX(transfer), "overflow, actual_length=%d", urb->actual_length); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_OVERFLOW; + goto completed; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg(TRANSFER_CTX(transfer), "low-level bus error %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + default: + usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + } + + /* if we've reaped all urbs or we got less data than requested then we're + * done */ + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); + goto completed; + } else if (urb->actual_length < urb->buffer_length) { + usbi_dbg(TRANSFER_CTX(transfer), "short transfer %d/%d --> complete!", + urb->actual_length, urb->buffer_length); + if (tpriv->reap_action == NORMAL) + tpriv->reap_action = COMPLETED_EARLY; + } else { + goto out_unlock; + } + +cancel_remaining: + if (tpriv->reap_action == ERROR && tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + + if (tpriv->num_retired == tpriv->num_urbs) /* nothing to cancel */ + goto completed; + + /* cancel remaining urbs and wait for their completion before + * reporting results */ + discard_urbs(itransfer, urb_idx + 1, tpriv->num_urbs); + +out_unlock: + usbi_mutex_unlock(&itransfer->lock); + return 0; + +completed: + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return tpriv->reap_action == CANCELLED ? + usbi_handle_transfer_cancellation(itransfer) : + usbi_handle_transfer_completion(itransfer, tpriv->reap_status); +} + +static int handle_iso_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + int num_urbs = tpriv->num_urbs; + int urb_idx = 0; + int i; + enum libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; + + usbi_mutex_lock(&itransfer->lock); + for (i = 0; i < num_urbs; i++) { + if (urb == tpriv->iso_urbs[i]) { + urb_idx = i + 1; + break; + } + } + if (urb_idx == 0) { + usbi_err(TRANSFER_CTX(transfer), "could not locate urb!"); + usbi_mutex_unlock(&itransfer->lock); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of iso urb %d/%d", urb->status, + urb_idx, num_urbs); + + /* copy isochronous results back in */ + + for (i = 0; i < urb->number_of_packets; i++) { + struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i]; + struct libusb_iso_packet_descriptor *lib_desc = + &transfer->iso_packet_desc[tpriv->iso_packet_offset++]; + + lib_desc->status = LIBUSB_TRANSFER_COMPLETED; + switch (urb_desc->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - device removed", i); + lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - detected endpoint stall", i); + lib_desc->status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - overflow error", i); + lib_desc->status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + case -EXDEV: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - low-level USB error %d", i, urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "packet %d - unrecognised urb status %d", + i, urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + } + lib_desc->actual_length = urb_desc->actual_length; + } + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */ + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: urb status %d", urb->status); + + if (tpriv->num_retired == num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: last URB handled, reporting"); + free_iso_urbs(tpriv); + if (tpriv->reap_action == CANCELLED) { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } else { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_ERROR); + } + } + goto out; + } + + switch (urb->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + /* if we've reaped all urbs then we're done */ + if (tpriv->num_retired == num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); + free_iso_urbs(tpriv); + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); + } + +out: + usbi_mutex_unlock(&itransfer->lock); + return 0; +} + +static int handle_control_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + int status; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg(ITRANSFER_CTX(itransfer), "handling completion status %d", urb->status); + + itransfer->transferred += urb->actual_length; + + if (tpriv->reap_action == CANCELLED) { + if (urb->status && urb->status != -ENOENT) + usbi_warn(ITRANSFER_CTX(itransfer), "cancel: unrecognised urb status %d", + urb->status); + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } + + switch (urb->status) { + case 0: + status = LIBUSB_TRANSFER_COMPLETED; + break; + case -ENOENT: /* cancelled */ + status = LIBUSB_TRANSFER_CANCELLED; + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(ITRANSFER_CTX(itransfer), "device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg(ITRANSFER_CTX(itransfer), "unsupported control request"); + status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg(ITRANSFER_CTX(itransfer), "overflow, actual_length=%d", urb->actual_length); + status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg(ITRANSFER_CTX(itransfer), "low-level bus error %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); +} + +static int reap_for_handle(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r; + struct usbfs_urb *urb = NULL; + struct usbi_transfer *itransfer; + struct libusb_transfer *transfer; + + r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb); + if (r < 0) { + if (errno == EAGAIN) + return 1; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "reap failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + itransfer = urb->usercontext; + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + usbi_dbg(HANDLE_CTX(handle), "urb type=%u status=%d transferred=%d", urb->type, urb->status, urb->actual_length); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return handle_iso_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return handle_bulk_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_CONTROL: + return handle_control_completion(itransfer, urb); + default: + usbi_err(HANDLE_CTX(handle), "unrecognised transfer type %u", transfer->type); + return LIBUSB_ERROR_OTHER; + } +} + +static int op_handle_events(struct libusb_context *ctx, + void *event_data, unsigned int count, unsigned int num_ready) +{ + struct pollfd *fds = event_data; + unsigned int n; + int r; + + usbi_mutex_lock(&ctx->open_devs_lock); + for (n = 0; n < count && num_ready > 0; n++) { + struct pollfd *pollfd = &fds[n]; + struct libusb_device_handle *handle; + struct linux_device_handle_priv *hpriv = NULL; + int reap_count; + + if (!pollfd->revents) + continue; + + num_ready--; + for_each_open_device(ctx, handle) { + hpriv = usbi_get_device_handle_priv(handle); + if (hpriv->fd == pollfd->fd) + break; + } + + if (!hpriv || hpriv->fd != pollfd->fd) { + usbi_err(ctx, "cannot find handle for fd %d", + pollfd->fd); + continue; + } + + if (pollfd->revents & POLLERR) { + /* remove the fd from the pollfd set so that it doesn't continuously + * trigger an event, and flag that it has been removed so op_close() + * doesn't try to remove it a second time */ + usbi_remove_event_source(HANDLE_CTX(handle), hpriv->fd); + hpriv->fd_removed = 1; + + /* device will still be marked as attached if hotplug monitor thread + * hasn't processed remove event yet */ + usbi_mutex_static_lock(&linux_hotplug_lock); + if (usbi_atomic_load(&handle->dev->attached)) + linux_device_disconnected(handle->dev->bus_number, + handle->dev->device_address); + usbi_mutex_static_unlock(&linux_hotplug_lock); + + if (hpriv->caps & USBFS_CAP_REAP_AFTER_DISCONNECT) { + do { + r = reap_for_handle(handle); + } while (r == 0); + } + + usbi_handle_disconnect(handle); + continue; + } + + reap_count = 0; + do { + r = reap_for_handle(handle); + } while (r == 0 && ++reap_count <= 25); + + if (r == 1 || r == LIBUSB_ERROR_NO_DEVICE) + continue; + else if (r < 0) + goto out; + } + + r = 0; +out: + usbi_mutex_unlock(&ctx->open_devs_lock); + return r; +} + +const struct usbi_os_backend usbi_backend = { + .name = "Linux usbfs", + .caps = USBI_CAP_HAS_HID_ACCESS|USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, + .init = op_init, + .exit = op_exit, + .set_option = op_set_option, + .hotplug_poll = op_hotplug_poll, + .get_active_config_descriptor = op_get_active_config_descriptor, + .get_config_descriptor = op_get_config_descriptor, + .get_config_descriptor_by_value = op_get_config_descriptor_by_value, + + .wrap_sys_device = op_wrap_sys_device, + .open = op_open, + .close = op_close, + .get_configuration = op_get_configuration, + .set_configuration = op_set_configuration, + .claim_interface = op_claim_interface, + .release_interface = op_release_interface, + + .set_interface_altsetting = op_set_interface, + .clear_halt = op_clear_halt, + .reset_device = op_reset_device, + + .alloc_streams = op_alloc_streams, + .free_streams = op_free_streams, + + .dev_mem_alloc = op_dev_mem_alloc, + .dev_mem_free = op_dev_mem_free, + + .kernel_driver_active = op_kernel_driver_active, + .detach_kernel_driver = op_detach_kernel_driver, + .attach_kernel_driver = op_attach_kernel_driver, + + .destroy_device = op_destroy_device, + + .submit_transfer = op_submit_transfer, + .cancel_transfer = op_cancel_transfer, + .clear_transfer_priv = op_clear_transfer_priv, + + .handle_events = op_handle_events, + + .device_priv_size = sizeof(struct linux_device_priv), + .device_handle_priv_size = sizeof(struct linux_device_handle_priv), + .transfer_priv_size = sizeof(struct linux_transfer_priv), +}; diff --git a/src/os/linux_usbfs.h b/src/os/linux_usbfs.h new file mode 100644 index 0000000..1238ffa --- /dev/null +++ b/src/os/linux_usbfs.h @@ -0,0 +1,211 @@ +/* + * usbfs header structures + * Copyright © 2007 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_USBFS_H +#define LIBUSB_USBFS_H + +#include <linux/magic.h> +#include <linux/types.h> + +#define SYSFS_MOUNT_PATH "/sys" +#define SYSFS_DEVICE_PATH SYSFS_MOUNT_PATH "/bus/usb/devices" + +struct usbfs_ctrltransfer { + /* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */ + __u8 bmRequestType; + __u8 bRequest; + __u16 wValue; + __u16 wIndex; + __u16 wLength; + __u32 timeout; /* in milliseconds */ + + /* pointer to data */ + void *data; +}; + +struct usbfs_setinterface { + /* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */ + unsigned int interface; + unsigned int altsetting; +}; + +#define USBFS_MAXDRIVERNAME 255 + +struct usbfs_getdriver { + unsigned int interface; + char driver[USBFS_MAXDRIVERNAME + 1]; +}; + +#define USBFS_URB_SHORT_NOT_OK 0x01 +#define USBFS_URB_ISO_ASAP 0x02 +#define USBFS_URB_BULK_CONTINUATION 0x04 +#define USBFS_URB_QUEUE_BULK 0x10 +#define USBFS_URB_ZERO_PACKET 0x40 + +#define USBFS_URB_TYPE_ISO 0 +#define USBFS_URB_TYPE_INTERRUPT 1 +#define USBFS_URB_TYPE_CONTROL 2 +#define USBFS_URB_TYPE_BULK 3 + +struct usbfs_iso_packet_desc { + unsigned int length; + unsigned int actual_length; + unsigned int status; +}; + +#define MAX_BULK_BUFFER_LENGTH 16384 +#define MAX_CTRL_BUFFER_LENGTH 4096 + +#define MAX_ISO_PACKETS_PER_URB 128 + +struct usbfs_urb { + unsigned char type; + unsigned char endpoint; + int status; + unsigned int flags; + void *buffer; + int buffer_length; + int actual_length; + int start_frame; + union { + int number_of_packets; /* Only used for isoc urbs */ + unsigned int stream_id; /* Only used with bulk streams */ + }; + int error_count; + unsigned int signr; + void *usercontext; + struct usbfs_iso_packet_desc iso_frame_desc[0]; +}; + +struct usbfs_connectinfo { + unsigned int devnum; + unsigned char slow; +}; + +struct usbfs_ioctl { + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in <asm/ioctl.h> give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +#define USBFS_CAP_ZERO_PACKET 0x01 +#define USBFS_CAP_BULK_CONTINUATION 0x02 +#define USBFS_CAP_NO_PACKET_SIZE_LIM 0x04 +#define USBFS_CAP_BULK_SCATTER_GATHER 0x08 +#define USBFS_CAP_REAP_AFTER_DISCONNECT 0x10 + +#define USBFS_DISCONNECT_CLAIM_IF_DRIVER 0x01 +#define USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER 0x02 + +struct usbfs_disconnect_claim { + unsigned int interface; + unsigned int flags; + char driver[USBFS_MAXDRIVERNAME + 1]; +}; + +struct usbfs_streams { + unsigned int num_streams; /* Not used by USBDEVFS_FREE_STREAMS */ + unsigned int num_eps; + unsigned char eps[0]; +}; + +#define USBFS_SPEED_UNKNOWN 0 +#define USBFS_SPEED_LOW 1 +#define USBFS_SPEED_FULL 2 +#define USBFS_SPEED_HIGH 3 +#define USBFS_SPEED_WIRELESS 4 +#define USBFS_SPEED_SUPER 5 +#define USBFS_SPEED_SUPER_PLUS 6 + +#define IOCTL_USBFS_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) +#define IOCTL_USBFS_SETINTERFACE _IOR('U', 4, struct usbfs_setinterface) +#define IOCTL_USBFS_SETCONFIGURATION _IOR('U', 5, unsigned int) +#define IOCTL_USBFS_GETDRIVER _IOW('U', 8, struct usbfs_getdriver) +#define IOCTL_USBFS_SUBMITURB _IOR('U', 10, struct usbfs_urb) +#define IOCTL_USBFS_DISCARDURB _IO('U', 11) +#define IOCTL_USBFS_REAPURBNDELAY _IOW('U', 13, void *) +#define IOCTL_USBFS_CLAIMINTERFACE _IOR('U', 15, unsigned int) +#define IOCTL_USBFS_RELEASEINTERFACE _IOR('U', 16, unsigned int) +#define IOCTL_USBFS_CONNECTINFO _IOW('U', 17, struct usbfs_connectinfo) +#define IOCTL_USBFS_IOCTL _IOWR('U', 18, struct usbfs_ioctl) +#define IOCTL_USBFS_RESET _IO('U', 20) +#define IOCTL_USBFS_CLEAR_HALT _IOR('U', 21, unsigned int) +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) +#define IOCTL_USBFS_GET_CAPABILITIES _IOR('U', 26, __u32) +#define IOCTL_USBFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbfs_disconnect_claim) +#define IOCTL_USBFS_ALLOC_STREAMS _IOR('U', 28, struct usbfs_streams) +#define IOCTL_USBFS_FREE_STREAMS _IOR('U', 29, struct usbfs_streams) +#define IOCTL_USBFS_DROP_PRIVILEGES _IOW('U', 30, __u32) +#define IOCTL_USBFS_GET_SPEED _IO('U', 31) + +extern usbi_mutex_static_t linux_hotplug_lock; + +#ifdef HAVE_LIBUDEV +int linux_udev_start_event_monitor(void); +int linux_udev_stop_event_monitor(void); +int linux_udev_scan_devices(struct libusb_context *ctx); +void linux_udev_hotplug_poll(void); +#else +int linux_netlink_start_event_monitor(void); +int linux_netlink_stop_event_monitor(void); +void linux_netlink_hotplug_poll(void); +#endif + +static inline int linux_start_event_monitor(void) +{ +#if defined(HAVE_LIBUDEV) + return linux_udev_start_event_monitor(); +#elif !defined(__ANDROID__) + return linux_netlink_start_event_monitor(); +#else + return LIBUSB_SUCCESS; +#endif +} + +static inline void linux_stop_event_monitor(void) +{ +#if defined(HAVE_LIBUDEV) + linux_udev_stop_event_monitor(); +#elif !defined(__ANDROID__) + linux_netlink_stop_event_monitor(); +#endif +} + +static inline void linux_hotplug_poll(void) +{ +#if defined(HAVE_LIBUDEV) + linux_udev_hotplug_poll(); +#elif !defined(__ANDROID__) + linux_netlink_hotplug_poll(); +#endif +} + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name); +void linux_device_disconnected(uint8_t busnum, uint8_t devaddr); + +int linux_get_device_address(struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, const char *dev_node, + const char *sys_name, int fd); +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, const char *sysfs_dir); + +#endif diff --git a/src/os/netbsd_usb.c b/src/os/netbsd_usb.c new file mode 100644 index 0000000..74833f6 --- /dev/null +++ b/src/os/netbsd_usb.c @@ -0,0 +1,617 @@ +/* + * Copyright © 2011 Martin Pieuchot <mpi@openbsd.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include <sys/time.h> +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <dev/usb/usb.h> + +#include "libusbi.h" + +struct device_priv { + char devnode[16]; + int fd; + + usb_config_descriptor_t *cdesc; /* active config descriptor */ +}; + +struct handle_priv { + int endpoints[USB_MAX_ENDPOINTS]; +}; + +/* + * Backend functions + */ +static int netbsd_get_device_list(struct libusb_context *, + struct discovered_devs **); +static int netbsd_open(struct libusb_device_handle *); +static void netbsd_close(struct libusb_device_handle *); + +static int netbsd_get_active_config_descriptor(struct libusb_device *, + void *, size_t); +static int netbsd_get_config_descriptor(struct libusb_device *, uint8_t, + void *, size_t); + +static int netbsd_get_configuration(struct libusb_device_handle *, uint8_t *); +static int netbsd_set_configuration(struct libusb_device_handle *, int); + +static int netbsd_claim_interface(struct libusb_device_handle *, uint8_t); +static int netbsd_release_interface(struct libusb_device_handle *, uint8_t); + +static int netbsd_set_interface_altsetting(struct libusb_device_handle *, + uint8_t, uint8_t); +static int netbsd_clear_halt(struct libusb_device_handle *, unsigned char); +static void netbsd_destroy_device(struct libusb_device *); + +static int netbsd_submit_transfer(struct usbi_transfer *); +static int netbsd_cancel_transfer(struct usbi_transfer *); +static int netbsd_handle_transfer_completion(struct usbi_transfer *); + +/* + * Private functions + */ +static int _errno_to_libusb(int); +static int _cache_active_config_descriptor(struct libusb_device *, int); +static int _sync_control_transfer(struct usbi_transfer *); +static int _sync_gen_transfer(struct usbi_transfer *); +static int _access_endpoint(struct libusb_transfer *); + +const struct usbi_os_backend usbi_backend = { + .name = "Synchronous NetBSD backend", + .caps = 0, + .get_device_list = netbsd_get_device_list, + .open = netbsd_open, + .close = netbsd_close, + + .get_active_config_descriptor = netbsd_get_active_config_descriptor, + .get_config_descriptor = netbsd_get_config_descriptor, + + .get_configuration = netbsd_get_configuration, + .set_configuration = netbsd_set_configuration, + + .claim_interface = netbsd_claim_interface, + .release_interface = netbsd_release_interface, + + .set_interface_altsetting = netbsd_set_interface_altsetting, + .clear_halt = netbsd_clear_halt, + + .destroy_device = netbsd_destroy_device, + + .submit_transfer = netbsd_submit_transfer, + .cancel_transfer = netbsd_cancel_transfer, + + .handle_transfer_completion = netbsd_handle_transfer_completion, + + .device_priv_size = sizeof(struct device_priv), + .device_handle_priv_size = sizeof(struct handle_priv), +}; + +int +netbsd_get_device_list(struct libusb_context * ctx, + struct discovered_devs **discdevs) +{ + struct libusb_device *dev; + struct device_priv *dpriv; + struct usb_device_info di; + usb_device_descriptor_t ddesc; + unsigned long session_id; + char devnode[16]; + int fd, err, i; + + usbi_dbg(ctx, " "); + + /* Only ugen(4) is supported */ + for (i = 0; i < USB_MAX_DEVICES; i++) { + /* Control endpoint is always .00 */ + snprintf(devnode, sizeof(devnode), "/dev/ugen%d.00", i); + + if ((fd = open(devnode, O_RDONLY)) < 0) { + if (errno != ENOENT && errno != ENXIO) + usbi_err(ctx, "could not open %s", devnode); + continue; + } + + if (ioctl(fd, USB_GET_DEVICEINFO, &di) < 0) + continue; + + session_id = (di.udi_bus << 8 | di.udi_addr); + dev = usbi_get_device_by_session_id(ctx, session_id); + + if (dev == NULL) { + dev = usbi_alloc_device(ctx, session_id); + if (dev == NULL) + return (LIBUSB_ERROR_NO_MEM); + + dev->bus_number = di.udi_bus; + dev->device_address = di.udi_addr; + dev->speed = di.udi_speed; + + dpriv = usbi_get_device_priv(dev); + strlcpy(dpriv->devnode, devnode, sizeof(devnode)); + dpriv->fd = -1; + + if (ioctl(fd, USB_GET_DEVICE_DESC, &ddesc) < 0) { + err = errno; + goto error; + } + + static_assert(sizeof(dev->device_descriptor) == sizeof(ddesc), + "mismatch between libusb and OS device descriptor sizes"); + memcpy(&dev->device_descriptor, &ddesc, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + + if (_cache_active_config_descriptor(dev, fd)) { + err = errno; + goto error; + } + + if ((err = usbi_sanitize_device(dev))) + goto error; + } + close(fd); + + if (discovered_devs_append(*discdevs, dev) == NULL) + return (LIBUSB_ERROR_NO_MEM); + + libusb_unref_device(dev); + } + + return (LIBUSB_SUCCESS); + +error: + close(fd); + libusb_unref_device(dev); + return _errno_to_libusb(err); +} + +int +netbsd_open(struct libusb_device_handle *handle) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + struct handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int i; + + dpriv->fd = open(dpriv->devnode, O_RDWR); + if (dpriv->fd < 0) { + dpriv->fd = open(dpriv->devnode, O_RDONLY); + if (dpriv->fd < 0) + return _errno_to_libusb(errno); + } + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + hpriv->endpoints[i] = -1; + + usbi_dbg(HANDLE_CTX(handle), "open %s: fd %d", dpriv->devnode, dpriv->fd); + + return (LIBUSB_SUCCESS); +} + +void +netbsd_close(struct libusb_device_handle *handle) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + + usbi_dbg(HANDLE_CTX(handle), "close: fd %d", dpriv->fd); + + close(dpriv->fd); + dpriv->fd = -1; +} + +int +netbsd_get_active_config_descriptor(struct libusb_device *dev, + void *buf, size_t len) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + + len = MIN(len, (size_t)UGETW(dpriv->cdesc->wTotalLength)); + + usbi_dbg(DEVICE_CTX(dev), "len %zu", len); + + memcpy(buf, dpriv->cdesc, len); + + return (int)len; +} + +int +netbsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, + void *buf, size_t len) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + struct usb_full_desc ufd; + int fd, err; + + usbi_dbg(DEVICE_CTX(dev), "index %u, len %zu", idx, len); + + /* A config descriptor may be requested before opening the device */ + if (dpriv->fd >= 0) { + fd = dpriv->fd; + } else { + fd = open(dpriv->devnode, O_RDONLY); + if (fd < 0) + return _errno_to_libusb(errno); + } + + ufd.ufd_config_index = idx; + ufd.ufd_size = len; + ufd.ufd_data = buf; + + if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) { + err = errno; + if (dpriv->fd < 0) + close(fd); + return _errno_to_libusb(err); + } + + if (dpriv->fd < 0) + close(fd); + + return (int)len; +} + +int +netbsd_get_configuration(struct libusb_device_handle *handle, uint8_t *config) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + int tmp; + + usbi_dbg(HANDLE_CTX(handle), " "); + + if (ioctl(dpriv->fd, USB_GET_CONFIG, &tmp) < 0) + return _errno_to_libusb(errno); + + usbi_dbg(HANDLE_CTX(handle), "configuration %d", tmp); + *config = (uint8_t)tmp; + + return (LIBUSB_SUCCESS); +} + +int +netbsd_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + + usbi_dbg(HANDLE_CTX(handle), "configuration %d", config); + + if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) + return _errno_to_libusb(errno); + + return _cache_active_config_descriptor(handle->dev, dpriv->fd); +} + +int +netbsd_claim_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + struct handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int i; + + UNUSED(iface); + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + hpriv->endpoints[i] = -1; + + return (LIBUSB_SUCCESS); +} + +int +netbsd_release_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + struct handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int i; + + UNUSED(iface); + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + if (hpriv->endpoints[i] >= 0) + close(hpriv->endpoints[i]); + + return (LIBUSB_SUCCESS); +} + +int +netbsd_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t iface, + uint8_t altsetting) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + struct usb_alt_interface intf; + + usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting); + + memset(&intf, 0, sizeof(intf)); + + intf.uai_interface_index = iface; + intf.uai_alt_no = altsetting; + + if (ioctl(dpriv->fd, USB_SET_ALTINTERFACE, &intf) < 0) + return _errno_to_libusb(errno); + + return (LIBUSB_SUCCESS); +} + +int +netbsd_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + struct usb_ctl_request req; + + usbi_dbg(HANDLE_CTX(handle), " "); + + req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; + req.ucr_request.bRequest = UR_CLEAR_FEATURE; + USETW(req.ucr_request.wValue, UF_ENDPOINT_HALT); + USETW(req.ucr_request.wIndex, endpoint); + USETW(req.ucr_request.wLength, 0); + + if (ioctl(dpriv->fd, USB_DO_REQUEST, &req) < 0) + return _errno_to_libusb(errno); + + return (LIBUSB_SUCCESS); +} + +void +netbsd_destroy_device(struct libusb_device *dev) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + + usbi_dbg(DEVICE_CTX(dev), " "); + + free(dpriv->cdesc); +} + +int +netbsd_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + int err = 0; + + usbi_dbg(ITRANSFER_CTX(itransfer), " "); + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + err = _sync_control_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + if (IS_XFEROUT(transfer)) { + /* Isochronous write is not supported */ + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + err = _sync_gen_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (IS_XFEROUT(transfer) && + transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + err = _sync_gen_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + + if (err) + return (err); + + usbi_signal_transfer_completion(itransfer); + + return (LIBUSB_SUCCESS); +} + +int +netbsd_cancel_transfer(struct usbi_transfer *itransfer) +{ + UNUSED(itransfer); + + usbi_dbg(ITRANSFER_CTX(itransfer), " "); + + return (LIBUSB_ERROR_NOT_SUPPORTED); +} + +int +netbsd_handle_transfer_completion(struct usbi_transfer *itransfer) +{ + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); +} + +int +_errno_to_libusb(int err) +{ + switch (err) { + case EIO: + return (LIBUSB_ERROR_IO); + case EACCES: + return (LIBUSB_ERROR_ACCESS); + case ENOENT: + return (LIBUSB_ERROR_NO_DEVICE); + case ENOMEM: + return (LIBUSB_ERROR_NO_MEM); + case EWOULDBLOCK: + case ETIMEDOUT: + return (LIBUSB_ERROR_TIMEOUT); + } + + usbi_dbg(NULL, "error: %s", strerror(err)); + + return (LIBUSB_ERROR_OTHER); +} + +int +_cache_active_config_descriptor(struct libusb_device *dev, int fd) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + struct usb_config_desc ucd; + struct usb_full_desc ufd; + void *buf; + int len; + + usbi_dbg(DEVICE_CTX(dev), "fd %d", fd); + + ucd.ucd_config_index = USB_CURRENT_CONFIG_INDEX; + + if ((ioctl(fd, USB_GET_CONFIG_DESC, &ucd)) < 0) + return _errno_to_libusb(errno); + + usbi_dbg(DEVICE_CTX(dev), "active bLength %d", ucd.ucd_desc.bLength); + + len = UGETW(ucd.ucd_desc.wTotalLength); + buf = malloc((size_t)len); + if (buf == NULL) + return (LIBUSB_ERROR_NO_MEM); + + ufd.ufd_config_index = ucd.ucd_config_index; + ufd.ufd_size = len; + ufd.ufd_data = buf; + + usbi_dbg(DEVICE_CTX(dev), "index %d, len %d", ufd.ufd_config_index, len); + + if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) { + free(buf); + return _errno_to_libusb(errno); + } + + if (dpriv->cdesc) + free(dpriv->cdesc); + dpriv->cdesc = buf; + + return (0); +} + +int +_sync_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + struct libusb_control_setup *setup; + struct device_priv *dpriv; + struct usb_ctl_request req; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + dpriv = usbi_get_device_priv(transfer->dev_handle->dev); + setup = (struct libusb_control_setup *)transfer->buffer; + + usbi_dbg(ITRANSFER_CTX(itransfer), "type %d request %d value %d index %d length %d timeout %d", + setup->bmRequestType, setup->bRequest, + libusb_le16_to_cpu(setup->wValue), + libusb_le16_to_cpu(setup->wIndex), + libusb_le16_to_cpu(setup->wLength), transfer->timeout); + + req.ucr_request.bmRequestType = setup->bmRequestType; + req.ucr_request.bRequest = setup->bRequest; + /* Don't use USETW, libusb already deals with the endianness */ + (*(uint16_t *)req.ucr_request.wValue) = setup->wValue; + (*(uint16_t *)req.ucr_request.wIndex) = setup->wIndex; + (*(uint16_t *)req.ucr_request.wLength) = setup->wLength; + req.ucr_data = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + + if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) + req.ucr_flags = USBD_SHORT_XFER_OK; + + if ((ioctl(dpriv->fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(dpriv->fd, USB_DO_REQUEST, &req)) < 0) + return _errno_to_libusb(errno); + + itransfer->transferred = req.ucr_actlen; + + usbi_dbg(ITRANSFER_CTX(itransfer), "transferred %d", itransfer->transferred); + + return (0); +} + +int +_access_endpoint(struct libusb_transfer *transfer) +{ + struct handle_priv *hpriv; + struct device_priv *dpriv; + char *s, devnode[16]; + int fd, endpt; + mode_t mode; + + hpriv = usbi_get_device_handle_priv(transfer->dev_handle); + dpriv = usbi_get_device_priv(transfer->dev_handle->dev); + + endpt = UE_GET_ADDR(transfer->endpoint); + mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; + + usbi_dbg(TRANSFER_CTX(transfer), "endpoint %d mode %d", endpt, mode); + + if (hpriv->endpoints[endpt] < 0) { + /* Pick the right node given the control one */ + strlcpy(devnode, dpriv->devnode, sizeof(devnode)); + s = strchr(devnode, '.'); + snprintf(s, 4, ".%02d", endpt); + + /* We may need to read/write to the same endpoint later. */ + if (((fd = open(devnode, O_RDWR)) < 0) && (errno == ENXIO)) + if ((fd = open(devnode, mode)) < 0) + return (-1); + + hpriv->endpoints[endpt] = fd; + } + + return (hpriv->endpoints[endpt]); +} + +int +_sync_gen_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + int fd, nr = 1; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + /* + * Bulk, Interrupt or Isochronous transfer depends on the + * endpoint and thus the node to open. + */ + if ((fd = _access_endpoint(transfer)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) + return _errno_to_libusb(errno); + + if (IS_XFERIN(transfer)) { + if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) + if ((ioctl(fd, USB_SET_SHORT_XFER, &nr)) < 0) + return _errno_to_libusb(errno); + + nr = read(fd, transfer->buffer, transfer->length); + } else { + nr = write(fd, transfer->buffer, transfer->length); + } + + if (nr < 0) + return _errno_to_libusb(errno); + + itransfer->transferred = nr; + + return (0); +} diff --git a/src/os/null_usb.c b/src/os/null_usb.c new file mode 100644 index 0000000..0cd531d --- /dev/null +++ b/src/os/null_usb.c @@ -0,0 +1,111 @@ +/* + * Copyright © 2019 Pino Toscano <toscano.pino@tiscali.it> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" + +static int +null_get_device_list(struct libusb_context * ctx, + struct discovered_devs **discdevs) +{ + return LIBUSB_SUCCESS; +} + +static int +null_open(struct libusb_device_handle *handle) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static void +null_close(struct libusb_device_handle *handle) +{ +} + +static int +null_get_active_config_descriptor(struct libusb_device *dev, + void *buf, size_t len) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_get_config_descriptor(struct libusb_device *dev, uint8_t idx, + void *buf, size_t len) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_set_configuration(struct libusb_device_handle *handle, int config) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_claim_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_release_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t iface, + uint8_t altsetting) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_submit_transfer(struct usbi_transfer *itransfer) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int +null_cancel_transfer(struct usbi_transfer *itransfer) +{ + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +const struct usbi_os_backend usbi_backend = { + .name = "Null backend", + .caps = 0, + .get_device_list = null_get_device_list, + .open = null_open, + .close = null_close, + .get_active_config_descriptor = null_get_active_config_descriptor, + .get_config_descriptor = null_get_config_descriptor, + .set_configuration = null_set_configuration, + .claim_interface = null_claim_interface, + .release_interface = null_release_interface, + .set_interface_altsetting = null_set_interface_altsetting, + .clear_halt = null_clear_halt, + .submit_transfer = null_submit_transfer, + .cancel_transfer = null_cancel_transfer, +}; diff --git a/src/os/openbsd_usb.c b/src/os/openbsd_usb.c new file mode 100644 index 0000000..9a5c604 --- /dev/null +++ b/src/os/openbsd_usb.c @@ -0,0 +1,700 @@ +/* + * Copyright © 2011-2013 Martin Pieuchot <mpi@openbsd.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include <sys/time.h> +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <dev/usb/usb.h> + +#include "libusbi.h" + +struct device_priv { + char *devname; /* name of the ugen(4) node */ + int fd; /* device file descriptor */ + + usb_config_descriptor_t *cdesc; /* active config descriptor */ +}; + +struct handle_priv { + int endpoints[USB_MAX_ENDPOINTS]; +}; + +/* + * Backend functions + */ +static int obsd_get_device_list(struct libusb_context *, + struct discovered_devs **); +static int obsd_open(struct libusb_device_handle *); +static void obsd_close(struct libusb_device_handle *); + +static int obsd_get_active_config_descriptor(struct libusb_device *, + void *, size_t); +static int obsd_get_config_descriptor(struct libusb_device *, uint8_t, + void *, size_t); + +static int obsd_get_configuration(struct libusb_device_handle *, uint8_t *); +static int obsd_set_configuration(struct libusb_device_handle *, int); + +static int obsd_claim_interface(struct libusb_device_handle *, uint8_t); +static int obsd_release_interface(struct libusb_device_handle *, uint8_t); + +static int obsd_set_interface_altsetting(struct libusb_device_handle *, uint8_t, + uint8_t); +static int obsd_clear_halt(struct libusb_device_handle *, unsigned char); +static void obsd_destroy_device(struct libusb_device *); + +static int obsd_submit_transfer(struct usbi_transfer *); +static int obsd_cancel_transfer(struct usbi_transfer *); +static int obsd_handle_transfer_completion(struct usbi_transfer *); + +/* + * Private functions + */ +static int _errno_to_libusb(int); +static int _cache_active_config_descriptor(struct libusb_device *); +static int _sync_control_transfer(struct usbi_transfer *); +static int _sync_gen_transfer(struct usbi_transfer *); +static int _access_endpoint(struct libusb_transfer *); + +static int _bus_open(int); + + +const struct usbi_os_backend usbi_backend = { + .name = "Synchronous OpenBSD backend", + .get_device_list = obsd_get_device_list, + .open = obsd_open, + .close = obsd_close, + + .get_active_config_descriptor = obsd_get_active_config_descriptor, + .get_config_descriptor = obsd_get_config_descriptor, + + .get_configuration = obsd_get_configuration, + .set_configuration = obsd_set_configuration, + + .claim_interface = obsd_claim_interface, + .release_interface = obsd_release_interface, + + .set_interface_altsetting = obsd_set_interface_altsetting, + .clear_halt = obsd_clear_halt, + .destroy_device = obsd_destroy_device, + + .submit_transfer = obsd_submit_transfer, + .cancel_transfer = obsd_cancel_transfer, + + .handle_transfer_completion = obsd_handle_transfer_completion, + + .device_priv_size = sizeof(struct device_priv), + .device_handle_priv_size = sizeof(struct handle_priv), +}; + +#define DEVPATH "/dev/" +#define USBDEV DEVPATH "usb" + +int +obsd_get_device_list(struct libusb_context * ctx, + struct discovered_devs **discdevs) +{ + struct discovered_devs *ddd; + struct libusb_device *dev; + struct device_priv *dpriv; + struct usb_device_info di; + struct usb_device_ddesc dd; + unsigned long session_id; + char devices[USB_MAX_DEVICES]; + char busnode[16]; + char *udevname; + int fd, addr, i, j; + + usbi_dbg(ctx, " "); + + for (i = 0; i < 8; i++) { + snprintf(busnode, sizeof(busnode), USBDEV "%d", i); + + if ((fd = open(busnode, O_RDWR)) < 0) { + if (errno != ENOENT && errno != ENXIO) + usbi_err(ctx, "could not open %s", busnode); + continue; + } + + bzero(devices, sizeof(devices)); + for (addr = 1; addr < USB_MAX_DEVICES; addr++) { + if (devices[addr]) + continue; + + di.udi_addr = addr; + if (ioctl(fd, USB_DEVICEINFO, &di) < 0) + continue; + + /* + * XXX If ugen(4) is attached to the USB device + * it will be used. + */ + udevname = NULL; + for (j = 0; j < USB_MAX_DEVNAMES; j++) + if (!strncmp("ugen", di.udi_devnames[j], 4)) { + udevname = strdup(di.udi_devnames[j]); + break; + } + + session_id = (di.udi_bus << 8 | di.udi_addr); + dev = usbi_get_device_by_session_id(ctx, session_id); + + if (dev == NULL) { + dev = usbi_alloc_device(ctx, session_id); + if (dev == NULL) { + close(fd); + return (LIBUSB_ERROR_NO_MEM); + } + + dev->bus_number = di.udi_bus; + dev->device_address = di.udi_addr; + dev->speed = di.udi_speed; + dev->port_number = di.udi_port; + + dpriv = usbi_get_device_priv(dev); + dpriv->fd = -1; + dpriv->devname = udevname; + + dd.udd_bus = di.udi_bus; + dd.udd_addr = di.udi_addr; + if (ioctl(fd, USB_DEVICE_GET_DDESC, &dd) < 0) { + libusb_unref_device(dev); + continue; + } + + static_assert(sizeof(dev->device_descriptor) == sizeof(dd.udd_desc), + "mismatch between libusb and OS device descriptor sizes"); + memcpy(&dev->device_descriptor, &dd.udd_desc, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + + if (_cache_active_config_descriptor(dev)) { + libusb_unref_device(dev); + continue; + } + + if (usbi_sanitize_device(dev)) { + libusb_unref_device(dev); + continue; + } + } + + ddd = discovered_devs_append(*discdevs, dev); + if (ddd == NULL) { + close(fd); + return (LIBUSB_ERROR_NO_MEM); + } + libusb_unref_device(dev); + + *discdevs = ddd; + devices[addr] = 1; + } + + close(fd); + } + + return (LIBUSB_SUCCESS); +} + +int +obsd_open(struct libusb_device_handle *handle) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + char devnode[16]; + + if (dpriv->devname) { + int fd; + /* + * Only open ugen(4) attached devices read-write, all + * read-only operations are done through the bus node. + */ + snprintf(devnode, sizeof(devnode), DEVPATH "%s.00", + dpriv->devname); + fd = open(devnode, O_RDWR); + if (fd < 0) + return _errno_to_libusb(errno); + dpriv->fd = fd; + + usbi_dbg(HANDLE_CTX(handle), "open %s: fd %d", devnode, dpriv->fd); + } + + return (LIBUSB_SUCCESS); +} + +void +obsd_close(struct libusb_device_handle *handle) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + + if (dpriv->devname) { + usbi_dbg(HANDLE_CTX(handle), "close: fd %d", dpriv->fd); + + close(dpriv->fd); + dpriv->fd = -1; + } +} + +int +obsd_get_active_config_descriptor(struct libusb_device *dev, + void *buf, size_t len) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + + len = MIN(len, (size_t)UGETW(dpriv->cdesc->wTotalLength)); + + usbi_dbg(DEVICE_CTX(dev), "len %zu", len); + + memcpy(buf, dpriv->cdesc, len); + + return ((int)len); +} + +int +obsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, + void *buf, size_t len) +{ + struct usb_device_fdesc udf; + int fd, err; + + if ((fd = _bus_open(dev->bus_number)) < 0) + return _errno_to_libusb(errno); + + udf.udf_bus = dev->bus_number; + udf.udf_addr = dev->device_address; + udf.udf_config_index = idx; + udf.udf_size = len; + udf.udf_data = buf; + + usbi_dbg(DEVICE_CTX(dev), "index %d, len %zu", udf.udf_config_index, len); + + if (ioctl(fd, USB_DEVICE_GET_FDESC, &udf) < 0) { + err = errno; + close(fd); + return _errno_to_libusb(err); + } + close(fd); + + return ((int)len); +} + +int +obsd_get_configuration(struct libusb_device_handle *handle, uint8_t *config) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + + *config = dpriv->cdesc->bConfigurationValue; + + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %u", *config); + + return (LIBUSB_SUCCESS); +} + +int +obsd_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + + if (dpriv->devname == NULL) + return (LIBUSB_ERROR_NOT_SUPPORTED); + + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %d", config); + + if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) + return _errno_to_libusb(errno); + + return _cache_active_config_descriptor(handle->dev); +} + +int +obsd_claim_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + struct handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int i; + + UNUSED(iface); + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + hpriv->endpoints[i] = -1; + + return (LIBUSB_SUCCESS); +} + +int +obsd_release_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + struct handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int i; + + UNUSED(iface); + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) + if (hpriv->endpoints[i] >= 0) + close(hpriv->endpoints[i]); + + return (LIBUSB_SUCCESS); +} + +int +obsd_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t iface, + uint8_t altsetting) +{ + struct device_priv *dpriv = usbi_get_device_priv(handle->dev); + struct usb_alt_interface intf; + + if (dpriv->devname == NULL) + return (LIBUSB_ERROR_NOT_SUPPORTED); + + usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting); + + memset(&intf, 0, sizeof(intf)); + + intf.uai_interface_index = iface; + intf.uai_alt_no = altsetting; + + if (ioctl(dpriv->fd, USB_SET_ALTINTERFACE, &intf) < 0) + return _errno_to_libusb(errno); + + return (LIBUSB_SUCCESS); +} + +int +obsd_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) +{ + struct usb_ctl_request req; + int fd, err; + + if ((fd = _bus_open(handle->dev->bus_number)) < 0) + return _errno_to_libusb(errno); + + usbi_dbg(HANDLE_CTX(handle), " "); + + req.ucr_addr = handle->dev->device_address; + req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; + req.ucr_request.bRequest = UR_CLEAR_FEATURE; + USETW(req.ucr_request.wValue, UF_ENDPOINT_HALT); + USETW(req.ucr_request.wIndex, endpoint); + USETW(req.ucr_request.wLength, 0); + + if (ioctl(fd, USB_REQUEST, &req) < 0) { + err = errno; + close(fd); + return _errno_to_libusb(err); + } + close(fd); + + return (LIBUSB_SUCCESS); +} + +void +obsd_destroy_device(struct libusb_device *dev) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + + usbi_dbg(DEVICE_CTX(dev), " "); + + free(dpriv->cdesc); + free(dpriv->devname); +} + +int +obsd_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + int err = 0; + + usbi_dbg(ITRANSFER_CTX(itransfer), " "); + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + err = _sync_control_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + if (IS_XFEROUT(transfer)) { + /* Isochronous write is not supported */ + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + err = _sync_gen_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (IS_XFEROUT(transfer) && + transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + err = _sync_gen_transfer(itransfer); + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + + if (err) + return (err); + + usbi_signal_transfer_completion(itransfer); + + return (LIBUSB_SUCCESS); +} + +int +obsd_cancel_transfer(struct usbi_transfer *itransfer) +{ + UNUSED(itransfer); + + usbi_dbg(ITRANSFER_CTX(itransfer), " "); + + return (LIBUSB_ERROR_NOT_SUPPORTED); +} + +int +obsd_handle_transfer_completion(struct usbi_transfer *itransfer) +{ + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); +} + +int +_errno_to_libusb(int err) +{ + usbi_dbg(NULL, "error: %s (%d)", strerror(err), err); + + switch (err) { + case EIO: + return (LIBUSB_ERROR_IO); + case EACCES: + return (LIBUSB_ERROR_ACCESS); + case ENOENT: + return (LIBUSB_ERROR_NO_DEVICE); + case ENOMEM: + return (LIBUSB_ERROR_NO_MEM); + case ETIMEDOUT: + return (LIBUSB_ERROR_TIMEOUT); + } + + return (LIBUSB_ERROR_OTHER); +} + +int +_cache_active_config_descriptor(struct libusb_device *dev) +{ + struct device_priv *dpriv = usbi_get_device_priv(dev); + struct usb_device_cdesc udc; + struct usb_device_fdesc udf; + void *buf; + int fd, len, err; + + if ((fd = _bus_open(dev->bus_number)) < 0) + return _errno_to_libusb(errno); + + usbi_dbg(DEVICE_CTX(dev), "fd %d, addr %d", fd, dev->device_address); + + udc.udc_bus = dev->bus_number; + udc.udc_addr = dev->device_address; + udc.udc_config_index = USB_CURRENT_CONFIG_INDEX; + if (ioctl(fd, USB_DEVICE_GET_CDESC, &udc) < 0) { + err = errno; + close(fd); + return _errno_to_libusb(errno); + } + + usbi_dbg(DEVICE_CTX(dev), "active bLength %d", udc.udc_desc.bLength); + + len = UGETW(udc.udc_desc.wTotalLength); + buf = malloc((size_t)len); + if (buf == NULL) + return (LIBUSB_ERROR_NO_MEM); + + udf.udf_bus = dev->bus_number; + udf.udf_addr = dev->device_address; + udf.udf_config_index = udc.udc_config_index; + udf.udf_size = len; + udf.udf_data = buf; + + usbi_dbg(DEVICE_CTX(dev), "index %d, len %d", udf.udf_config_index, len); + + if (ioctl(fd, USB_DEVICE_GET_FDESC, &udf) < 0) { + err = errno; + close(fd); + free(buf); + return _errno_to_libusb(err); + } + close(fd); + + if (dpriv->cdesc) + free(dpriv->cdesc); + dpriv->cdesc = buf; + + return (LIBUSB_SUCCESS); +} + +int +_sync_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + struct libusb_control_setup *setup; + struct device_priv *dpriv; + struct usb_ctl_request req; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + dpriv = usbi_get_device_priv(transfer->dev_handle->dev); + setup = (struct libusb_control_setup *)transfer->buffer; + + usbi_dbg(ITRANSFER_CTX(itransfer), "type 0x%x request 0x%x value 0x%x index %d length %d timeout %d", + setup->bmRequestType, setup->bRequest, + libusb_le16_to_cpu(setup->wValue), + libusb_le16_to_cpu(setup->wIndex), + libusb_le16_to_cpu(setup->wLength), transfer->timeout); + + req.ucr_addr = transfer->dev_handle->dev->device_address; + req.ucr_request.bmRequestType = setup->bmRequestType; + req.ucr_request.bRequest = setup->bRequest; + /* Don't use USETW, libusb already deals with the endianness */ + (*(uint16_t *)req.ucr_request.wValue) = setup->wValue; + (*(uint16_t *)req.ucr_request.wIndex) = setup->wIndex; + (*(uint16_t *)req.ucr_request.wLength) = setup->wLength; + req.ucr_data = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + + if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) + req.ucr_flags = USBD_SHORT_XFER_OK; + + if (dpriv->devname == NULL) { + /* + * XXX If the device is not attached to ugen(4) it is + * XXX still possible to submit a control transfer but + * XXX with the default timeout only. + */ + int fd, err; + + if ((fd = _bus_open(transfer->dev_handle->dev->bus_number)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(fd, USB_REQUEST, &req)) < 0) { + err = errno; + close(fd); + return _errno_to_libusb(err); + } + close(fd); + } else { + if ((ioctl(dpriv->fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(dpriv->fd, USB_DO_REQUEST, &req)) < 0) + return _errno_to_libusb(errno); + } + + itransfer->transferred = req.ucr_actlen; + + usbi_dbg(ITRANSFER_CTX(itransfer), "transferred %d", itransfer->transferred); + + return (0); +} + +int +_access_endpoint(struct libusb_transfer *transfer) +{ + struct handle_priv *hpriv; + struct device_priv *dpriv; + char devnode[16]; + int fd, endpt; + mode_t mode; + + hpriv = usbi_get_device_handle_priv(transfer->dev_handle); + dpriv = usbi_get_device_priv(transfer->dev_handle->dev); + + endpt = UE_GET_ADDR(transfer->endpoint); + mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; + + usbi_dbg(TRANSFER_CTX(transfer), "endpoint %d mode %d", endpt, mode); + + if (hpriv->endpoints[endpt] < 0) { + /* Pick the right endpoint node */ + snprintf(devnode, sizeof(devnode), DEVPATH "%s.%02d", + dpriv->devname, endpt); + + /* We may need to read/write to the same endpoint later. */ + if (((fd = open(devnode, O_RDWR)) < 0) && (errno == ENXIO)) + if ((fd = open(devnode, mode)) < 0) + return (-1); + + hpriv->endpoints[endpt] = fd; + } + + return (hpriv->endpoints[endpt]); +} + +int +_sync_gen_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + struct device_priv *dpriv; + int fd, nr = 1; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + dpriv = usbi_get_device_priv(transfer->dev_handle->dev); + + if (dpriv->devname == NULL) + return (LIBUSB_ERROR_NOT_SUPPORTED); + + /* + * Bulk, Interrupt or Isochronous transfer depends on the + * endpoint and thus the node to open. + */ + if ((fd = _access_endpoint(transfer)) < 0) + return _errno_to_libusb(errno); + + if ((ioctl(fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) + return _errno_to_libusb(errno); + + if (IS_XFERIN(transfer)) { + if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) + if ((ioctl(fd, USB_SET_SHORT_XFER, &nr)) < 0) + return _errno_to_libusb(errno); + + nr = read(fd, transfer->buffer, transfer->length); + } else { + nr = write(fd, transfer->buffer, transfer->length); + } + + if (nr < 0) + return _errno_to_libusb(errno); + + itransfer->transferred = nr; + + return (0); +} + +int +_bus_open(int number) +{ + char busnode[16]; + + snprintf(busnode, sizeof(busnode), USBDEV "%d", number); + + return open(busnode, O_RDWR); +} diff --git a/src/os/sunos_usb.c b/src/os/sunos_usb.c new file mode 100644 index 0000000..28b167f --- /dev/null +++ b/src/os/sunos_usb.c @@ -0,0 +1,1609 @@ +/* + * + * Copyright (c) 2016, Oracle and/or its affiliates. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <strings.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wait.h> +#include <unistd.h> +#include <aio.h> +#include <libdevinfo.h> +#include <sys/nvpair.h> +#include <sys/devctl.h> +#include <sys/usb/clients/ugen/usb_ugen.h> +#include <sys/usb/usba.h> +#include <sys/pci.h> + +#include "libusbi.h" +#include "sunos_usb.h" + +#define UPDATEDRV_PATH "/usr/sbin/update_drv" +#define UPDATEDRV "update_drv" + +#define DEFAULT_LISTSIZE 6 + +typedef struct { + int nargs; + int listsize; + char **string; +} string_list_t; + +/* + * Backend functions + */ +static int sunos_get_device_list(struct libusb_context *, + struct discovered_devs **); +static int sunos_open(struct libusb_device_handle *); +static void sunos_close(struct libusb_device_handle *); +static int sunos_get_active_config_descriptor(struct libusb_device *, + void *, size_t); +static int sunos_get_config_descriptor(struct libusb_device *, uint8_t, + void *, size_t); +static int sunos_get_configuration(struct libusb_device_handle *, uint8_t *); +static int sunos_set_configuration(struct libusb_device_handle *, int); +static int sunos_claim_interface(struct libusb_device_handle *, uint8_t); +static int sunos_release_interface(struct libusb_device_handle *, uint8_t); +static int sunos_set_interface_altsetting(struct libusb_device_handle *, + uint8_t, uint8_t); +static int sunos_clear_halt(struct libusb_device_handle *, unsigned char); +static void sunos_destroy_device(struct libusb_device *); +static int sunos_submit_transfer(struct usbi_transfer *); +static int sunos_cancel_transfer(struct usbi_transfer *); +static int sunos_handle_transfer_completion(struct usbi_transfer *); +static int sunos_kernel_driver_active(struct libusb_device_handle *, uint8_t); +static int sunos_detach_kernel_driver(struct libusb_device_handle *, uint8_t); +static int sunos_attach_kernel_driver(struct libusb_device_handle *, uint8_t); +static int sunos_usb_open_ep0(sunos_dev_handle_priv_t *hpriv, sunos_dev_priv_t *dpriv); +static int sunos_usb_ioctl(struct libusb_device *dev, int cmd); + +static int sunos_get_link(di_devlink_t devlink, void *arg) +{ + walk_link_t *larg = (walk_link_t *)arg; + const char *p; + const char *q; + + if (larg->path) { + char *content = (char *)di_devlink_content(devlink); + char *start = strstr(content, "/devices/"); + start += strlen("/devices"); + usbi_dbg(NULL, "%s", start); + + /* line content must have minor node */ + if (start == NULL || + strncmp(start, larg->path, larg->len) != 0 || + start[larg->len] != ':') + return (DI_WALK_CONTINUE); + } + + p = di_devlink_path(devlink); + q = strrchr(p, '/'); + usbi_dbg(NULL, "%s", q); + + *(larg->linkpp) = strndup(p, strlen(p) - strlen(q)); + + return (DI_WALK_TERMINATE); +} + + +static int sunos_physpath_to_devlink( + const char *node_path, const char *match, char **link_path) +{ + walk_link_t larg; + di_devlink_handle_t hdl; + + *link_path = NULL; + larg.linkpp = link_path; + if ((hdl = di_devlink_init(NULL, 0)) == NULL) { + usbi_dbg(NULL, "di_devlink_init failure"); + return (-1); + } + + larg.len = strlen(node_path); + larg.path = (char *)node_path; + + (void) di_devlink_walk(hdl, match, NULL, DI_PRIMARY_LINK, + (void *)&larg, sunos_get_link); + + (void) di_devlink_fini(&hdl); + + if (*link_path == NULL) { + usbi_dbg(NULL, "there is no devlink for this path"); + return (-1); + } + + return 0; +} + +static int +sunos_usb_ioctl(struct libusb_device *dev, int cmd) +{ + int fd; + nvlist_t *nvlist; + char *end; + char *phypath; + char *hubpath; + char path_arg[PATH_MAX]; + sunos_dev_priv_t *dpriv; + devctl_ap_state_t devctl_ap_state; + struct devctl_iocdata iocdata; + + dpriv = usbi_get_device_priv(dev); + phypath = dpriv->phypath; + + end = strrchr(phypath, '/'); + if (end == NULL) + return (-1); + hubpath = strndup(phypath, end - phypath); + if (hubpath == NULL) + return (-1); + + end = strrchr(hubpath, '@'); + if (end == NULL) { + free(hubpath); + return (-1); + } + end++; + usbi_dbg(DEVICE_CTX(dev), "unitaddr: %s", end); + + nvlist_alloc(&nvlist, NV_UNIQUE_NAME_TYPE, KM_NOSLEEP); + nvlist_add_int32(nvlist, "port", dev->port_number); + //find the hub path + snprintf(path_arg, sizeof(path_arg), "/devices%s:hubd", hubpath); + usbi_dbg(DEVICE_CTX(dev), "ioctl hub path: %s", path_arg); + + fd = open(path_arg, O_RDONLY); + if (fd < 0) { + usbi_err(DEVICE_CTX(dev), "open failed: errno %d (%s)", errno, strerror(errno)); + nvlist_free(nvlist); + free(hubpath); + return (-1); + } + + memset(&iocdata, 0, sizeof(iocdata)); + memset(&devctl_ap_state, 0, sizeof(devctl_ap_state)); + + nvlist_pack(nvlist, (char **)&iocdata.nvl_user, &iocdata.nvl_usersz, NV_ENCODE_NATIVE, 0); + + iocdata.cmd = DEVCTL_AP_GETSTATE; + iocdata.flags = 0; + iocdata.c_nodename = (char *)"hub"; + iocdata.c_unitaddr = end; + iocdata.cpyout_buf = &devctl_ap_state; + usbi_dbg(DEVICE_CTX(dev), "%p, %" PRIuPTR, iocdata.nvl_user, iocdata.nvl_usersz); + + errno = 0; + if (ioctl(fd, DEVCTL_AP_GETSTATE, &iocdata) == -1) { + usbi_err(DEVICE_CTX(dev), "ioctl failed: fd %d, cmd %x, errno %d (%s)", + fd, DEVCTL_AP_GETSTATE, errno, strerror(errno)); + } else { + usbi_dbg(DEVICE_CTX(dev), "dev rstate: %d", devctl_ap_state.ap_rstate); + usbi_dbg(DEVICE_CTX(dev), "dev ostate: %d", devctl_ap_state.ap_ostate); + } + + errno = 0; + iocdata.cmd = cmd; + if (ioctl(fd, (int)cmd, &iocdata) != 0) { + usbi_err(DEVICE_CTX(dev), "ioctl failed: fd %d, cmd %x, errno %d (%s)", + fd, cmd, errno, strerror(errno)); + sleep(2); + } + + close(fd); + free(iocdata.nvl_user); + nvlist_free(nvlist); + free(hubpath); + + return (-errno); +} + +static int +sunos_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) +{ + sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev_handle->dev); + + UNUSED(interface); + + usbi_dbg(HANDLE_CTX(dev_handle), "%s", dpriv->ugenpath); + + return (dpriv->ugenpath == NULL); +} + +/* + * Private functions + */ +static int _errno_to_libusb(int); +static int sunos_usb_get_status(struct libusb_context *ctx, int fd); + +static string_list_t * +sunos_new_string_list(void) +{ + string_list_t *list; + + list = calloc(1, sizeof(string_list_t)); + if (list == NULL) + return (NULL); + list->string = calloc(DEFAULT_LISTSIZE, sizeof(char *)); + if (list->string == NULL) { + free(list); + return (NULL); + } + list->nargs = 0; + list->listsize = DEFAULT_LISTSIZE; + + return (list); +} + +static int +sunos_append_to_string_list(string_list_t *list, const char *arg) +{ + char *str = strdup(arg); + + if (str == NULL) + return (-1); + + if ((list->nargs + 1) == list->listsize) { /* +1 is for NULL */ + char **tmp = realloc(list->string, + sizeof(char *) * (list->listsize + 1)); + if (tmp == NULL) { + free(str); + return (-1); + } + list->string = tmp; + list->string[list->listsize++] = NULL; + } + list->string[list->nargs++] = str; + + return (0); +} + +static void +sunos_free_string_list(string_list_t *list) +{ + int i; + + for (i = 0; i < list->nargs; i++) { + free(list->string[i]); + } + + free(list->string); + free(list); +} + +static char ** +sunos_build_argv_list(string_list_t *list) +{ + return (list->string); +} + + +static int +sunos_exec_command(struct libusb_context *ctx, const char *path, + string_list_t *list) +{ + pid_t pid; + int status; + int waitstat; + int exit_status; + char **argv_list; + + argv_list = sunos_build_argv_list(list); + if (argv_list == NULL) + return (-1); + + pid = fork(); + if (pid == 0) { + /* child */ + execv(path, argv_list); + _exit(127); + } else if (pid > 0) { + /* parent */ + do { + waitstat = waitpid(pid, &status, 0); + } while ((waitstat == -1 && errno == EINTR) || + (waitstat == 0 && !WIFEXITED(status) && !WIFSIGNALED(status))); + + if (waitstat == 0) { + if (WIFEXITED(status)) + exit_status = WEXITSTATUS(status); + else + exit_status = WTERMSIG(status); + } else { + usbi_err(ctx, "waitpid failed: errno %d (%s)", errno, strerror(errno)); + exit_status = -1; + } + } else { + /* fork failed */ + usbi_err(ctx, "fork failed: errno %d (%s)", errno, strerror(errno)); + exit_status = -1; + } + + return (exit_status); +} + +static int +sunos_detach_kernel_driver(struct libusb_device_handle *dev_handle, + uint8_t interface_number) +{ + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + string_list_t *list; + char path_arg[PATH_MAX]; + sunos_dev_priv_t *dpriv; + int r; + + UNUSED(interface_number); + + dpriv = usbi_get_device_priv(dev_handle->dev); + snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath); + usbi_dbg(HANDLE_CTX(dev_handle), "%s", path_arg); + + list = sunos_new_string_list(); + if (list == NULL) + return (LIBUSB_ERROR_NO_MEM); + + /* attach ugen driver */ + r = 0; + r |= sunos_append_to_string_list(list, UPDATEDRV); + r |= sunos_append_to_string_list(list, "-a"); /* add rule */ + r |= sunos_append_to_string_list(list, "-i"); /* specific device */ + r |= sunos_append_to_string_list(list, path_arg); /* physical path */ + r |= sunos_append_to_string_list(list, "ugen"); + if (r) { + sunos_free_string_list(list); + return (LIBUSB_ERROR_NO_MEM); + } + + r = sunos_exec_command(ctx, UPDATEDRV_PATH, list); + sunos_free_string_list(list); + if (r < 0) + return (LIBUSB_ERROR_OTHER); + + /* reconfigure the driver node */ + r = 0; + r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_DISCONNECT); + r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_CONFIGURE); + if (r) + usbi_warn(HANDLE_CTX(dev_handle), "one or more ioctls failed"); + + snprintf(path_arg, sizeof(path_arg), "^usb/%x.%x", + dev_handle->dev->device_descriptor.idVendor, + dev_handle->dev->device_descriptor.idProduct); + sunos_physpath_to_devlink(dpriv->phypath, path_arg, &dpriv->ugenpath); + + if (access(dpriv->ugenpath, F_OK) == -1) { + usbi_err(HANDLE_CTX(dev_handle), "fail to detach kernel driver"); + return (LIBUSB_ERROR_IO); + } + + return sunos_usb_open_ep0(usbi_get_device_handle_priv(dev_handle), dpriv); +} + +static int +sunos_attach_kernel_driver(struct libusb_device_handle *dev_handle, + uint8_t interface_number) +{ + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + string_list_t *list; + char path_arg[PATH_MAX]; + sunos_dev_priv_t *dpriv; + int r; + + UNUSED(interface_number); + + /* we open the dev in detach driver, so we need close it first. */ + sunos_close(dev_handle); + + dpriv = usbi_get_device_priv(dev_handle->dev); + snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath); + usbi_dbg(HANDLE_CTX(dev_handle), "%s", path_arg); + + list = sunos_new_string_list(); + if (list == NULL) + return (LIBUSB_ERROR_NO_MEM); + + /* detach ugen driver */ + r = 0; + r |= sunos_append_to_string_list(list, UPDATEDRV); + r |= sunos_append_to_string_list(list, "-d"); /* add rule */ + r |= sunos_append_to_string_list(list, "-i"); /* specific device */ + r |= sunos_append_to_string_list(list, path_arg); /* physical path */ + r |= sunos_append_to_string_list(list, "ugen"); + if (r) { + sunos_free_string_list(list); + return (LIBUSB_ERROR_NO_MEM); + } + + r = sunos_exec_command(ctx, UPDATEDRV_PATH, list); + sunos_free_string_list(list); + if (r < 0) + return (LIBUSB_ERROR_OTHER); + + /* reconfigure the driver node */ + r = 0; + r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_CONFIGURE); + r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_DISCONNECT); + r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_CONFIGURE); + if (r) + usbi_warn(HANDLE_CTX(dev_handle), "one or more ioctls failed"); + + return 0; +} + +static int +sunos_fill_in_dev_info(di_node_t node, struct libusb_device *dev) +{ + int proplen; + int *i, n, *addr, *port_prop; + char *phypath; + uint8_t *rdata; + sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); + char match_str[PATH_MAX]; + + /* Device descriptors */ + proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, + "usb-dev-descriptor", &rdata); + if (proplen <= 0) { + return (LIBUSB_ERROR_IO); + } + bcopy(rdata, &dev->device_descriptor, LIBUSB_DT_DEVICE_SIZE); + + /* Raw configuration descriptors */ + proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, + "usb-raw-cfg-descriptors", &rdata); + if (proplen <= 0) { + usbi_dbg(DEVICE_CTX(dev), "can't find raw config descriptors"); + + return (LIBUSB_ERROR_IO); + } + dpriv->raw_cfgdescr = calloc(1, proplen); + if (dpriv->raw_cfgdescr == NULL) { + return (LIBUSB_ERROR_NO_MEM); + } else { + bcopy(rdata, dpriv->raw_cfgdescr, proplen); + dpriv->cfgvalue = ((struct libusb_config_descriptor *) + rdata)->bConfigurationValue; + } + + n = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", &port_prop); + + if ((n != 1) || (*port_prop <= 0)) { + return (LIBUSB_ERROR_IO); + } + dev->port_number = *port_prop; + + /* device physical path */ + phypath = di_devfs_path(node); + if (phypath) { + dpriv->phypath = strdup(phypath); + snprintf(match_str, sizeof(match_str), "^usb/%x.%x", + dev->device_descriptor.idVendor, + dev->device_descriptor.idProduct); + usbi_dbg(DEVICE_CTX(dev), "match is %s", match_str); + sunos_physpath_to_devlink(dpriv->phypath, match_str, &dpriv->ugenpath); + di_devfs_path_free(phypath); + + } else { + free(dpriv->raw_cfgdescr); + + return (LIBUSB_ERROR_IO); + } + + /* address */ + n = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "assigned-address", &addr); + if (n != 1 || *addr == 0) { + usbi_dbg(DEVICE_CTX(dev), "can't get address"); + } else { + dev->device_address = *addr; + } + + /* speed */ + if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "low-speed", &i) >= 0) { + dev->speed = LIBUSB_SPEED_LOW; + } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "high-speed", &i) >= 0) { + dev->speed = LIBUSB_SPEED_HIGH; + } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "full-speed", &i) >= 0) { + dev->speed = LIBUSB_SPEED_FULL; + } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "super-speed", &i) >= 0) { + dev->speed = LIBUSB_SPEED_SUPER; + } + + usbi_dbg(DEVICE_CTX(dev), "vid=%x pid=%x, path=%s, bus_nmber=0x%x, port_number=%d, speed=%d", + dev->device_descriptor.idVendor, dev->device_descriptor.idProduct, + dpriv->phypath, dev->bus_number, dev->port_number, dev->speed); + + return (LIBUSB_SUCCESS); +} + +static int +sunos_add_devices(di_devlink_t link, void *arg) +{ + struct devlink_cbarg *largs = (struct devlink_cbarg *)arg; + struct node_args *nargs; + di_node_t myself, dn; + uint64_t session_id = 0; + uint64_t sid = 0; + uint64_t bdf = 0; + struct libusb_device *dev; + sunos_dev_priv_t *devpriv; + int n, *j; + int i = 0; + int *addr_prop; + uint8_t bus_number = 0; + uint32_t * regbuf = NULL; + uint32_t reg; + + UNUSED(link); + + nargs = (struct node_args *)largs->nargs; + myself = largs->myself; + + /* + * Construct session ID. + * session ID = dev_addr | hub addr |parent hub addr|...|root hub bdf + * 8 bits 8bits 8 bits 16bits + */ + if (myself == DI_NODE_NIL) + return (DI_WALK_CONTINUE); + + dn = myself; + /* find the root hub */ + while (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "root-hub", &j) != 0) { + usbi_dbg(NULL, "find_root_hub:%s", di_devfs_path(dn)); + n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, + "assigned-address", &addr_prop); + session_id |= ((addr_prop[0] & 0xff) << i++ * 8); + dn = di_parent_node(dn); + } + + /* dn is the root hub node */ + n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "reg", (int **)®buf); + reg = regbuf[0]; + bdf = (PCI_REG_BUS_G(reg) << 8) | (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg); + /* bdf must larger than i*8 bits */ + session_id |= (bdf << i * 8); + bus_number = (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg); + + usbi_dbg(NULL, "device bus address=%s:%x, name:%s", + di_bus_addr(myself), bus_number, di_node_name(dn)); + usbi_dbg(NULL, "session id org:%" PRIx64, session_id); + + /* dn is the usb device */ + for (dn = di_child_node(myself); dn != DI_NODE_NIL; dn = di_sibling_node(dn)) { + usbi_dbg(NULL, "device path:%s", di_devfs_path(dn)); + /* skip hub devices, because its driver can not been unload */ + if (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "usb-port-count", &addr_prop) != -1) + continue; + /* usb_addr */ + n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, + "assigned-address", &addr_prop); + if ((n != 1) || (addr_prop[0] == 0)) { + usbi_dbg(NULL, "cannot get valid usb_addr"); + continue; + } + + sid = (session_id << 8) | (addr_prop[0] & 0xff) ; + usbi_dbg(NULL, "session id %" PRIX64, sid); + + dev = usbi_get_device_by_session_id(nargs->ctx, sid); + if (dev == NULL) { + dev = usbi_alloc_device(nargs->ctx, sid); + if (dev == NULL) { + usbi_dbg(NULL, "can't alloc device"); + continue; + } + devpriv = usbi_get_device_priv(dev); + dev->bus_number = bus_number; + + if (sunos_fill_in_dev_info(dn, dev) != LIBUSB_SUCCESS) { + libusb_unref_device(dev); + usbi_dbg(NULL, "get information fail"); + continue; + } + if (usbi_sanitize_device(dev) < 0) { + libusb_unref_device(dev); + usbi_dbg(NULL, "sanatize failed: "); + return (DI_WALK_TERMINATE); + } + } else { + devpriv = usbi_get_device_priv(dev); + usbi_dbg(NULL, "Dev %s exists", devpriv->ugenpath); + } + + if (discovered_devs_append(*(nargs->discdevs), dev) == NULL) { + usbi_dbg(NULL, "cannot append device"); + } + + /* + * we alloc and hence ref this dev. We don't need to ref it + * hereafter. Front end or app should take care of their ref. + */ + libusb_unref_device(dev); + + usbi_dbg(NULL, "Device %s %s id=0x%" PRIx64 ", devcount:%" PRIuPTR + ", bdf=%" PRIx64, + devpriv->ugenpath, di_devfs_path(dn), (uint64_t)sid, + (*nargs->discdevs)->len, bdf); + } + + return (DI_WALK_CONTINUE); +} + +static int +sunos_walk_minor_node_link(di_node_t node, void *args) +{ + di_minor_t minor = DI_MINOR_NIL; + char *minor_path; + struct devlink_cbarg arg; + struct node_args *nargs = (struct node_args *)args; + di_devlink_handle_t devlink_hdl = nargs->dlink_hdl; + + /* walk each minor to find usb devices */ + while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) { + minor_path = di_devfs_minor_path(minor); + arg.nargs = args; + arg.myself = node; + arg.minor = minor; + (void) di_devlink_walk(devlink_hdl, + "^usb/hub[0-9]+", minor_path, + DI_PRIMARY_LINK, (void *)&arg, sunos_add_devices); + di_devfs_path_free(minor_path); + } + + /* switch to a different node */ + nargs->last_ugenpath = NULL; + + return (DI_WALK_CONTINUE); +} + +int +sunos_get_device_list(struct libusb_context * ctx, + struct discovered_devs **discdevs) +{ + di_node_t root_node; + struct node_args args; + di_devlink_handle_t devlink_hdl; + + args.ctx = ctx; + args.discdevs = discdevs; + args.last_ugenpath = NULL; + if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) { + usbi_dbg(ctx, "di_int() failed: errno %d (%s)", errno, strerror(errno)); + return (LIBUSB_ERROR_IO); + } + + if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL) { + di_fini(root_node); + usbi_dbg(ctx, "di_devlink_init() failed: errno %d (%s)", errno, strerror(errno)); + + return (LIBUSB_ERROR_IO); + } + args.dlink_hdl = devlink_hdl; + + /* walk each node to find USB devices */ + if (di_walk_node(root_node, DI_WALK_SIBFIRST, &args, + sunos_walk_minor_node_link) == -1) { + usbi_dbg(ctx, "di_walk_node() failed: errno %d (%s)", errno, strerror(errno)); + di_fini(root_node); + + return (LIBUSB_ERROR_IO); + } + + di_fini(root_node); + di_devlink_fini(&devlink_hdl); + + usbi_dbg(ctx, "%zu devices", (*discdevs)->len); + + return ((*discdevs)->len); +} + +static int +sunos_usb_open_ep0(sunos_dev_handle_priv_t *hpriv, sunos_dev_priv_t *dpriv) +{ + char filename[PATH_MAX + 1]; + + if (hpriv->eps[0].datafd > 0) { + return (LIBUSB_SUCCESS); + } + snprintf(filename, PATH_MAX, "%s/cntrl0", dpriv->ugenpath); + + usbi_dbg(NULL, "opening %s", filename); + hpriv->eps[0].datafd = open(filename, O_RDWR); + if (hpriv->eps[0].datafd < 0) { + return(_errno_to_libusb(errno)); + } + + snprintf(filename, PATH_MAX, "%s/cntrl0stat", dpriv->ugenpath); + hpriv->eps[0].statfd = open(filename, O_RDONLY); + if (hpriv->eps[0].statfd < 0) { + close(hpriv->eps[0].datafd); + hpriv->eps[0].datafd = -1; + + return(_errno_to_libusb(errno)); + } + + return (LIBUSB_SUCCESS); +} + +static void +sunos_usb_close_all_eps(sunos_dev_handle_priv_t *hdev) +{ + int i; + + /* not close ep0 */ + for (i = 1; i < USB_MAXENDPOINTS; i++) { + if (hdev->eps[i].datafd != -1) { + (void) close(hdev->eps[i].datafd); + hdev->eps[i].datafd = -1; + } + if (hdev->eps[i].statfd != -1) { + (void) close(hdev->eps[i].statfd); + hdev->eps[i].statfd = -1; + } + } +} + +static void +sunos_usb_close_ep0(sunos_dev_handle_priv_t *hdev) +{ + if (hdev->eps[0].datafd >= 0) { + close(hdev->eps[0].datafd); + close(hdev->eps[0].statfd); + hdev->eps[0].datafd = -1; + hdev->eps[0].statfd = -1; + } +} + +static uchar_t +sunos_usb_ep_index(uint8_t ep_addr) +{ + return ((ep_addr & LIBUSB_ENDPOINT_ADDRESS_MASK) + + ((ep_addr & LIBUSB_ENDPOINT_DIR_MASK) ? 16 : 0)); +} + +static int +sunos_find_interface(struct libusb_device_handle *hdev, + uint8_t endpoint, uint8_t *interface) +{ + struct libusb_config_descriptor *config; + int r; + int iface_idx; + + r = libusb_get_active_config_descriptor(hdev->dev, &config); + if (r < 0) { + return (LIBUSB_ERROR_INVALID_PARAM); + } + + for (iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) { + const struct libusb_interface *iface = + &config->interface[iface_idx]; + int altsetting_idx; + + for (altsetting_idx = 0; altsetting_idx < iface->num_altsetting; + altsetting_idx++) { + const struct libusb_interface_descriptor *altsetting = + &iface->altsetting[altsetting_idx]; + int ep_idx; + + for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; + ep_idx++) { + const struct libusb_endpoint_descriptor *ep = + &altsetting->endpoint[ep_idx]; + if (ep->bEndpointAddress == endpoint) { + *interface = iface_idx; + libusb_free_config_descriptor(config); + + return (LIBUSB_SUCCESS); + } + } + } + } + libusb_free_config_descriptor(config); + + return (LIBUSB_ERROR_INVALID_PARAM); +} + +static int +sunos_check_device_and_status_open(struct libusb_device_handle *hdl, + uint8_t ep_addr, int ep_type) +{ + char filename[PATH_MAX + 1], statfilename[PATH_MAX + 1]; + char cfg_num[16], alt_num[16]; + int fd, fdstat, mode; + uint8_t ifc = 0; + uint8_t ep_index; + sunos_dev_handle_priv_t *hpriv; + + usbi_dbg(HANDLE_CTX(hdl), "open ep 0x%02x", ep_addr); + hpriv = usbi_get_device_handle_priv(hdl); + ep_index = sunos_usb_ep_index(ep_addr); + /* ep already opened */ + if ((hpriv->eps[ep_index].datafd > 0) && + (hpriv->eps[ep_index].statfd > 0)) { + usbi_dbg(HANDLE_CTX(hdl), "ep 0x%02x already opened, return success", + ep_addr); + + return (0); + } + + if (sunos_find_interface(hdl, ep_addr, &ifc) < 0) { + usbi_dbg(HANDLE_CTX(hdl), "can't find interface for endpoint 0x%02x", + ep_addr); + + return (EACCES); + } + + /* create filename */ + if (hpriv->config_index > 0) { + (void) snprintf(cfg_num, sizeof(cfg_num), "cfg%d", + hpriv->config_index + 1); + } else { + bzero(cfg_num, sizeof(cfg_num)); + } + + if (hpriv->altsetting[ifc] > 0) { + (void) snprintf(alt_num, sizeof(alt_num), ".%d", + hpriv->altsetting[ifc]); + } else { + bzero(alt_num, sizeof(alt_num)); + } + + (void) snprintf(filename, PATH_MAX, "%s/%sif%d%s%s%d", + hpriv->dpriv->ugenpath, cfg_num, ifc, alt_num, + (ep_addr & LIBUSB_ENDPOINT_DIR_MASK) ? "in" : + "out", (ep_addr & LIBUSB_ENDPOINT_ADDRESS_MASK)); + (void) snprintf(statfilename, PATH_MAX, "%sstat", filename); + + /* + * In case configuration has been switched, the xfer endpoint needs + * to be opened before the status endpoint, due to a ugen issue. + * However, to enable the one transfer mode for an Interrupt-In pipe, + * the status endpoint needs to be opened before the xfer endpoint. + * So, open the xfer mode first and close it immediately + * as a workaround. This will handle the configuration switch. + * Then, open the status endpoint. If for an Interrupt-in pipe, + * write the USB_EP_INTR_ONE_XFER control to the status endpoint + * to enable the one transfer mode. Then, re-open the xfer mode. + */ + if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + mode = O_RDWR; + } else if (ep_addr & LIBUSB_ENDPOINT_IN) { + mode = O_RDONLY; + } else { + mode = O_WRONLY; + } + /* Open the xfer endpoint first */ + if ((fd = open(filename, mode)) == -1) { + usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", filename, errno, + strerror(errno)); + + return (errno); + } + /* And immediately close the xfer endpoint */ + (void) close(fd); + + /* + * Open the status endpoint. + * If for an Interrupt-IN pipe, need to enable the one transfer mode + * by writing USB_EP_INTR_ONE_XFER control to the status endpoint + * before opening the xfer endpoint + */ + if ((ep_type == LIBUSB_TRANSFER_TYPE_INTERRUPT) && + (ep_addr & LIBUSB_ENDPOINT_IN)) { + char control = USB_EP_INTR_ONE_XFER; + ssize_t count; + + /* Open the status endpoint with RDWR */ + if ((fdstat = open(statfilename, O_RDWR)) == -1) { + usbi_dbg(HANDLE_CTX(hdl), "can't open %s RDWR: errno %d (%s)", + statfilename, errno, strerror(errno)); + + return (errno); + } else { + count = write(fdstat, &control, sizeof(control)); + if (count != 1) { + /* this should have worked */ + usbi_dbg(HANDLE_CTX(hdl), "can't write to %s: errno %d (%s)", + statfilename, errno, strerror(errno)); + (void) close(fdstat); + + return (errno); + } + } + } else { + if ((fdstat = open(statfilename, O_RDONLY)) == -1) { + usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", statfilename, errno, + strerror(errno)); + + return (errno); + } + } + + /* Re-open the xfer endpoint */ + if ((fd = open(filename, mode)) == -1) { + usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", filename, errno, + strerror(errno)); + (void) close(fdstat); + + return (errno); + } + + hpriv->eps[ep_index].datafd = fd; + hpriv->eps[ep_index].statfd = fdstat; + usbi_dbg(HANDLE_CTX(hdl), "ep=0x%02x datafd=%d, statfd=%d", ep_addr, fd, fdstat); + + return (0); +} + +int +sunos_open(struct libusb_device_handle *handle) +{ + sunos_dev_handle_priv_t *hpriv; + sunos_dev_priv_t *dpriv; + int i; + int ret; + + hpriv = usbi_get_device_handle_priv(handle); + dpriv = usbi_get_device_priv(handle->dev); + hpriv->dpriv = dpriv; + + /* set all file descriptors to "closed" */ + for (i = 0; i < USB_MAXENDPOINTS; i++) { + hpriv->eps[i].datafd = -1; + hpriv->eps[i].statfd = -1; + } + + if (sunos_kernel_driver_active(handle, 0)) { + /* pretend we can open the device */ + return (LIBUSB_SUCCESS); + } + + if ((ret = sunos_usb_open_ep0(hpriv, dpriv)) != LIBUSB_SUCCESS) { + usbi_dbg(HANDLE_CTX(handle), "fail: %d", ret); + return (ret); + } + + return (LIBUSB_SUCCESS); +} + +void +sunos_close(struct libusb_device_handle *handle) +{ + sunos_dev_handle_priv_t *hpriv; + + usbi_dbg(HANDLE_CTX(handle), " "); + + hpriv = usbi_get_device_handle_priv(handle); + + sunos_usb_close_all_eps(hpriv); + sunos_usb_close_ep0(hpriv); +} + +int +sunos_get_active_config_descriptor(struct libusb_device *dev, + void *buf, size_t len) +{ + sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); + struct libusb_config_descriptor *cfg; + int proplen; + di_node_t node; + uint8_t *rdata; + + /* + * Keep raw configuration descriptors updated, in case config + * has ever been changed through setCfg. + */ + if ((node = di_init(dpriv->phypath, DINFOCPYALL)) == DI_NODE_NIL) { + usbi_dbg(DEVICE_CTX(dev), "di_int() failed: errno %d (%s)", errno, + strerror(errno)); + return (LIBUSB_ERROR_IO); + } + proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, + "usb-raw-cfg-descriptors", &rdata); + if (proplen <= 0) { + usbi_dbg(DEVICE_CTX(dev), "can't find raw config descriptors"); + + return (LIBUSB_ERROR_IO); + } + dpriv->raw_cfgdescr = realloc(dpriv->raw_cfgdescr, proplen); + if (dpriv->raw_cfgdescr == NULL) { + return (LIBUSB_ERROR_NO_MEM); + } else { + bcopy(rdata, dpriv->raw_cfgdescr, proplen); + dpriv->cfgvalue = ((struct libusb_config_descriptor *) + rdata)->bConfigurationValue; + } + di_fini(node); + + cfg = (struct libusb_config_descriptor *)dpriv->raw_cfgdescr; + len = MIN(len, libusb_le16_to_cpu(cfg->wTotalLength)); + memcpy(buf, dpriv->raw_cfgdescr, len); + usbi_dbg(DEVICE_CTX(dev), "path:%s len %zu", dpriv->phypath, len); + + return (len); +} + +int +sunos_get_config_descriptor(struct libusb_device *dev, uint8_t idx, + void *buf, size_t len) +{ + UNUSED(idx); + /* XXX */ + return(sunos_get_active_config_descriptor(dev, buf, len)); +} + +int +sunos_get_configuration(struct libusb_device_handle *handle, uint8_t *config) +{ + sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev); + + *config = dpriv->cfgvalue; + + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %u", *config); + + return (LIBUSB_SUCCESS); +} + +int +sunos_set_configuration(struct libusb_device_handle *handle, int config) +{ + sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev); + sunos_dev_handle_priv_t *hpriv; + + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %d", config); + hpriv = usbi_get_device_handle_priv(handle); + + if (dpriv->ugenpath == NULL) + return (LIBUSB_ERROR_NOT_SUPPORTED); + + if (config < 1) + return (LIBUSB_ERROR_NOT_SUPPORTED); + + dpriv->cfgvalue = config; + hpriv->config_index = config - 1; + + return (LIBUSB_SUCCESS); +} + +int +sunos_claim_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + UNUSED(handle); + + usbi_dbg(HANDLE_CTX(handle), "iface %u", iface); + + return (LIBUSB_SUCCESS); +} + +int +sunos_release_interface(struct libusb_device_handle *handle, uint8_t iface) +{ + sunos_dev_handle_priv_t *hpriv = usbi_get_device_handle_priv(handle); + + usbi_dbg(HANDLE_CTX(handle), "iface %u", iface); + + /* XXX: can we release it? */ + hpriv->altsetting[iface] = 0; + + return (LIBUSB_SUCCESS); +} + +int +sunos_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t iface, + uint8_t altsetting) +{ + sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev); + sunos_dev_handle_priv_t *hpriv = usbi_get_device_handle_priv(handle); + + usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting); + + if (dpriv->ugenpath == NULL) + return (LIBUSB_ERROR_NOT_FOUND); + + /* XXX: can we switch altsetting? */ + hpriv->altsetting[iface] = altsetting; + + return (LIBUSB_SUCCESS); +} + +static void +usb_dump_data(const void *data, size_t size) +{ + const uint8_t *p = data; + size_t i; + + if (getenv("LIBUSB_DEBUG") == NULL) { + return; + } + + (void) fprintf(stderr, "data dump:"); + for (i = 0; i < size; i++) { + if (i % 16 == 0) { + (void) fprintf(stderr, "\n%08zx ", i); + } + (void) fprintf(stderr, "%02x ", p[i]); + } + (void) fprintf(stderr, "\n"); +} + +static void +sunos_async_callback(union sigval arg) +{ + struct sunos_transfer_priv *tpriv = + (struct sunos_transfer_priv *)arg.sival_ptr; + struct libusb_transfer *xfer = tpriv->transfer; + struct aiocb *aiocb = &tpriv->aiocb; + int ret; + sunos_dev_handle_priv_t *hpriv; + uint8_t ep; + libusb_device_handle *dev_handle; + + dev_handle = xfer->dev_handle; + + /* libusb can forcibly interrupt transfer in do_close() */ + if (dev_handle != NULL) { + hpriv = usbi_get_device_handle_priv(dev_handle); + ep = sunos_usb_ep_index(xfer->endpoint); + + ret = aio_error(aiocb); + if (ret != 0) { + xfer->status = sunos_usb_get_status(TRANSFER_CTX(xfer), hpriv->eps[ep].statfd); + } else { + xfer->actual_length = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(xfer)->transferred = + aio_return(aiocb); + } + + usb_dump_data(xfer->buffer, xfer->actual_length); + + usbi_dbg(TRANSFER_CTX(xfer), "ret=%d, len=%d, actual_len=%d", ret, xfer->length, + xfer->actual_length); + + /* async notification */ + usbi_signal_transfer_completion(LIBUSB_TRANSFER_TO_USBI_TRANSFER(xfer)); + } +} + +static int +sunos_do_async_io(struct libusb_transfer *transfer) +{ + int ret = -1; + struct aiocb *aiocb; + sunos_dev_handle_priv_t *hpriv; + uint8_t ep; + struct sunos_transfer_priv *tpriv; + + usbi_dbg(TRANSFER_CTX(transfer), " "); + + tpriv = usbi_get_transfer_priv(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)); + hpriv = usbi_get_device_handle_priv(transfer->dev_handle); + ep = sunos_usb_ep_index(transfer->endpoint); + + tpriv->transfer = transfer; + aiocb = &tpriv->aiocb; + bzero(aiocb, sizeof(*aiocb)); + aiocb->aio_fildes = hpriv->eps[ep].datafd; + aiocb->aio_buf = transfer->buffer; + aiocb->aio_nbytes = transfer->length; + aiocb->aio_lio_opcode = + ((transfer->endpoint & LIBUSB_ENDPOINT_DIR_MASK) == + LIBUSB_ENDPOINT_IN) ? LIO_READ:LIO_WRITE; + aiocb->aio_sigevent.sigev_notify = SIGEV_THREAD; + aiocb->aio_sigevent.sigev_value.sival_ptr = tpriv; + aiocb->aio_sigevent.sigev_notify_function = sunos_async_callback; + + if (aiocb->aio_lio_opcode == LIO_READ) { + ret = aio_read(aiocb); + } else { + ret = aio_write(aiocb); + } + + return (ret); +} + +/* return the number of bytes read/written */ +static ssize_t +usb_do_io(struct libusb_context *ctx, int fd, int stat_fd, void *data, size_t size, int flag, int *status) +{ + int error; + ssize_t ret = -1; + + usbi_dbg(ctx, "usb_do_io(): datafd=%d statfd=%d size=0x%zx flag=%s", + fd, stat_fd, size, flag? "WRITE":"READ"); + + switch (flag) { + case READ: + errno = 0; + ret = read(fd, data, size); + usb_dump_data(data, size); + break; + case WRITE: + usb_dump_data(data, size); + errno = 0; + ret = write(fd, data, size); + break; + } + + usbi_dbg(ctx, "usb_do_io(): amount=%zd", ret); + + if (ret < 0) { + int save_errno = errno; + + usbi_dbg(ctx, "TID=%x io %s errno %d (%s)", pthread_self(), + flag?"WRITE":"READ", errno, strerror(errno)); + + /* sunos_usb_get_status will do a read and overwrite errno */ + error = sunos_usb_get_status(ctx, stat_fd); + usbi_dbg(ctx, "io status=%d errno %d (%s)", error, + save_errno, strerror(save_errno)); + + if (status) { + *status = save_errno; + } + + return (save_errno); + + } else if (status) { + *status = 0; + } + + return (ret); +} + +static int +solaris_submit_ctrl_on_default(struct libusb_transfer *transfer) +{ + ssize_t ret = -1, setup_ret; + int status; + sunos_dev_handle_priv_t *hpriv; + struct libusb_device_handle *hdl = transfer->dev_handle; + uint16_t wLength; + uint8_t *data = transfer->buffer; + + hpriv = usbi_get_device_handle_priv(hdl); + wLength = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; + + if (hpriv->eps[0].datafd == -1) { + usbi_dbg(TRANSFER_CTX(transfer), "ep0 not opened"); + + return (LIBUSB_ERROR_NOT_FOUND); + } + + if ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { + usbi_dbg(TRANSFER_CTX(transfer), "IN request"); + ret = usb_do_io(TRANSFER_CTX(transfer), hpriv->eps[0].datafd, + hpriv->eps[0].statfd, data, LIBUSB_CONTROL_SETUP_SIZE, + WRITE, &status); + } else { + usbi_dbg(TRANSFER_CTX(transfer), "OUT request"); + ret = usb_do_io(TRANSFER_CTX(transfer), hpriv->eps[0].datafd, hpriv->eps[0].statfd, + transfer->buffer, transfer->length, WRITE, + (int *)&transfer->status); + } + + setup_ret = ret; + if (ret < (ssize_t)LIBUSB_CONTROL_SETUP_SIZE) { + usbi_dbg(TRANSFER_CTX(transfer), "error sending control msg: %zd", ret); + + return (LIBUSB_ERROR_IO); + } + + ret = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; + + /* Read the remaining bytes for IN request */ + if ((wLength) && ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) == + LIBUSB_ENDPOINT_IN)) { + usbi_dbg(TRANSFER_CTX(transfer), "DATA: %d", transfer->length - (int)setup_ret); + ret = usb_do_io(TRANSFER_CTX(transfer), hpriv->eps[0].datafd, + hpriv->eps[0].statfd, + transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, + wLength, READ, (int *)&transfer->status); + } + + if (ret >= 0) { + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->transferred = ret; + } + usbi_dbg(TRANSFER_CTX(transfer), "Done: ctrl data bytes %zd", ret); + + /** + * Sync transfer handling. + * We should release transfer lock here and later get it back + * as usbi_handle_transfer_completion() takes its own transfer lock. + */ + usbi_mutex_unlock(&LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->lock); + ret = usbi_handle_transfer_completion(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer), + transfer->status); + usbi_mutex_lock(&LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->lock); + + return (ret); +} + +int +sunos_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) +{ + int ret; + + usbi_dbg(HANDLE_CTX(handle), "endpoint=0x%02x", endpoint); + + ret = libusb_control_transfer(handle, LIBUSB_ENDPOINT_OUT | + LIBUSB_RECIPIENT_ENDPOINT | LIBUSB_REQUEST_TYPE_STANDARD, + LIBUSB_REQUEST_CLEAR_FEATURE, 0, endpoint, NULL, 0, 1000); + + usbi_dbg(HANDLE_CTX(handle), "ret=%d", ret); + + return (ret); +} + +void +sunos_destroy_device(struct libusb_device *dev) +{ + sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); + + usbi_dbg(DEVICE_CTX(dev), "destroy everything"); + free(dpriv->raw_cfgdescr); + free(dpriv->ugenpath); + free(dpriv->phypath); +} + +int +sunos_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer; + struct libusb_device_handle *hdl; + int err = 0; + + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + hdl = transfer->dev_handle; + + err = sunos_check_device_and_status_open(hdl, + transfer->endpoint, transfer->type); + if (err < 0) { + + return (_errno_to_libusb(err)); + } + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + /* sync transfer */ + usbi_dbg(ITRANSFER_CTX(itransfer), "CTRL transfer: %d", transfer->length); + err = solaris_submit_ctrl_on_default(transfer); + break; + + case LIBUSB_TRANSFER_TYPE_BULK: + /* fallthru */ + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (transfer->type == LIBUSB_TRANSFER_TYPE_BULK) + usbi_dbg(ITRANSFER_CTX(itransfer), "BULK transfer: %d", transfer->length); + else + usbi_dbg(ITRANSFER_CTX(itransfer), "INTR transfer: %d", transfer->length); + err = sunos_do_async_io(transfer); + break; + + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + /* Isochronous/Stream is not supported */ + + /* fallthru */ + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + usbi_dbg(ITRANSFER_CTX(itransfer), "ISOC transfer: %d", transfer->length); + else + usbi_dbg(ITRANSFER_CTX(itransfer), "BULK STREAM transfer: %d", transfer->length); + err = LIBUSB_ERROR_NOT_SUPPORTED; + break; + } + + return (err); +} + +int +sunos_cancel_transfer(struct usbi_transfer *itransfer) +{ + sunos_xfer_priv_t *tpriv; + sunos_dev_handle_priv_t *hpriv; + struct libusb_transfer *transfer; + struct aiocb *aiocb; + uint8_t ep; + int ret; + + tpriv = usbi_get_transfer_priv(itransfer); + aiocb = &tpriv->aiocb; + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + hpriv = usbi_get_device_handle_priv(transfer->dev_handle); + ep = sunos_usb_ep_index(transfer->endpoint); + + ret = aio_cancel(hpriv->eps[ep].datafd, aiocb); + + usbi_dbg(ITRANSFER_CTX(itransfer), "aio->fd=%d fd=%d ret = %d, %s", aiocb->aio_fildes, + hpriv->eps[ep].datafd, ret, (ret == AIO_CANCELED)? + strerror(0):strerror(errno)); + + if (ret != AIO_CANCELED) { + ret = _errno_to_libusb(errno); + } else { + /* + * we don't need to call usbi_handle_transfer_cancellation(), + * because we'll handle everything in sunos_async_callback. + */ + ret = LIBUSB_SUCCESS; + } + + return (ret); +} + +int +sunos_handle_transfer_completion(struct usbi_transfer *itransfer) +{ + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); +} + +int +_errno_to_libusb(int err) +{ + usbi_dbg(NULL, "error: %s (%d)", strerror(err), err); + + switch (err) { + case EIO: + return (LIBUSB_ERROR_IO); + case EACCES: + return (LIBUSB_ERROR_ACCESS); + case ENOENT: + return (LIBUSB_ERROR_NO_DEVICE); + case ENOMEM: + return (LIBUSB_ERROR_NO_MEM); + case ETIMEDOUT: + return (LIBUSB_ERROR_TIMEOUT); + } + + return (LIBUSB_ERROR_OTHER); +} + +/* + * sunos_usb_get_status: + * gets status of endpoint + * + * Returns: ugen's last cmd status + */ +static int +sunos_usb_get_status(struct libusb_context *ctx, int fd) +{ + int status; + ssize_t ret; + + usbi_dbg(ctx, "sunos_usb_get_status(): fd=%d", fd); + + ret = read(fd, &status, sizeof(status)); + if (ret == sizeof(status)) { + switch (status) { + case USB_LC_STAT_NOERROR: + usbi_dbg(ctx, "No Error"); + break; + case USB_LC_STAT_CRC: + usbi_dbg(ctx, "CRC Timeout Detected\n"); + break; + case USB_LC_STAT_BITSTUFFING: + usbi_dbg(ctx, "Bit Stuffing Violation\n"); + break; + case USB_LC_STAT_DATA_TOGGLE_MM: + usbi_dbg(ctx, "Data Toggle Mismatch\n"); + break; + case USB_LC_STAT_STALL: + usbi_dbg(ctx, "End Point Stalled\n"); + break; + case USB_LC_STAT_DEV_NOT_RESP: + usbi_dbg(ctx, "Device is Not Responding\n"); + break; + case USB_LC_STAT_PID_CHECKFAILURE: + usbi_dbg(ctx, "PID Check Failure\n"); + break; + case USB_LC_STAT_UNEXP_PID: + usbi_dbg(ctx, "Unexpected PID\n"); + break; + case USB_LC_STAT_DATA_OVERRUN: + usbi_dbg(ctx, "Data Exceeded Size\n"); + break; + case USB_LC_STAT_DATA_UNDERRUN: + usbi_dbg(ctx, "Less data received\n"); + break; + case USB_LC_STAT_BUFFER_OVERRUN: + usbi_dbg(ctx, "Buffer Size Exceeded\n"); + break; + case USB_LC_STAT_BUFFER_UNDERRUN: + usbi_dbg(ctx, "Buffer Underrun\n"); + break; + case USB_LC_STAT_TIMEOUT: + usbi_dbg(ctx, "Command Timed Out\n"); + break; + case USB_LC_STAT_NOT_ACCESSED: + usbi_dbg(ctx, "Not Accessed by h/w\n"); + break; + case USB_LC_STAT_UNSPECIFIED_ERR: + usbi_dbg(ctx, "Unspecified Error\n"); + break; + case USB_LC_STAT_NO_BANDWIDTH: + usbi_dbg(ctx, "No Bandwidth\n"); + break; + case USB_LC_STAT_HW_ERR: + usbi_dbg(ctx, "Host Controller h/w Error\n"); + break; + case USB_LC_STAT_SUSPENDED: + usbi_dbg(ctx, "Device was Suspended\n"); + break; + case USB_LC_STAT_DISCONNECTED: + usbi_dbg(ctx, "Device was Disconnected\n"); + break; + case USB_LC_STAT_INTR_BUF_FULL: + usbi_dbg(ctx, "Interrupt buffer was full\n"); + break; + case USB_LC_STAT_INVALID_REQ: + usbi_dbg(ctx, "Request was Invalid\n"); + break; + case USB_LC_STAT_INTERRUPTED: + usbi_dbg(ctx, "Request was Interrupted\n"); + break; + case USB_LC_STAT_NO_RESOURCES: + usbi_dbg(ctx, "No resources available for " + "request\n"); + break; + case USB_LC_STAT_INTR_POLLING_FAILED: + usbi_dbg(ctx, "Failed to Restart Poll"); + break; + default: + usbi_dbg(ctx, "Error Not Determined %d\n", + status); + break; + } + } else { + usbi_dbg(ctx, "read stat error: %s",strerror(errno)); + status = -1; + } + + return (status); +} + +const struct usbi_os_backend usbi_backend = { + .name = "Solaris", + .caps = 0, + .get_device_list = sunos_get_device_list, + .get_active_config_descriptor = sunos_get_active_config_descriptor, + .get_config_descriptor = sunos_get_config_descriptor, + .open = sunos_open, + .close = sunos_close, + .get_configuration = sunos_get_configuration, + .set_configuration = sunos_set_configuration, + .claim_interface = sunos_claim_interface, + .release_interface = sunos_release_interface, + .set_interface_altsetting = sunos_set_interface_altsetting, + .clear_halt = sunos_clear_halt, + .kernel_driver_active = sunos_kernel_driver_active, + .detach_kernel_driver = sunos_detach_kernel_driver, + .attach_kernel_driver = sunos_attach_kernel_driver, + .destroy_device = sunos_destroy_device, + .submit_transfer = sunos_submit_transfer, + .cancel_transfer = sunos_cancel_transfer, + .handle_transfer_completion = sunos_handle_transfer_completion, + .device_priv_size = sizeof(sunos_dev_priv_t), + .device_handle_priv_size = sizeof(sunos_dev_handle_priv_t), + .transfer_priv_size = sizeof(sunos_xfer_priv_t), +}; diff --git a/src/os/sunos_usb.h b/src/os/sunos_usb.h new file mode 100644 index 0000000..2988398 --- /dev/null +++ b/src/os/sunos_usb.h @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2016, Oracle and/or its affiliates. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_SUNOS_H +#define LIBUSB_SUNOS_H + +#include <libdevinfo.h> +#include <pthread.h> +#include "libusbi.h" + +#define READ 0 +#define WRITE 1 + +typedef struct sunos_device_priv { + uint8_t cfgvalue; /* active config value */ + uint8_t *raw_cfgdescr; /* active config descriptor */ + char *ugenpath; /* name of the ugen(4) node */ + char *phypath; /* physical path */ +} sunos_dev_priv_t; + +typedef struct endpoint { + int datafd; /* data file */ + int statfd; /* state file */ +} sunos_ep_priv_t; + +typedef struct sunos_device_handle_priv { + uint8_t altsetting[USB_MAXINTERFACES]; /* a interface's alt */ + uint8_t config_index; + sunos_ep_priv_t eps[USB_MAXENDPOINTS]; + sunos_dev_priv_t *dpriv; /* device private */ +} sunos_dev_handle_priv_t; + +typedef struct sunos_transfer_priv { + struct aiocb aiocb; + struct libusb_transfer *transfer; +} sunos_xfer_priv_t; + +struct node_args { + struct libusb_context *ctx; + struct discovered_devs **discdevs; + const char *last_ugenpath; + di_devlink_handle_t dlink_hdl; +}; + +struct devlink_cbarg { + struct node_args *nargs; /* di node walk arguments */ + di_node_t myself; /* the di node */ + di_minor_t minor; +}; + +typedef struct walk_link { + char *path; + int len; + char **linkpp; +} walk_link_t; + +/* AIO callback args */ +struct aio_callback_args{ + struct libusb_transfer *transfer; + struct aiocb aiocb; +}; + +#endif /* LIBUSB_SUNOS_H */ diff --git a/src/os/threads_posix.c b/src/os/threads_posix.c new file mode 100644 index 0000000..0e0e221 --- /dev/null +++ b/src/os/threads_posix.c @@ -0,0 +1,129 @@ +/* + * libusb synchronization using POSIX Threads + * + * Copyright © 2011 Vitali Lovich <vlovich@aliph.com> + * Copyright © 2011 Peter Stuge <peter@stuge.se> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" + +#include <errno.h> +#if defined(__ANDROID__) +# include <unistd.h> +#elif defined(__HAIKU__) +# include <os/kernel/OS.h> +#elif defined(__linux__) +# include <sys/syscall.h> +# include <unistd.h> +#elif defined(__NetBSD__) +# include <lwp.h> +#elif defined(__OpenBSD__) +# define _BSD_SOURCE +# include <sys/syscall.h> +# include <unistd.h> +#elif defined(__sun__) +# include <sys/lwp.h> +#endif + +void usbi_cond_init(pthread_cond_t *cond) +{ +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + pthread_condattr_t condattr; + + PTHREAD_CHECK(pthread_condattr_init(&condattr)); + PTHREAD_CHECK(pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC)); + PTHREAD_CHECK(pthread_cond_init(cond, &condattr)); + PTHREAD_CHECK(pthread_condattr_destroy(&condattr)); +#else + PTHREAD_CHECK(pthread_cond_init(cond, NULL)); +#endif +} + +int usbi_cond_timedwait(pthread_cond_t *cond, + pthread_mutex_t *mutex, const struct timeval *tv) +{ + struct timespec timeout; + int r; + +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + usbi_get_monotonic_time(&timeout); +#else + usbi_get_real_time(&timeout); +#endif + + timeout.tv_sec += tv->tv_sec; + timeout.tv_nsec += tv->tv_usec * 1000L; + if (timeout.tv_nsec >= NSEC_PER_SEC) { + timeout.tv_nsec -= NSEC_PER_SEC; + timeout.tv_sec++; + } + + r = pthread_cond_timedwait(cond, mutex, &timeout); + if (r == 0) + return 0; + else if (r == ETIMEDOUT) + return LIBUSB_ERROR_TIMEOUT; + else + return LIBUSB_ERROR_OTHER; +} + +unsigned int usbi_get_tid(void) +{ + static _Thread_local unsigned int tl_tid; + int tid; + + if (tl_tid) + return tl_tid; + +#if defined(__ANDROID__) + tid = gettid(); +#elif defined(__APPLE__) +#ifdef HAVE_PTHREAD_THREADID_NP + uint64_t thread_id; + + if (pthread_threadid_np(NULL, &thread_id) == 0) + tid = (int)thread_id; + else + tid = -1; +#else + tid = (int)pthread_mach_thread_np(pthread_self()); +#endif +#elif defined(__HAIKU__) + tid = get_pthread_thread_id(pthread_self()); +#elif defined(__linux__) + tid = (int)syscall(SYS_gettid); +#elif defined(__NetBSD__) + tid = _lwp_self(); +#elif defined(__OpenBSD__) + /* The following only works with OpenBSD > 5.1 as it requires + * real thread support. For 5.1 and earlier, -1 is returned. */ + tid = syscall(SYS_getthrid); +#elif defined(__sun__) + tid = _lwp_self(); +#else + tid = -1; +#endif + + if (tid == -1) { + /* If we don't have a thread ID, at least return a unique + * value that can be used to distinguish individual + * threads. */ + tid = (int)(intptr_t)pthread_self(); + } + + return tl_tid = (unsigned int)tid; +} diff --git a/src/os/threads_posix.h b/src/os/threads_posix.h new file mode 100644 index 0000000..9322834 --- /dev/null +++ b/src/os/threads_posix.h @@ -0,0 +1,98 @@ +/* + * libusb synchronization using POSIX Threads + * + * Copyright © 2010 Peter Stuge <peter@stuge.se> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_THREADS_POSIX_H +#define LIBUSB_THREADS_POSIX_H + +#include <pthread.h> + +#define PTHREAD_CHECK(expression) ASSERT_EQ(expression, 0) + +#define USBI_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +typedef pthread_mutex_t usbi_mutex_static_t; +static inline void usbi_mutex_static_lock(usbi_mutex_static_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_lock(mutex)); +} +static inline void usbi_mutex_static_unlock(usbi_mutex_static_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_unlock(mutex)); +} + +typedef pthread_mutex_t usbi_mutex_t; +static inline void usbi_mutex_init(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_init(mutex, NULL)); +} +static inline void usbi_mutex_lock(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_lock(mutex)); +} +static inline void usbi_mutex_unlock(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_unlock(mutex)); +} +static inline int usbi_mutex_trylock(usbi_mutex_t *mutex) +{ + return pthread_mutex_trylock(mutex) == 0; +} +static inline void usbi_mutex_destroy(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_destroy(mutex)); +} + +typedef pthread_cond_t usbi_cond_t; +void usbi_cond_init(pthread_cond_t *cond); +static inline void usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_cond_wait(cond, mutex)); +} +int usbi_cond_timedwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, const struct timeval *tv); +static inline void usbi_cond_broadcast(usbi_cond_t *cond) +{ + PTHREAD_CHECK(pthread_cond_broadcast(cond)); +} +static inline void usbi_cond_destroy(usbi_cond_t *cond) +{ + PTHREAD_CHECK(pthread_cond_destroy(cond)); +} + +typedef pthread_key_t usbi_tls_key_t; +static inline void usbi_tls_key_create(usbi_tls_key_t *key) +{ + PTHREAD_CHECK(pthread_key_create(key, NULL)); +} +static inline void *usbi_tls_key_get(usbi_tls_key_t key) +{ + return pthread_getspecific(key); +} +static inline void usbi_tls_key_set(usbi_tls_key_t key, void *ptr) +{ + PTHREAD_CHECK(pthread_setspecific(key, ptr)); +} +static inline void usbi_tls_key_delete(usbi_tls_key_t key) +{ + PTHREAD_CHECK(pthread_key_delete(key)); +} + +unsigned int usbi_get_tid(void); + +#endif /* LIBUSB_THREADS_POSIX_H */ diff --git a/src/os/threads_windows.c b/src/os/threads_windows.c new file mode 100644 index 0000000..4a57f42 --- /dev/null +++ b/src/os/threads_windows.c @@ -0,0 +1,40 @@ +/* + * libusb synchronization on Microsoft Windows + * + * Copyright © 2010 Michael Plante <michael.plante@gmail.com> + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include "libusbi.h" + +int usbi_cond_timedwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, const struct timeval *tv) +{ + DWORD millis; + + millis = (DWORD)(tv->tv_sec * 1000L) + (tv->tv_usec / 1000L); + /* round up to next millisecond */ + if (tv->tv_usec % 1000L) + millis++; + + if (SleepConditionVariableCS(cond, mutex, millis)) + return 0; + else if (GetLastError() == ERROR_TIMEOUT) + return LIBUSB_ERROR_TIMEOUT; + else + return LIBUSB_ERROR_OTHER; +} diff --git a/src/os/threads_windows.h b/src/os/threads_windows.h new file mode 100644 index 0000000..dfef158 --- /dev/null +++ b/src/os/threads_windows.h @@ -0,0 +1,113 @@ +/* + * libusb synchronization on Microsoft Windows + * + * Copyright © 2010 Michael Plante <michael.plante@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_THREADS_WINDOWS_H +#define LIBUSB_THREADS_WINDOWS_H + +#define WINAPI_CHECK(expression) ASSERT_NE(expression, 0) + +#define USBI_MUTEX_INITIALIZER 0L +typedef LONG usbi_mutex_static_t; +static inline void usbi_mutex_static_lock(usbi_mutex_static_t *mutex) +{ + while (InterlockedExchange(mutex, 1L) == 1L) + SleepEx(0, TRUE); +} +static inline void usbi_mutex_static_unlock(usbi_mutex_static_t *mutex) +{ + InterlockedExchange(mutex, 0L); +} + +typedef CRITICAL_SECTION usbi_mutex_t; +static inline void usbi_mutex_init(usbi_mutex_t *mutex) +{ + InitializeCriticalSection(mutex); +} +static inline void usbi_mutex_lock(usbi_mutex_t *mutex) +{ + EnterCriticalSection(mutex); +} +static inline void usbi_mutex_unlock(usbi_mutex_t *mutex) +{ + LeaveCriticalSection(mutex); +} +static inline int usbi_mutex_trylock(usbi_mutex_t *mutex) +{ + return TryEnterCriticalSection(mutex) != 0; +} +static inline void usbi_mutex_destroy(usbi_mutex_t *mutex) +{ + DeleteCriticalSection(mutex); +} + +#if !defined(HAVE_STRUCT_TIMESPEC) && !defined(_TIMESPEC_DEFINED) +#define HAVE_STRUCT_TIMESPEC 1 +#define _TIMESPEC_DEFINED 1 +struct timespec { + long tv_sec; + long tv_nsec; +}; +#endif /* HAVE_STRUCT_TIMESPEC || _TIMESPEC_DEFINED */ + +typedef CONDITION_VARIABLE usbi_cond_t; +static inline void usbi_cond_init(usbi_cond_t *cond) +{ + InitializeConditionVariable(cond); +} +static inline void usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex) +{ + WINAPI_CHECK(SleepConditionVariableCS(cond, mutex, INFINITE)); +} +int usbi_cond_timedwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, const struct timeval *tv); +static inline void usbi_cond_broadcast(usbi_cond_t *cond) +{ + WakeAllConditionVariable(cond); +} +static inline void usbi_cond_destroy(usbi_cond_t *cond) +{ + UNUSED(cond); +} + +typedef DWORD usbi_tls_key_t; +static inline void usbi_tls_key_create(usbi_tls_key_t *key) +{ + *key = TlsAlloc(); + assert(*key != TLS_OUT_OF_INDEXES); +} +static inline void *usbi_tls_key_get(usbi_tls_key_t key) +{ + return TlsGetValue(key); +} +static inline void usbi_tls_key_set(usbi_tls_key_t key, void *ptr) +{ + WINAPI_CHECK(TlsSetValue(key, ptr)); +} +static inline void usbi_tls_key_delete(usbi_tls_key_t key) +{ + WINAPI_CHECK(TlsFree(key)); +} + +static inline unsigned int usbi_get_tid(void) +{ + return (unsigned int)GetCurrentThreadId(); +} + +#endif /* LIBUSB_THREADS_WINDOWS_H */ diff --git a/src/os/windows_common.c b/src/os/windows_common.c new file mode 100644 index 0000000..eefd01e --- /dev/null +++ b/src/os/windows_common.c @@ -0,0 +1,904 @@ +/* + * windows backend for libusb 1.0 + * Copyright © 2009-2012 Pete Batard <pete@akeo.ie> + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * HID Reports IOCTLs inspired from HIDAPI by Alan Ott, Signal 11 Software + * Hash table functions adapted from glibc, by Ulrich Drepper et al. + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include <stdio.h> + +#include "libusbi.h" +#include "windows_common.h" + +#define EPOCH_TIME UINT64_C(116444736000000000) // 1970.01.01 00:00:000 in MS Filetime + +#define STATUS_SUCCESS ((ULONG_PTR)0UL) + +// Public +enum windows_version windows_version = WINDOWS_UNDEFINED; + +// Global variables for init/exit +static unsigned int init_count; +static bool usbdk_available; + +/* +* Converts a windows error to human readable string +* uses retval as errorcode, or, if 0, use GetLastError() +*/ +#if defined(ENABLE_LOGGING) +const char *windows_error_str(DWORD error_code) +{ + static char err_string[256]; + + DWORD size; + int len; + + if (error_code == 0) + error_code = GetLastError(); + + len = sprintf(err_string, "[%lu] ", ULONG_CAST(error_code)); + + // Translate codes returned by SetupAPI. The ones we are dealing with are either + // in 0x0000xxxx or 0xE000xxxx and can be distinguished from standard error codes. + // See http://msdn.microsoft.com/en-us/library/windows/hardware/ff545011.aspx + switch (error_code & 0xE0000000) { + case 0: + error_code = HRESULT_FROM_WIN32(error_code); // Still leaves ERROR_SUCCESS unmodified + break; + case 0xE0000000: + error_code = 0x80000000 | (FACILITY_SETUPAPI << 16) | (error_code & 0x0000FFFF); + break; + default: + break; + } + + size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + &err_string[len], sizeof(err_string) - len, NULL); + if (size == 0) { + DWORD format_error = GetLastError(); + if (format_error) + snprintf(err_string, sizeof(err_string), + "Windows error code %lu (FormatMessage error code %lu)", + ULONG_CAST(error_code), ULONG_CAST(format_error)); + else + snprintf(err_string, sizeof(err_string), "Unknown error code %lu", + ULONG_CAST(error_code)); + } else { + // Remove CRLF from end of message, if present + size_t pos = len + size - 2; + if (err_string[pos] == '\r') + err_string[pos] = '\0'; + } + + return err_string; +} +#endif + +/* + * Dynamically loads a DLL from the Windows system directory. Unlike the + * LoadLibraryA() function, this function will not search through any + * directories to try and find the library. + */ +HMODULE load_system_library(struct libusb_context *ctx, const char *name) +{ + char library_path[MAX_PATH]; + char *filename_start; + UINT length; + + length = GetSystemDirectoryA(library_path, sizeof(library_path)); + if ((length == 0) || (length >= (UINT)sizeof(library_path))) { + usbi_err(ctx, "program assertion failed - could not get system directory"); + return NULL; + } + + filename_start = library_path + length; + // Append '\' + name + ".dll" + NUL + length += 1 + (UINT)strlen(name) + 4 + 1; + if (length >= (UINT)sizeof(library_path)) { + usbi_err(ctx, "program assertion failed - library path buffer overflow"); + return NULL; + } + + sprintf(filename_start, "\\%s.dll", name); + return LoadLibraryA(library_path); +} + +/* Hash table functions - modified From glibc 2.3.2: + [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986 + [Knuth] The Art of Computer Programming, part 3 (6.4) */ + +#define HTAB_SIZE 1021UL // *MUST* be a prime number!! + +typedef struct htab_entry { + unsigned long used; + char *str; +} htab_entry; + +static htab_entry *htab_table; +static usbi_mutex_t htab_mutex; +static unsigned long htab_filled; + +/* Before using the hash table we must allocate memory for it. + We allocate one element more as the found prime number says. + This is done for more effective indexing as explained in the + comment for the hash function. */ +static bool htab_create(struct libusb_context *ctx) +{ + if (htab_table != NULL) { + usbi_err(ctx, "program assertion failed - hash table already allocated"); + return true; + } + + // Create a mutex + usbi_mutex_init(&htab_mutex); + + usbi_dbg(ctx, "using %lu entries hash table", HTAB_SIZE); + htab_filled = 0; + + // allocate memory and zero out. + htab_table = calloc(HTAB_SIZE + 1, sizeof(htab_entry)); + if (htab_table == NULL) { + usbi_err(ctx, "could not allocate space for hash table"); + return false; + } + + return true; +} + +/* After using the hash table it has to be destroyed. */ +static void htab_destroy(void) +{ + unsigned long i; + + if (htab_table == NULL) + return; + + for (i = 0; i < HTAB_SIZE; i++) + free(htab_table[i].str); + + safe_free(htab_table); + + usbi_mutex_destroy(&htab_mutex); +} + +/* This is the search function. It uses double hashing with open addressing. + We use a trick to speed up the lookup. The table is created with one + more element available. This enables us to use the index zero special. + This index will never be used because we store the first hash index in + the field used where zero means not used. Every other value means used. + The used field can be used as a first fast comparison for equality of + the stored and the parameter value. This helps to prevent unnecessary + expensive calls of strcmp. */ +unsigned long htab_hash(const char *str) +{ + unsigned long hval, hval2; + unsigned long idx; + unsigned long r = 5381UL; + int c; + const char *sz = str; + + if (str == NULL) + return 0; + + // Compute main hash value (algorithm suggested by Nokia) + while ((c = *sz++) != 0) + r = ((r << 5) + r) + c; + if (r == 0) + ++r; + + // compute table hash: simply take the modulus + hval = r % HTAB_SIZE; + if (hval == 0) + ++hval; + + // Try the first index + idx = hval; + + // Mutually exclusive access (R/W lock would be better) + usbi_mutex_lock(&htab_mutex); + + if (htab_table[idx].used) { + if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0)) + goto out_unlock; // existing hash + + usbi_dbg(NULL, "hash collision ('%s' vs '%s')", str, htab_table[idx].str); + + // Second hash function, as suggested in [Knuth] + hval2 = 1UL + hval % (HTAB_SIZE - 2); + + do { + // Because size is prime this guarantees to step through all available indexes + if (idx <= hval2) + idx = HTAB_SIZE + idx - hval2; + else + idx -= hval2; + + // If we visited all entries leave the loop unsuccessfully + if (idx == hval) + break; + + // If entry is found use it. + if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0)) + goto out_unlock; + } while (htab_table[idx].used); + } + + // Not found => New entry + + // If the table is full return an error + if (htab_filled >= HTAB_SIZE) { + usbi_err(NULL, "hash table is full (%lu entries)", HTAB_SIZE); + idx = 0UL; + goto out_unlock; + } + + htab_table[idx].str = _strdup(str); + if (htab_table[idx].str == NULL) { + usbi_err(NULL, "could not duplicate string for hash table"); + idx = 0UL; + goto out_unlock; + } + + htab_table[idx].used = hval; + ++htab_filled; + +out_unlock: + usbi_mutex_unlock(&htab_mutex); + + return idx; +} + +enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS status) +{ + if (USBD_SUCCESS(status)) + return LIBUSB_TRANSFER_COMPLETED; + + switch (status) { + case USBD_STATUS_TIMEOUT: + return LIBUSB_TRANSFER_TIMED_OUT; + case USBD_STATUS_CANCELED: + return LIBUSB_TRANSFER_CANCELLED; + case USBD_STATUS_ENDPOINT_HALTED: + return LIBUSB_TRANSFER_STALL; + case USBD_STATUS_DEVICE_GONE: + return LIBUSB_TRANSFER_NO_DEVICE; + default: + usbi_dbg(NULL, "USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status)); + return LIBUSB_TRANSFER_ERROR; + } +} + +/* + * Make a transfer complete synchronously + */ +void windows_force_sync_completion(struct usbi_transfer *itransfer, ULONG size) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_context_priv *priv = usbi_get_context_priv(TRANSFER_CTX(transfer)); + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + OVERLAPPED *overlapped = &transfer_priv->overlapped; + + usbi_dbg(TRANSFER_CTX(transfer), "transfer %p, length %lu", transfer, ULONG_CAST(size)); + + overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS; + overlapped->InternalHigh = (ULONG_PTR)size; + + if (!PostQueuedCompletionStatus(priv->completion_port, (DWORD)size, (ULONG_PTR)transfer->dev_handle, overlapped)) + usbi_err(TRANSFER_CTX(transfer), "failed to post I/O completion: %s", windows_error_str(0)); +} + +/* Windows version detection */ +static BOOL is_x64(void) +{ + BOOL ret = FALSE; + + // Detect if we're running a 32 or 64 bit system + if (sizeof(uintptr_t) < 8) { + IsWow64Process(GetCurrentProcess(), &ret); + } else { + ret = TRUE; + } + + return ret; +} + +static enum windows_version get_windows_version(void) +{ + enum windows_version winver; + OSVERSIONINFOEXA vi, vi2; + unsigned major, minor, version; + ULONGLONG major_equal, minor_equal; + const char *w, *arch; + bool ws; + + memset(&vi, 0, sizeof(vi)); + vi.dwOSVersionInfoSize = sizeof(vi); + if (!GetVersionExA((OSVERSIONINFOA *)&vi)) { + memset(&vi, 0, sizeof(vi)); + vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); + if (!GetVersionExA((OSVERSIONINFOA *)&vi)) + return WINDOWS_UNDEFINED; + } + + if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT) + return WINDOWS_UNDEFINED; + + if ((vi.dwMajorVersion > 6) || ((vi.dwMajorVersion == 6) && (vi.dwMinorVersion >= 2))) { + // Starting with Windows 8.1 Preview, GetVersionEx() does no longer report the actual OS version + // See: http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx + + major_equal = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL); + for (major = vi.dwMajorVersion; major <= 9; major++) { + memset(&vi2, 0, sizeof(vi2)); + vi2.dwOSVersionInfoSize = sizeof(vi2); + vi2.dwMajorVersion = major; + if (!VerifyVersionInfoA(&vi2, VER_MAJORVERSION, major_equal)) + continue; + + if (vi.dwMajorVersion < major) { + vi.dwMajorVersion = major; + vi.dwMinorVersion = 0; + } + + minor_equal = VerSetConditionMask(0, VER_MINORVERSION, VER_EQUAL); + for (minor = vi.dwMinorVersion; minor <= 9; minor++) { + memset(&vi2, 0, sizeof(vi2)); + vi2.dwOSVersionInfoSize = sizeof(vi2); + vi2.dwMinorVersion = minor; + if (!VerifyVersionInfoA(&vi2, VER_MINORVERSION, minor_equal)) + continue; + + vi.dwMinorVersion = minor; + break; + } + + break; + } + } + + if ((vi.dwMajorVersion > 0xf) || (vi.dwMinorVersion > 0xf)) + return WINDOWS_UNDEFINED; + + ws = (vi.wProductType <= VER_NT_WORKSTATION); + version = vi.dwMajorVersion << 4 | vi.dwMinorVersion; + switch (version) { + case 0x50: winver = WINDOWS_2000; w = "2000"; break; + case 0x51: winver = WINDOWS_XP; w = "XP"; break; + case 0x52: winver = WINDOWS_2003; w = "2003"; break; + case 0x60: winver = WINDOWS_VISTA; w = (ws ? "Vista" : "2008"); break; + case 0x61: winver = WINDOWS_7; w = (ws ? "7" : "2008_R2"); break; + case 0x62: winver = WINDOWS_8; w = (ws ? "8" : "2012"); break; + case 0x63: winver = WINDOWS_8_1; w = (ws ? "8.1" : "2012_R2"); break; + case 0x64: // Early Windows 10 Insider Previews and Windows Server 2017 Technical Preview 1 used version 6.4 + case 0xA0: winver = WINDOWS_10; w = (ws ? "10" : "2016"); break; + default: + if (version < 0x50) + return WINDOWS_UNDEFINED; + winver = WINDOWS_11_OR_LATER; + w = "11 or later"; + } + + arch = is_x64() ? "64-bit" : "32-bit"; + + if (vi.wServicePackMinor) + usbi_dbg(NULL, "Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch); + else if (vi.wServicePackMajor) + usbi_dbg(NULL, "Windows %s SP%u %s", w, vi.wServicePackMajor, arch); + else + usbi_dbg(NULL, "Windows %s %s", w, arch); + + return winver; +} + +static unsigned __stdcall windows_iocp_thread(void *arg) +{ + struct libusb_context *ctx = arg; + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + HANDLE iocp = priv->completion_port; + DWORD num_bytes; + ULONG_PTR completion_key; + OVERLAPPED *overlapped; + struct libusb_device_handle *dev_handle; + struct libusb_device_handle *opened_device_handle; + struct windows_device_handle_priv *handle_priv; + struct windows_transfer_priv *transfer_priv; + struct usbi_transfer *itransfer; + bool found; + + usbi_dbg(ctx, "I/O completion thread started"); + + while (true) { + overlapped = NULL; + if (!GetQueuedCompletionStatus(iocp, &num_bytes, &completion_key, &overlapped, INFINITE) && (overlapped == NULL)) { + usbi_err(ctx, "GetQueuedCompletionStatus failed: %s", windows_error_str(0)); + break; + } + + if (overlapped == NULL) { + // Signal to quit + if (completion_key != (ULONG_PTR)ctx) + usbi_err(ctx, "program assertion failed - overlapped is NULL"); + break; + } + + // Find the transfer associated with the OVERLAPPED that just completed. + // If we cannot find a match, the I/O operation originated from outside of libusb + // (e.g. within libusbK) and we need to ignore it. + dev_handle = (struct libusb_device_handle *)completion_key; + + found = false; + transfer_priv = NULL; + + // Issue 912: lock opened device handles in context to search the current device handle + // to avoid accessing unallocated memory after device has been closed + usbi_mutex_lock(&ctx->open_devs_lock); + for_each_open_device(ctx, opened_device_handle) { + if (dev_handle == opened_device_handle) { + handle_priv = usbi_get_device_handle_priv(dev_handle); + + usbi_mutex_lock(&dev_handle->lock); + list_for_each_entry(transfer_priv, &handle_priv->active_transfers, list, struct windows_transfer_priv) { + if (overlapped == &transfer_priv->overlapped) { + // This OVERLAPPED belongs to us, remove the transfer from the device handle's list + list_del(&transfer_priv->list); + found = true; + break; + } + } + usbi_mutex_unlock(&dev_handle->lock); + } + } + usbi_mutex_unlock(&ctx->open_devs_lock); + + if (!found) { + usbi_dbg(ctx, "ignoring overlapped %p for handle %p (device %u.%u)", + overlapped, dev_handle, dev_handle->dev->bus_number, dev_handle->dev->device_address); + continue; + } + + itransfer = (struct usbi_transfer *)((unsigned char *)transfer_priv + PTR_ALIGN(sizeof(*transfer_priv))); + usbi_dbg(ctx, "transfer %p completed, length %lu", + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(num_bytes)); + usbi_signal_transfer_completion(itransfer); + } + + usbi_dbg(ctx, "I/O completion thread exiting"); + + return 0; +} + +static int windows_init(struct libusb_context *ctx) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + bool winusb_backend_init = false; + int r; + + // NB: concurrent usage supposes that init calls are equally balanced with + // exit calls. If init is called more than exit, we will not exit properly + if (++init_count == 1) { // First init? + windows_version = get_windows_version(); + if (windows_version == WINDOWS_UNDEFINED) { + usbi_err(ctx, "failed to detect Windows version"); + r = LIBUSB_ERROR_NOT_SUPPORTED; + goto init_exit; + } else if (windows_version < WINDOWS_VISTA) { + usbi_err(ctx, "Windows version is too old"); + r = LIBUSB_ERROR_NOT_SUPPORTED; + goto init_exit; + } + + if (!htab_create(ctx)) { + r = LIBUSB_ERROR_NO_MEM; + goto init_exit; + } + + r = winusb_backend.init(ctx); + if (r != LIBUSB_SUCCESS) + goto init_exit; + winusb_backend_init = true; + + r = usbdk_backend.init(ctx); + if (r == LIBUSB_SUCCESS) { + usbi_dbg(ctx, "UsbDk backend is available"); + usbdk_available = true; + } else { + usbi_info(ctx, "UsbDk backend is not available"); + // Do not report this as an error + } + } + + // By default, new contexts will use the WinUSB backend + priv->backend = &winusb_backend; + + r = LIBUSB_ERROR_NO_MEM; + + // Use an I/O completion port to manage all transfers for this context + priv->completion_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); + if (priv->completion_port == NULL) { + usbi_err(ctx, "failed to create I/O completion port: %s", windows_error_str(0)); + goto init_exit; + } + + // And a dedicated thread to wait for I/O completions + priv->completion_port_thread = (HANDLE)_beginthreadex(NULL, 0, windows_iocp_thread, ctx, 0, NULL); + if (priv->completion_port_thread == NULL) { + usbi_err(ctx, "failed to create I/O completion port thread"); + CloseHandle(priv->completion_port); + goto init_exit; + } + + r = LIBUSB_SUCCESS; + +init_exit: // Holds semaphore here + if ((init_count == 1) && (r != LIBUSB_SUCCESS)) { // First init failed? + if (usbdk_available) { + usbdk_backend.exit(ctx); + usbdk_available = false; + } + if (winusb_backend_init) + winusb_backend.exit(ctx); + htab_destroy(); + --init_count; + } + + return r; +} + +static void windows_exit(struct libusb_context *ctx) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + + // A NULL completion status will indicate to the thread that it is time to exit + if (!PostQueuedCompletionStatus(priv->completion_port, 0, (ULONG_PTR)ctx, NULL)) + usbi_err(ctx, "failed to post I/O completion: %s", windows_error_str(0)); + + if (WaitForSingleObject(priv->completion_port_thread, INFINITE) == WAIT_FAILED) + usbi_err(ctx, "failed to wait for I/O completion port thread: %s", windows_error_str(0)); + + CloseHandle(priv->completion_port_thread); + CloseHandle(priv->completion_port); + + // Only works if exits and inits are balanced exactly + if (--init_count == 0) { // Last exit + if (usbdk_available) { + usbdk_backend.exit(ctx); + usbdk_available = false; + } + winusb_backend.exit(ctx); + htab_destroy(); + } +} + +static int windows_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + + UNUSED(ap); + + if (option == LIBUSB_OPTION_USE_USBDK) { + if (!usbdk_available) { + usbi_err(ctx, "UsbDk backend not available"); + return LIBUSB_ERROR_NOT_FOUND; + } + usbi_dbg(ctx, "switching context %p to use UsbDk backend", ctx); + priv->backend = &usbdk_backend; + return LIBUSB_SUCCESS; + } + + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **discdevs) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + return priv->backend->get_device_list(ctx, discdevs); +} + +static int windows_open(struct libusb_device_handle *dev_handle) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + + list_init(&handle_priv->active_transfers); + return priv->backend->open(dev_handle); +} + +static void windows_close(struct libusb_device_handle *dev_handle) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + priv->backend->close(dev_handle); +} + +static int windows_get_active_config_descriptor(struct libusb_device *dev, + void *buffer, size_t len) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + return priv->backend->get_active_config_descriptor(dev, buffer, len); +} + +static int windows_get_config_descriptor(struct libusb_device *dev, + uint8_t config_index, void *buffer, size_t len) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + return priv->backend->get_config_descriptor(dev, config_index, buffer, len); +} + +static int windows_get_config_descriptor_by_value(struct libusb_device *dev, + uint8_t bConfigurationValue, void **buffer) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + return priv->backend->get_config_descriptor_by_value(dev, bConfigurationValue, buffer); +} + +static int windows_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->get_configuration(dev_handle, config); +} + +static int windows_set_configuration(struct libusb_device_handle *dev_handle, int config) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + if (config == -1) + config = 0; + return priv->backend->set_configuration(dev_handle, (uint8_t)config); +} + +static int windows_claim_interface(struct libusb_device_handle *dev_handle, uint8_t interface_number) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->claim_interface(dev_handle, interface_number); +} + +static int windows_release_interface(struct libusb_device_handle *dev_handle, uint8_t interface_number) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->release_interface(dev_handle, interface_number); +} + +static int windows_set_interface_altsetting(struct libusb_device_handle *dev_handle, + uint8_t interface_number, uint8_t altsetting) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->set_interface_altsetting(dev_handle, interface_number, altsetting); +} + +static int windows_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->clear_halt(dev_handle, endpoint); +} + +static int windows_reset_device(struct libusb_device_handle *dev_handle) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->reset_device(dev_handle); +} + +static void windows_destroy_device(struct libusb_device *dev) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + priv->backend->destroy_device(dev); +} + +static int windows_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_device_handle *dev_handle = transfer->dev_handle; + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + int r; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + usbi_warn(ctx, "bulk stream transfers are not yet supported on this platform"); + return LIBUSB_ERROR_NOT_SUPPORTED; + default: + usbi_err(ctx, "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (transfer_priv->handle != NULL) { + usbi_err(ctx, "program assertion failed - transfer HANDLE is not NULL"); + transfer_priv->handle = NULL; + } + + // Add transfer to the device handle's list + usbi_mutex_lock(&dev_handle->lock); + list_add_tail(&transfer_priv->list, &handle_priv->active_transfers); + usbi_mutex_unlock(&dev_handle->lock); + + r = priv->backend->submit_transfer(itransfer); + if (r != LIBUSB_SUCCESS) { + // Remove the unsuccessful transfer from the device handle's list + usbi_mutex_lock(&dev_handle->lock); + list_del(&transfer_priv->list); + usbi_mutex_unlock(&dev_handle->lock); + + // Always call the backend's clear_transfer_priv() function on failure + priv->backend->clear_transfer_priv(itransfer); + transfer_priv->handle = NULL; + return r; + } + + // The backend should set the HANDLE used for each submitted transfer + // by calling set_transfer_priv_handle() + if (transfer_priv->handle == NULL) + usbi_err(ctx, "program assertion failed - transfer HANDLE is NULL after transfer was submitted"); + + return r; +} + +static int windows_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ITRANSFER_CTX(itransfer)); + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + + // Try CancelIoEx() on the transfer + // If that fails, fall back to the backend's cancel_transfer() + // function if it is available + if (CancelIoEx(transfer_priv->handle, &transfer_priv->overlapped)) + return LIBUSB_SUCCESS; + else if (GetLastError() == ERROR_NOT_FOUND) + return LIBUSB_ERROR_NOT_FOUND; + + if (priv->backend->cancel_transfer) + return priv->backend->cancel_transfer(itransfer); + + usbi_warn(ITRANSFER_CTX(itransfer), "cancellation not supported for this transfer's driver"); + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int windows_handle_transfer_completion(struct usbi_transfer *itransfer) +{ + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + const struct windows_backend *backend = priv->backend; + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + enum libusb_transfer_status status, istatus; + DWORD result, bytes_transferred; + + if (GetOverlappedResult(transfer_priv->handle, &transfer_priv->overlapped, &bytes_transferred, FALSE)) + result = NO_ERROR; + else + result = GetLastError(); + + usbi_dbg(ctx, "handling transfer %p completion with errcode %lu, length %lu", + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(result), ULONG_CAST(bytes_transferred)); + + switch (result) { + case NO_ERROR: + status = backend->copy_transfer_data(itransfer, bytes_transferred); + break; + case ERROR_GEN_FAILURE: + usbi_dbg(ctx, "detected endpoint stall"); + status = LIBUSB_TRANSFER_STALL; + break; + case ERROR_SEM_TIMEOUT: + usbi_dbg(ctx, "detected semaphore timeout"); + status = LIBUSB_TRANSFER_TIMED_OUT; + break; + case ERROR_OPERATION_ABORTED: + istatus = backend->copy_transfer_data(itransfer, bytes_transferred); + if (istatus != LIBUSB_TRANSFER_COMPLETED) + usbi_dbg(ctx, "failed to copy partial data in aborted operation: %d", (int)istatus); + + usbi_dbg(ctx, "detected operation aborted"); + status = LIBUSB_TRANSFER_CANCELLED; + break; + case ERROR_FILE_NOT_FOUND: + case ERROR_DEVICE_NOT_CONNECTED: + case ERROR_NO_SUCH_DEVICE: + usbi_dbg(ctx, "detected device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + default: + usbi_err(ctx, "detected I/O error %lu: %s", + ULONG_CAST(result), windows_error_str(result)); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + transfer_priv->handle = NULL; + + // Backend-specific cleanup + backend->clear_transfer_priv(itransfer); + + if (status == LIBUSB_TRANSFER_CANCELLED) + return usbi_handle_transfer_cancellation(itransfer); + else + return usbi_handle_transfer_completion(itransfer, status); +} + +void usbi_get_monotonic_time(struct timespec *tp) +{ + static LONG hires_counter_init; + static uint64_t hires_ticks_to_ps; + static uint64_t hires_frequency; + LARGE_INTEGER hires_counter; + + if (InterlockedExchange(&hires_counter_init, 1L) == 0L) { + LARGE_INTEGER li_frequency; + + // Microsoft says that the QueryPerformanceFrequency() and + // QueryPerformanceCounter() functions always succeed on XP and later + QueryPerformanceFrequency(&li_frequency); + + // The hires frequency can go as high as 4 GHz, so we'll use a conversion + // to picoseconds to compute the tv_nsecs part + hires_frequency = li_frequency.QuadPart; + hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency; + } + + QueryPerformanceCounter(&hires_counter); + tp->tv_sec = (long)(hires_counter.QuadPart / hires_frequency); + tp->tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency) * hires_ticks_to_ps) / UINT64_C(1000)); +} + +// NB: MSVC6 does not support named initializers. +const struct usbi_os_backend usbi_backend = { + "Windows", + USBI_CAP_HAS_HID_ACCESS, + windows_init, + windows_exit, + windows_set_option, + windows_get_device_list, + NULL, /* hotplug_poll */ + NULL, /* wrap_sys_device */ + windows_open, + windows_close, + windows_get_active_config_descriptor, + windows_get_config_descriptor, + windows_get_config_descriptor_by_value, + windows_get_configuration, + windows_set_configuration, + windows_claim_interface, + windows_release_interface, + windows_set_interface_altsetting, + windows_clear_halt, + windows_reset_device, + NULL, /* alloc_streams */ + NULL, /* free_streams */ + NULL, /* dev_mem_alloc */ + NULL, /* dev_mem_free */ + NULL, /* kernel_driver_active */ + NULL, /* detach_kernel_driver */ + NULL, /* attach_kernel_driver */ + windows_destroy_device, + windows_submit_transfer, + windows_cancel_transfer, + NULL, /* clear_transfer_priv */ + NULL, /* handle_events */ + windows_handle_transfer_completion, + sizeof(struct windows_context_priv), + sizeof(union windows_device_priv), + sizeof(struct windows_device_handle_priv), + sizeof(struct windows_transfer_priv), +}; diff --git a/src/os/windows_common.h b/src/os/windows_common.h new file mode 100644 index 0000000..792a31e --- /dev/null +++ b/src/os/windows_common.h @@ -0,0 +1,415 @@ +/* + * Windows backend common header for libusb 1.0 + * + * This file brings together header code common between + * the desktop Windows backends. + * Copyright © 2012-2013 RealVNC Ltd. + * Copyright © 2009-2012 Pete Batard <pete@akeo.ie> + * Copyright © 2014-2020 Chris Dickens <christopher.a.dickens@gmail.com> + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_WINDOWS_COMMON_H +#define LIBUSB_WINDOWS_COMMON_H + +#include <stdbool.h> + +/* + * Workaround for the mess that exists with the DWORD and ULONG types. + * Visual Studio unconditionally defines these types as 'unsigned long' + * and a long is always 32-bits, even on 64-bit builds. GCC on the other + * hand varies the width of a long, matching it to the build. To make + * matters worse, the platform headers for these GCC builds define a + * DWORD/ULONG to be 'unsigned long' on 32-bit builds and 'unsigned int' + * on 64-bit builds. This creates a great deal of warnings for compilers + * that support printf format checking since it will never actually be + * an unsigned long. + */ +#if defined(_MSC_VER) +#define ULONG_CAST(x) (x) +#else +#define ULONG_CAST(x) ((unsigned long)(x)) +#endif + +#if defined(__CYGWIN__) +#define _stricmp strcasecmp +#define _strdup strdup +// _beginthreadex is MSVCRT => unavailable for cygwin. Fallback to using CreateThread +#define _beginthreadex(a, b, c, d, e, f) CreateThread(a, b, (LPTHREAD_START_ROUTINE)c, d, e, (LPDWORD)f) +#else +#include <process.h> +#endif + +#define safe_free(p) do {if (p != NULL) {free((void *)p); p = NULL;}} while (0) + +/* + * API macros - leveraged from libusb-win32 1.x + */ +#define DLL_STRINGIFY(s) #s + +/* + * Macros for handling DLL themselves + */ +#define DLL_HANDLE_NAME(name) __dll_##name##_handle + +#define DLL_DECLARE_HANDLE(name) \ + static HMODULE DLL_HANDLE_NAME(name) + +#define DLL_GET_HANDLE(ctx, name) \ + do { \ + DLL_HANDLE_NAME(name) = load_system_library(ctx, \ + DLL_STRINGIFY(name)); \ + if (!DLL_HANDLE_NAME(name)) \ + return false; \ + } while (0) + +#define DLL_FREE_HANDLE(name) \ + do { \ + if (DLL_HANDLE_NAME(name)) { \ + FreeLibrary(DLL_HANDLE_NAME(name)); \ + DLL_HANDLE_NAME(name) = NULL; \ + } \ + } while (0) + +/* + * Macros for handling functions within a DLL + */ +#define DLL_FUNC_NAME(name) __dll_##name##_func_t + +#define DLL_DECLARE_FUNC_PREFIXNAME(api, ret, prefixname, name, args) \ + typedef ret (api * DLL_FUNC_NAME(name))args; \ + static DLL_FUNC_NAME(name) prefixname + +#define DLL_DECLARE_FUNC(api, ret, name, args) \ + DLL_DECLARE_FUNC_PREFIXNAME(api, ret, name, name, args) +#define DLL_DECLARE_FUNC_PREFIXED(api, ret, prefix, name, args) \ + DLL_DECLARE_FUNC_PREFIXNAME(api, ret, prefix##name, name, args) + +#define DLL_LOAD_FUNC_PREFIXNAME(dll, prefixname, name, ret_on_failure) \ + do { \ + HMODULE h = DLL_HANDLE_NAME(dll); \ + prefixname = (DLL_FUNC_NAME(name))GetProcAddress(h, \ + DLL_STRINGIFY(name)); \ + if (prefixname) \ + break; \ + prefixname = (DLL_FUNC_NAME(name))GetProcAddress(h, \ + DLL_STRINGIFY(name) DLL_STRINGIFY(A)); \ + if (prefixname) \ + break; \ + prefixname = (DLL_FUNC_NAME(name))GetProcAddress(h, \ + DLL_STRINGIFY(name) DLL_STRINGIFY(W)); \ + if (prefixname) \ + break; \ + if (ret_on_failure) \ + return false; \ + } while (0) + +#define DLL_LOAD_FUNC(dll, name, ret_on_failure) \ + DLL_LOAD_FUNC_PREFIXNAME(dll, name, name, ret_on_failure) +#define DLL_LOAD_FUNC_PREFIXED(dll, prefix, name, ret_on_failure) \ + DLL_LOAD_FUNC_PREFIXNAME(dll, prefix##name, name, ret_on_failure) + +// https://msdn.microsoft.com/en-us/library/windows/hardware/ff539136(v=vs.85).aspx +#if !defined(USBD_SUCCESS) +typedef LONG USBD_STATUS; + +#define USBD_SUCCESS(Status) ((USBD_STATUS)(Status) >= 0) + +#define USBD_STATUS_ENDPOINT_HALTED ((USBD_STATUS)0xC0000030L) +#define USBD_STATUS_TIMEOUT ((USBD_STATUS)0xC0006000L) +#define USBD_STATUS_DEVICE_GONE ((USBD_STATUS)0xC0007000L) +#define USBD_STATUS_CANCELED ((USBD_STATUS)0xC0010000L) +#endif + +// error code added with Windows SDK 10.0.18362 +#ifndef ERROR_NO_SUCH_DEVICE +#define ERROR_NO_SUCH_DEVICE 433L +#endif + +/* Windows versions */ +enum windows_version { + WINDOWS_UNDEFINED, + WINDOWS_2000, + WINDOWS_XP, + WINDOWS_2003, // Also XP x64 + WINDOWS_VISTA, + WINDOWS_7, + WINDOWS_8, + WINDOWS_8_1, + WINDOWS_10, + WINDOWS_11_OR_LATER +}; + +extern enum windows_version windows_version; + +#include <pshpack1.h> + +typedef struct USB_DEVICE_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + USHORT bcdUSB; + UCHAR bDeviceClass; + UCHAR bDeviceSubClass; + UCHAR bDeviceProtocol; + UCHAR bMaxPacketSize0; + USHORT idVendor; + USHORT idProduct; + USHORT bcdDevice; + UCHAR iManufacturer; + UCHAR iProduct; + UCHAR iSerialNumber; + UCHAR bNumConfigurations; +} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR; + +typedef struct USB_CONFIGURATION_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + USHORT wTotalLength; + UCHAR bNumInterfaces; + UCHAR bConfigurationValue; + UCHAR iConfiguration; + UCHAR bmAttributes; + UCHAR MaxPower; +} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR; + +#include <poppack.h> + +#define MAX_DEVICE_ID_LEN 200 + +typedef struct USB_DK_DEVICE_ID { + WCHAR DeviceID[MAX_DEVICE_ID_LEN]; + WCHAR InstanceID[MAX_DEVICE_ID_LEN]; +} USB_DK_DEVICE_ID, *PUSB_DK_DEVICE_ID; + +typedef struct USB_DK_DEVICE_INFO { + USB_DK_DEVICE_ID ID; + ULONG64 FilterID; + ULONG64 Port; + ULONG64 Speed; + USB_DEVICE_DESCRIPTOR DeviceDescriptor; +} USB_DK_DEVICE_INFO, *PUSB_DK_DEVICE_INFO; + +typedef struct USB_DK_ISO_TRANSFER_RESULT { + ULONG64 ActualLength; + ULONG64 TransferResult; +} USB_DK_ISO_TRANSFER_RESULT, *PUSB_DK_ISO_TRANSFER_RESULT; + +typedef struct USB_DK_GEN_TRANSFER_RESULT { + ULONG64 BytesTransferred; + ULONG64 UsbdStatus; // USBD_STATUS code +} USB_DK_GEN_TRANSFER_RESULT, *PUSB_DK_GEN_TRANSFER_RESULT; + +typedef struct USB_DK_TRANSFER_RESULT { + USB_DK_GEN_TRANSFER_RESULT GenResult; + PVOID64 IsochronousResultsArray; // array of USB_DK_ISO_TRANSFER_RESULT +} USB_DK_TRANSFER_RESULT, *PUSB_DK_TRANSFER_RESULT; + +typedef struct USB_DK_TRANSFER_REQUEST { + ULONG64 EndpointAddress; + PVOID64 Buffer; + ULONG64 BufferLength; + ULONG64 TransferType; + ULONG64 IsochronousPacketsArraySize; + PVOID64 IsochronousPacketsArray; + USB_DK_TRANSFER_RESULT Result; +} USB_DK_TRANSFER_REQUEST, *PUSB_DK_TRANSFER_REQUEST; + +struct usbdk_device_priv { + USB_DK_DEVICE_ID ID; + PUSB_CONFIGURATION_DESCRIPTOR *config_descriptors; + HANDLE redirector_handle; + HANDLE system_handle; + uint8_t active_configuration; +}; + +struct winusb_device_priv { + bool initialized; + bool root_hub; + uint8_t active_config; + uint8_t depth; // distance to HCD + const struct windows_usb_api_backend *apib; + char *dev_id; + char *path; // device interface path + int sub_api; // for WinUSB-like APIs + struct { + char *path; // each interface needs a device interface path, + const struct windows_usb_api_backend *apib; // an API backend (multiple drivers support), + int sub_api; + int8_t nb_endpoints; // and a set of endpoint addresses (USB_MAXENDPOINTS) + uint8_t *endpoint; + int current_altsetting; + bool restricted_functionality; // indicates if the interface functionality is restricted + // by Windows (eg. HID keyboards or mice cannot do R/W) + } usb_interface[USB_MAXINTERFACES]; + struct hid_device_priv *hid; + PUSB_CONFIGURATION_DESCRIPTOR *config_descriptor; // list of pointers to the cached config descriptors + GUID class_guid; // checked for change during re-enumeration +}; + +struct usbdk_device_handle_priv { + // Not currently used + char dummy; +}; + +enum WINUSB_ZLP { + WINUSB_ZLP_UNSET = 0, + WINUSB_ZLP_OFF = 1, + WINUSB_ZLP_ON = 2 +}; + +struct winusb_device_handle_priv { + int active_interface; + struct { + HANDLE dev_handle; // WinUSB needs an extra handle for the file + HANDLE api_handle; // used by the API to communicate with the device + uint8_t zlp[USB_MAXENDPOINTS]; // Current per-endpoint SHORT_PACKET_TERMINATE status (enum WINUSB_ZLP) + } interface_handle[USB_MAXINTERFACES]; + int autoclaim_count[USB_MAXINTERFACES]; // For auto-release +}; + +struct usbdk_transfer_priv { + USB_DK_TRANSFER_REQUEST request; + PULONG64 IsochronousPacketsArray; + PUSB_DK_ISO_TRANSFER_RESULT IsochronousResultsArray; +}; + +struct winusb_transfer_priv { + uint8_t interface_number; + + uint8_t *hid_buffer; // 1 byte extended data buffer, required for HID + uint8_t *hid_dest; // transfer buffer destination, required for HID + size_t hid_expected_size; + + // For isochronous transfers with LibUSBk driver: + void *iso_context; + + // For isochronous transfers with Microsoft WinUSB driver: + void *isoch_buffer_handle; // The isoch_buffer_handle to free at the end of the transfer + BOOL iso_break_stream; // Whether the isoch. stream was to be continued in the last call of libusb_submit_transfer. + // As we this structure is zeroed out upon initialization, we need to use inverse logic here. + libusb_transfer_cb_fn iso_user_callback; // Original transfer callback of the user. Might be used for isochronous transfers. +}; + +struct windows_backend { + int (*init)(struct libusb_context *ctx); + void (*exit)(struct libusb_context *ctx); + int (*get_device_list)(struct libusb_context *ctx, + struct discovered_devs **discdevs); + int (*open)(struct libusb_device_handle *dev_handle); + void (*close)(struct libusb_device_handle *dev_handle); + int (*get_active_config_descriptor)(struct libusb_device *device, + void *buffer, size_t len); + int (*get_config_descriptor)(struct libusb_device *device, + uint8_t config_index, void *buffer, size_t len); + int (*get_config_descriptor_by_value)(struct libusb_device *device, + uint8_t bConfigurationValue, void **buffer); + int (*get_configuration)(struct libusb_device_handle *dev_handle, uint8_t *config); + int (*set_configuration)(struct libusb_device_handle *dev_handle, uint8_t config); + int (*claim_interface)(struct libusb_device_handle *dev_handle, uint8_t interface_number); + int (*release_interface)(struct libusb_device_handle *dev_handle, uint8_t interface_number); + int (*set_interface_altsetting)(struct libusb_device_handle *dev_handle, + uint8_t interface_number, uint8_t altsetting); + int (*clear_halt)(struct libusb_device_handle *dev_handle, + unsigned char endpoint); + int (*reset_device)(struct libusb_device_handle *dev_handle); + void (*destroy_device)(struct libusb_device *dev); + int (*submit_transfer)(struct usbi_transfer *itransfer); + int (*cancel_transfer)(struct usbi_transfer *itransfer); + void (*clear_transfer_priv)(struct usbi_transfer *itransfer); + enum libusb_transfer_status (*copy_transfer_data)(struct usbi_transfer *itransfer, DWORD length); +}; + +struct windows_context_priv { + const struct windows_backend *backend; + HANDLE completion_port; + HANDLE completion_port_thread; +}; + +union windows_device_priv { + struct usbdk_device_priv usbdk_priv; + struct winusb_device_priv winusb_priv; +}; + +struct windows_device_handle_priv { + struct list_head active_transfers; + union { + struct usbdk_device_handle_priv usbdk_priv; + struct winusb_device_handle_priv winusb_priv; + }; +}; + +struct windows_transfer_priv { + OVERLAPPED overlapped; + HANDLE handle; + struct list_head list; + union { + struct usbdk_transfer_priv usbdk_priv; + struct winusb_transfer_priv winusb_priv; + }; +}; + +static inline struct usbdk_device_handle_priv *get_usbdk_device_handle_priv(struct libusb_device_handle *dev_handle) +{ + struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + return &handle_priv->usbdk_priv; +} + +static inline struct winusb_device_handle_priv *get_winusb_device_handle_priv(struct libusb_device_handle *dev_handle) +{ + struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + return &handle_priv->winusb_priv; +} + +static inline OVERLAPPED *get_transfer_priv_overlapped(struct usbi_transfer *itransfer) +{ + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + return &transfer_priv->overlapped; +} + +static inline void set_transfer_priv_handle(struct usbi_transfer *itransfer, HANDLE handle) +{ + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + transfer_priv->handle = handle; +} + +static inline struct usbdk_transfer_priv *get_usbdk_transfer_priv(struct usbi_transfer *itransfer) +{ + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + return &transfer_priv->usbdk_priv; +} + +static inline struct winusb_transfer_priv *get_winusb_transfer_priv(struct usbi_transfer *itransfer) +{ + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + return &transfer_priv->winusb_priv; +} + +extern const struct windows_backend usbdk_backend; +extern const struct windows_backend winusb_backend; + +HMODULE load_system_library(struct libusb_context *ctx, const char *name); +unsigned long htab_hash(const char *str); +enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS status); +void windows_force_sync_completion(struct usbi_transfer *itransfer, ULONG size); + +#if defined(ENABLE_LOGGING) +const char *windows_error_str(DWORD error_code); +#endif + +#endif diff --git a/src/os/windows_usbdk.c b/src/os/windows_usbdk.c new file mode 100644 index 0000000..9f52b48 --- /dev/null +++ b/src/os/windows_usbdk.c @@ -0,0 +1,724 @@ +/* + * windows UsbDk backend for libusb 1.0 + * Copyright © 2014 Red Hat, Inc. + + * Authors: + * Dmitry Fleytman <dmitry@daynix.com> + * Pavel Gurvich <pavel@daynix.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include <windows.h> +#include <stdio.h> + +#include "libusbi.h" +#include "windows_usbdk.h" + +#if !defined(STATUS_SUCCESS) +typedef LONG NTSTATUS; +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +#if !defined(STATUS_CANCELLED) +#define STATUS_CANCELLED ((NTSTATUS)0xC0000120L) +#endif + +#if !defined(STATUS_REQUEST_CANCELED) +#define STATUS_REQUEST_CANCELED ((NTSTATUS)0xC0000703L) +#endif + +static struct { + HMODULE module; + + USBDK_GET_DEVICES_LIST GetDevicesList; + USBDK_RELEASE_DEVICES_LIST ReleaseDevicesList; + USBDK_START_REDIRECT StartRedirect; + USBDK_STOP_REDIRECT StopRedirect; + USBDK_GET_CONFIGURATION_DESCRIPTOR GetConfigurationDescriptor; + USBDK_RELEASE_CONFIGURATION_DESCRIPTOR ReleaseConfigurationDescriptor; + USBDK_READ_PIPE ReadPipe; + USBDK_WRITE_PIPE WritePipe; + USBDK_ABORT_PIPE AbortPipe; + USBDK_RESET_PIPE ResetPipe; + USBDK_SET_ALTSETTING SetAltsetting; + USBDK_RESET_DEVICE ResetDevice; + USBDK_GET_REDIRECTOR_SYSTEM_HANDLE GetRedirectorSystemHandle; +} usbdk_helper; + +static FARPROC get_usbdk_proc_addr(struct libusb_context *ctx, LPCSTR api_name) +{ + FARPROC api_ptr = GetProcAddress(usbdk_helper.module, api_name); + + if (api_ptr == NULL) + usbi_err(ctx, "UsbDkHelper API %s not found: %s", api_name, windows_error_str(0)); + + return api_ptr; +} + +static void unload_usbdk_helper_dll(void) +{ + if (usbdk_helper.module != NULL) { + FreeLibrary(usbdk_helper.module); + usbdk_helper.module = NULL; + } +} + +static int load_usbdk_helper_dll(struct libusb_context *ctx) +{ + usbdk_helper.module = load_system_library(ctx, "UsbDkHelper"); + if (usbdk_helper.module == NULL) { + usbi_err(ctx, "Failed to load UsbDkHelper.dll: %s", windows_error_str(0)); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbdk_helper.GetDevicesList = (USBDK_GET_DEVICES_LIST)get_usbdk_proc_addr(ctx, "UsbDk_GetDevicesList"); + if (usbdk_helper.GetDevicesList == NULL) + goto error_unload; + + usbdk_helper.ReleaseDevicesList = (USBDK_RELEASE_DEVICES_LIST)get_usbdk_proc_addr(ctx, "UsbDk_ReleaseDevicesList"); + if (usbdk_helper.ReleaseDevicesList == NULL) + goto error_unload; + + usbdk_helper.StartRedirect = (USBDK_START_REDIRECT)get_usbdk_proc_addr(ctx, "UsbDk_StartRedirect"); + if (usbdk_helper.StartRedirect == NULL) + goto error_unload; + + usbdk_helper.StopRedirect = (USBDK_STOP_REDIRECT)get_usbdk_proc_addr(ctx, "UsbDk_StopRedirect"); + if (usbdk_helper.StopRedirect == NULL) + goto error_unload; + + usbdk_helper.GetConfigurationDescriptor = (USBDK_GET_CONFIGURATION_DESCRIPTOR)get_usbdk_proc_addr(ctx, "UsbDk_GetConfigurationDescriptor"); + if (usbdk_helper.GetConfigurationDescriptor == NULL) + goto error_unload; + + usbdk_helper.ReleaseConfigurationDescriptor = (USBDK_RELEASE_CONFIGURATION_DESCRIPTOR)get_usbdk_proc_addr(ctx, "UsbDk_ReleaseConfigurationDescriptor"); + if (usbdk_helper.ReleaseConfigurationDescriptor == NULL) + goto error_unload; + + usbdk_helper.ReadPipe = (USBDK_READ_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_ReadPipe"); + if (usbdk_helper.ReadPipe == NULL) + goto error_unload; + + usbdk_helper.WritePipe = (USBDK_WRITE_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_WritePipe"); + if (usbdk_helper.WritePipe == NULL) + goto error_unload; + + usbdk_helper.AbortPipe = (USBDK_ABORT_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_AbortPipe"); + if (usbdk_helper.AbortPipe == NULL) + goto error_unload; + + usbdk_helper.ResetPipe = (USBDK_RESET_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_ResetPipe"); + if (usbdk_helper.ResetPipe == NULL) + goto error_unload; + + usbdk_helper.SetAltsetting = (USBDK_SET_ALTSETTING)get_usbdk_proc_addr(ctx, "UsbDk_SetAltsetting"); + if (usbdk_helper.SetAltsetting == NULL) + goto error_unload; + + usbdk_helper.ResetDevice = (USBDK_RESET_DEVICE)get_usbdk_proc_addr(ctx, "UsbDk_ResetDevice"); + if (usbdk_helper.ResetDevice == NULL) + goto error_unload; + + usbdk_helper.GetRedirectorSystemHandle = (USBDK_GET_REDIRECTOR_SYSTEM_HANDLE)get_usbdk_proc_addr(ctx, "UsbDk_GetRedirectorSystemHandle"); + if (usbdk_helper.GetRedirectorSystemHandle == NULL) + goto error_unload; + + return LIBUSB_SUCCESS; + +error_unload: + FreeLibrary(usbdk_helper.module); + usbdk_helper.module = NULL; + return LIBUSB_ERROR_NOT_FOUND; +} + +typedef SC_HANDLE (WINAPI *POPENSCMANAGERA)(LPCSTR, LPCSTR, DWORD); +typedef SC_HANDLE (WINAPI *POPENSERVICEA)(SC_HANDLE, LPCSTR, DWORD); +typedef BOOL (WINAPI *PCLOSESERVICEHANDLE)(SC_HANDLE); + +static int usbdk_init(struct libusb_context *ctx) +{ + POPENSCMANAGERA pOpenSCManagerA; + POPENSERVICEA pOpenServiceA; + PCLOSESERVICEHANDLE pCloseServiceHandle; + SC_HANDLE managerHandle; + SC_HANDLE serviceHandle; + HMODULE h; + + h = load_system_library(ctx, "Advapi32"); + if (h == NULL) { + usbi_warn(ctx, "failed to open Advapi32\n"); + return LIBUSB_ERROR_OTHER; + } + + pOpenSCManagerA = (POPENSCMANAGERA)GetProcAddress(h, "OpenSCManagerA"); + if (pOpenSCManagerA == NULL) { + usbi_warn(ctx, "failed to find %s in Advapi32\n", "OpenSCManagerA"); + goto error_free_library; + } + pOpenServiceA = (POPENSERVICEA)GetProcAddress(h, "OpenServiceA"); + if (pOpenServiceA == NULL) { + usbi_warn(ctx, "failed to find %s in Advapi32\n", "OpenServiceA"); + goto error_free_library; + } + pCloseServiceHandle = (PCLOSESERVICEHANDLE)GetProcAddress(h, "CloseServiceHandle"); + if (pCloseServiceHandle == NULL) { + usbi_warn(ctx, "failed to find %s in Advapi32\n", "CloseServiceHandle"); + goto error_free_library; + } + + managerHandle = pOpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT); + if (managerHandle == NULL) { + usbi_warn(ctx, "failed to open service control manager: %s", windows_error_str(0)); + goto error_free_library; + } + + serviceHandle = pOpenServiceA(managerHandle, "UsbDk", GENERIC_READ); + pCloseServiceHandle(managerHandle); + + if (serviceHandle == NULL) { + if (GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST) + usbi_warn(ctx, "failed to open UsbDk service: %s", windows_error_str(0)); + FreeLibrary(h); + return LIBUSB_ERROR_NOT_FOUND; + } + + pCloseServiceHandle(serviceHandle); + FreeLibrary(h); + + return load_usbdk_helper_dll(ctx); + +error_free_library: + FreeLibrary(h); + return LIBUSB_ERROR_OTHER; +} + +static void usbdk_exit(struct libusb_context *ctx) +{ + UNUSED(ctx); + unload_usbdk_helper_dll(); +} + +static int usbdk_get_session_id_for_device(struct libusb_context *ctx, + PUSB_DK_DEVICE_ID id, unsigned long *session_id) +{ + char dev_identity[ARRAYSIZE(id->DeviceID) + ARRAYSIZE(id->InstanceID) + 1]; + + if (snprintf(dev_identity, sizeof(dev_identity), "%S%S", id->DeviceID, id->InstanceID) == -1) { + usbi_warn(ctx, "cannot form device identity"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + *session_id = htab_hash(dev_identity); + + return LIBUSB_SUCCESS; +} + +static void usbdk_release_config_descriptors(struct usbdk_device_priv *priv, uint8_t count) +{ + uint8_t i; + + for (i = 0; i < count; i++) + usbdk_helper.ReleaseConfigurationDescriptor(priv->config_descriptors[i]); + + free(priv->config_descriptors); + priv->config_descriptors = NULL; +} + +static int usbdk_cache_config_descriptors(struct libusb_context *ctx, + struct usbdk_device_priv *priv, PUSB_DK_DEVICE_INFO info) +{ + uint8_t i; + USB_DK_CONFIG_DESCRIPTOR_REQUEST Request; + Request.ID = info->ID; + + priv->config_descriptors = calloc(info->DeviceDescriptor.bNumConfigurations, sizeof(PUSB_CONFIGURATION_DESCRIPTOR)); + if (priv->config_descriptors == NULL) { + usbi_err(ctx, "failed to allocate configuration descriptors holder"); + return LIBUSB_ERROR_NO_MEM; + } + + for (i = 0; i < info->DeviceDescriptor.bNumConfigurations; i++) { + ULONG Length; + + Request.Index = i; + if (!usbdk_helper.GetConfigurationDescriptor(&Request, &priv->config_descriptors[i], &Length)) { + usbi_err(ctx, "failed to retrieve configuration descriptors"); + usbdk_release_config_descriptors(priv, i); + return LIBUSB_ERROR_OTHER; + } + } + + return LIBUSB_SUCCESS; +} + +static inline int usbdk_device_priv_init(struct libusb_context *ctx, struct libusb_device *dev, PUSB_DK_DEVICE_INFO info) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev); + + priv->ID = info->ID; + priv->active_configuration = 0; + + return usbdk_cache_config_descriptors(ctx, priv, info); +} + +static void usbdk_device_init(struct libusb_device *dev, PUSB_DK_DEVICE_INFO info) +{ + dev->bus_number = (uint8_t)info->FilterID; + dev->port_number = (uint8_t)info->Port; + dev->parent_dev = NULL; + + // Addresses in libusb are 1-based + dev->device_address = (uint8_t)(info->Port + 1); + + static_assert(sizeof(dev->device_descriptor) == sizeof(info->DeviceDescriptor), + "mismatch between libusb and OS device descriptor sizes"); + memcpy(&dev->device_descriptor, &info->DeviceDescriptor, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + + switch (info->Speed) { + case LowSpeed: + dev->speed = LIBUSB_SPEED_LOW; + break; + case FullSpeed: + dev->speed = LIBUSB_SPEED_FULL; + break; + case HighSpeed: + dev->speed = LIBUSB_SPEED_HIGH; + break; + case SuperSpeed: + dev->speed = LIBUSB_SPEED_SUPER; + break; + case NoSpeed: + default: + dev->speed = LIBUSB_SPEED_UNKNOWN; + break; + } +} + +static int usbdk_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs) +{ + int r = LIBUSB_SUCCESS; + ULONG i; + struct discovered_devs *discdevs = NULL; + ULONG dev_number; + PUSB_DK_DEVICE_INFO devices; + + if (!usbdk_helper.GetDevicesList(&devices, &dev_number)) + return LIBUSB_ERROR_OTHER; + + for (i = 0; i < dev_number; i++) { + unsigned long session_id; + struct libusb_device *dev = NULL; + + if (usbdk_get_session_id_for_device(ctx, &devices[i].ID, &session_id)) + continue; + + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev == NULL) { + dev = usbi_alloc_device(ctx, session_id); + if (dev == NULL) { + usbi_err(ctx, "failed to allocate a new device structure"); + continue; + } + + usbdk_device_init(dev, &devices[i]); + if (usbdk_device_priv_init(ctx, dev, &devices[i]) != LIBUSB_SUCCESS) { + libusb_unref_device(dev); + continue; + } + } + + discdevs = discovered_devs_append(*_discdevs, dev); + libusb_unref_device(dev); + if (!discdevs) { + usbi_err(ctx, "cannot append new device to list"); + r = LIBUSB_ERROR_NO_MEM; + goto func_exit; + } + + *_discdevs = discdevs; + } + +func_exit: + usbdk_helper.ReleaseDevicesList(devices); + return r; +} + +static int usbdk_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev); + PUSB_CONFIGURATION_DESCRIPTOR config_header; + size_t size; + + config_header = (PUSB_CONFIGURATION_DESCRIPTOR)priv->config_descriptors[config_index]; + + size = min(config_header->wTotalLength, len); + memcpy(buffer, config_header, size); + return (int)size; +} + +static int usbdk_get_config_descriptor_by_value(struct libusb_device *dev, uint8_t bConfigurationValue, + void **buffer) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev); + PUSB_CONFIGURATION_DESCRIPTOR config_header; + uint8_t index; + + for (index = 0; index < dev->device_descriptor.bNumConfigurations; index++) { + config_header = priv->config_descriptors[index]; + if (config_header->bConfigurationValue == bConfigurationValue) { + *buffer = priv->config_descriptors[index]; + return (int)config_header->wTotalLength; + } + } + + return LIBUSB_ERROR_NOT_FOUND; +} + +static int usbdk_get_active_config_descriptor(struct libusb_device *dev, void *buffer, size_t len) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev); + + return usbdk_get_config_descriptor(dev, priv->active_configuration, buffer, len); +} + +static int usbdk_open(struct libusb_device_handle *dev_handle) +{ + struct libusb_device *dev = dev_handle->dev; + struct libusb_context *ctx = DEVICE_CTX(dev); + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + struct usbdk_device_priv *device_priv = usbi_get_device_priv(dev); + + device_priv->redirector_handle = usbdk_helper.StartRedirect(&device_priv->ID); + if (device_priv->redirector_handle == INVALID_HANDLE_VALUE) { + usbi_err(ctx, "Redirector startup failed"); + device_priv->redirector_handle = NULL; + return LIBUSB_ERROR_OTHER; + } + + device_priv->system_handle = usbdk_helper.GetRedirectorSystemHandle(device_priv->redirector_handle); + + if (CreateIoCompletionPort(device_priv->system_handle, priv->completion_port, (ULONG_PTR)dev_handle, 0) == NULL) { + usbi_err(ctx, "failed to associate handle to I/O completion port: %s", windows_error_str(0)); + usbdk_helper.StopRedirect(device_priv->redirector_handle); + device_priv->system_handle = NULL; + device_priv->redirector_handle = NULL; + return LIBUSB_ERROR_OTHER; + } + + return LIBUSB_SUCCESS; +} + +static void usbdk_close(struct libusb_device_handle *dev_handle) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + if (!usbdk_helper.StopRedirect(priv->redirector_handle)) + usbi_err(HANDLE_CTX(dev_handle), "Redirector shutdown failed"); + + priv->system_handle = NULL; + priv->redirector_handle = NULL; +} + +static int usbdk_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + *config = priv->active_configuration; + + return LIBUSB_SUCCESS; +} + +static int usbdk_set_configuration(struct libusb_device_handle *dev_handle, uint8_t config) +{ + UNUSED(dev_handle); + UNUSED(config); + return LIBUSB_SUCCESS; +} + +static int usbdk_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) +{ + UNUSED(dev_handle); + UNUSED(iface); + return LIBUSB_SUCCESS; +} + +static int usbdk_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + if (!usbdk_helper.SetAltsetting(priv->redirector_handle, iface, altsetting)) { + usbi_err(HANDLE_CTX(dev_handle), "SetAltsetting failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +static int usbdk_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) +{ + UNUSED(dev_handle); + UNUSED(iface); + return LIBUSB_SUCCESS; +} + +static int usbdk_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + if (!usbdk_helper.ResetPipe(priv->redirector_handle, endpoint)) { + usbi_err(HANDLE_CTX(dev_handle), "ResetPipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +static int usbdk_reset_device(struct libusb_device_handle *dev_handle) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + if (!usbdk_helper.ResetDevice(priv->redirector_handle)) { + usbi_err(HANDLE_CTX(dev_handle), "ResetDevice failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +static void usbdk_destroy_device(struct libusb_device *dev) +{ + struct usbdk_device_priv *priv = usbi_get_device_priv(dev); + + if (priv->config_descriptors != NULL) + usbdk_release_config_descriptors(priv, dev->device_descriptor.bNumConfigurations); +} + +static void usbdk_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct usbdk_transfer_priv *transfer_priv = get_usbdk_transfer_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + safe_free(transfer_priv->IsochronousPacketsArray); + safe_free(transfer_priv->IsochronousResultsArray); + } +} + +static int usbdk_do_control_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct usbdk_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + struct usbdk_transfer_priv *transfer_priv = get_usbdk_transfer_priv(itransfer); + OVERLAPPED *overlapped = get_transfer_priv_overlapped(itransfer); + TransferResult transResult; + + transfer_priv->request.Buffer = (PVOID64)transfer->buffer; + transfer_priv->request.BufferLength = transfer->length; + transfer_priv->request.TransferType = ControlTransferType; + + set_transfer_priv_handle(itransfer, priv->system_handle); + + if (transfer->buffer[0] & LIBUSB_ENDPOINT_IN) + transResult = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, overlapped); + else + transResult = usbdk_helper.WritePipe(priv->redirector_handle, &transfer_priv->request, overlapped); + + switch (transResult) { + case TransferSuccess: + windows_force_sync_completion(itransfer, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred); + break; + case TransferSuccessAsync: + break; + case TransferFailure: + usbi_err(TRANSFER_CTX(transfer), "ControlTransfer failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + +static int usbdk_do_bulk_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct usbdk_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + struct usbdk_transfer_priv *transfer_priv = get_usbdk_transfer_priv(itransfer); + OVERLAPPED *overlapped = get_transfer_priv_overlapped(itransfer); + TransferResult transferRes; + + transfer_priv->request.Buffer = (PVOID64)transfer->buffer; + transfer_priv->request.BufferLength = transfer->length; + transfer_priv->request.EndpointAddress = transfer->endpoint; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + transfer_priv->request.TransferType = BulkTransferType; + break; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + transfer_priv->request.TransferType = InterruptTransferType; + break; + } + + set_transfer_priv_handle(itransfer, priv->system_handle); + + if (IS_XFERIN(transfer)) + transferRes = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, overlapped); + else + transferRes = usbdk_helper.WritePipe(priv->redirector_handle, &transfer_priv->request, overlapped); + + switch (transferRes) { + case TransferSuccess: + windows_force_sync_completion(itransfer, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred); + break; + case TransferSuccessAsync: + break; + case TransferFailure: + usbi_err(TRANSFER_CTX(transfer), "ReadPipe/WritePipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + +static int usbdk_do_iso_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct usbdk_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + struct usbdk_transfer_priv *transfer_priv = get_usbdk_transfer_priv(itransfer); + OVERLAPPED *overlapped = get_transfer_priv_overlapped(itransfer); + TransferResult transferRes; + int i; + + transfer_priv->request.Buffer = (PVOID64)transfer->buffer; + transfer_priv->request.BufferLength = transfer->length; + transfer_priv->request.EndpointAddress = transfer->endpoint; + transfer_priv->request.TransferType = IsochronousTransferType; + transfer_priv->request.IsochronousPacketsArraySize = transfer->num_iso_packets; + transfer_priv->IsochronousPacketsArray = malloc(transfer->num_iso_packets * sizeof(ULONG64)); + transfer_priv->request.IsochronousPacketsArray = (PVOID64)transfer_priv->IsochronousPacketsArray; + if (!transfer_priv->IsochronousPacketsArray) { + usbi_err(TRANSFER_CTX(transfer), "Allocation of IsochronousPacketsArray failed"); + return LIBUSB_ERROR_NO_MEM; + } + + transfer_priv->IsochronousResultsArray = malloc(transfer->num_iso_packets * sizeof(USB_DK_ISO_TRANSFER_RESULT)); + transfer_priv->request.Result.IsochronousResultsArray = (PVOID64)transfer_priv->IsochronousResultsArray; + if (!transfer_priv->IsochronousResultsArray) { + usbi_err(TRANSFER_CTX(transfer), "Allocation of isochronousResultsArray failed"); + return LIBUSB_ERROR_NO_MEM; + } + + for (i = 0; i < transfer->num_iso_packets; i++) + transfer_priv->IsochronousPacketsArray[i] = transfer->iso_packet_desc[i].length; + + set_transfer_priv_handle(itransfer, priv->system_handle); + + if (IS_XFERIN(transfer)) + transferRes = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, overlapped); + else + transferRes = usbdk_helper.WritePipe(priv->redirector_handle, &transfer_priv->request, overlapped); + + switch (transferRes) { + case TransferSuccess: + windows_force_sync_completion(itransfer, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred); + break; + case TransferSuccessAsync: + break; + case TransferFailure: + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + +static int usbdk_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return usbdk_do_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (IS_XFEROUT(transfer) && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)) + return LIBUSB_ERROR_NOT_SUPPORTED; //TODO: Check whether we can support this in UsbDk + return usbdk_do_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return usbdk_do_iso_transfer(itransfer); + default: + // Should not get here since windows_submit_transfer() validates + // the transfer->type field + usbi_err(TRANSFER_CTX(transfer), "unsupported endpoint type %d", transfer->type); + return LIBUSB_ERROR_NOT_SUPPORTED; + } +} + +static enum libusb_transfer_status usbdk_copy_transfer_data(struct usbi_transfer *itransfer, DWORD length) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct usbdk_transfer_priv *transfer_priv = get_usbdk_transfer_priv(itransfer); + + UNUSED(length); + + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + ULONG64 i; + + for (i = 0; i < transfer_priv->request.IsochronousPacketsArraySize; i++) { + struct libusb_iso_packet_descriptor *lib_desc = &transfer->iso_packet_desc[i]; + + switch (transfer_priv->IsochronousResultsArray[i].TransferResult) { + case STATUS_SUCCESS: + case STATUS_CANCELLED: + case STATUS_REQUEST_CANCELED: + lib_desc->status = LIBUSB_TRANSFER_COMPLETED; // == ERROR_SUCCESS + break; + default: + lib_desc->status = LIBUSB_TRANSFER_ERROR; // ERROR_UNKNOWN_EXCEPTION; + break; + } + + lib_desc->actual_length = (unsigned int)transfer_priv->IsochronousResultsArray[i].ActualLength; + } + } + + itransfer->transferred += (int)transfer_priv->request.Result.GenResult.BytesTransferred; + return usbd_status_to_libusb_transfer_status((USBD_STATUS)transfer_priv->request.Result.GenResult.UsbdStatus); +} + +const struct windows_backend usbdk_backend = { + usbdk_init, + usbdk_exit, + usbdk_get_device_list, + usbdk_open, + usbdk_close, + usbdk_get_active_config_descriptor, + usbdk_get_config_descriptor, + usbdk_get_config_descriptor_by_value, + usbdk_get_configuration, + usbdk_set_configuration, + usbdk_claim_interface, + usbdk_release_interface, + usbdk_set_interface_altsetting, + usbdk_clear_halt, + usbdk_reset_device, + usbdk_destroy_device, + usbdk_submit_transfer, + NULL, /* cancel_transfer */ + usbdk_clear_transfer_priv, + usbdk_copy_transfer_data, +}; diff --git a/src/os/windows_usbdk.h b/src/os/windows_usbdk.h new file mode 100644 index 0000000..0eb3779 --- /dev/null +++ b/src/os/windows_usbdk.h @@ -0,0 +1,106 @@ +/* +* windows UsbDk backend for libusb 1.0 +* Copyright © 2014 Red Hat, Inc. + +* Authors: +* Dmitry Fleytman <dmitry@daynix.com> +* Pavel Gurvich <pavel@daynix.com> +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser 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 +*/ + +#ifndef LIBUSB_WINDOWS_USBDK_H +#define LIBUSB_WINDOWS_USBDK_H + +#include "windows_common.h" + +typedef struct USB_DK_CONFIG_DESCRIPTOR_REQUEST { + USB_DK_DEVICE_ID ID; + ULONG64 Index; +} USB_DK_CONFIG_DESCRIPTOR_REQUEST, *PUSB_DK_CONFIG_DESCRIPTOR_REQUEST; + +typedef enum { + TransferFailure = 0, + TransferSuccess, + TransferSuccessAsync +} TransferResult; + +typedef enum { + NoSpeed = 0, + LowSpeed, + FullSpeed, + HighSpeed, + SuperSpeed +} USB_DK_DEVICE_SPEED; + +typedef enum { + ControlTransferType, + BulkTransferType, + InterruptTransferType, + IsochronousTransferType +} USB_DK_TRANSFER_TYPE; + +typedef BOOL (__cdecl *USBDK_GET_DEVICES_LIST)( + PUSB_DK_DEVICE_INFO *DeviceInfo, + PULONG DeviceNumber +); +typedef void (__cdecl *USBDK_RELEASE_DEVICES_LIST)( + PUSB_DK_DEVICE_INFO DeviceInfo +); +typedef HANDLE (__cdecl *USBDK_START_REDIRECT)( + PUSB_DK_DEVICE_ID DeviceId +); +typedef BOOL (__cdecl *USBDK_STOP_REDIRECT)( + HANDLE DeviceHandle +); +typedef BOOL (__cdecl *USBDK_GET_CONFIGURATION_DESCRIPTOR)( + PUSB_DK_CONFIG_DESCRIPTOR_REQUEST Request, + PUSB_CONFIGURATION_DESCRIPTOR *Descriptor, + PULONG Length +); +typedef void (__cdecl *USBDK_RELEASE_CONFIGURATION_DESCRIPTOR)( + PUSB_CONFIGURATION_DESCRIPTOR Descriptor +); +typedef TransferResult (__cdecl *USBDK_WRITE_PIPE)( + HANDLE DeviceHandle, + PUSB_DK_TRANSFER_REQUEST Request, + LPOVERLAPPED lpOverlapped +); +typedef TransferResult (__cdecl *USBDK_READ_PIPE)( + HANDLE DeviceHandle, + PUSB_DK_TRANSFER_REQUEST Request, + LPOVERLAPPED lpOverlapped +); +typedef BOOL (__cdecl *USBDK_ABORT_PIPE)( + HANDLE DeviceHandle, + ULONG64 PipeAddress +); +typedef BOOL (__cdecl *USBDK_RESET_PIPE)( + HANDLE DeviceHandle, + ULONG64 PipeAddress +); +typedef BOOL (__cdecl *USBDK_SET_ALTSETTING)( + HANDLE DeviceHandle, + ULONG64 InterfaceIdx, + ULONG64 AltSettingIdx +); +typedef BOOL (__cdecl *USBDK_RESET_DEVICE)( + HANDLE DeviceHandle +); +typedef HANDLE (__cdecl *USBDK_GET_REDIRECTOR_SYSTEM_HANDLE)( + HANDLE DeviceHandle +); + +#endif diff --git a/src/os/windows_winusb.c b/src/os/windows_winusb.c new file mode 100644 index 0000000..a03d6a5 --- /dev/null +++ b/src/os/windows_winusb.c @@ -0,0 +1,4527 @@ +/* + * windows backend for libusb 1.0 + * Copyright © 2009-2012 Pete Batard <pete@akeo.ie> + * Copyright © 2016-2018 Chris Dickens <christopher.a.dickens@gmail.com> + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * HID Reports IOCTLs inspired from HIDAPI by Alan Ott, Signal 11 Software + * Hash table functions adapted from glibc, by Ulrich Drepper et al. + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#include <config.h> + +#include <windows.h> +#include <setupapi.h> +#include <ctype.h> +#include <stdio.h> + +#include "libusbi.h" +#include "windows_winusb.h" + +#define HANDLE_VALID(h) (((h) != NULL) && ((h) != INVALID_HANDLE_VALUE)) + +// The below macro is used in conjunction with safe loops. +#define LOOP_BREAK(err) \ + { \ + r = err; \ + continue; \ + } + +// WinUSB-like API prototypes +static bool winusbx_init(struct libusb_context *ctx); +static void winusbx_exit(void); +static int winusbx_open(int sub_api, struct libusb_device_handle *dev_handle); +static void winusbx_close(int sub_api, struct libusb_device_handle *dev_handle); +static int winusbx_configure_endpoints(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int winusbx_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *itransfer); +static int winusbx_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting); +static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itransfer); +static int winusbx_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer); +static int winusbx_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int winusbx_cancel_transfer(int sub_api, struct usbi_transfer *itransfer); +static int winusbx_reset_device(int sub_api, struct libusb_device_handle *dev_handle); +static enum libusb_transfer_status winusbx_copy_transfer_data(int sub_api, struct usbi_transfer *itransfer, DWORD length); +// HID API prototypes +static bool hid_init(struct libusb_context *ctx); +static void hid_exit(void); +static int hid_open(int sub_api, struct libusb_device_handle *dev_handle); +static void hid_close(int sub_api, struct libusb_device_handle *dev_handle); +static int hid_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int hid_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int hid_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting); +static int hid_submit_control_transfer(int sub_api, struct usbi_transfer *itransfer); +static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer); +static int hid_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int hid_reset_device(int sub_api, struct libusb_device_handle *dev_handle); +static enum libusb_transfer_status hid_copy_transfer_data(int sub_api, struct usbi_transfer *itransfer, DWORD length); +// Composite API prototypes +static int composite_open(int sub_api, struct libusb_device_handle *dev_handle); +static void composite_close(int sub_api, struct libusb_device_handle *dev_handle); +static int composite_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int composite_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting); +static int composite_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); +static int composite_submit_control_transfer(int sub_api, struct usbi_transfer *itransfer); +static int composite_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer); +static int composite_submit_iso_transfer(int sub_api, struct usbi_transfer *itransfer); +static int composite_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int composite_cancel_transfer(int sub_api, struct usbi_transfer *itransfer); +static int composite_reset_device(int sub_api, struct libusb_device_handle *dev_handle); +static enum libusb_transfer_status composite_copy_transfer_data(int sub_api, struct usbi_transfer *itransfer, DWORD length); + +static usbi_mutex_t autoclaim_lock; + +// API globals +static struct winusb_interface WinUSBX[SUB_API_MAX]; +#define CHECK_WINUSBX_AVAILABLE(sub_api) \ + do { \ + if (sub_api == SUB_API_NOTSET) \ + sub_api = priv->sub_api; \ + if (WinUSBX[sub_api].hDll == NULL) \ + return LIBUSB_ERROR_ACCESS; \ + } while (0) + +#define CHECK_HID_AVAILABLE \ + do { \ + if (DLL_HANDLE_NAME(hid) == NULL) \ + return LIBUSB_ERROR_ACCESS; \ + } while (0) + +#if defined(ENABLE_LOGGING) +static const char *guid_to_string(const GUID *guid, char guid_string[MAX_GUID_STRING_LENGTH]) +{ + if (guid == NULL) { + guid_string[0] = '\0'; + return guid_string; + } + + sprintf(guid_string, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (unsigned int)guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); + + return guid_string; +} +#endif + +static bool string_to_guid(const char guid_string[MAX_GUID_STRING_LENGTH], GUID *guid) +{ + unsigned short tmp[4]; + int num_chars = -1; + char extra; + int r; + + // Unfortunately MinGW complains that '%hhx' is not a valid format specifier, + // even though Visual Studio 2013 and later support it. Rather than complicating + // the logic in this function with '#ifdef's, use a temporary array on the stack + // to store the conversions. + r = sscanf(guid_string, "{%8x-%4hx-%4hx-%4hx-%4hx%4hx%4hx}%n%c", + (unsigned int *)&guid->Data1, &guid->Data2, &guid->Data3, + &tmp[0], &tmp[1], &tmp[2], &tmp[3], &num_chars, &extra); + + if ((r != 7) || (num_chars != 38)) + return false; + + // Extract the bytes from the 2-byte shorts + guid->Data4[0] = (unsigned char)((tmp[0] >> 8) & 0xFF); + guid->Data4[1] = (unsigned char)(tmp[0] & 0xFF); + guid->Data4[2] = (unsigned char)((tmp[1] >> 8) & 0xFF); + guid->Data4[3] = (unsigned char)(tmp[1] & 0xFF); + guid->Data4[4] = (unsigned char)((tmp[2] >> 8) & 0xFF); + guid->Data4[5] = (unsigned char)(tmp[2] & 0xFF); + guid->Data4[6] = (unsigned char)((tmp[3] >> 8) & 0xFF); + guid->Data4[7] = (unsigned char)(tmp[3] & 0xFF); + + return true; +} + +/* + * Normalize Microsoft's paths: return a duplicate of the given path + * with all characters converted to uppercase + */ +static char *normalize_path(const char *path) +{ + char *ret_path = _strdup(path); + char *p; + + if (ret_path == NULL) + return NULL; + + for (p = ret_path; *p != '\0'; p++) + *p = (char)toupper((unsigned char)*p); + + return ret_path; +} + +/* + * Cfgmgr32, AdvAPI32, OLE32 and SetupAPI DLL functions + */ +static bool init_dlls(struct libusb_context *ctx) +{ + DLL_GET_HANDLE(ctx, Cfgmgr32); + DLL_LOAD_FUNC(Cfgmgr32, CM_Get_Parent, true); + DLL_LOAD_FUNC(Cfgmgr32, CM_Get_Child, true); + + // Prefixed to avoid conflict with header files + DLL_GET_HANDLE(ctx, AdvAPI32); + DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegQueryValueExA, true); + DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegCloseKey, true); + + DLL_GET_HANDLE(ctx, SetupAPI); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiGetClassDevsA, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiEnumDeviceInfo, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiEnumDeviceInterfaces, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiGetDeviceInstanceIdA, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiGetDeviceInterfaceDetailA, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiGetDeviceRegistryPropertyA, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiDestroyDeviceInfoList, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiOpenDevRegKey, true); + DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiOpenDeviceInterfaceRegKey, true); + + return true; +} + +static void exit_dlls(void) +{ + DLL_FREE_HANDLE(SetupAPI); + DLL_FREE_HANDLE(AdvAPI32); + DLL_FREE_HANDLE(Cfgmgr32); +} + +/* + * enumerate interfaces for the whole USB class + * + * Parameters: + * dev_info: a pointer to a dev_info list + * dev_info_data: a pointer to an SP_DEVINFO_DATA to be filled (or NULL if not needed) + * enumerator: the generic USB class for which to retrieve interface details + * index: zero based index of the interface in the device info list + * + * Note: it is the responsibility of the caller to free the DEVICE_INTERFACE_DETAIL_DATA + * structure returned and call this function repeatedly using the same guid (with an + * incremented index starting at zero) until all interfaces have been returned. + */ +static bool get_devinfo_data(struct libusb_context *ctx, + HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, const char *enumerator, unsigned _index) +{ + if (_index == 0) { + *dev_info = pSetupDiGetClassDevsA(NULL, enumerator, NULL, DIGCF_PRESENT|DIGCF_ALLCLASSES); + if (*dev_info == INVALID_HANDLE_VALUE) { + usbi_err(ctx, "could not obtain device info set for PnP enumerator '%s': %s", + enumerator, windows_error_str(0)); + return false; + } + } + + dev_info_data->cbSize = sizeof(SP_DEVINFO_DATA); + if (!pSetupDiEnumDeviceInfo(*dev_info, _index, dev_info_data)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) + usbi_err(ctx, "could not obtain device info data for PnP enumerator '%s' index %u: %s", + enumerator, _index, windows_error_str(0)); + + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return false; + } + return true; +} + +/* + * enumerate interfaces for a specific GUID + * + * Parameters: + * dev_info: a pointer to a dev_info list + * dev_info_data: a pointer to an SP_DEVINFO_DATA to be filled (or NULL if not needed) + * guid: the GUID for which to retrieve interface details + * index: zero based index of the interface in the device info list + * + * Note: it is the responsibility of the caller to free the DEVICE_INTERFACE_DETAIL_DATA + * structure returned and call this function repeatedly using the same guid (with an + * incremented index starting at zero) until all interfaces have been returned. + */ +static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, + PSP_DEVINFO_DATA dev_info_data, LPCGUID guid, DWORD *_index, char **dev_interface_path) +{ + SP_DEVICE_INTERFACE_DATA dev_interface_data; + PSP_DEVICE_INTERFACE_DETAIL_DATA_A dev_interface_details; + char guid_string[MAX_GUID_STRING_LENGTH]; + DWORD size; + + dev_info_data->cbSize = sizeof(SP_DEVINFO_DATA); + dev_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + for (;;) { + if (!pSetupDiEnumDeviceInfo(dev_info, *_index, dev_info_data)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + usbi_err(ctx, "Could not obtain device info data for %s index %lu: %s", + guid_to_string(guid, guid_string), ULONG_CAST(*_index), windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + // No more devices + return LIBUSB_SUCCESS; + } + + // Always advance the index for the next iteration + (*_index)++; + + if (pSetupDiEnumDeviceInterfaces(dev_info, dev_info_data, guid, 0, &dev_interface_data)) + break; + + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + usbi_err(ctx, "Could not obtain interface data for %s devInst %lX: %s", + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + + // Device does not have an interface matching this GUID, skip + } + + // Read interface data (dummy + actual) to access the device path + if (!pSetupDiGetDeviceInterfaceDetailA(dev_info, &dev_interface_data, NULL, 0, &size, NULL)) { + // The dummy call should fail with ERROR_INSUFFICIENT_BUFFER + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + usbi_err(ctx, "could not access interface data (dummy) for %s devInst %lX: %s", + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + } else { + usbi_err(ctx, "program assertion failed - http://msdn.microsoft.com/en-us/library/ms792901.aspx is wrong"); + return LIBUSB_ERROR_OTHER; + } + + dev_interface_details = malloc(size); + if (dev_interface_details == NULL) { + usbi_err(ctx, "could not allocate interface data for %s devInst %lX", + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst)); + return LIBUSB_ERROR_NO_MEM; + } + + dev_interface_details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + if (!pSetupDiGetDeviceInterfaceDetailA(dev_info, &dev_interface_data, + dev_interface_details, size, NULL, NULL)) { + usbi_err(ctx, "could not access interface data (actual) for %s devInst %lX: %s", + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); + free(dev_interface_details); + return LIBUSB_ERROR_OTHER; + } + + *dev_interface_path = normalize_path(dev_interface_details->DevicePath); + free(dev_interface_details); + + if (*dev_interface_path == NULL) { + usbi_err(ctx, "could not allocate interface path for %s devInst %lX", + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst)); + return LIBUSB_ERROR_NO_MEM; + } + + return LIBUSB_SUCCESS; +} + +/* For libusb0 filter */ +static int get_interface_details_filter(struct libusb_context *ctx, HDEVINFO *dev_info, + DWORD _index, char *filter_path, char **dev_interface_path) +{ + const GUID *libusb0_guid = &GUID_DEVINTERFACE_LIBUSB0_FILTER; + SP_DEVICE_INTERFACE_DATA dev_interface_data; + PSP_DEVICE_INTERFACE_DETAIL_DATA_A dev_interface_details; + HKEY hkey_dev_interface; + DWORD size; + int err = LIBUSB_ERROR_OTHER; + + if (_index == 0) { + *dev_info = pSetupDiGetClassDevsA(libusb0_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (*dev_info == INVALID_HANDLE_VALUE) { + usbi_err(ctx, "could not obtain device info set: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + } + + dev_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + if (!pSetupDiEnumDeviceInterfaces(*dev_info, NULL, libusb0_guid, _index, &dev_interface_data)) { + if (GetLastError() != ERROR_NO_MORE_ITEMS) { + usbi_err(ctx, "Could not obtain interface data for index %lu: %s", + ULONG_CAST(_index), windows_error_str(0)); + goto err_exit; + } + + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return LIBUSB_SUCCESS; + } + + // Read interface data (dummy + actual) to access the device path + if (!pSetupDiGetDeviceInterfaceDetailA(*dev_info, &dev_interface_data, NULL, 0, &size, NULL)) { + // The dummy call should fail with ERROR_INSUFFICIENT_BUFFER + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + usbi_err(ctx, "could not access interface data (dummy) for index %lu: %s", + ULONG_CAST(_index), windows_error_str(0)); + goto err_exit; + } + } else { + usbi_err(ctx, "program assertion failed - http://msdn.microsoft.com/en-us/library/ms792901.aspx is wrong"); + goto err_exit; + } + + dev_interface_details = malloc(size); + if (dev_interface_details == NULL) { + usbi_err(ctx, "could not allocate interface data for index %lu", ULONG_CAST(_index)); + err = LIBUSB_ERROR_NO_MEM; + goto err_exit; + } + + dev_interface_details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + if (!pSetupDiGetDeviceInterfaceDetailA(*dev_info, &dev_interface_data, dev_interface_details, size, NULL, NULL)) { + usbi_err(ctx, "could not access interface data (actual) for index %lu: %s", + ULONG_CAST(_index), windows_error_str(0)); + free(dev_interface_details); + goto err_exit; + } + + *dev_interface_path = normalize_path(dev_interface_details->DevicePath); + free(dev_interface_details); + + if (*dev_interface_path == NULL) { + usbi_err(ctx, "could not allocate interface path for index %lu", ULONG_CAST(_index)); + err = LIBUSB_ERROR_NO_MEM; + goto err_exit; + } + + // [trobinso] lookup the libusb0 symbolic index. + hkey_dev_interface = pSetupDiOpenDeviceInterfaceRegKey(*dev_info, &dev_interface_data, 0, KEY_READ); + if (hkey_dev_interface != INVALID_HANDLE_VALUE) { + DWORD libusb0_symboliclink_index = 0; + DWORD value_length = sizeof(DWORD); + LONG status; + + status = pRegQueryValueExA(hkey_dev_interface, "LUsb0", NULL, NULL, + (LPBYTE)&libusb0_symboliclink_index, &value_length); + if (status == ERROR_SUCCESS) { + if (libusb0_symboliclink_index < 256) { + // libusb0.sys is connected to this device instance. + // If the the device interface guid is {F9F3FF14-AE21-48A0-8A25-8011A7A931D9} then it's a filter. + sprintf(filter_path, "\\\\.\\libusb0-%04u", (unsigned int)libusb0_symboliclink_index); + usbi_dbg(ctx, "assigned libusb0 symbolic link %s", filter_path); + } else { + // libusb0.sys was connected to this device instance at one time; but not anymore. + } + } + pRegCloseKey(hkey_dev_interface); + } else { + usbi_warn(ctx, "could not open device interface registry key for index %lu: %s", + ULONG_CAST(_index), windows_error_str(0)); + // TODO: should this be an error? + } + + return LIBUSB_SUCCESS; + +err_exit: + pSetupDiDestroyDeviceInfoList(*dev_info); + *dev_info = INVALID_HANDLE_VALUE; + return err; +} + +/* + * Returns the first known ancestor of a device + */ +static struct libusb_device *get_ancestor(struct libusb_context *ctx, + DEVINST devinst, PDEVINST _parent_devinst) +{ + struct libusb_device *dev = NULL; + DEVINST parent_devinst; + + while (dev == NULL) { + if (CM_Get_Parent(&parent_devinst, devinst, 0) != CR_SUCCESS) + break; + devinst = parent_devinst; + dev = usbi_get_device_by_session_id(ctx, (unsigned long)devinst); + } + + if ((dev != NULL) && (_parent_devinst != NULL)) + *_parent_devinst = devinst; + + return dev; +} + +/* + * Determine which interface the given endpoint address belongs to + */ +static int get_interface_by_endpoint(struct libusb_config_descriptor *conf_desc, uint8_t ep) +{ + const struct libusb_interface *intf; + const struct libusb_interface_descriptor *intf_desc; + uint8_t i, k; + int j; + + for (i = 0; i < conf_desc->bNumInterfaces; i++) { + intf = &conf_desc->interface[i]; + for (j = 0; j < intf->num_altsetting; j++) { + intf_desc = &intf->altsetting[j]; + for (k = 0; k < intf_desc->bNumEndpoints; k++) { + if (intf_desc->endpoint[k].bEndpointAddress == ep) { + usbi_dbg(NULL, "found endpoint %02X on interface %d", intf_desc->bInterfaceNumber, i); + return intf_desc->bInterfaceNumber; + } + } + } + } + + usbi_dbg(NULL, "endpoint %02X not found on any interface", ep); + return LIBUSB_ERROR_NOT_FOUND; +} + +/* + * Open a device and associate the HANDLE with the context's I/O completion port + */ +static HANDLE windows_open(struct libusb_device_handle *dev_handle, const char *path, DWORD access) +{ + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + HANDLE handle; + + handle = CreateFileA(path, access, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (handle == INVALID_HANDLE_VALUE) + return handle; + + if (CreateIoCompletionPort(handle, priv->completion_port, (ULONG_PTR)dev_handle, 0) == NULL) { + usbi_err(ctx, "failed to associate handle to I/O completion port: %s", windows_error_str(0)); + CloseHandle(handle); + return INVALID_HANDLE_VALUE; + } + + return handle; +} + +/* + * Populate the endpoints addresses of the device_priv interface helper structs + */ +static int windows_assign_endpoints(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + struct libusb_config_descriptor *conf_desc; + const struct libusb_interface_descriptor *if_desc; + int i, r; + + r = libusb_get_active_config_descriptor(dev_handle->dev, &conf_desc); + if (r != LIBUSB_SUCCESS) { + usbi_warn(HANDLE_CTX(dev_handle), "could not read config descriptor: error %d", r); + return r; + } + + if_desc = &conf_desc->interface[iface].altsetting[altsetting]; + safe_free(priv->usb_interface[iface].endpoint); + + if (if_desc->bNumEndpoints == 0) { + usbi_dbg(HANDLE_CTX(dev_handle), "no endpoints found for interface %u", iface); + } else { + priv->usb_interface[iface].endpoint = malloc(if_desc->bNumEndpoints); + if (priv->usb_interface[iface].endpoint == NULL) { + libusb_free_config_descriptor(conf_desc); + return LIBUSB_ERROR_NO_MEM; + } + priv->usb_interface[iface].nb_endpoints = if_desc->bNumEndpoints; + for (i = 0; i < if_desc->bNumEndpoints; i++) { + priv->usb_interface[iface].endpoint[i] = if_desc->endpoint[i].bEndpointAddress; + usbi_dbg(HANDLE_CTX(dev_handle), "(re)assigned endpoint %02X to interface %u", priv->usb_interface[iface].endpoint[i], iface); + } + } + libusb_free_config_descriptor(conf_desc); + + // Extra init may be required to configure endpoints + if (priv->apib->configure_endpoints) + r = priv->apib->configure_endpoints(SUB_API_NOTSET, dev_handle, iface); + + if (r == LIBUSB_SUCCESS) + priv->usb_interface[iface].current_altsetting = altsetting; + + return r; +} + +// Lookup for a match in the list of API driver names +// return -1 if not found, driver match number otherwise +static int get_sub_api(char *driver, int api) +{ + const char sep_str[2] = {LIST_SEPARATOR, 0}; + char *tok, *tmp_str; + size_t len = strlen(driver); + int i; + + if (len == 0) + return SUB_API_NOTSET; + + tmp_str = _strdup(driver); + if (tmp_str == NULL) + return SUB_API_NOTSET; + + tok = strtok(tmp_str, sep_str); + while (tok != NULL) { + for (i = 0; i < usb_api_backend[api].nb_driver_names; i++) { + if (_stricmp(tok, usb_api_backend[api].driver_name_list[i]) == 0) { + free(tmp_str); + return i; + } + } + tok = strtok(NULL, sep_str); + } + + free(tmp_str); + return SUB_API_NOTSET; +} + +/* + * auto-claiming and auto-release helper functions + */ +static int auto_claim(struct libusb_transfer *transfer, int *interface_number, int api_type) +{ + struct winusb_device_handle_priv *handle_priv = + get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int current_interface = *interface_number; + int r = LIBUSB_SUCCESS; + + switch (api_type) { + case USB_API_WINUSBX: + case USB_API_HID: + break; + default: + return LIBUSB_ERROR_INVALID_PARAM; + } + + usbi_mutex_lock(&autoclaim_lock); + if (current_interface < 0) { // No serviceable interface was found + for (current_interface = 0; current_interface < USB_MAXINTERFACES; current_interface++) { + // Must claim an interface of the same API type + if ((priv->usb_interface[current_interface].apib->id == api_type) + && (libusb_claim_interface(transfer->dev_handle, current_interface) == LIBUSB_SUCCESS)) { + usbi_dbg(TRANSFER_CTX(transfer), "auto-claimed interface %d for control request", current_interface); + if (handle_priv->autoclaim_count[current_interface] != 0) + usbi_err(TRANSFER_CTX(transfer), "program assertion failed - autoclaim_count was nonzero"); + handle_priv->autoclaim_count[current_interface]++; + break; + } + } + if (current_interface == USB_MAXINTERFACES) { + usbi_err(TRANSFER_CTX(transfer), "could not auto-claim any interface"); + r = LIBUSB_ERROR_NOT_FOUND; + } + } else { + // If we have a valid interface that was autoclaimed, we must increment + // its autoclaim count so that we can prevent an early release. + if (handle_priv->autoclaim_count[current_interface] != 0) + handle_priv->autoclaim_count[current_interface]++; + } + usbi_mutex_unlock(&autoclaim_lock); + + *interface_number = current_interface; + return r; +} + +static void auto_release(struct usbi_transfer *itransfer) +{ + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + libusb_device_handle *dev_handle = transfer->dev_handle; + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + int r; + + usbi_mutex_lock(&autoclaim_lock); + if (handle_priv->autoclaim_count[transfer_priv->interface_number] > 0) { + handle_priv->autoclaim_count[transfer_priv->interface_number]--; + if (handle_priv->autoclaim_count[transfer_priv->interface_number] == 0) { + r = libusb_release_interface(dev_handle, transfer_priv->interface_number); + if (r == LIBUSB_SUCCESS) + usbi_dbg(ITRANSFER_CTX(itransfer), "auto-released interface %d", transfer_priv->interface_number); + else + usbi_dbg(ITRANSFER_CTX(itransfer), "failed to auto-release interface %d (%s)", + transfer_priv->interface_number, libusb_error_name((enum libusb_error)r)); + } + } + usbi_mutex_unlock(&autoclaim_lock); +} + +/* + * init: libusb backend init function + */ +static int winusb_init(struct libusb_context *ctx) +{ + int i; + + // Load DLL imports + if (!init_dlls(ctx)) { + usbi_err(ctx, "could not resolve DLL functions"); + return LIBUSB_ERROR_OTHER; + } + + // Initialize the low level APIs (we don't care about errors at this stage) + for (i = 0; i < USB_API_MAX; i++) { + if (usb_api_backend[i].init && !usb_api_backend[i].init(ctx)) + usbi_warn(ctx, "error initializing %s backend", + usb_api_backend[i].designation); + } + + // We need a lock for proper auto-release + usbi_mutex_init(&autoclaim_lock); + + return LIBUSB_SUCCESS; +} + +/* +* exit: libusb backend deinitialization function +*/ +static void winusb_exit(struct libusb_context *ctx) +{ + int i; + + UNUSED(ctx); + + usbi_mutex_destroy(&autoclaim_lock); + + for (i = 0; i < USB_API_MAX; i++) { + if (usb_api_backend[i].exit) + usb_api_backend[i].exit(); + } + + exit_dlls(); +} + +/* + * fetch and cache all the config descriptors through I/O + */ +static void cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + DWORD size, ret_size; + uint8_t i, num_configurations; + + USB_CONFIGURATION_DESCRIPTOR_SHORT cd_buf_short; // dummy request + PUSB_DESCRIPTOR_REQUEST cd_buf_actual = NULL; // actual request + PUSB_CONFIGURATION_DESCRIPTOR cd_data; + + num_configurations = dev->device_descriptor.bNumConfigurations; + if (num_configurations == 0) + return; + + assert(sizeof(USB_DESCRIPTOR_REQUEST) == USB_DESCRIPTOR_REQUEST_SIZE); + + priv->config_descriptor = calloc(num_configurations, sizeof(PUSB_CONFIGURATION_DESCRIPTOR)); + if (priv->config_descriptor == NULL) { + usbi_err(ctx, "could not allocate configuration descriptor array for '%s'", priv->dev_id); + return; + } + + for (i = 0; i <= num_configurations; i++) { + safe_free(cd_buf_actual); + + if (i == num_configurations) + break; + + size = sizeof(cd_buf_short); + memset(&cd_buf_short.desc, 0, sizeof(cd_buf_short.desc)); + + cd_buf_short.req.ConnectionIndex = (ULONG)dev->port_number; + cd_buf_short.req.SetupPacket.bmRequest = LIBUSB_ENDPOINT_IN; + cd_buf_short.req.SetupPacket.bRequest = LIBUSB_REQUEST_GET_DESCRIPTOR; + cd_buf_short.req.SetupPacket.wValue = (LIBUSB_DT_CONFIG << 8) | i; + cd_buf_short.req.SetupPacket.wIndex = 0; + cd_buf_short.req.SetupPacket.wLength = (USHORT)sizeof(USB_CONFIGURATION_DESCRIPTOR); + + // Dummy call to get the required data size. Initial failures are reported as info rather + // than error as they can occur for non-penalizing situations, such as with some hubs. + // coverity[tainted_data_argument] + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, &cd_buf_short, size, + &cd_buf_short, size, &ret_size, NULL)) { + usbi_info(ctx, "could not access configuration descriptor %u (dummy) for '%s': %s", i, priv->dev_id, windows_error_str(0)); + continue; + } + + if ((ret_size != size) || (cd_buf_short.desc.wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR))) { + usbi_info(ctx, "unexpected configuration descriptor %u size (dummy) for '%s'", i, priv->dev_id); + continue; + } + + size = sizeof(USB_DESCRIPTOR_REQUEST) + cd_buf_short.desc.wTotalLength; + cd_buf_actual = malloc(size); + if (cd_buf_actual == NULL) { + usbi_err(ctx, "could not allocate configuration descriptor %u buffer for '%s'", i, priv->dev_id); + continue; + } + + // Actual call + cd_buf_actual->ConnectionIndex = (ULONG)dev->port_number; + cd_buf_actual->SetupPacket.bmRequest = LIBUSB_ENDPOINT_IN; + cd_buf_actual->SetupPacket.bRequest = LIBUSB_REQUEST_GET_DESCRIPTOR; + cd_buf_actual->SetupPacket.wValue = (LIBUSB_DT_CONFIG << 8) | i; + cd_buf_actual->SetupPacket.wIndex = 0; + cd_buf_actual->SetupPacket.wLength = cd_buf_short.desc.wTotalLength; + + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, cd_buf_actual, size, + cd_buf_actual, size, &ret_size, NULL)) { + usbi_err(ctx, "could not access configuration descriptor %u (actual) for '%s': %s", i, priv->dev_id, windows_error_str(0)); + continue; + } + + cd_data = (PUSB_CONFIGURATION_DESCRIPTOR)((UCHAR *)cd_buf_actual + USB_DESCRIPTOR_REQUEST_SIZE); + + if ((size != ret_size) || (cd_data->wTotalLength != cd_buf_short.desc.wTotalLength)) { + usbi_err(ctx, "unexpected configuration descriptor %u size (actual) for '%s'", i, priv->dev_id); + continue; + } + + if (cd_data->bDescriptorType != LIBUSB_DT_CONFIG) { + usbi_err(ctx, "descriptor %u not a configuration descriptor for '%s'", i, priv->dev_id); + continue; + } + + usbi_dbg(ctx, "cached config descriptor %u (bConfigurationValue=%u, %u bytes)", + i, cd_data->bConfigurationValue, cd_data->wTotalLength); + + // Cache the descriptor + priv->config_descriptor[i] = cd_data; + cd_buf_actual = NULL; + } +} + +#define ROOT_HUB_FS_CONFIG_DESC_LENGTH 0x19 +#define ROOT_HUB_HS_CONFIG_DESC_LENGTH 0x19 +#define ROOT_HUB_SS_CONFIG_DESC_LENGTH 0x1f +#define CONFIG_DESC_WTOTAL_LENGTH_OFFSET 0x02 +#define CONFIG_DESC_EP_MAX_PACKET_OFFSET 0x16 +#define CONFIG_DESC_EP_BINTERVAL_OFFSET 0x18 + +static const uint8_t root_hub_config_descriptor_template[] = { + // Configuration Descriptor + LIBUSB_DT_CONFIG_SIZE, // bLength + LIBUSB_DT_CONFIG, // bDescriptorType + 0x00, 0x00, // wTotalLength (filled in) + 0x01, // bNumInterfaces + 0x01, // bConfigurationValue + 0x00, // iConfiguration + 0xc0, // bmAttributes (reserved + self-powered) + 0x00, // bMaxPower + // Interface Descriptor + LIBUSB_DT_INTERFACE_SIZE, // bLength + LIBUSB_DT_INTERFACE, // bDescriptorType + 0x00, // bInterfaceNumber + 0x00, // bAlternateSetting + 0x01, // bNumEndpoints + LIBUSB_CLASS_HUB, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface + // Endpoint Descriptor + LIBUSB_DT_ENDPOINT_SIZE, // bLength + LIBUSB_DT_ENDPOINT, // bDescriptorType + 0x81, // bEndpointAddress + 0x03, // bmAttributes (Interrupt) + 0x00, 0x00, // wMaxPacketSize (filled in) + 0x00, // bInterval (filled in) + // SuperSpeed Endpoint Companion Descriptor + LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE, // bLength + LIBUSB_DT_SS_ENDPOINT_COMPANION, // bDescriptorType + 0x00, // bMaxBurst + 0x00, // bmAttributes + 0x02, 0x00 // wBytesPerInterval +}; + +static int alloc_root_hub_config_desc(struct libusb_device *dev, ULONG num_ports, + uint8_t config_desc_length, uint8_t ep_interval) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + uint8_t *ptr; + + priv->config_descriptor = malloc(sizeof(*priv->config_descriptor)); + if (priv->config_descriptor == NULL) + return LIBUSB_ERROR_NO_MEM; + + // Most config descriptors come from cache_config_descriptors() which obtains the + // descriptors from the hub using an allocated USB_DESCRIPTOR_REQUEST structure. + // To avoid an extra malloc + memcpy we just hold on to the USB_DESCRIPTOR_REQUEST + // structure we already have and back up the pointer in windows_device_priv_release() + // when freeing the descriptors. To keep a single execution path, we need to offset + // the pointer here by the same amount. + ptr = malloc(USB_DESCRIPTOR_REQUEST_SIZE + config_desc_length); + if (ptr == NULL) + return LIBUSB_ERROR_NO_MEM; + + ptr += USB_DESCRIPTOR_REQUEST_SIZE; + + memcpy(ptr, root_hub_config_descriptor_template, config_desc_length); + ptr[CONFIG_DESC_WTOTAL_LENGTH_OFFSET] = config_desc_length; + ptr[CONFIG_DESC_EP_MAX_PACKET_OFFSET] = (uint8_t)((num_ports + 7) / 8); + ptr[CONFIG_DESC_EP_BINTERVAL_OFFSET] = ep_interval; + + priv->config_descriptor[0] = (PUSB_CONFIGURATION_DESCRIPTOR)ptr; + priv->active_config = 1; + + return 0; +} + +static int init_root_hub(struct libusb_device *dev) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + USB_NODE_CONNECTION_INFORMATION_EX conn_info; + USB_NODE_CONNECTION_INFORMATION_EX_V2 conn_info_v2; + USB_NODE_INFORMATION hub_info; + enum libusb_speed speed = LIBUSB_SPEED_UNKNOWN; + uint8_t config_desc_length; + uint8_t ep_interval; + HANDLE handle; + ULONG port_number, num_ports; + DWORD size; + int r; + + // Determining the speed of a root hub is painful. Microsoft does not directly report the speed + // capabilities of the root hub itself, only its ports and/or connected devices. Therefore we + // are forced to query each individual port of the root hub to try and infer the root hub's + // speed. Note that we have to query all ports because the presence of a device on that port + // changes if/how Windows returns any useful speed information. + handle = CreateFileA(priv->path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (handle == INVALID_HANDLE_VALUE) { + usbi_err(ctx, "could not open root hub %s: %s", priv->path, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + + if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_INFORMATION, NULL, 0, &hub_info, sizeof(hub_info), &size, NULL)) { + usbi_warn(ctx, "could not get root hub info for '%s': %s", priv->dev_id, windows_error_str(0)); + CloseHandle(handle); + return LIBUSB_ERROR_ACCESS; + } + + num_ports = hub_info.u.HubInformation.HubDescriptor.bNumberOfPorts; + usbi_dbg(ctx, "root hub '%s' reports %lu ports", priv->dev_id, ULONG_CAST(num_ports)); + + if (windows_version >= WINDOWS_8) { + // Windows 8 and later is better at reporting the speed capabilities of the root hub, + // but it is not perfect. If no device is attached to the port being queried, the + // returned information will only indicate whether that port supports USB 3.0 signalling. + // That is not enough information to distinguish between SuperSpeed and SuperSpeed Plus. + for (port_number = 1; port_number <= num_ports; port_number++) { + conn_info_v2.ConnectionIndex = port_number; + conn_info_v2.Length = sizeof(conn_info_v2); + conn_info_v2.SupportedUsbProtocols.Usb300 = 1; + if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, + &conn_info_v2, sizeof(conn_info_v2), &conn_info_v2, sizeof(conn_info_v2), &size, NULL)) { + usbi_warn(ctx, "could not get node connection information (V2) for root hub '%s' port %lu: %s", + priv->dev_id, ULONG_CAST(port_number), windows_error_str(0)); + break; + } + + if (conn_info_v2.Flags.DeviceIsSuperSpeedPlusCapableOrHigher) + speed = MAX(speed, LIBUSB_SPEED_SUPER_PLUS); + else if (conn_info_v2.Flags.DeviceIsSuperSpeedCapableOrHigher || conn_info_v2.SupportedUsbProtocols.Usb300) + speed = MAX(speed, LIBUSB_SPEED_SUPER); + else if (conn_info_v2.SupportedUsbProtocols.Usb200) + speed = MAX(speed, LIBUSB_SPEED_HIGH); + else + speed = MAX(speed, LIBUSB_SPEED_FULL); + } + + if (speed != LIBUSB_SPEED_UNKNOWN) + goto make_descriptors; + } + + // At this point the speed is still not known, most likely because we are executing on + // Windows 7 or earlier. The following hackery peeks into the root hub's Device ID and + // tries to extract speed information from it, based on observed naming conventions. + // If this does not work, we will query individual ports of the root hub. + if (strstr(priv->dev_id, "ROOT_HUB31") != NULL) + speed = LIBUSB_SPEED_SUPER_PLUS; + else if (strstr(priv->dev_id, "ROOT_HUB30") != NULL) + speed = LIBUSB_SPEED_SUPER; + else if (strstr(priv->dev_id, "ROOT_HUB20") != NULL) + speed = LIBUSB_SPEED_HIGH; + + if (speed != LIBUSB_SPEED_UNKNOWN) + goto make_descriptors; + + // Windows only reports speed information about a connected device. This means that a root + // hub with no connected devices or devices that are all operating at a speed less than the + // highest speed that the root hub supports will not give us the correct speed. + for (port_number = 1; port_number <= num_ports; port_number++) { + conn_info.ConnectionIndex = port_number; + if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, &conn_info, sizeof(conn_info), + &conn_info, sizeof(conn_info), &size, NULL)) { + usbi_warn(ctx, "could not get node connection information for root hub '%s' port %lu: %s", + priv->dev_id, ULONG_CAST(port_number), windows_error_str(0)); + continue; + } + + if (conn_info.ConnectionStatus != DeviceConnected) + continue; + + if (conn_info.Speed == UsbHighSpeed) { + speed = LIBUSB_SPEED_HIGH; + break; + } + } + +make_descriptors: + CloseHandle(handle); + + dev->device_descriptor.bLength = LIBUSB_DT_DEVICE_SIZE; + dev->device_descriptor.bDescriptorType = LIBUSB_DT_DEVICE; + dev->device_descriptor.bDeviceClass = LIBUSB_CLASS_HUB; + if ((dev->device_descriptor.idVendor == 0) && (dev->device_descriptor.idProduct == 0)) { + dev->device_descriptor.idVendor = 0x1d6b; // Linux Foundation + dev->device_descriptor.idProduct = (uint16_t)speed; + } + dev->device_descriptor.bcdDevice = 0x0100; + dev->device_descriptor.bNumConfigurations = 1; + + switch (speed) { + case LIBUSB_SPEED_SUPER_PLUS: + dev->device_descriptor.bcdUSB = 0x0310; + config_desc_length = ROOT_HUB_SS_CONFIG_DESC_LENGTH; + ep_interval = 0x0c; // 256ms + break; + case LIBUSB_SPEED_SUPER: + dev->device_descriptor.bcdUSB = 0x0300; + config_desc_length = ROOT_HUB_SS_CONFIG_DESC_LENGTH; + ep_interval = 0x0c; // 256ms + break; + case LIBUSB_SPEED_HIGH: + dev->device_descriptor.bcdUSB = 0x0200; + config_desc_length = ROOT_HUB_HS_CONFIG_DESC_LENGTH; + ep_interval = 0x0c; // 256ms + break; + case LIBUSB_SPEED_LOW: // Not used, but keeps compiler happy + case LIBUSB_SPEED_UNKNOWN: + // This case means absolutely no information about this root hub was determined. + // There is not much choice than to be pessimistic and label this as a + // full-speed device. + speed = LIBUSB_SPEED_FULL; + // fallthrough + case LIBUSB_SPEED_FULL: + dev->device_descriptor.bcdUSB = 0x0110; + config_desc_length = ROOT_HUB_FS_CONFIG_DESC_LENGTH; + ep_interval = 0xff; // 255ms + break; + default: // Impossible, buts keeps compiler happy + usbi_err(ctx, "program assertion failed - unknown root hub speed"); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (speed >= LIBUSB_SPEED_SUPER) { + dev->device_descriptor.bDeviceProtocol = 0x03; // USB 3.0 Hub + dev->device_descriptor.bMaxPacketSize0 = 0x09; // 2^9 bytes + } else { + dev->device_descriptor.bMaxPacketSize0 = 0x40; // 64 bytes + } + + dev->speed = speed; + + r = alloc_root_hub_config_desc(dev, num_ports, config_desc_length, ep_interval); + if (r) + usbi_err(ctx, "could not allocate config descriptor for root hub '%s'", priv->dev_id); + + return r; +} + +/* + * Populate a libusb device structure + */ +static int init_device(struct libusb_device *dev, struct libusb_device *parent_dev, + uint8_t port_number, DEVINST devinst) +{ + struct libusb_context *ctx = NULL; + struct libusb_device *tmp_dev; + struct winusb_device_priv *priv, *parent_priv, *tmp_priv; + USB_NODE_CONNECTION_INFORMATION_EX conn_info; + USB_NODE_CONNECTION_INFORMATION_EX_V2 conn_info_v2; + HANDLE hub_handle; + DWORD size; + uint8_t bus_number, depth; + int r; + int ginfotimeout; + + priv = usbi_get_device_priv(dev); + + // If the device is already initialized, we can stop here + if (priv->initialized) + return LIBUSB_SUCCESS; + + if (parent_dev != NULL) { // Not a HCD root hub + ctx = DEVICE_CTX(dev); + parent_priv = usbi_get_device_priv(parent_dev); + if (parent_priv->apib->id != USB_API_HUB) { + usbi_warn(ctx, "parent for device '%s' is not a hub", priv->dev_id); + return LIBUSB_ERROR_NOT_FOUND; + } + + // Calculate depth and fetch bus number + bus_number = parent_dev->bus_number; + if (bus_number == 0) { + tmp_dev = get_ancestor(ctx, devinst, &devinst); + if (tmp_dev != parent_dev) { + usbi_err(ctx, "program assertion failed - first ancestor is not parent"); + return LIBUSB_ERROR_NOT_FOUND; + } + libusb_unref_device(tmp_dev); + + for (depth = 1; bus_number == 0; depth++) { + tmp_dev = get_ancestor(ctx, devinst, &devinst); + if (tmp_dev == NULL) { + usbi_warn(ctx, "ancestor for device '%s' not found at depth %u", priv->dev_id, depth); + return LIBUSB_ERROR_NO_DEVICE; + } + if (tmp_dev->bus_number != 0) { + bus_number = tmp_dev->bus_number; + tmp_priv = usbi_get_device_priv(tmp_dev); + depth += tmp_priv->depth; + } + libusb_unref_device(tmp_dev); + } + } else { + depth = parent_priv->depth + 1; + } + + if (bus_number == 0) { + usbi_err(ctx, "program assertion failed - bus number not found for '%s'", priv->dev_id); + return LIBUSB_ERROR_NOT_FOUND; + } + + dev->bus_number = bus_number; + dev->port_number = port_number; + dev->parent_dev = parent_dev; + priv->depth = depth; + + hub_handle = CreateFileA(parent_priv->path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hub_handle == INVALID_HANDLE_VALUE) { + usbi_warn(ctx, "could not open hub %s: %s", parent_priv->path, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + + conn_info.ConnectionIndex = (ULONG)port_number; + // coverity[tainted_data_argument] + ginfotimeout = 20; + do { + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, &conn_info, sizeof(conn_info), + &conn_info, sizeof(conn_info), &size, NULL)) { + usbi_warn(ctx, "could not get node connection information for device '%s': %s", + priv->dev_id, windows_error_str(0)); + CloseHandle(hub_handle); + return LIBUSB_ERROR_NO_DEVICE; + } + + if (conn_info.ConnectionStatus == NoDeviceConnected) { + usbi_err(ctx, "device '%s' is no longer connected!", priv->dev_id); + CloseHandle(hub_handle); + return LIBUSB_ERROR_NO_DEVICE; + } + + if ((conn_info.DeviceDescriptor.bLength != LIBUSB_DT_DEVICE_SIZE) + || (conn_info.DeviceDescriptor.bDescriptorType != LIBUSB_DT_DEVICE)) { + SleepEx(50, TRUE); + continue; + } + + static_assert(sizeof(dev->device_descriptor) == sizeof(conn_info.DeviceDescriptor), + "mismatch between libusb and OS device descriptor sizes"); + memcpy(&dev->device_descriptor, &conn_info.DeviceDescriptor, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + + priv->active_config = conn_info.CurrentConfigurationValue; + if (priv->active_config == 0) { + usbi_dbg(ctx, "0x%x:0x%x found %u configurations (not configured)", + dev->device_descriptor.idVendor, + dev->device_descriptor.idProduct, + dev->device_descriptor.bNumConfigurations); + SleepEx(50, TRUE); + } + } while (priv->active_config == 0 && --ginfotimeout >= 0); + + if ((conn_info.DeviceDescriptor.bLength != LIBUSB_DT_DEVICE_SIZE) + || (conn_info.DeviceDescriptor.bDescriptorType != LIBUSB_DT_DEVICE)) { + usbi_err(ctx, "device '%s' has invalid descriptor!", priv->dev_id); + CloseHandle(hub_handle); + return LIBUSB_ERROR_OTHER; + } + + if (priv->active_config == 0) { + usbi_info(ctx, "0x%x:0x%x found %u configurations but device isn't configured, " + "forcing current configuration to 1", + dev->device_descriptor.idVendor, + dev->device_descriptor.idProduct, + dev->device_descriptor.bNumConfigurations); + priv->active_config = 1; + } else { + usbi_dbg(ctx, "found %u configurations (current config: %u)", dev->device_descriptor.bNumConfigurations, priv->active_config); + } + + // Cache as many config descriptors as we can + cache_config_descriptors(dev, hub_handle); + + // In their great wisdom, Microsoft decided to BREAK the USB speed report between Windows 7 and Windows 8 + if (windows_version >= WINDOWS_8) { + conn_info_v2.ConnectionIndex = (ULONG)port_number; + conn_info_v2.Length = sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2); + conn_info_v2.SupportedUsbProtocols.Usb300 = 1; + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, + &conn_info_v2, sizeof(conn_info_v2), &conn_info_v2, sizeof(conn_info_v2), &size, NULL)) { + usbi_warn(ctx, "could not get node connection information (V2) for device '%s': %s", + priv->dev_id, windows_error_str(0)); + } else if (conn_info_v2.Flags.DeviceIsOperatingAtSuperSpeedPlusOrHigher) { + conn_info.Speed = UsbSuperSpeedPlus; + } else if (conn_info_v2.Flags.DeviceIsOperatingAtSuperSpeedOrHigher) { + conn_info.Speed = UsbSuperSpeed; + } + } + + CloseHandle(hub_handle); + + if (conn_info.DeviceAddress > UINT8_MAX) + usbi_err(ctx, "program assertion failed - device address overflow"); + + dev->device_address = (uint8_t)conn_info.DeviceAddress; + + switch (conn_info.Speed) { + case UsbLowSpeed: dev->speed = LIBUSB_SPEED_LOW; break; + case UsbFullSpeed: dev->speed = LIBUSB_SPEED_FULL; break; + case UsbHighSpeed: dev->speed = LIBUSB_SPEED_HIGH; break; + case UsbSuperSpeed: dev->speed = LIBUSB_SPEED_SUPER; break; + case UsbSuperSpeedPlus: dev->speed = LIBUSB_SPEED_SUPER_PLUS; break; + default: + usbi_warn(ctx, "unknown device speed %u", conn_info.Speed); + break; + } + } else { + r = init_root_hub(dev); + if (r) + return r; + } + + r = usbi_sanitize_device(dev); + if (r) + return r; + + priv->initialized = true; + + usbi_dbg(ctx, "(bus: %u, addr: %u, depth: %u, port: %u): '%s'", + dev->bus_number, dev->device_address, priv->depth, dev->port_number, priv->dev_id); + + return LIBUSB_SUCCESS; +} + +static bool get_dev_port_number(HDEVINFO dev_info, SP_DEVINFO_DATA *dev_info_data, DWORD *port_nr) +{ + char buffer[MAX_KEY_LENGTH]; + DWORD size; + + // First try SPDRP_LOCATION_INFORMATION, which returns a REG_SZ. The string *may* have a format + // similar to "Port_#0002.Hub_#000D", in which case we can extract the port number. However, we + // cannot extract the port if the returned string does not follow this format. + if (pSetupDiGetDeviceRegistryPropertyA(dev_info, dev_info_data, SPDRP_LOCATION_INFORMATION, + NULL, (PBYTE)buffer, sizeof(buffer), NULL)) { + // Check for the required format. + if (strncmp(buffer, "Port_#", 6) == 0) { + *port_nr = atoi(buffer + 6); + return true; + } + } + + // Next try SPDRP_LOCATION_PATHS, which returns a REG_MULTI_SZ (but we only examine the first + // string in it). Each path has a format similar to, + // "PCIROOT(B2)#PCI(0300)#PCI(0000)#USBROOT(0)#USB(1)#USB(2)#USBMI(3)", and the port number is + // the number within the last "USB(x)" token. + if (pSetupDiGetDeviceRegistryPropertyA(dev_info, dev_info_data, SPDRP_LOCATION_PATHS, + NULL, (PBYTE)buffer, sizeof(buffer), NULL)) { + // Find the last "#USB(x)" substring + for (char *token = strrchr(buffer, '#'); token != NULL; token = strrchr(buffer, '#')) { + if (strncmp(token, "#USB(", 5) == 0) { + *port_nr = atoi(token + 5); + return true; + } + // Shorten the string and try again. + *token = '\0'; + } + } + + // Lastly, try SPDRP_ADDRESS, which returns a REG_DWORD. The address *may* be the port number, + // which is true for the Microsoft driver but may not be true for other drivers. However, we + // have no other options here but to accept what it returns. + return pSetupDiGetDeviceRegistryPropertyA(dev_info, dev_info_data, SPDRP_ADDRESS, + NULL, (PBYTE)port_nr, sizeof(*port_nr), &size) && (size == sizeof(*port_nr)); +} + +static int enumerate_hcd_root_hub(struct libusb_context *ctx, const char *dev_id, + uint8_t bus_number, DEVINST devinst) +{ + struct libusb_device *dev; + struct winusb_device_priv *priv; + unsigned long session_id; + DEVINST child_devinst; + + if (CM_Get_Child(&child_devinst, devinst, 0) != CR_SUCCESS) { + usbi_warn(ctx, "could not get child devinst for '%s'", dev_id); + return LIBUSB_SUCCESS; + } + + session_id = (unsigned long)child_devinst; + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev == NULL) { + usbi_err(ctx, "program assertion failed - HCD '%s' child not found", dev_id); + return LIBUSB_SUCCESS; + } + + if (dev->bus_number == 0) { + // Only do this once + usbi_dbg(ctx, "assigning HCD '%s' bus number %u", dev_id, bus_number); + dev->bus_number = bus_number; + + if (sscanf(dev_id, "PCI\\VEN_%04hx&DEV_%04hx%*s", &dev->device_descriptor.idVendor, &dev->device_descriptor.idProduct) != 2) + usbi_warn(ctx, "could not infer VID/PID of HCD root hub from '%s'", dev_id); + + priv = usbi_get_device_priv(dev); + priv->root_hub = true; + } + + libusb_unref_device(dev); + return LIBUSB_SUCCESS; +} + +// Returns the api type, or 0 if not found/unsupported +static void get_api_type(HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, + int *api, int *sub_api) +{ + // Precedence for filter drivers vs driver is in the order of this array + struct driver_lookup lookup[3] = { + {"\0\0", SPDRP_SERVICE, "driver"}, + {"\0\0", SPDRP_UPPERFILTERS, "upper filter driver"}, + {"\0\0", SPDRP_LOWERFILTERS, "lower filter driver"} + }; + DWORD size, reg_type; + unsigned k, l; + int i, j; + + // Check the service & filter names to know the API we should use + for (k = 0; k < 3; k++) { + if (pSetupDiGetDeviceRegistryPropertyA(*dev_info, dev_info_data, lookup[k].reg_prop, + ®_type, (PBYTE)lookup[k].list, MAX_KEY_LENGTH, &size)) { + // Turn the REG_SZ SPDRP_SERVICE into REG_MULTI_SZ + if (lookup[k].reg_prop == SPDRP_SERVICE) + // our buffers are MAX_KEY_LENGTH + 1 so we can overflow if needed + lookup[k].list[strlen(lookup[k].list) + 1] = 0; + + // MULTI_SZ is a pain to work with. Turn it into something much more manageable + // NB: none of the driver names we check against contain LIST_SEPARATOR, + // (currently ';'), so even if an unsupported one does, it's not an issue + for (l = 0; (lookup[k].list[l] != 0) || (lookup[k].list[l + 1] != 0); l++) { + if (lookup[k].list[l] == 0) + lookup[k].list[l] = LIST_SEPARATOR; + } + usbi_dbg(NULL, "%s(s): %s", lookup[k].designation, lookup[k].list); + } else { + if (GetLastError() != ERROR_INVALID_DATA) + usbi_dbg(NULL, "could not access %s: %s", lookup[k].designation, windows_error_str(0)); + lookup[k].list[0] = 0; + } + } + + for (i = 2; i < USB_API_MAX; i++) { + for (k = 0; k < 3; k++) { + j = get_sub_api(lookup[k].list, i); + if (j >= 0) { + usbi_dbg(NULL, "matched %s name against %s", lookup[k].designation, + (i != USB_API_WINUSBX) ? usb_api_backend[i].designation : usb_api_backend[i].driver_name_list[j]); + *api = i; + *sub_api = j; + return; + } + } + } +} + +static int set_composite_interface(struct libusb_context *ctx, struct libusb_device *dev, + char *dev_interface_path, char *device_id, int api, int sub_api) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + int interface_number; + const char *mi_str; + + // Because MI_## are not necessarily in sequential order (some composite + // devices will have only MI_00 & MI_03 for instance), we retrieve the actual + // interface number from the path's MI value + mi_str = strstr(device_id, "MI_"); + if ((mi_str != NULL) && isdigit((unsigned char)mi_str[3]) && isdigit((unsigned char)mi_str[4])) { + interface_number = ((mi_str[3] - '0') * 10) + (mi_str[4] - '0'); + } else { + usbi_warn(ctx, "failure to read interface number for %s, using default value", device_id); + interface_number = 0; + } + + if (interface_number >= USB_MAXINTERFACES) { + usbi_warn(ctx, "interface %d too large - ignoring interface path %s", interface_number, dev_interface_path); + return LIBUSB_ERROR_ACCESS; + } + + if (priv->usb_interface[interface_number].path != NULL) { + if (api == USB_API_HID) { + // HID devices can have multiple collections (COL##) for each MI_## interface + usbi_dbg(ctx, "interface[%d] already set - ignoring HID collection: %s", + interface_number, device_id); + return LIBUSB_ERROR_ACCESS; + } + // In other cases, just use the latest data + safe_free(priv->usb_interface[interface_number].path); + } + + usbi_dbg(ctx, "interface[%d] = %s", interface_number, dev_interface_path); + priv->usb_interface[interface_number].path = dev_interface_path; + priv->usb_interface[interface_number].apib = &usb_api_backend[api]; + priv->usb_interface[interface_number].sub_api = sub_api; + if ((api == USB_API_HID) && (priv->hid == NULL)) { + priv->hid = calloc(1, sizeof(struct hid_device_priv)); + if (priv->hid == NULL) + return LIBUSB_ERROR_NO_MEM; + } + + return LIBUSB_SUCCESS; +} + +static int set_hid_interface(struct libusb_context *ctx, struct libusb_device *dev, + char *dev_interface_path) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + uint8_t i; + + if (priv->hid == NULL) { + usbi_err(ctx, "program assertion failed - parent is not HID"); + return LIBUSB_ERROR_NO_DEVICE; + } else if (priv->hid->nb_interfaces == USB_MAXINTERFACES) { + usbi_err(ctx, "program assertion failed - max USB interfaces reached for HID device"); + return LIBUSB_ERROR_NO_DEVICE; + } + + for (i = 0; i < priv->hid->nb_interfaces; i++) { + if ((priv->usb_interface[i].path != NULL) && strcmp(priv->usb_interface[i].path, dev_interface_path) == 0) { + usbi_dbg(ctx, "interface[%u] already set to %s", i, dev_interface_path); + return LIBUSB_ERROR_ACCESS; + } + } + + priv->usb_interface[priv->hid->nb_interfaces].path = dev_interface_path; + priv->usb_interface[priv->hid->nb_interfaces].apib = &usb_api_backend[USB_API_HID]; + usbi_dbg(ctx, "interface[%u] = %s", priv->hid->nb_interfaces, dev_interface_path); + priv->hid->nb_interfaces++; + return LIBUSB_SUCCESS; +} + +/* + * get_device_list: libusb backend device enumeration function + */ +static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs) +{ + struct discovered_devs *discdevs; + HDEVINFO *dev_info, dev_info_intf, dev_info_enum; + SP_DEVINFO_DATA dev_info_data; + DWORD _index = 0; + GUID hid_guid; + int r = LIBUSB_SUCCESS; + int api, sub_api; + unsigned int pass, i, j; + char enumerator[16]; + char dev_id[MAX_PATH_LENGTH]; + struct libusb_device *dev, *parent_dev; + struct winusb_device_priv *priv, *parent_priv; + char *dev_interface_path = NULL; + unsigned long session_id; + DWORD size, port_nr, reg_type, install_state; + HKEY key; + char guid_string[MAX_GUID_STRING_LENGTH]; + GUID *if_guid; + LONG s; +#define HUB_PASS 0 +#define DEV_PASS 1 +#define HCD_PASS 2 +#define GEN_PASS 3 +#define HID_PASS 4 +#define EXT_PASS 5 + // Keep a list of guids that will be enumerated +#define GUID_SIZE_STEP 8 + const GUID **guid_list, **new_guid_list; + unsigned int guid_size = GUID_SIZE_STEP; + unsigned int nb_guids; + // Keep a list of PnP enumerator strings that are found + const char *usb_enumerator[8] = { "USB" }; + unsigned int nb_usb_enumerators = 1; + unsigned int usb_enum_index = 0; + // Keep a list of newly allocated devs to unref +#define UNREF_SIZE_STEP 16 + libusb_device **unref_list, **new_unref_list; + unsigned int unref_size = UNREF_SIZE_STEP; + unsigned int unref_cur = 0; + + // PASS 1 : (re)enumerate HCDs (allows for HCD hotplug) + // PASS 2 : (re)enumerate HUBS + // PASS 3 : (re)enumerate generic USB devices (including driverless) + // and list additional USB device interface GUIDs to explore + // PASS 4 : (re)enumerate master USB devices that have a device interface + // PASS 5+: (re)enumerate device interfaced GUIDs (including HID) and + // set the device interfaces. + + // Init the GUID table + guid_list = malloc(guid_size * sizeof(void *)); + if (guid_list == NULL) { + usbi_err(ctx, "failed to alloc guid list"); + return LIBUSB_ERROR_NO_MEM; + } + + guid_list[HUB_PASS] = &GUID_DEVINTERFACE_USB_HUB; + guid_list[DEV_PASS] = &GUID_DEVINTERFACE_USB_DEVICE; + guid_list[HCD_PASS] = &GUID_DEVINTERFACE_USB_HOST_CONTROLLER; + guid_list[GEN_PASS] = NULL; + if (HidD_GetHidGuid != NULL) { + HidD_GetHidGuid(&hid_guid); + guid_list[HID_PASS] = &hid_guid; + } else { + guid_list[HID_PASS] = NULL; + } + nb_guids = EXT_PASS; + + unref_list = malloc(unref_size * sizeof(void *)); + if (unref_list == NULL) { + usbi_err(ctx, "failed to alloc unref list"); + free((void *)guid_list); + return LIBUSB_ERROR_NO_MEM; + } + + dev_info_intf = pSetupDiGetClassDevsA(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (dev_info_intf == INVALID_HANDLE_VALUE) { + usbi_err(ctx, "failed to obtain device info list: %s", windows_error_str(0)); + free(unref_list); + free((void *)guid_list); + return LIBUSB_ERROR_OTHER; + } + + for (pass = 0; ((pass < nb_guids) && (r == LIBUSB_SUCCESS)); pass++) { +//#define ENUM_DEBUG +#if defined(ENABLE_LOGGING) && defined(ENUM_DEBUG) + const char * const passname[] = {"HUB", "DEV", "HCD", "GEN", "HID", "EXT"}; + usbi_dbg(ctx, "#### PROCESSING %ss %s", passname[MIN(pass, EXT_PASS)], guid_to_string(guid_list[pass], guid_string)); +#endif + if ((pass == HID_PASS) && (guid_list[HID_PASS] == NULL)) + continue; + + dev_info = (pass != GEN_PASS) ? &dev_info_intf : &dev_info_enum; + + for (i = 0; ; i++) { + // safe loop: free up any (unprotected) dynamic resource + // NB: this is always executed before breaking the loop + safe_free(dev_interface_path); + priv = parent_priv = NULL; + dev = parent_dev = NULL; + + // Safe loop: end of loop conditions + if (r != LIBUSB_SUCCESS) + break; + + if ((pass == HCD_PASS) && (i == UINT8_MAX)) { + usbi_warn(ctx, "program assertion failed - found more than %u buses, skipping the rest", UINT8_MAX); + break; + } + + if (pass != GEN_PASS) { + // Except for GEN, all passes deal with device interfaces + r = get_interface_details(ctx, *dev_info, &dev_info_data, guid_list[pass], &_index, &dev_interface_path); + if ((r != LIBUSB_SUCCESS) || (dev_interface_path == NULL)) { + _index = 0; + break; + } + } else { + // Workaround for a Nec/Renesas USB 3.0 driver bug where root hubs are + // being listed under the "NUSB3" PnP Symbolic Name rather than "USB". + // The Intel USB 3.0 driver behaves similar, but uses "IUSB3" + // The Intel Alpine Ridge USB 3.1 driver uses "IARUSB3" + for (; usb_enum_index < nb_usb_enumerators; usb_enum_index++) { + if (get_devinfo_data(ctx, dev_info, &dev_info_data, usb_enumerator[usb_enum_index], i)) + break; + i = 0; + } + if (usb_enum_index == nb_usb_enumerators) + break; + } + + // Read the Device ID path + if (!pSetupDiGetDeviceInstanceIdA(*dev_info, &dev_info_data, dev_id, sizeof(dev_id), NULL)) { + usbi_warn(ctx, "could not read the device instance ID for devInst %lX, skipping", + ULONG_CAST(dev_info_data.DevInst)); + continue; + } + +#ifdef ENUM_DEBUG + usbi_dbg(ctx, "PRO: %s", dev_id); +#endif + + // Set API to use or get additional data from generic pass + api = USB_API_UNSUPPORTED; + sub_api = SUB_API_NOTSET; + switch (pass) { + case HCD_PASS: + break; + case HUB_PASS: + api = USB_API_HUB; + // Fetch the PnP enumerator class for this hub + // This will allow us to enumerate all classes during the GEN pass + if (!pSetupDiGetDeviceRegistryPropertyA(*dev_info, &dev_info_data, SPDRP_ENUMERATOR_NAME, + NULL, (PBYTE)enumerator, sizeof(enumerator), NULL)) { + usbi_err(ctx, "could not read enumerator string for device '%s': %s", dev_id, windows_error_str(0)); + LOOP_BREAK(LIBUSB_ERROR_OTHER); + } + for (j = 0; j < nb_usb_enumerators; j++) { + if (strcmp(usb_enumerator[j], enumerator) == 0) + break; + } + if (j == nb_usb_enumerators) { + usbi_dbg(ctx, "found new PnP enumerator string '%s'", enumerator); + if (nb_usb_enumerators < ARRAYSIZE(usb_enumerator)) { + usb_enumerator[nb_usb_enumerators] = _strdup(enumerator); + if (usb_enumerator[nb_usb_enumerators] != NULL) { + nb_usb_enumerators++; + } else { + usbi_err(ctx, "could not allocate enumerator string '%s'", enumerator); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + } else { + usbi_warn(ctx, "too many enumerator strings, some devices may not be accessible"); + } + } + break; + case GEN_PASS: + // We use the GEN pass to detect driverless devices... + if (!pSetupDiGetDeviceRegistryPropertyA(*dev_info, &dev_info_data, SPDRP_DRIVER, + NULL, NULL, 0, NULL) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + usbi_info(ctx, "The following device has no driver: '%s'", dev_id); + usbi_info(ctx, "libusb will not be able to access it"); + } + // ...and to add the additional device interface GUIDs + key = pSetupDiOpenDevRegKey(*dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (key == INVALID_HANDLE_VALUE) + break; + // Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order + // If multiple GUIDs just process the first and ignore the others + size = sizeof(guid_string); + s = pRegQueryValueExA(key, "DeviceInterfaceGUIDs", NULL, ®_type, + (LPBYTE)guid_string, &size); + if (s == ERROR_FILE_NOT_FOUND) + s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, ®_type, + (LPBYTE)guid_string, &size); + pRegCloseKey(key); + if (s == ERROR_FILE_NOT_FOUND) { + break; /* no DeviceInterfaceGUID registered */ + } else if (s != ERROR_SUCCESS && s != ERROR_MORE_DATA) { + usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id); + break; + } + // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa#remarks + // - "string may not have been stored with the proper terminating null characters" + // - "Note that REG_MULTI_SZ strings could have two terminating null characters" + if ((reg_type == REG_SZ && size >= sizeof(guid_string) - sizeof(char)) + || (reg_type == REG_MULTI_SZ && size >= sizeof(guid_string) - 2 * sizeof(char))) { + if (nb_guids == guid_size) { + new_guid_list = realloc((void *)guid_list, (guid_size + GUID_SIZE_STEP) * sizeof(void *)); + if (new_guid_list == NULL) { + usbi_err(ctx, "failed to realloc guid list"); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + guid_list = new_guid_list; + guid_size += GUID_SIZE_STEP; + } + if_guid = malloc(sizeof(*if_guid)); + if (if_guid == NULL) { + usbi_err(ctx, "failed to alloc if_guid"); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + if (!string_to_guid(guid_string, if_guid)) { + usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string '%s', skipping", dev_id, guid_string); + free(if_guid); + } else { + // Check if we've already seen this GUID + for (j = EXT_PASS; j < nb_guids; j++) { + if (memcmp(guid_list[j], if_guid, sizeof(*if_guid)) == 0) + break; + } + if (j == nb_guids) { + usbi_dbg(ctx, "extra GUID: %s", guid_string); + guid_list[nb_guids++] = if_guid; + } else { + // Duplicate, ignore + free(if_guid); + } + } + } else { + usbi_warn(ctx, "unexpected type/size of DeviceInterfaceGUID for '%s'", dev_id); + } + break; + case HID_PASS: + api = USB_API_HID; + break; + default: + // Get the API type (after checking that the driver installation is OK) + if ((!pSetupDiGetDeviceRegistryPropertyA(*dev_info, &dev_info_data, SPDRP_INSTALL_STATE, + NULL, (PBYTE)&install_state, sizeof(install_state), &size)) || (size != sizeof(install_state))) { + usbi_warn(ctx, "could not detect installation state of driver for '%s': %s", + dev_id, windows_error_str(0)); + } else if (install_state != 0) { + usbi_warn(ctx, "driver for device '%s' is reporting an issue (code: %lu) - skipping", + dev_id, ULONG_CAST(install_state)); + continue; + } + get_api_type(dev_info, &dev_info_data, &api, &sub_api); + break; + } + + // Find parent device (for the passes that need it) + if (pass >= GEN_PASS) { + parent_dev = get_ancestor(ctx, dev_info_data.DevInst, NULL); + if (parent_dev == NULL) { + // Root hubs will not have a parent + dev = usbi_get_device_by_session_id(ctx, (unsigned long)dev_info_data.DevInst); + if (dev != NULL) { + priv = usbi_get_device_priv(dev); + if (priv->root_hub) + goto track_unref; + libusb_unref_device(dev); + } + + usbi_dbg(ctx, "unlisted ancestor for '%s' (non USB HID, newly connected, etc.) - ignoring", dev_id); + continue; + } + + parent_priv = usbi_get_device_priv(parent_dev); + // virtual USB devices are also listed during GEN - don't process these yet + if ((pass == GEN_PASS) && (parent_priv->apib->id != USB_API_HUB)) { + libusb_unref_device(parent_dev); + continue; + } + } + + // Create new or match existing device, using the devInst as session id + if ((pass <= GEN_PASS) && (pass != HCD_PASS)) { // For subsequent passes, we'll lookup the parent + // These are the passes that create "new" devices + session_id = (unsigned long)dev_info_data.DevInst; + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev == NULL) { + alloc_device: + usbi_dbg(ctx, "allocating new device for session [%lX]", session_id); + dev = usbi_alloc_device(ctx, session_id); + if (dev == NULL) + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + + priv = winusb_device_priv_init(dev); + priv->dev_id = _strdup(dev_id); + priv->class_guid = dev_info_data.ClassGuid; + if (priv->dev_id == NULL) { + libusb_unref_device(dev); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + } else { + usbi_dbg(ctx, "found existing device for session [%lX]", session_id); + + priv = usbi_get_device_priv(dev); + if (strcmp(priv->dev_id, dev_id) != 0) { + usbi_dbg(ctx, "device instance ID for session [%lX] changed", session_id); + usbi_disconnect_device(dev); + libusb_unref_device(dev); + goto alloc_device; + } + if (!IsEqualGUID(&priv->class_guid, &dev_info_data.ClassGuid)) { + usbi_dbg(ctx, "device class GUID for session [%lX] changed", session_id); + usbi_disconnect_device(dev); + libusb_unref_device(dev); + goto alloc_device; + } + } + + track_unref: + // Keep track of devices that need unref + if (unref_cur == unref_size) { + new_unref_list = realloc(unref_list, (unref_size + UNREF_SIZE_STEP) * sizeof(void *)); + if (new_unref_list == NULL) { + usbi_err(ctx, "could not realloc list for unref - aborting"); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + unref_list = new_unref_list; + unref_size += UNREF_SIZE_STEP; + } + unref_list[unref_cur++] = dev; + } + + // Setup device + switch (pass) { + case HUB_PASS: + case DEV_PASS: + // If the device has already been setup, don't do it again + if (priv->path != NULL) + break; + // Take care of API initialization + priv->path = dev_interface_path; + dev_interface_path = NULL; + priv->apib = &usb_api_backend[api]; + priv->sub_api = sub_api; + switch (api) { + case USB_API_COMPOSITE: + case USB_API_HUB: + break; + case USB_API_HID: + priv->hid = calloc(1, sizeof(struct hid_device_priv)); + if (priv->hid == NULL) + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + break; + default: + // For other devices, the first interface is the same as the device + priv->usb_interface[0].path = _strdup(priv->path); + if (priv->usb_interface[0].path == NULL) + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + // The following is needed if we want API calls to work for both simple + // and composite devices. + for (j = 0; j < USB_MAXINTERFACES; j++) + priv->usb_interface[j].apib = &usb_api_backend[api]; + break; + } + break; + case HCD_PASS: + r = enumerate_hcd_root_hub(ctx, dev_id, (uint8_t)(i + 1), dev_info_data.DevInst); + break; + case GEN_PASS: + port_nr = 0; + if (!get_dev_port_number(*dev_info, &dev_info_data, &port_nr)) + usbi_warn(ctx, "could not retrieve port number for device '%s': %s", dev_id, windows_error_str(0)); + r = init_device(dev, parent_dev, (uint8_t)port_nr, dev_info_data.DevInst); + if (r == LIBUSB_SUCCESS) { + // Append device to the list of discovered devices + discdevs = discovered_devs_append(*_discdevs, dev); + if (!discdevs) + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + + *_discdevs = discdevs; + } else { + // Failed to initialize a single device doesn't stop us from enumerating all other devices, + // but we skip it (don't add to list of discovered devices) + usbi_warn(ctx, "failed to initialize device '%s'", priv->dev_id); + r = LIBUSB_SUCCESS; + } + break; + default: // HID_PASS and later + if (parent_priv->apib->id == USB_API_HID || parent_priv->apib->id == USB_API_COMPOSITE) { + if (parent_priv->apib->id == USB_API_HID) { + usbi_dbg(ctx, "setting HID interface for [%lX]:", parent_dev->session_data); + r = set_hid_interface(ctx, parent_dev, dev_interface_path); + } else { + usbi_dbg(ctx, "setting composite interface for [%lX]:", parent_dev->session_data); + r = set_composite_interface(ctx, parent_dev, dev_interface_path, dev_id, api, sub_api); + } + switch (r) { + case LIBUSB_SUCCESS: + dev_interface_path = NULL; + break; + case LIBUSB_ERROR_ACCESS: + // interface has already been set => make sure dev_interface_path is freed then + r = LIBUSB_SUCCESS; + break; + default: + LOOP_BREAK(r); + break; + } + } + libusb_unref_device(parent_dev); + break; + } + } + } + + pSetupDiDestroyDeviceInfoList(dev_info_intf); + + // Free any additional GUIDs + for (pass = EXT_PASS; pass < nb_guids; pass++) + free((void *)guid_list[pass]); + free((void *)guid_list); + + // Free any PnP enumerator strings + for (i = 1; i < nb_usb_enumerators; i++) + free((void *)usb_enumerator[i]); + + // Unref newly allocated devs + for (i = 0; i < unref_cur; i++) + libusb_unref_device(unref_list[i]); + free(unref_list); + + return r; +} + +static int winusb_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + PUSB_CONFIGURATION_DESCRIPTOR config_header; + + if ((priv->config_descriptor == NULL) || (priv->config_descriptor[config_index] == NULL)) + return LIBUSB_ERROR_NOT_FOUND; + + config_header = priv->config_descriptor[config_index]; + + len = MIN(len, config_header->wTotalLength); + memcpy(buffer, config_header, len); + return (int)len; +} + +static int winusb_get_config_descriptor_by_value(struct libusb_device *dev, uint8_t bConfigurationValue, + void **buffer) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + PUSB_CONFIGURATION_DESCRIPTOR config_header; + uint8_t index; + + if (priv->config_descriptor == NULL) + return LIBUSB_ERROR_NOT_FOUND; + + for (index = 0; index < dev->device_descriptor.bNumConfigurations; index++) { + config_header = priv->config_descriptor[index]; + if (config_header == NULL) + continue; + if (config_header->bConfigurationValue == bConfigurationValue) { + *buffer = config_header; + return (int)config_header->wTotalLength; + } + } + + return LIBUSB_ERROR_NOT_FOUND; +} + +/* + * return the cached copy of the active config descriptor + */ +static int winusb_get_active_config_descriptor(struct libusb_device *dev, void *buffer, size_t len) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + void *config_desc; + int r; + + if (priv->active_config == 0) + return LIBUSB_ERROR_NOT_FOUND; + + r = winusb_get_config_descriptor_by_value(dev, priv->active_config, &config_desc); + if (r < 0) + return r; + + len = MIN(len, (size_t)r); + memcpy(buffer, config_desc, len); + return (int)len; +} + +static int winusb_open(struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + CHECK_SUPPORTED_API(priv->apib, open); + + return priv->apib->open(SUB_API_NOTSET, dev_handle); +} + +static void winusb_close(struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + if (priv->apib->close) + priv->apib->close(SUB_API_NOTSET, dev_handle); +} + +static int winusb_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + *config = priv->active_config; + return LIBUSB_SUCCESS; +} + +/* + * from http://msdn.microsoft.com/en-us/library/ms793522.aspx: "The port driver + * does not currently expose a service that allows higher-level drivers to set + * the configuration." + */ +static int winusb_set_configuration(struct libusb_device_handle *dev_handle, uint8_t config) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int r = LIBUSB_SUCCESS; + + r = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE, + LIBUSB_REQUEST_SET_CONFIGURATION, config, + 0, NULL, 0, 1000); + + if (r == LIBUSB_SUCCESS) + priv->active_config = config; + + return r; +} + +static int winusb_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int r; + + CHECK_SUPPORTED_API(priv->apib, claim_interface); + + safe_free(priv->usb_interface[iface].endpoint); + priv->usb_interface[iface].nb_endpoints = 0; + + r = priv->apib->claim_interface(SUB_API_NOTSET, dev_handle, iface); + + if (r == LIBUSB_SUCCESS) + r = windows_assign_endpoints(dev_handle, iface, 0); + + return r; +} + +static int winusb_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int r; + + CHECK_SUPPORTED_API(priv->apib, set_interface_altsetting); + + safe_free(priv->usb_interface[iface].endpoint); + priv->usb_interface[iface].nb_endpoints = 0; + + r = priv->apib->set_interface_altsetting(SUB_API_NOTSET, dev_handle, iface, altsetting); + + if (r == LIBUSB_SUCCESS) + r = windows_assign_endpoints(dev_handle, iface, altsetting); + + return r; +} + +static int winusb_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + CHECK_SUPPORTED_API(priv->apib, release_interface); + + return priv->apib->release_interface(SUB_API_NOTSET, dev_handle, iface); +} + +static int winusb_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + CHECK_SUPPORTED_API(priv->apib, clear_halt); + + return priv->apib->clear_halt(SUB_API_NOTSET, dev_handle, endpoint); +} + +static int winusb_reset_device(struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + CHECK_SUPPORTED_API(priv->apib, reset_device); + + return priv->apib->reset_device(SUB_API_NOTSET, dev_handle); +} + +static void winusb_destroy_device(struct libusb_device *dev) +{ + winusb_device_priv_release(dev); +} + +static void winusb_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int sub_api = priv->sub_api; + + safe_free(transfer_priv->hid_buffer); + + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS && sub_api == SUB_API_WINUSB) { + if (transfer_priv->isoch_buffer_handle != NULL) { + if (WinUSBX[sub_api].UnregisterIsochBuffer(transfer_priv->isoch_buffer_handle)) { + transfer_priv->isoch_buffer_handle = NULL; + } else { + usbi_warn(TRANSFER_CTX(transfer), "failed to unregister WinUSB isoch buffer: %s", windows_error_str(0)); + } + } + } + + safe_free(transfer_priv->iso_context); + + // When auto claim is in use, attempt to release the auto-claimed interface + auto_release(itransfer); +} + +static int winusb_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int (*transfer_fn)(int, struct usbi_transfer *); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + transfer_fn = priv->apib->submit_control_transfer; + break; + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + transfer_fn = priv->apib->submit_bulk_transfer; + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + transfer_fn = priv->apib->submit_iso_transfer; + break; + default: + // Should not get here since windows_submit_transfer() validates + // the transfer->type field + usbi_err(TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (transfer_fn == NULL) { + usbi_warn(TRANSFER_CTX(transfer), + "unsupported transfer type %d (unrecognized device driver)", + transfer->type); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + return transfer_fn(SUB_API_NOTSET, itransfer); +} + +static int winusb_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + + CHECK_SUPPORTED_API(priv->apib, cancel_transfer); + + return priv->apib->cancel_transfer(SUB_API_NOTSET, itransfer); +} + +static enum libusb_transfer_status winusb_copy_transfer_data(struct usbi_transfer *itransfer, DWORD length) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + + if (priv->apib->copy_transfer_data == NULL) { + usbi_err(TRANSFER_CTX(transfer), "program assertion failed - no function to copy transfer data"); + return LIBUSB_TRANSFER_ERROR; + } + + return priv->apib->copy_transfer_data(SUB_API_NOTSET, itransfer, length); +} + +// NB: MSVC6 does not support named initializers. +const struct windows_backend winusb_backend = { + winusb_init, + winusb_exit, + winusb_get_device_list, + winusb_open, + winusb_close, + winusb_get_active_config_descriptor, + winusb_get_config_descriptor, + winusb_get_config_descriptor_by_value, + winusb_get_configuration, + winusb_set_configuration, + winusb_claim_interface, + winusb_release_interface, + winusb_set_interface_altsetting, + winusb_clear_halt, + winusb_reset_device, + winusb_destroy_device, + winusb_submit_transfer, + winusb_cancel_transfer, + winusb_clear_transfer_priv, + winusb_copy_transfer_data, +}; + +/* + * USB API backends + */ + +static const char * const composite_driver_names[] = {"USBCCGP"}; +static const char * const winusbx_driver_names[] = {"libusbK", "libusb0", "WinUSB"}; +static const char * const hid_driver_names[] = {"HIDUSB", "MOUHID", "KBDHID"}; +const struct windows_usb_api_backend usb_api_backend[USB_API_MAX] = { + { + USB_API_UNSUPPORTED, + "Unsupported API", + NULL, /* driver_name_list */ + 0, /* nb_driver_names */ + NULL, /* init */ + NULL, /* exit */ + NULL, /* open */ + NULL, /* close */ + NULL, /* configure_endpoints */ + NULL, /* claim_interface */ + NULL, /* set_interface_altsetting */ + NULL, /* release_interface */ + NULL, /* clear_halt */ + NULL, /* reset_device */ + NULL, /* submit_bulk_transfer */ + NULL, /* submit_iso_transfer */ + NULL, /* submit_control_transfer */ + NULL, /* cancel_transfer */ + NULL, /* copy_transfer_data */ + }, + { + USB_API_HUB, + "HUB API", + NULL, /* driver_name_list */ + 0, /* nb_driver_names */ + NULL, /* init */ + NULL, /* exit */ + NULL, /* open */ + NULL, /* close */ + NULL, /* configure_endpoints */ + NULL, /* claim_interface */ + NULL, /* set_interface_altsetting */ + NULL, /* release_interface */ + NULL, /* clear_halt */ + NULL, /* reset_device */ + NULL, /* submit_bulk_transfer */ + NULL, /* submit_iso_transfer */ + NULL, /* submit_control_transfer */ + NULL, /* cancel_transfer */ + NULL, /* copy_transfer_data */ + }, + { + USB_API_COMPOSITE, + "Composite API", + composite_driver_names, + ARRAYSIZE(composite_driver_names), + NULL, /* init */ + NULL, /* exit */ + composite_open, + composite_close, + NULL, /* configure_endpoints */ + composite_claim_interface, + composite_set_interface_altsetting, + composite_release_interface, + composite_clear_halt, + composite_reset_device, + composite_submit_bulk_transfer, + composite_submit_iso_transfer, + composite_submit_control_transfer, + composite_cancel_transfer, + composite_copy_transfer_data, + }, + { + USB_API_WINUSBX, + "WinUSB-like APIs", + winusbx_driver_names, + ARRAYSIZE(winusbx_driver_names), + winusbx_init, + winusbx_exit, + winusbx_open, + winusbx_close, + winusbx_configure_endpoints, + winusbx_claim_interface, + winusbx_set_interface_altsetting, + winusbx_release_interface, + winusbx_clear_halt, + winusbx_reset_device, + winusbx_submit_bulk_transfer, + winusbx_submit_iso_transfer, + winusbx_submit_control_transfer, + winusbx_cancel_transfer, + winusbx_copy_transfer_data, + }, + { + USB_API_HID, + "HID API", + hid_driver_names, + ARRAYSIZE(hid_driver_names), + hid_init, + hid_exit, + hid_open, + hid_close, + NULL, /* configure_endpoints */ + hid_claim_interface, + hid_set_interface_altsetting, + hid_release_interface, + hid_clear_halt, + hid_reset_device, + hid_submit_bulk_transfer, + NULL, /* submit_iso_transfer */ + hid_submit_control_transfer, + NULL, /* cancel_transfer */ + hid_copy_transfer_data, + }, +}; + + +/* + * WinUSB-like (WinUSB, libusb0/libusbK through libusbk DLL) API functions + */ +#define WinUSB_Set(h, fn, required) \ + do { \ + WinUSBX[SUB_API_WINUSB].fn = (WinUsb_##fn##_t)GetProcAddress(h, "WinUsb_" #fn); \ + if (required && (WinUSBX[SUB_API_WINUSB].fn == NULL)) { \ + usbi_err(ctx, "GetProcAddress() failed for WinUsb_%s", #fn); \ + goto cleanup_winusb; \ + } \ + } while (0) + +#define libusbK_Set(sub_api, fn, required) \ + do { \ + pLibK_GetProcAddress((PVOID *)&WinUSBX[sub_api].fn, sub_api, KUSB_FNID_##fn); \ + if (required && (WinUSBX[sub_api].fn == NULL)) { \ + usbi_err(ctx, "LibK_GetProcAddress() failed for LibK_%s", #fn); \ + goto cleanup_libusbk; \ + } \ + } while (0) + +static bool winusbx_init(struct libusb_context *ctx) +{ + HMODULE hWinUSB, hlibusbK; + + hWinUSB = load_system_library(ctx, "WinUSB"); + if (hWinUSB != NULL) { + WinUSB_Set(hWinUSB, AbortPipe, true); + WinUSB_Set(hWinUSB, ControlTransfer, true); + WinUSB_Set(hWinUSB, FlushPipe, true); + WinUSB_Set(hWinUSB, Free, true); + WinUSB_Set(hWinUSB, GetAssociatedInterface, true); + WinUSB_Set(hWinUSB, Initialize, true); + WinUSB_Set(hWinUSB, ReadPipe, true); + WinUSB_Set(hWinUSB, ResetPipe, true); + WinUSB_Set(hWinUSB, SetCurrentAlternateSetting, true); + WinUSB_Set(hWinUSB, SetPipePolicy, true); + WinUSB_Set(hWinUSB, WritePipe, true); + + // Check for isochronous transfers support (available starting with Windows 8.1) + WinUSB_Set(hWinUSB, ReadIsochPipeAsap, false); + if (WinUSBX[SUB_API_WINUSB].ReadIsochPipeAsap != NULL) { + WinUSB_Set(hWinUSB, QueryPipeEx, true); + WinUSB_Set(hWinUSB, RegisterIsochBuffer, true); + WinUSB_Set(hWinUSB, UnregisterIsochBuffer, true); + WinUSB_Set(hWinUSB, WriteIsochPipeAsap, true); + } + + WinUSBX[SUB_API_WINUSB].hDll = hWinUSB; + + usbi_info(ctx, "WinUSB DLL available (%s isoch support)", + (WinUSBX[SUB_API_WINUSB].ReadIsochPipeAsap != NULL) ? "with" : "without"); + +cleanup_winusb: + if (WinUSBX[SUB_API_WINUSB].hDll == NULL) { + usbi_err(ctx, "failed to initialize WinUSB"); + memset(&WinUSBX[SUB_API_WINUSB], 0, sizeof(WinUSBX[SUB_API_WINUSB])); + FreeLibrary(hWinUSB); + hWinUSB = NULL; + } + } else { + usbi_info(ctx, "WinUSB DLL is not available"); + } + + hlibusbK = load_system_library(ctx, "libusbK"); + if (hlibusbK != NULL) { + LibK_GetVersion_t pLibK_GetVersion; + LibK_GetProcAddress_t pLibK_GetProcAddress; + int sub_api = 0; + + pLibK_GetVersion = (LibK_GetVersion_t)GetProcAddress(hlibusbK, "LibK_GetVersion"); + if (pLibK_GetVersion != NULL) { + KLIB_VERSION LibK_Version; + + pLibK_GetVersion(&LibK_Version); + usbi_dbg(ctx, "libusbK DLL found, version: %d.%d.%d.%d", LibK_Version.Major, LibK_Version.Minor, + LibK_Version.Micro, LibK_Version.Nano); + } else { + usbi_dbg(ctx, "libusbK DLL found, version unknown"); + } + + pLibK_GetProcAddress = (LibK_GetProcAddress_t)GetProcAddress(hlibusbK, "LibK_GetProcAddress"); + if (pLibK_GetProcAddress == NULL) { + usbi_err(ctx, "LibK_GetProcAddress() not found in libusbK DLL"); + goto cleanup_libusbk; + } + + // NB: The below for loop works because the sub_api value for WinUSB + // is a higher value than that of libusbK and libusb0 + for (; sub_api < SUB_API_WINUSB; sub_api++) { + libusbK_Set(sub_api, AbortPipe, true); + libusbK_Set(sub_api, ControlTransfer, true); + libusbK_Set(sub_api, FlushPipe, true); + libusbK_Set(sub_api, Free, true); + libusbK_Set(sub_api, GetAssociatedInterface, true); + libusbK_Set(sub_api, Initialize, true); + libusbK_Set(sub_api, ReadPipe, true); + libusbK_Set(sub_api, ResetPipe, true); + libusbK_Set(sub_api, SetCurrentAlternateSetting, true); + libusbK_Set(sub_api, SetPipePolicy, true); + libusbK_Set(sub_api, WritePipe, true); + + // Optional isochronous support + libusbK_Set(sub_api, IsoReadPipe, false); + if (WinUSBX[sub_api].IsoReadPipe != NULL) + libusbK_Set(sub_api, IsoWritePipe, true); + + // Optional device reset support + libusbK_Set(sub_api, ResetDevice, false); + + WinUSBX[sub_api].hDll = hlibusbK; + } + +cleanup_libusbk: + if (sub_api < SUB_API_WINUSB) { + usbi_err(ctx, "failed to initialize libusbK"); + while (sub_api >= 0) { + memset(&WinUSBX[sub_api], 0, sizeof(WinUSBX[sub_api])); + sub_api--; + } + FreeLibrary(hlibusbK); + hlibusbK = NULL; + } + } else { + usbi_info(ctx, "libusbK DLL is not available"); + } + + if ((hWinUSB == NULL) && (hlibusbK == NULL)) { + usbi_warn(ctx, "neither WinUSB nor libusbK DLLs were found, " + "you will not be able to access devices outside of enumeration"); + return false; + } + + return true; +} + +static void winusbx_exit(void) +{ + bool loaded = false; + HMODULE hDll; + + hDll = WinUSBX[SUB_API_LIBUSBK].hDll; + if (hDll != NULL) { + FreeLibrary(hDll); + loaded = true; + } + + hDll = WinUSBX[SUB_API_WINUSB].hDll; + if (hDll != NULL) { + FreeLibrary(hDll); + loaded = true; + } + + // Reset the WinUSBX API structures if something was loaded + if (loaded) + memset(&WinUSBX, 0, sizeof(WinUSBX)); +} + +// NB: open and close must ensure that they only handle interface of +// the right API type, as these functions can be called wholesale from +// composite_open(), with interfaces belonging to different APIs +static int winusbx_open(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + HANDLE file_handle; + int i; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + // WinUSB requires a separate handle for each interface + for (i = 0; i < USB_MAXINTERFACES; i++) { + if ((priv->usb_interface[i].path != NULL) + && (priv->usb_interface[i].apib->id == USB_API_WINUSBX)) { + file_handle = windows_open(dev_handle, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE); + if (file_handle == INVALID_HANDLE_VALUE) { + usbi_err(HANDLE_CTX(dev_handle), "could not open device %s (interface %d): %s", priv->usb_interface[i].path, i, windows_error_str(0)); + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: // The device was disconnected + return LIBUSB_ERROR_NO_DEVICE; + case ERROR_ACCESS_DENIED: + return LIBUSB_ERROR_ACCESS; + default: + return LIBUSB_ERROR_IO; + } + } + + handle_priv->interface_handle[i].dev_handle = file_handle; + } + } + + return LIBUSB_SUCCESS; +} + +static void winusbx_close(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE handle; + int i; + + if (sub_api == SUB_API_NOTSET) + sub_api = priv->sub_api; + + if (WinUSBX[sub_api].hDll == NULL) + return; + + if (priv->apib->id == USB_API_COMPOSITE) { + // If this is a composite device, just free and close all WinUSB-like + // interfaces directly (each is independent and not associated with another) + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (priv->usb_interface[i].apib->id == USB_API_WINUSBX) { + handle = handle_priv->interface_handle[i].api_handle; + if (HANDLE_VALID(handle)) + WinUSBX[sub_api].Free(handle); + + handle = handle_priv->interface_handle[i].dev_handle; + if (HANDLE_VALID(handle)) + CloseHandle(handle); + } + } + } else { + // If this is a WinUSB device, free all interfaces above interface 0, + // then free and close interface 0 last + for (i = 1; i < USB_MAXINTERFACES; i++) { + handle = handle_priv->interface_handle[i].api_handle; + if (HANDLE_VALID(handle)) + WinUSBX[sub_api].Free(handle); + } + handle = handle_priv->interface_handle[0].api_handle; + if (HANDLE_VALID(handle)) + WinUSBX[sub_api].Free(handle); + + handle = handle_priv->interface_handle[0].dev_handle; + if (HANDLE_VALID(handle)) + CloseHandle(handle); + } +} + +static int winusbx_configure_endpoints(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE winusb_handle = handle_priv->interface_handle[iface].api_handle; + UCHAR policy; + ULONG timeout = 0; + uint8_t endpoint_address; + int i; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + // With handle and endpoints set (in parent), we can setup the default pipe properties + // see http://download.microsoft.com/download/D/1/D/D1DD7745-426B-4CC3-A269-ABBBE427C0EF/DVC-T705_DDC08.pptx + for (i = -1; i < priv->usb_interface[iface].nb_endpoints; i++) { + endpoint_address = (i == -1) ? 0 : priv->usb_interface[iface].endpoint[i]; + if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address, + PIPE_TRANSFER_TIMEOUT, sizeof(ULONG), &timeout)) + usbi_dbg(HANDLE_CTX(dev_handle), "failed to set PIPE_TRANSFER_TIMEOUT for control endpoint %02X", endpoint_address); + + if ((i == -1) || (sub_api == SUB_API_LIBUSB0)) + continue; // Other policies don't apply to control endpoint or libusb0 + + policy = false; + handle_priv->interface_handle[iface].zlp[endpoint_address] = WINUSB_ZLP_UNSET; + if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address, + SHORT_PACKET_TERMINATE, sizeof(UCHAR), &policy)) + usbi_dbg(HANDLE_CTX(dev_handle), "failed to disable SHORT_PACKET_TERMINATE for endpoint %02X", endpoint_address); + + if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address, + IGNORE_SHORT_PACKETS, sizeof(UCHAR), &policy)) + usbi_dbg(HANDLE_CTX(dev_handle), "failed to disable IGNORE_SHORT_PACKETS for endpoint %02X", endpoint_address); + + policy = true; + /* ALLOW_PARTIAL_READS must be enabled due to likely libusbK bug. See: + https://sourceforge.net/mailarchive/message.php?msg_id=29736015 */ + if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address, + ALLOW_PARTIAL_READS, sizeof(UCHAR), &policy)) + usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable ALLOW_PARTIAL_READS for endpoint %02X", endpoint_address); + + if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address, + AUTO_CLEAR_STALL, sizeof(UCHAR), &policy)) + usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable AUTO_CLEAR_STALL for endpoint %02X", endpoint_address); + + if (sub_api == SUB_API_LIBUSBK) { + if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address, + ISO_ALWAYS_START_ASAP, sizeof(UCHAR), &policy)) + usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable ISO_ALWAYS_START_ASAP for endpoint %02X", endpoint_address); + } + } + + return LIBUSB_SUCCESS; +} + +static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + bool is_using_usbccgp = (priv->apib->id == USB_API_COMPOSITE); + HDEVINFO dev_info; + char *dev_interface_path = NULL; + char *dev_interface_path_guid_start; + char filter_path[] = "\\\\.\\libusb0-0000"; + bool found_filter = false; + HANDLE file_handle, winusb_handle; + DWORD err, _index; + int r; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + // If the device is composite, but using the default Windows composite parent driver (usbccgp) + // or if it's the first WinUSB-like interface, we get a handle through Initialize(). + if ((is_using_usbccgp) || (iface == 0)) { + // composite device (independent interfaces) or interface 0 + file_handle = handle_priv->interface_handle[iface].dev_handle; + if (!HANDLE_VALID(file_handle)) + return LIBUSB_ERROR_NOT_FOUND; + + if (!WinUSBX[sub_api].Initialize(file_handle, &winusb_handle)) { + handle_priv->interface_handle[iface].api_handle = INVALID_HANDLE_VALUE; + err = GetLastError(); + switch (err) { + case ERROR_BAD_COMMAND: + // The device was disconnected + usbi_err(ctx, "could not access interface %u: %s", iface, windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + default: + // it may be that we're using the libusb0 filter driver. + // TODO: can we move this whole business into the K/0 DLL? + r = LIBUSB_SUCCESS; + for (_index = 0; ; _index++) { + safe_free(dev_interface_path); + + if (found_filter) + break; + + r = get_interface_details_filter(ctx, &dev_info, _index, filter_path, &dev_interface_path); + if ((r != LIBUSB_SUCCESS) || (dev_interface_path == NULL)) + break; + + // ignore GUID part + dev_interface_path_guid_start = strchr(dev_interface_path, '{'); + if (dev_interface_path_guid_start == NULL) + continue; + *dev_interface_path_guid_start = '\0'; + + if (strncmp(dev_interface_path, priv->usb_interface[iface].path, strlen(dev_interface_path)) == 0) { + file_handle = windows_open(dev_handle, filter_path, GENERIC_READ | GENERIC_WRITE); + if (file_handle != INVALID_HANDLE_VALUE) { + if (WinUSBX[sub_api].Initialize(file_handle, &winusb_handle)) { + // Replace the existing file handle with the working one + CloseHandle(handle_priv->interface_handle[iface].dev_handle); + handle_priv->interface_handle[iface].dev_handle = file_handle; + found_filter = true; + } else { + usbi_err(ctx, "could not initialize filter driver for %s", filter_path); + CloseHandle(file_handle); + } + } else { + usbi_err(ctx, "could not open device %s: %s", filter_path, windows_error_str(0)); + } + } + } + if (r != LIBUSB_SUCCESS) + return r; + if (!found_filter) { + usbi_err(ctx, "could not access interface %u: %s", iface, windows_error_str(err)); + return LIBUSB_ERROR_ACCESS; + } + } + } + handle_priv->interface_handle[iface].api_handle = winusb_handle; + } else { + // For all other interfaces, use GetAssociatedInterface() + winusb_handle = handle_priv->interface_handle[0].api_handle; + // It is a requirement for multiple interface devices on Windows that, to you + // must first claim the first interface before you claim the others + if (!HANDLE_VALID(winusb_handle)) { + file_handle = handle_priv->interface_handle[0].dev_handle; + if (WinUSBX[sub_api].Initialize(file_handle, &winusb_handle)) { + handle_priv->interface_handle[0].api_handle = winusb_handle; + usbi_warn(ctx, "auto-claimed interface 0 (required to claim %u with WinUSB)", iface); + } else { + usbi_warn(ctx, "failed to auto-claim interface 0 (required to claim %u with WinUSB): %s", iface, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + } + if (!WinUSBX[sub_api].GetAssociatedInterface(winusb_handle, (UCHAR)(iface - 1), + &handle_priv->interface_handle[iface].api_handle)) { + handle_priv->interface_handle[iface].api_handle = INVALID_HANDLE_VALUE; + switch (GetLastError()) { + case ERROR_NO_MORE_ITEMS: // invalid iface + return LIBUSB_ERROR_NOT_FOUND; + case ERROR_BAD_COMMAND: // The device was disconnected + return LIBUSB_ERROR_NO_DEVICE; + case ERROR_ALREADY_EXISTS: // already claimed + return LIBUSB_ERROR_BUSY; + default: + usbi_err(ctx, "could not claim interface %u: %s", iface, windows_error_str(0)); + return LIBUSB_ERROR_ACCESS; + } + } + handle_priv->interface_handle[iface].dev_handle = handle_priv->interface_handle[0].dev_handle; + } + usbi_dbg(ctx, "claimed interface %u", iface); + handle_priv->active_interface = iface; + + return LIBUSB_SUCCESS; +} + +static int winusbx_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE winusb_handle; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + winusb_handle = handle_priv->interface_handle[iface].api_handle; + if (!HANDLE_VALID(winusb_handle)) + return LIBUSB_ERROR_NOT_FOUND; + + WinUSBX[sub_api].Free(winusb_handle); + handle_priv->interface_handle[iface].api_handle = INVALID_HANDLE_VALUE; + + return LIBUSB_SUCCESS; +} + +/* + * Return the first valid interface (of the same API type), for control transfers + */ +static int get_valid_interface(struct libusb_device_handle *dev_handle, int api_id) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int i; + + if ((api_id < USB_API_WINUSBX) || (api_id > USB_API_HID)) { + usbi_dbg(HANDLE_CTX(dev_handle), "unsupported API ID"); + return -1; + } + + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (HANDLE_VALID(handle_priv->interface_handle[i].dev_handle) + && HANDLE_VALID(handle_priv->interface_handle[i].api_handle) + && (priv->usb_interface[i].apib->id == api_id)) + return i; + } + + return -1; +} + +/* +* Check a specific interface is valid (of the same API type), for control transfers +*/ +static int check_valid_interface(struct libusb_device_handle *dev_handle, unsigned short interface, int api_id) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + if (interface >= USB_MAXINTERFACES) + return -1; + + if ((api_id < USB_API_WINUSBX) || (api_id > USB_API_HID)) { + usbi_dbg(HANDLE_CTX(dev_handle), "unsupported API ID"); + return -1; + } + + // try the requested interface + if (HANDLE_VALID(handle_priv->interface_handle[interface].dev_handle) + && HANDLE_VALID(handle_priv->interface_handle[interface].api_handle) + && (priv->usb_interface[interface].apib->id == api_id)) + return interface; + + return -1; +} + +/* + * Lookup interface by endpoint address. -1 if not found + */ +static int interface_by_endpoint(struct winusb_device_priv *priv, + struct winusb_device_handle_priv *handle_priv, uint8_t endpoint_address) +{ + int i, j; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (!HANDLE_VALID(handle_priv->interface_handle[i].api_handle)) + continue; + if (priv->usb_interface[i].endpoint == NULL) + continue; + for (j = 0; j < priv->usb_interface[i].nb_endpoints; j++) { + if (priv->usb_interface[i].endpoint[j] == endpoint_address) + return i; + } + } + + return -1; +} + +static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + PWINUSB_SETUP_PACKET setup = (PWINUSB_SETUP_PACKET)transfer->buffer; + ULONG size, transferred; + HANDLE winusb_handle; + OVERLAPPED *overlapped; + int current_interface; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + size = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; + + // Windows places upper limits on the control transfer size + // See: https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-bandwidth-allocation#maximum-transfer-size + if (size > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + if ((setup->RequestType & 0x1F) == LIBUSB_RECIPIENT_INTERFACE) + current_interface = check_valid_interface(transfer->dev_handle, setup->Index & 0xff, USB_API_WINUSBX); + else + current_interface = get_valid_interface(transfer->dev_handle, USB_API_WINUSBX); + if (current_interface < 0) { + if (auto_claim(transfer, ¤t_interface, USB_API_WINUSBX) != LIBUSB_SUCCESS) + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(ITRANSFER_CTX(itransfer), "will use interface %d", current_interface); + + transfer_priv->interface_number = (uint8_t)current_interface; + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + set_transfer_priv_handle(itransfer, handle_priv->interface_handle[current_interface].dev_handle); + overlapped = get_transfer_priv_overlapped(itransfer); + + // Sending of set configuration control requests from WinUSB creates issues, except when using libusb0.sys + if (sub_api != SUB_API_LIBUSB0 + && (LIBUSB_REQ_TYPE(setup->RequestType) == LIBUSB_REQUEST_TYPE_STANDARD) + && (setup->Request == LIBUSB_REQUEST_SET_CONFIGURATION)) { + if (setup->Value != priv->active_config) { + usbi_warn(TRANSFER_CTX(transfer), "cannot set configuration other than the default one"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + windows_force_sync_completion(itransfer, 0); + } else { + if (!WinUSBX[sub_api].ControlTransfer(winusb_handle, *setup, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, size, &transferred, overlapped)) { + if (GetLastError() != ERROR_IO_PENDING) { + usbi_warn(TRANSFER_CTX(transfer), "ControlTransfer failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + } else { + windows_force_sync_completion(itransfer, transferred); + } + } + + return LIBUSB_SUCCESS; +} + +static int winusbx_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE winusb_handle; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + winusb_handle = handle_priv->interface_handle[iface].api_handle; + if (!HANDLE_VALID(winusb_handle)) { + usbi_err(HANDLE_CTX(dev_handle), "interface must be claimed first"); + return LIBUSB_ERROR_NOT_FOUND; + } + + if (!WinUSBX[sub_api].SetCurrentAlternateSetting(winusb_handle, altsetting)) { + usbi_err(HANDLE_CTX(dev_handle), "SetCurrentAlternateSetting failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + + +static void WINAPI winusbx_native_iso_transfer_continue_stream_callback(struct libusb_transfer *transfer) +{ + // If this callback is invoked, this means that we attempted to set ContinueStream + // to TRUE when calling Read/WriteIsochPipeAsap in winusbx_do_iso_transfer. + // The role of this callback is to fallback to ContinueStream = FALSE if the transfer + // did not succeed. + + struct winusb_transfer_priv *transfer_priv = + get_winusb_transfer_priv(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)); + bool fallback = (transfer->status != LIBUSB_TRANSFER_COMPLETED); + int idx; + + // Restore the user callback + transfer->callback = transfer_priv->iso_user_callback; + + for (idx = 0; idx < transfer->num_iso_packets && !fallback; idx++) { + if (transfer->iso_packet_desc[idx].status != LIBUSB_TRANSFER_COMPLETED) + fallback = true; + } + + if (!fallback) { + // If the transfer was successful, we restore the user callback and call it. + if (transfer->callback) + transfer->callback(transfer); + } else { + // If the transfer wasn't successful we reschedule the transfer while forcing it + // not to continue the stream. This might results in a 5-ms delay. + transfer_priv->iso_break_stream = TRUE; + libusb_submit_transfer(transfer); + } +} +static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + HANDLE winusb_handle; + OVERLAPPED *overlapped; + BOOL ret; + int current_interface; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(TRANSFER_CTX(transfer), "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(TRANSFER_CTX(transfer), "matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + + transfer_priv->interface_number = (uint8_t)current_interface; + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + set_transfer_priv_handle(itransfer, handle_priv->interface_handle[current_interface].dev_handle); + overlapped = get_transfer_priv_overlapped(itransfer); + + if ((sub_api == SUB_API_LIBUSBK) || (sub_api == SUB_API_LIBUSB0)) { + int i; + UINT offset; + size_t iso_ctx_size; + PKISO_CONTEXT iso_context; + + if (WinUSBX[sub_api].IsoReadPipe == NULL) { + usbi_warn(TRANSFER_CTX(transfer), "libusbK DLL does not support isoch transfers"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + iso_ctx_size = sizeof(KISO_CONTEXT) + (transfer->num_iso_packets * sizeof(KISO_PACKET)); + transfer_priv->iso_context = iso_context = calloc(1, iso_ctx_size); + if (transfer_priv->iso_context == NULL) + return LIBUSB_ERROR_NO_MEM; + + // start ASAP + iso_context->StartFrame = 0; + iso_context->NumberOfPackets = (SHORT)transfer->num_iso_packets; + + // convert the transfer packet lengths to iso_packet offsets + offset = 0; + for (i = 0; i < transfer->num_iso_packets; i++) { + iso_context->IsoPackets[i].offset = offset; + offset += transfer->iso_packet_desc[i].length; + } + + if (IS_XFERIN(transfer)) { + usbi_dbg(TRANSFER_CTX(transfer), "reading %d iso packets", transfer->num_iso_packets); + ret = WinUSBX[sub_api].IsoReadPipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context); + } else { + usbi_dbg(TRANSFER_CTX(transfer), "writing %d iso packets", transfer->num_iso_packets); + ret = WinUSBX[sub_api].IsoWritePipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context); + } + + if (!ret && GetLastError() != ERROR_IO_PENDING) { + usbi_err(TRANSFER_CTX(transfer), "IsoReadPipe/IsoWritePipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; + } else if (sub_api == SUB_API_WINUSB) { + WINUSB_PIPE_INFORMATION_EX pipe_info_ex = { 0 }; + WINUSB_ISOCH_BUFFER_HANDLE buffer_handle; + ULONG iso_transfer_size_multiple; + int out_transfer_length = 0; + int idx; + + // Depending on the version of Microsoft WinUSB, isochronous transfers may not be supported. + if (WinUSBX[sub_api].ReadIsochPipeAsap == NULL) { + usbi_warn(TRANSFER_CTX(transfer), "WinUSB DLL does not support isoch transfers"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + if (sizeof(struct libusb_iso_packet_descriptor) != sizeof(USBD_ISO_PACKET_DESCRIPTOR)) { + usbi_err(TRANSFER_CTX(transfer), "size of WinUsb and libusb isoch packet descriptors don't match"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + // Query the pipe extended information to find the pipe index corresponding to the endpoint. + for (idx = 0; idx < priv->usb_interface[current_interface].nb_endpoints; ++idx) { + ret = WinUSBX[sub_api].QueryPipeEx(winusb_handle, (UINT8)priv->usb_interface[current_interface].current_altsetting, (UCHAR)idx, &pipe_info_ex); + if (!ret) { + usbi_err(TRANSFER_CTX(transfer), "couldn't query interface settings for USB pipe with index %d. Error: %s", idx, windows_error_str(0)); + return LIBUSB_ERROR_NOT_FOUND; + } + + if (pipe_info_ex.PipeId == transfer->endpoint && pipe_info_ex.PipeType == UsbdPipeTypeIsochronous) + break; + } + + // Make sure we found the index. + if (idx == priv->usb_interface[current_interface].nb_endpoints) { + usbi_err(TRANSFER_CTX(transfer), "couldn't find isoch endpoint 0x%02x", transfer->endpoint); + return LIBUSB_ERROR_NOT_FOUND; + } + + if (IS_XFERIN(transfer)) { + int interval = pipe_info_ex.Interval; + + // For high-speed and SuperSpeed device, the interval is 2**(bInterval-1). + if (transfer->dev_handle->dev->speed >= LIBUSB_SPEED_HIGH) + interval = (1 << (pipe_info_ex.Interval - 1)); + + // WinUSB only supports isoch transfers spanning a full USB frames. Later, we might be smarter about this + // and allocate a temporary buffer. However, this is harder than it seems as its destruction would depend on overlapped + // IO... + iso_transfer_size_multiple = (pipe_info_ex.MaximumBytesPerInterval * 8) / interval; + if (transfer->length % iso_transfer_size_multiple != 0) { + usbi_err(TRANSFER_CTX(transfer), "length of isoch buffer must be a multiple of the MaximumBytesPerInterval * 8 / Interval"); + return LIBUSB_ERROR_INVALID_PARAM; + } + } else { + // If this is an OUT transfer, we make sure the isoch packets are contiguous as this isn't supported otherwise. + bool size_should_be_zero = false; + + for (idx = 0; idx < transfer->num_iso_packets; ++idx) { + if ((size_should_be_zero && transfer->iso_packet_desc[idx].length != 0) || + (transfer->iso_packet_desc[idx].length != pipe_info_ex.MaximumBytesPerInterval && idx + 1 < transfer->num_iso_packets && transfer->iso_packet_desc[idx + 1].length > 0)) { + usbi_err(TRANSFER_CTX(transfer), "isoch packets for OUT transfer with WinUSB must be contiguous in memory"); + return LIBUSB_ERROR_INVALID_PARAM; + } + + size_should_be_zero = (transfer->iso_packet_desc[idx].length == 0); + out_transfer_length += transfer->iso_packet_desc[idx].length; + } + } + + if (transfer_priv->isoch_buffer_handle != NULL) { + if (WinUSBX[sub_api].UnregisterIsochBuffer(transfer_priv->isoch_buffer_handle)) { + transfer_priv->isoch_buffer_handle = NULL; + } else { + usbi_err(TRANSFER_CTX(transfer), "failed to unregister WinUSB isoch buffer: %s", windows_error_str(0)); + return LIBUSB_ERROR_OTHER; + } + } + + // Register the isoch buffer to the operating system. + ret = WinUSBX[sub_api].RegisterIsochBuffer(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, &buffer_handle); + if (!ret) { + usbi_err(TRANSFER_CTX(transfer), "failed to register WinUSB isoch buffer: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_MEM; + } + + // Important note: the WinUSB_Read/WriteIsochPipeAsap API requires a ContinueStream parameter that tells whether the isochronous + // stream must be continued or if the WinUSB driver can schedule the transfer at its convenience. Profiling subsequent transfers + // with ContinueStream = FALSE showed that 5 frames, i.e. about 5 milliseconds, were left empty between each transfer. This + // is critical as this greatly diminish the achievable isochronous bandwidth. We solved the problem using the following strategy: + // - Transfers are first scheduled with ContinueStream = TRUE and with winusbx_iso_transfer_continue_stream_callback as user callback. + // - If the transfer succeeds, winusbx_iso_transfer_continue_stream_callback restore the user callback and calls its. + // - If the transfer fails, winusbx_iso_transfer_continue_stream_callback reschedule the transfer and force ContinueStream = FALSE. + if (!transfer_priv->iso_break_stream) { + transfer_priv->iso_user_callback = transfer->callback; + transfer->callback = winusbx_native_iso_transfer_continue_stream_callback; + } + + // Initiate the transfers. + if (IS_XFERIN(transfer)) + ret = WinUSBX[sub_api].ReadIsochPipeAsap(buffer_handle, 0, transfer->length, !transfer_priv->iso_break_stream, transfer->num_iso_packets, (PUSBD_ISO_PACKET_DESCRIPTOR)transfer->iso_packet_desc, overlapped); + else + ret = WinUSBX[sub_api].WriteIsochPipeAsap(buffer_handle, 0, out_transfer_length, !transfer_priv->iso_break_stream, overlapped); + + if (!ret && GetLastError() != ERROR_IO_PENDING) { + usbi_err(TRANSFER_CTX(transfer), "ReadIsochPipeAsap/WriteIsochPipeAsap failed: %s", windows_error_str(0)); + if (!WinUSBX[sub_api].UnregisterIsochBuffer(buffer_handle)) + usbi_warn(TRANSFER_CTX(transfer), "failed to unregister WinUSB isoch buffer: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + // Restore the ContinueStream parameter to TRUE. + transfer_priv->iso_break_stream = FALSE; + + transfer_priv->isoch_buffer_handle = buffer_handle; + + return LIBUSB_SUCCESS; + } else { + PRINT_UNSUPPORTED_API(winusbx_submit_iso_transfer); + return LIBUSB_ERROR_NOT_SUPPORTED; + } +} + +static int winusbx_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + HANDLE winusb_handle; + OVERLAPPED *overlapped; + BOOL ret; + int current_interface; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(TRANSFER_CTX(transfer), "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(TRANSFER_CTX(transfer), "matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + + transfer_priv->interface_number = (uint8_t)current_interface; + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + set_transfer_priv_handle(itransfer, handle_priv->interface_handle[current_interface].dev_handle); + overlapped = get_transfer_priv_overlapped(itransfer); + + if (IS_XFERIN(transfer)) { + usbi_dbg(TRANSFER_CTX(transfer), "reading %d bytes", transfer->length); + ret = WinUSBX[sub_api].ReadPipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, NULL, overlapped); + } else { + // Set SHORT_PACKET_TERMINATE if ZLP requested. + // Changing this can be a problem with packets in flight, so only allow on the first transfer. + UCHAR policy = (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) != 0; + uint8_t* current_zlp = &handle_priv->interface_handle[current_interface].zlp[transfer->endpoint]; + if (*current_zlp == WINUSB_ZLP_UNSET) { + if (policy && + !WinUSBX[sub_api].SetPipePolicy(winusb_handle, transfer->endpoint, + SHORT_PACKET_TERMINATE, sizeof(UCHAR), &policy)) { + usbi_err(TRANSFER_CTX(transfer), "failed to set SHORT_PACKET_TERMINATE for endpoint %02X", transfer->endpoint); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + *current_zlp = policy ? WINUSB_ZLP_ON : WINUSB_ZLP_OFF; + } else if (policy != (*current_zlp == WINUSB_ZLP_ON)) { + usbi_err(TRANSFER_CTX(transfer), "cannot change ZERO_PACKET for endpoint %02X on Windows", transfer->endpoint); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + usbi_dbg(TRANSFER_CTX(transfer), "writing %d bytes", transfer->length); + ret = WinUSBX[sub_api].WritePipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, NULL, overlapped); + } + + if (!ret && GetLastError() != ERROR_IO_PENDING) { + usbi_err(TRANSFER_CTX(transfer), "ReadPipe/WritePipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + +static int winusbx_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE winusb_handle; + int current_interface; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + current_interface = interface_by_endpoint(priv, handle_priv, endpoint); + if (current_interface < 0) { + usbi_err(HANDLE_CTX(dev_handle), "unable to match endpoint to an open interface - cannot clear"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(HANDLE_CTX(dev_handle), "matched endpoint %02X with interface %d", endpoint, current_interface); + winusb_handle = handle_priv->interface_handle[current_interface].api_handle; + + if (!WinUSBX[sub_api].ResetPipe(winusb_handle, endpoint)) { + usbi_err(HANDLE_CTX(dev_handle), "ResetPipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +static int winusbx_cancel_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int current_interface = transfer_priv->interface_number; + HANDLE handle; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + usbi_dbg(TRANSFER_CTX(transfer), "will use interface %d", current_interface); + + handle = handle_priv->interface_handle[current_interface].api_handle; + if (!WinUSBX[sub_api].AbortPipe(handle, transfer->endpoint)) { + usbi_err(TRANSFER_CTX(transfer), "AbortPipe failed: %s", windows_error_str(0)); + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +/* + * from the "How to Use WinUSB to Communicate with a USB Device" Microsoft white paper + * (http://www.microsoft.com/whdc/connect/usb/winusb_howto.mspx): + * "WinUSB does not support host-initiated reset port and cycle port operations" and + * IOCTL_INTERNAL_USB_CYCLE_PORT is only available in kernel mode and the + * IOCTL_USB_HUB_CYCLE_PORT ioctl was removed from Vista => the best we can do is + * cycle the pipes (and even then, the control pipe can not be reset using WinUSB) + */ +// TODO: (post hotplug): see if we can force eject the device and redetect it (reuse hotplug?) +static int winusbx_reset_device(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE winusb_handle; + int i, j; + + CHECK_WINUSBX_AVAILABLE(sub_api); + + // Reset any available pipe (except control) + for (i = 0; i < USB_MAXINTERFACES; i++) { + winusb_handle = handle_priv->interface_handle[i].api_handle; + if (HANDLE_VALID(winusb_handle)) { + for (j = 0; j < priv->usb_interface[i].nb_endpoints; j++) { + usbi_dbg(HANDLE_CTX(dev_handle), "resetting ep %02X", priv->usb_interface[i].endpoint[j]); + if (!WinUSBX[sub_api].AbortPipe(winusb_handle, priv->usb_interface[i].endpoint[j])) + usbi_err(HANDLE_CTX(dev_handle), "AbortPipe (pipe address %02X) failed: %s", + priv->usb_interface[i].endpoint[j], windows_error_str(0)); + + // FlushPipe seems to fail on OUT pipes + if (IS_EPIN(priv->usb_interface[i].endpoint[j]) + && (!WinUSBX[sub_api].FlushPipe(winusb_handle, priv->usb_interface[i].endpoint[j]))) + usbi_err(HANDLE_CTX(dev_handle), "FlushPipe (pipe address %02X) failed: %s", + priv->usb_interface[i].endpoint[j], windows_error_str(0)); + + if (!WinUSBX[sub_api].ResetPipe(winusb_handle, priv->usb_interface[i].endpoint[j])) + usbi_err(HANDLE_CTX(dev_handle), "ResetPipe (pipe address %02X) failed: %s", + priv->usb_interface[i].endpoint[j], windows_error_str(0)); + } + } + } + + // libusbK & libusb0 have the ability to issue an actual device reset + if ((sub_api != SUB_API_WINUSB) && (WinUSBX[sub_api].ResetDevice != NULL)) { + winusb_handle = handle_priv->interface_handle[0].api_handle; + if (HANDLE_VALID(winusb_handle)) + WinUSBX[sub_api].ResetDevice(winusb_handle); + } + + return LIBUSB_SUCCESS; +} + +static enum libusb_transfer_status winusbx_copy_transfer_data(int sub_api, struct usbi_transfer *itransfer, DWORD length) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + int i; + + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + // for isochronous, need to copy the individual iso packet actual_lengths and statuses + if ((sub_api == SUB_API_LIBUSBK) || (sub_api == SUB_API_LIBUSB0)) { + // iso only supported on libusbk-based backends for now + PKISO_CONTEXT iso_context = transfer_priv->iso_context; + for (i = 0; i < transfer->num_iso_packets; i++) { + transfer->iso_packet_desc[i].actual_length = iso_context->IsoPackets[i].actual_length; + // TODO translate USDB_STATUS codes http://msdn.microsoft.com/en-us/library/ff539136(VS.85).aspx to libusb_transfer_status + //transfer->iso_packet_desc[i].status = transfer_priv->iso_context->IsoPackets[i].status; + } + } else if (sub_api == SUB_API_WINUSB) { + if (IS_XFERIN(transfer)) { + /* Convert isochronous packet descriptor between Windows and libusb representation. + * Both representation are guaranteed to have the same length in bytes.*/ + PUSBD_ISO_PACKET_DESCRIPTOR usbd_iso_packet_desc = (PUSBD_ISO_PACKET_DESCRIPTOR)transfer->iso_packet_desc; + for (i = 0; i < transfer->num_iso_packets; i++) { + unsigned int packet_length = (i < transfer->num_iso_packets - 1) ? (usbd_iso_packet_desc[i + 1].Offset - usbd_iso_packet_desc[i].Offset) : usbd_iso_packet_desc[i].Length; + unsigned int actual_length = usbd_iso_packet_desc[i].Length; + USBD_STATUS status = usbd_iso_packet_desc[i].Status; + + transfer->iso_packet_desc[i].length = packet_length; + transfer->iso_packet_desc[i].actual_length = actual_length; + transfer->iso_packet_desc[i].status = usbd_status_to_libusb_transfer_status(status); + } + } else { + for (i = 0; i < transfer->num_iso_packets; i++) { + transfer->iso_packet_desc[i].status = LIBUSB_TRANSFER_COMPLETED; + } + } + } else { + // This should only occur if backend is not set correctly or other backend isoc is partially implemented + PRINT_UNSUPPORTED_API(copy_transfer_data); + return LIBUSB_TRANSFER_ERROR; + } + } + + itransfer->transferred += (int)length; + return LIBUSB_TRANSFER_COMPLETED; +} + +/* + * Internal HID Support functions (from libusb-win32) + * Note that functions that complete data transfer synchronously must return + * LIBUSB_COMPLETED instead of LIBUSB_SUCCESS + */ +static int _hid_get_hid_descriptor(struct hid_device_priv *dev, void *data, size_t *size); +static int _hid_get_report_descriptor(struct hid_device_priv *dev, void *data, size_t *size); + +static int _hid_wcslen(WCHAR *str) +{ + int i = 0; + + while (str[i] && (str[i] != 0x409)) + i++; + + return i; +} + +static int _hid_get_device_descriptor(struct hid_device_priv *hid_priv, void *data, size_t *size) +{ + struct libusb_device_descriptor d; + + d.bLength = LIBUSB_DT_DEVICE_SIZE; + d.bDescriptorType = LIBUSB_DT_DEVICE; + d.bcdUSB = 0x0200; /* 2.00 */ + d.bDeviceClass = 0; + d.bDeviceSubClass = 0; + d.bDeviceProtocol = 0; + d.bMaxPacketSize0 = 64; /* fix this! */ + d.idVendor = (uint16_t)hid_priv->vid; + d.idProduct = (uint16_t)hid_priv->pid; + d.bcdDevice = 0x0100; + d.iManufacturer = hid_priv->string_index[0]; + d.iProduct = hid_priv->string_index[1]; + d.iSerialNumber = hid_priv->string_index[2]; + d.bNumConfigurations = 1; + + if (*size > LIBUSB_DT_DEVICE_SIZE) + *size = LIBUSB_DT_DEVICE_SIZE; + memcpy(data, &d, *size); + + return LIBUSB_COMPLETED; +} + +static int _hid_get_config_descriptor(struct hid_device_priv *hid_priv, void *data, size_t *size) +{ + char num_endpoints = 0; + size_t config_total_len = 0; + char tmp[HID_MAX_CONFIG_DESC_SIZE]; + struct libusb_config_descriptor *cd; + struct libusb_interface_descriptor *id; + struct libusb_hid_descriptor *hd; + struct libusb_endpoint_descriptor *ed; + size_t tmp_size; + + if (hid_priv->input_report_size) + num_endpoints++; + if (hid_priv->output_report_size) + num_endpoints++; + + config_total_len = LIBUSB_DT_CONFIG_SIZE + LIBUSB_DT_INTERFACE_SIZE + + LIBUSB_DT_HID_SIZE + num_endpoints * LIBUSB_DT_ENDPOINT_SIZE; + + cd = (struct libusb_config_descriptor *)tmp; + id = (struct libusb_interface_descriptor *)(tmp + LIBUSB_DT_CONFIG_SIZE); + hd = (struct libusb_hid_descriptor *)(tmp + LIBUSB_DT_CONFIG_SIZE + + LIBUSB_DT_INTERFACE_SIZE); + ed = (struct libusb_endpoint_descriptor *)(tmp + LIBUSB_DT_CONFIG_SIZE + + LIBUSB_DT_INTERFACE_SIZE + + LIBUSB_DT_HID_SIZE); + + cd->bLength = LIBUSB_DT_CONFIG_SIZE; + cd->bDescriptorType = LIBUSB_DT_CONFIG; + cd->wTotalLength = (uint16_t)config_total_len; + cd->bNumInterfaces = 1; + cd->bConfigurationValue = 1; + cd->iConfiguration = 0; + cd->bmAttributes = 1 << 7; /* bus powered */ + cd->MaxPower = 50; + + id->bLength = LIBUSB_DT_INTERFACE_SIZE; + id->bDescriptorType = LIBUSB_DT_INTERFACE; + id->bInterfaceNumber = 0; + id->bAlternateSetting = 0; + id->bNumEndpoints = num_endpoints; + id->bInterfaceClass = 3; + id->bInterfaceSubClass = 0; + id->bInterfaceProtocol = 0; + id->iInterface = 0; + + tmp_size = LIBUSB_DT_HID_SIZE; + _hid_get_hid_descriptor(hid_priv, hd, &tmp_size); + + if (hid_priv->input_report_size) { + ed->bLength = LIBUSB_DT_ENDPOINT_SIZE; + ed->bDescriptorType = LIBUSB_DT_ENDPOINT; + ed->bEndpointAddress = HID_IN_EP; + ed->bmAttributes = 3; + ed->wMaxPacketSize = hid_priv->input_report_size - 1; + ed->bInterval = 10; + ed = (struct libusb_endpoint_descriptor *)((char *)ed + LIBUSB_DT_ENDPOINT_SIZE); + } + + if (hid_priv->output_report_size) { + ed->bLength = LIBUSB_DT_ENDPOINT_SIZE; + ed->bDescriptorType = LIBUSB_DT_ENDPOINT; + ed->bEndpointAddress = HID_OUT_EP; + ed->bmAttributes = 3; + ed->wMaxPacketSize = hid_priv->output_report_size - 1; + ed->bInterval = 10; + } + + if (*size > config_total_len) + *size = config_total_len; + memcpy(data, tmp, *size); + + return LIBUSB_COMPLETED; +} + +static int _hid_get_string_descriptor(struct hid_device_priv *hid_priv, int _index, + void *data, size_t *size, HANDLE hid_handle) +{ + void *tmp = NULL; + WCHAR string[MAX_USB_STRING_LENGTH]; + size_t tmp_size = 0; + int i; + + /* language ID, EN-US */ + char string_langid[] = {0x09, 0x04}; + + if (_index == 0) { + tmp = string_langid; + tmp_size = sizeof(string_langid) + 2; + } else { + for (i = 0; i < 3; i++) { + if (_index == (hid_priv->string_index[i])) { + tmp = hid_priv->string[i]; + tmp_size = (_hid_wcslen(hid_priv->string[i]) + 1) * sizeof(WCHAR); + break; + } + } + + if (i == 3) { + if (!HidD_GetIndexedString(hid_handle, _index, string, sizeof(string))) + return LIBUSB_ERROR_INVALID_PARAM; + tmp = string; + tmp_size = (_hid_wcslen(string) + 1) * sizeof(WCHAR); + } + } + + if (!tmp_size) + return LIBUSB_ERROR_INVALID_PARAM; + + if (tmp_size < *size) + *size = tmp_size; + + // 2 byte header + ((uint8_t *)data)[0] = (uint8_t)*size; + ((uint8_t *)data)[1] = LIBUSB_DT_STRING; + memcpy((uint8_t *)data + 2, tmp, *size - 2); + + return LIBUSB_COMPLETED; +} + +static int _hid_get_hid_descriptor(struct hid_device_priv *hid_priv, void *data, size_t *size) +{ + struct libusb_hid_descriptor d; + uint8_t tmp[MAX_HID_DESCRIPTOR_SIZE]; + size_t report_len = MAX_HID_DESCRIPTOR_SIZE; + + _hid_get_report_descriptor(hid_priv, tmp, &report_len); + + d.bLength = LIBUSB_DT_HID_SIZE; + d.bDescriptorType = LIBUSB_DT_HID; + d.bcdHID = 0x0110; /* 1.10 */ + d.bCountryCode = 0; + d.bNumDescriptors = 1; + d.bClassDescriptorType = LIBUSB_DT_REPORT; + d.wClassDescriptorLength = (uint16_t)report_len; + + if (*size > LIBUSB_DT_HID_SIZE) + *size = LIBUSB_DT_HID_SIZE; + memcpy(data, &d, *size); + + return LIBUSB_COMPLETED; +} + +static int _hid_get_report_descriptor(struct hid_device_priv *hid_priv, void *data, size_t *size) +{ + uint8_t d[MAX_HID_DESCRIPTOR_SIZE]; + size_t i = 0; + + /* usage page */ + d[i++] = 0x06; d[i++] = hid_priv->usagePage & 0xFF; d[i++] = hid_priv->usagePage >> 8; + /* usage */ + d[i++] = 0x09; d[i++] = (uint8_t)hid_priv->usage; + /* start collection (application) */ + d[i++] = 0xA1; d[i++] = 0x01; + /* input report */ + if (hid_priv->input_report_size) { + /* usage (vendor defined) */ + d[i++] = 0x09; d[i++] = 0x01; + /* logical minimum (0) */ + d[i++] = 0x15; d[i++] = 0x00; + /* logical maximum (255) */ + d[i++] = 0x25; d[i++] = 0xFF; + /* report size (8 bits) */ + d[i++] = 0x75; d[i++] = 0x08; + /* report count */ + d[i++] = 0x95; d[i++] = (uint8_t)hid_priv->input_report_size - 1; + /* input (data, variable, absolute) */ + d[i++] = 0x81; d[i++] = 0x00; + } + /* output report */ + if (hid_priv->output_report_size) { + /* usage (vendor defined) */ + d[i++] = 0x09; d[i++] = 0x02; + /* logical minimum (0) */ + d[i++] = 0x15; d[i++] = 0x00; + /* logical maximum (255) */ + d[i++] = 0x25; d[i++] = 0xFF; + /* report size (8 bits) */ + d[i++] = 0x75; d[i++] = 0x08; + /* report count */ + d[i++] = 0x95; d[i++] = (uint8_t)hid_priv->output_report_size - 1; + /* output (data, variable, absolute) */ + d[i++] = 0x91; d[i++] = 0x00; + } + /* feature report */ + if (hid_priv->feature_report_size) { + /* usage (vendor defined) */ + d[i++] = 0x09; d[i++] = 0x03; + /* logical minimum (0) */ + d[i++] = 0x15; d[i++] = 0x00; + /* logical maximum (255) */ + d[i++] = 0x25; d[i++] = 0xFF; + /* report size (8 bits) */ + d[i++] = 0x75; d[i++] = 0x08; + /* report count */ + d[i++] = 0x95; d[i++] = (uint8_t)hid_priv->feature_report_size - 1; + /* feature (data, variable, absolute) */ + d[i++] = 0xb2; d[i++] = 0x02; d[i++] = 0x01; + } + + /* end collection */ + d[i++] = 0xC0; + + if (*size > i) + *size = i; + memcpy(data, d, *size); + + return LIBUSB_COMPLETED; +} + +static int _hid_get_descriptor(struct libusb_device *dev, HANDLE hid_handle, int recipient, + int type, int _index, void *data, size_t *size) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + UNUSED(recipient); + + switch (type) { + case LIBUSB_DT_DEVICE: + usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_DEVICE"); + return _hid_get_device_descriptor(priv->hid, data, size); + case LIBUSB_DT_CONFIG: + usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_CONFIG"); + if (!_index) + return _hid_get_config_descriptor(priv->hid, data, size); + return LIBUSB_ERROR_INVALID_PARAM; + case LIBUSB_DT_STRING: + usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_STRING"); + return _hid_get_string_descriptor(priv->hid, _index, data, size, hid_handle); + case LIBUSB_DT_HID: + usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_HID"); + if (!_index) + return _hid_get_hid_descriptor(priv->hid, data, size); + return LIBUSB_ERROR_INVALID_PARAM; + case LIBUSB_DT_REPORT: + usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_REPORT"); + if (!_index) + return _hid_get_report_descriptor(priv->hid, data, size); + return LIBUSB_ERROR_INVALID_PARAM; + case LIBUSB_DT_PHYSICAL: + usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_PHYSICAL"); + if (HidD_GetPhysicalDescriptor(hid_handle, data, (ULONG)*size)) + return LIBUSB_COMPLETED; + return LIBUSB_ERROR_OTHER; + } + + usbi_warn(DEVICE_CTX(dev), "unsupported"); + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int _hid_get_report(struct libusb_device *dev, HANDLE hid_handle, int id, void *data, + struct winusb_transfer_priv *tp, size_t size, OVERLAPPED *overlapped, int report_type) +{ + DWORD ioctl_code, expected_size = (DWORD)size; + uint8_t *buf; + + if (tp->hid_buffer != NULL) + usbi_err(DEVICE_CTX(dev), "program assertion failed - hid_buffer is not NULL"); + + if ((size == 0) || (size > MAX_HID_REPORT_SIZE)) { + usbi_warn(DEVICE_CTX(dev), "invalid size (%"PRIuPTR")", (uintptr_t)size); + return LIBUSB_ERROR_INVALID_PARAM; + } + + switch (report_type) { + case HID_REPORT_TYPE_INPUT: + ioctl_code = IOCTL_HID_GET_INPUT_REPORT; + break; + case HID_REPORT_TYPE_FEATURE: + ioctl_code = IOCTL_HID_GET_FEATURE; + break; + default: + usbi_warn(DEVICE_CTX(dev), "unknown HID report type %d", report_type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + // Add a trailing byte to detect overflows + buf = calloc(1, expected_size + 1); + if (buf == NULL) + return LIBUSB_ERROR_NO_MEM; + + buf[0] = (uint8_t)id; // Must be set always + usbi_dbg(DEVICE_CTX(dev), "report ID: 0x%02X", buf[0]); + + // NB: The size returned by DeviceIoControl doesn't include report IDs when not in use (0) + if (!DeviceIoControl(hid_handle, ioctl_code, buf, expected_size + 1, + buf, expected_size + 1, NULL, overlapped)) { + if (GetLastError() != ERROR_IO_PENDING) { + usbi_err(DEVICE_CTX(dev), "failed to read HID Report: %s", windows_error_str(0)); + free(buf); + return LIBUSB_ERROR_IO; + } + } + + // Asynchronous wait + tp->hid_buffer = buf; + tp->hid_dest = data; // copy dest, as not necessarily the start of the transfer buffer + tp->hid_expected_size = expected_size; + + return LIBUSB_SUCCESS; +} + +static int _hid_set_report(struct libusb_device *dev, HANDLE hid_handle, int id, void *data, + struct winusb_transfer_priv *tp, size_t size, OVERLAPPED *overlapped, int report_type) +{ + DWORD ioctl_code, write_size = (DWORD)size; + // If an id is reported, we must allow MAX_HID_REPORT_SIZE + 1 + size_t max_report_size = MAX_HID_REPORT_SIZE + (id ? 1 : 0); + uint8_t *buf; + + if (tp->hid_buffer != NULL) + usbi_err(DEVICE_CTX(dev), "program assertion failed - hid_buffer is not NULL"); + + if ((size == 0) || (size > max_report_size)) { + usbi_warn(DEVICE_CTX(dev), "invalid size (%"PRIuPTR")", (uintptr_t)size); + return LIBUSB_ERROR_INVALID_PARAM; + } + + switch (report_type) { + case HID_REPORT_TYPE_OUTPUT: + ioctl_code = IOCTL_HID_SET_OUTPUT_REPORT; + break; + case HID_REPORT_TYPE_FEATURE: + ioctl_code = IOCTL_HID_SET_FEATURE; + break; + default: + usbi_warn(DEVICE_CTX(dev), "unknown HID report type %d", report_type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + usbi_dbg(DEVICE_CTX(dev), "report ID: 0x%02X", id); + // When report IDs are not used (i.e. when id == 0), we must add + // a null report ID. Otherwise, we just use original data buffer + if (id == 0) + write_size++; + + buf = malloc(write_size); + if (buf == NULL) + return LIBUSB_ERROR_NO_MEM; + + if (id == 0) { + buf[0] = 0; + memcpy(buf + 1, data, size); + } else { + // This seems like a waste, but if we don't duplicate the + // data, we'll get issues when freeing hid_buffer + memcpy(buf, data, size); + if (buf[0] != id) + usbi_warn(DEVICE_CTX(dev), "mismatched report ID (data is %02X, parameter is %02X)", buf[0], id); + } + + // NB: The size returned by DeviceIoControl doesn't include report IDs when not in use (0) + if (!DeviceIoControl(hid_handle, ioctl_code, buf, write_size, + buf, write_size, NULL, overlapped)) { + if (GetLastError() != ERROR_IO_PENDING) { + usbi_err(DEVICE_CTX(dev), "failed to write HID Output Report: %s", windows_error_str(0)); + free(buf); + return LIBUSB_ERROR_IO; + } + } + + tp->hid_buffer = buf; + tp->hid_dest = NULL; + return LIBUSB_SUCCESS; +} + +static int _hid_class_request(struct libusb_device *dev, HANDLE hid_handle, int request_type, + int request, int value, int _index, void *data, struct winusb_transfer_priv *tp, + size_t size, OVERLAPPED *overlapped) +{ + int report_type = (value >> 8) & 0xFF; + int report_id = value & 0xFF; + + UNUSED(_index); + + if ((LIBUSB_REQ_RECIPIENT(request_type) != LIBUSB_RECIPIENT_INTERFACE) + && (LIBUSB_REQ_RECIPIENT(request_type) != LIBUSB_RECIPIENT_DEVICE)) + return LIBUSB_ERROR_INVALID_PARAM; + + if (LIBUSB_REQ_OUT(request_type) && request == HID_REQ_SET_REPORT) + return _hid_set_report(dev, hid_handle, report_id, data, tp, size, overlapped, report_type); + + if (LIBUSB_REQ_IN(request_type) && request == HID_REQ_GET_REPORT) + return _hid_get_report(dev, hid_handle, report_id, data, tp, size, overlapped, report_type); + + return LIBUSB_ERROR_INVALID_PARAM; +} + +/* + * HID API functions + */ +static bool hid_init(struct libusb_context *ctx) +{ + DLL_GET_HANDLE(ctx, hid); + + DLL_LOAD_FUNC(hid, HidD_GetAttributes, true); + DLL_LOAD_FUNC(hid, HidD_GetHidGuid, true); + DLL_LOAD_FUNC(hid, HidD_GetPreparsedData, true); + DLL_LOAD_FUNC(hid, HidD_FreePreparsedData, true); + DLL_LOAD_FUNC(hid, HidD_GetManufacturerString, true); + DLL_LOAD_FUNC(hid, HidD_GetProductString, true); + DLL_LOAD_FUNC(hid, HidD_GetSerialNumberString, true); + DLL_LOAD_FUNC(hid, HidD_GetIndexedString, true); + DLL_LOAD_FUNC(hid, HidP_GetCaps, true); + DLL_LOAD_FUNC(hid, HidD_SetNumInputBuffers, true); + DLL_LOAD_FUNC(hid, HidD_GetPhysicalDescriptor, true); + DLL_LOAD_FUNC(hid, HidD_FlushQueue, true); + DLL_LOAD_FUNC(hid, HidP_GetValueCaps, true); + + return true; +} + +static void hid_exit(void) +{ + DLL_FREE_HANDLE(hid); +} + +// NB: open and close must ensure that they only handle interface of +// the right API type, as these functions can be called wholesale from +// composite_open(), with interfaces belonging to different APIs +static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct libusb_device *dev = dev_handle->dev; + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + HIDD_ATTRIBUTES hid_attributes; + PHIDP_PREPARSED_DATA preparsed_data = NULL; + HIDP_CAPS capabilities; + HIDP_VALUE_CAPS *value_caps; + HANDLE hid_handle = INVALID_HANDLE_VALUE; + int i, j; + // report IDs handling + ULONG size[3]; + int nb_ids[2]; // zero and nonzero report IDs +#if defined(ENABLE_LOGGING) + const char * const type[3] = {"input", "output", "feature"}; +#endif + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + if (priv->hid == NULL) { + usbi_err(HANDLE_CTX(dev_handle), "program assertion failed - private HID structure is uninitialized"); + return LIBUSB_ERROR_NOT_FOUND; + } + + for (i = 0; i < USB_MAXINTERFACES; i++) { + if ((priv->usb_interface[i].path != NULL) + && (priv->usb_interface[i].apib->id == USB_API_HID)) { + hid_handle = windows_open(dev_handle, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE); + /* + * http://www.lvr.com/hidfaq.htm: Why do I receive "Access denied" when attempting to access my HID? + * "Windows 2000 and later have exclusive read/write access to HIDs that are configured as a system + * keyboards or mice. An application can obtain a handle to a system keyboard or mouse by not + * requesting READ or WRITE access with CreateFile. Applications can then use HidD_SetFeature and + * HidD_GetFeature (if the device supports Feature reports)." + */ + if (hid_handle == INVALID_HANDLE_VALUE) { + usbi_warn(HANDLE_CTX(dev_handle), "could not open HID device in R/W mode (keyboard or mouse?) - trying without"); + hid_handle = windows_open(dev_handle, priv->usb_interface[i].path, 0); + if (hid_handle == INVALID_HANDLE_VALUE) { + usbi_err(HANDLE_CTX(dev_handle), "could not open device %s (interface %d): %s", priv->path, i, windows_error_str(0)); + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: // The device was disconnected + return LIBUSB_ERROR_NO_DEVICE; + case ERROR_ACCESS_DENIED: + return LIBUSB_ERROR_ACCESS; + default: + return LIBUSB_ERROR_IO; + } + } + priv->usb_interface[i].restricted_functionality = true; + } + handle_priv->interface_handle[i].api_handle = hid_handle; + } + } + + hid_attributes.Size = sizeof(hid_attributes); + do { + if (!HidD_GetAttributes(hid_handle, &hid_attributes)) { + usbi_err(HANDLE_CTX(dev_handle), "could not gain access to HID top collection (HidD_GetAttributes)"); + break; + } + + priv->hid->vid = hid_attributes.VendorID; + priv->hid->pid = hid_attributes.ProductID; + + // Set the maximum available input buffer size + for (i = 32; HidD_SetNumInputBuffers(hid_handle, i); i *= 2); + usbi_dbg(HANDLE_CTX(dev_handle), "set maximum input buffer size to %d", i / 2); + + // Get the maximum input and output report size + if (!HidD_GetPreparsedData(hid_handle, &preparsed_data) || !preparsed_data) { + usbi_err(HANDLE_CTX(dev_handle), "could not read HID preparsed data (HidD_GetPreparsedData)"); + break; + } + if (HidP_GetCaps(preparsed_data, &capabilities) != HIDP_STATUS_SUCCESS) { + usbi_err(HANDLE_CTX(dev_handle), "could not parse HID capabilities (HidP_GetCaps)"); + break; + } + + // Find out if interrupt will need report IDs + size[0] = capabilities.NumberInputValueCaps; + size[1] = capabilities.NumberOutputValueCaps; + size[2] = capabilities.NumberFeatureValueCaps; + for (j = HidP_Input; j <= HidP_Feature; j++) { + usbi_dbg(HANDLE_CTX(dev_handle), "%lu HID %s report value(s) found", ULONG_CAST(size[j]), type[j]); + priv->hid->uses_report_ids[j] = false; + if (size[j] > 0) { + value_caps = calloc(size[j], sizeof(HIDP_VALUE_CAPS)); + if ((value_caps != NULL) + && (HidP_GetValueCaps((HIDP_REPORT_TYPE)j, value_caps, &size[j], preparsed_data) == HIDP_STATUS_SUCCESS) + && (size[j] >= 1)) { + nb_ids[0] = 0; + nb_ids[1] = 0; + for (i = 0; i < (int)size[j]; i++) { + usbi_dbg(HANDLE_CTX(dev_handle), " Report ID: 0x%02X", value_caps[i].ReportID); + if (value_caps[i].ReportID != 0) + nb_ids[1]++; + else + nb_ids[0]++; + } + if (nb_ids[1] != 0) { + if (nb_ids[0] != 0) + usbi_warn(HANDLE_CTX(dev_handle), "program assertion failed - zero and nonzero report IDs used for %s", + type[j]); + priv->hid->uses_report_ids[j] = true; + } + } else { + usbi_warn(HANDLE_CTX(dev_handle), " could not process %s report IDs", type[j]); + } + free(value_caps); + } + } + + // Set the report sizes + priv->hid->input_report_size = capabilities.InputReportByteLength; + priv->hid->output_report_size = capabilities.OutputReportByteLength; + priv->hid->feature_report_size = capabilities.FeatureReportByteLength; + + // Store usage and usagePage values + priv->hid->usage = capabilities.Usage; + priv->hid->usagePage = capabilities.UsagePage; + + // Fetch string descriptors + priv->hid->string_index[0] = dev->device_descriptor.iManufacturer; + if (priv->hid->string_index[0] != 0) + HidD_GetManufacturerString(hid_handle, priv->hid->string[0], sizeof(priv->hid->string[0])); + else + priv->hid->string[0][0] = 0; + + priv->hid->string_index[1] = dev->device_descriptor.iProduct; + if (priv->hid->string_index[1] != 0) + HidD_GetProductString(hid_handle, priv->hid->string[1], sizeof(priv->hid->string[1])); + else + priv->hid->string[1][0] = 0; + + priv->hid->string_index[2] = dev->device_descriptor.iSerialNumber; + if (priv->hid->string_index[2] != 0) + HidD_GetSerialNumberString(hid_handle, priv->hid->string[2], sizeof(priv->hid->string[2])); + else + priv->hid->string[2][0] = 0; + } while (0); + + if (preparsed_data) + HidD_FreePreparsedData(preparsed_data); + + return LIBUSB_SUCCESS; +} + +static void hid_close(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + HANDLE file_handle; + int i; + + UNUSED(sub_api); + + if (DLL_HANDLE_NAME(hid) == NULL) + return; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (priv->usb_interface[i].apib->id == USB_API_HID) { + file_handle = handle_priv->interface_handle[i].api_handle; + if (HANDLE_VALID(file_handle)) + CloseHandle(file_handle); + } + } +} + +static int hid_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + // NB: Disconnection detection is not possible in this function + if (priv->usb_interface[iface].path == NULL) + return LIBUSB_ERROR_NOT_FOUND; // invalid iface + + // We use dev_handle as a flag for interface claimed + if (handle_priv->interface_handle[iface].dev_handle == INTERFACE_CLAIMED) + return LIBUSB_ERROR_BUSY; // already claimed + + handle_priv->interface_handle[iface].dev_handle = INTERFACE_CLAIMED; + + usbi_dbg(HANDLE_CTX(dev_handle), "claimed interface %u", iface); + handle_priv->active_interface = iface; + + return LIBUSB_SUCCESS; +} + +static int hid_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + if (priv->usb_interface[iface].path == NULL) + return LIBUSB_ERROR_NOT_FOUND; // invalid iface + + if (handle_priv->interface_handle[iface].dev_handle != INTERFACE_CLAIMED) + return LIBUSB_ERROR_NOT_FOUND; // invalid iface + + handle_priv->interface_handle[iface].dev_handle = INVALID_HANDLE_VALUE; + + return LIBUSB_SUCCESS; +} + +static int hid_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) +{ + UNUSED(sub_api); + UNUSED(iface); + + CHECK_HID_AVAILABLE; + + if (altsetting != 0) { + usbi_err(HANDLE_CTX(dev_handle), "set interface altsetting not supported for altsetting >0"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + return LIBUSB_SUCCESS; +} + +static int hid_submit_control_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct libusb_device_handle *dev_handle = transfer->dev_handle; + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + WINUSB_SETUP_PACKET *setup = (WINUSB_SETUP_PACKET *)transfer->buffer; + HANDLE hid_handle; + OVERLAPPED *overlapped; + int current_interface; + uint8_t config; + size_t size; + int r; + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + safe_free(transfer_priv->hid_buffer); + transfer_priv->hid_dest = NULL; + size = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; + + if (size > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + current_interface = get_valid_interface(dev_handle, USB_API_HID); + if (current_interface < 0) { + if (auto_claim(transfer, ¤t_interface, USB_API_HID) != LIBUSB_SUCCESS) + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(ITRANSFER_CTX(itransfer), "will use interface %d", current_interface); + + transfer_priv->interface_number = (uint8_t)current_interface; + hid_handle = handle_priv->interface_handle[current_interface].api_handle; + set_transfer_priv_handle(itransfer, hid_handle); + overlapped = get_transfer_priv_overlapped(itransfer); + + switch (LIBUSB_REQ_TYPE(setup->RequestType)) { + case LIBUSB_REQUEST_TYPE_STANDARD: + switch (setup->Request) { + case LIBUSB_REQUEST_GET_DESCRIPTOR: + r = _hid_get_descriptor(dev_handle->dev, hid_handle, LIBUSB_REQ_RECIPIENT(setup->RequestType), + (setup->Value >> 8) & 0xFF, setup->Value & 0xFF, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, &size); + break; + case LIBUSB_REQUEST_GET_CONFIGURATION: + r = winusb_get_configuration(dev_handle, &config); + if (r == LIBUSB_SUCCESS) { + size = 1; + ((uint8_t *)transfer->buffer)[LIBUSB_CONTROL_SETUP_SIZE] = config; + r = LIBUSB_COMPLETED; + } + break; + case LIBUSB_REQUEST_SET_CONFIGURATION: + if (setup->Value == priv->active_config) { + r = LIBUSB_COMPLETED; + } else { + usbi_warn(TRANSFER_CTX(transfer), "cannot set configuration other than the default one"); + r = LIBUSB_ERROR_NOT_SUPPORTED; + } + break; + case LIBUSB_REQUEST_GET_INTERFACE: + size = 1; + ((uint8_t *)transfer->buffer)[LIBUSB_CONTROL_SETUP_SIZE] = 0; + r = LIBUSB_COMPLETED; + break; + case LIBUSB_REQUEST_SET_INTERFACE: + r = hid_set_interface_altsetting(0, dev_handle, (uint8_t)setup->Index, (uint8_t)setup->Value); + if (r == LIBUSB_SUCCESS) + r = LIBUSB_COMPLETED; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "unsupported HID control request"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + break; + case LIBUSB_REQUEST_TYPE_CLASS: + r = _hid_class_request(dev_handle->dev, hid_handle, setup->RequestType, setup->Request, setup->Value, + setup->Index, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, transfer_priv, + size, overlapped); + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "unsupported HID control request"); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + if (r < 0) + return r; + + if (r == LIBUSB_COMPLETED) { + // Force request to be completed synchronously. Transferred size has been set by previous call + windows_force_sync_completion(itransfer, (ULONG)size); + r = LIBUSB_SUCCESS; + } + + return LIBUSB_SUCCESS; +} + +static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + HANDLE hid_handle; + OVERLAPPED *overlapped; + bool direction_in; + BOOL ret; + int current_interface, length; + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + if (IS_XFEROUT(transfer) && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)) + return LIBUSB_ERROR_NOT_SUPPORTED; + + transfer_priv->hid_dest = NULL; + safe_free(transfer_priv->hid_buffer); + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(TRANSFER_CTX(transfer), "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(TRANSFER_CTX(transfer), "matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + + transfer_priv->interface_number = (uint8_t)current_interface; + hid_handle = handle_priv->interface_handle[current_interface].api_handle; + set_transfer_priv_handle(itransfer, hid_handle); + overlapped = get_transfer_priv_overlapped(itransfer); + direction_in = IS_XFERIN(transfer); + + // If report IDs are not in use, an extra prefix byte must be added + if (((direction_in) && (!priv->hid->uses_report_ids[0])) + || ((!direction_in) && (!priv->hid->uses_report_ids[1]))) + length = transfer->length + 1; + else + length = transfer->length; + + // Add a trailing byte to detect overflows on input + transfer_priv->hid_buffer = calloc(1, length + 1); + if (transfer_priv->hid_buffer == NULL) + return LIBUSB_ERROR_NO_MEM; + + transfer_priv->hid_expected_size = length; + + if (direction_in) { + transfer_priv->hid_dest = transfer->buffer; + usbi_dbg(TRANSFER_CTX(transfer), "reading %d bytes (report ID: 0x00)", length); + ret = ReadFile(hid_handle, transfer_priv->hid_buffer, length + 1, NULL, overlapped); + } else { + if (!priv->hid->uses_report_ids[1]) + memcpy(transfer_priv->hid_buffer + 1, transfer->buffer, transfer->length); + else + // We could actually do without the calloc and memcpy in this case + memcpy(transfer_priv->hid_buffer, transfer->buffer, transfer->length); + + usbi_dbg(TRANSFER_CTX(transfer), "writing %d bytes (report ID: 0x%02X)", length, transfer_priv->hid_buffer[0]); + ret = WriteFile(hid_handle, transfer_priv->hid_buffer, length, NULL, overlapped); + } + + if (!ret && GetLastError() != ERROR_IO_PENDING) { + usbi_err(TRANSFER_CTX(transfer), "HID transfer failed: %s", windows_error_str(0)); + safe_free(transfer_priv->hid_buffer); + return LIBUSB_ERROR_IO; + } + + return LIBUSB_SUCCESS; +} + +static int hid_reset_device(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + HANDLE hid_handle; + int current_interface; + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + // Flushing the queues on all interfaces is the best we can achieve + for (current_interface = 0; current_interface < USB_MAXINTERFACES; current_interface++) { + hid_handle = handle_priv->interface_handle[current_interface].api_handle; + if (HANDLE_VALID(hid_handle)) + HidD_FlushQueue(hid_handle); + } + + return LIBUSB_SUCCESS; +} + +static int hid_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + HANDLE hid_handle; + int current_interface; + + UNUSED(sub_api); + CHECK_HID_AVAILABLE; + + current_interface = interface_by_endpoint(priv, handle_priv, endpoint); + if (current_interface < 0) { + usbi_err(HANDLE_CTX(dev_handle), "unable to match endpoint to an open interface - cannot clear"); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(HANDLE_CTX(dev_handle), "matched endpoint %02X with interface %d", endpoint, current_interface); + hid_handle = handle_priv->interface_handle[current_interface].api_handle; + + // No endpoint selection with Microsoft's implementation, so we try to flush the + // whole interface. Should be OK for most case scenarios + if (!HidD_FlushQueue(hid_handle)) { + usbi_err(HANDLE_CTX(dev_handle), "Flushing of HID queue failed: %s", windows_error_str(0)); + // Device was probably disconnected + return LIBUSB_ERROR_NO_DEVICE; + } + + return LIBUSB_SUCCESS; +} + +// This extra function is only needed for HID +static enum libusb_transfer_status hid_copy_transfer_data(int sub_api, struct usbi_transfer *itransfer, DWORD length) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + enum libusb_transfer_status r = LIBUSB_TRANSFER_COMPLETED; + + UNUSED(sub_api); + + if (transfer_priv->hid_buffer != NULL) { + // If we have a valid hid_buffer, it means the transfer was async + if (transfer_priv->hid_dest != NULL) { // Data readout + if (length > 0) { + // First, check for overflow + if ((size_t)length > transfer_priv->hid_expected_size) { + usbi_err(TRANSFER_CTX(transfer), "OVERFLOW!"); + length = (DWORD)transfer_priv->hid_expected_size; + r = LIBUSB_TRANSFER_OVERFLOW; + } + + if (transfer_priv->hid_buffer[0] == 0) { + memcpy(transfer_priv->hid_dest, transfer_priv->hid_buffer + 1, length); + } else { + memcpy(transfer_priv->hid_dest, transfer_priv->hid_buffer, length); + } + } + transfer_priv->hid_dest = NULL; + } + // For write, we just need to free the hid buffer + safe_free(transfer_priv->hid_buffer); + } + + itransfer->transferred += (int)length; + return r; +} + + +/* + * Composite API functions + */ +static int composite_open(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int i, r = LIBUSB_ERROR_NOT_FOUND; + // SUB_API_MAX + 1 as the SUB_API_MAX pos is used to indicate availability of HID + bool available[SUB_API_MAX + 1]; + + UNUSED(sub_api); + + for (i = 0; i < SUB_API_MAX + 1; i++) + available[i] = false; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + switch (priv->usb_interface[i].apib->id) { + case USB_API_WINUSBX: + if (priv->usb_interface[i].sub_api != SUB_API_NOTSET) + available[priv->usb_interface[i].sub_api] = true; + break; + case USB_API_HID: + available[SUB_API_MAX] = true; + break; + default: + break; + } + } + + for (i = 0; i < SUB_API_MAX; i++) { // WinUSB-like drivers + if (available[i]) { + r = usb_api_backend[USB_API_WINUSBX].open(i, dev_handle); + if (r != LIBUSB_SUCCESS) + return r; + } + } + + if (available[SUB_API_MAX]) { // HID driver + r = hid_open(SUB_API_NOTSET, dev_handle); + + // On Windows 10 version 1903 (OS Build 18362) and later Windows blocks attempts to + // open HID devices with a U2F usage unless running as administrator. We ignore this + // failure and proceed without the HID device opened. + if (r == LIBUSB_ERROR_ACCESS) { + usbi_dbg(HANDLE_CTX(dev_handle), "ignoring access denied error while opening HID interface of composite device"); + r = LIBUSB_SUCCESS; + } + } + + return r; +} + +static void composite_close(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int i; + // SUB_API_MAX + 1 as the SUB_API_MAX pos is used to indicate availability of HID + bool available[SUB_API_MAX + 1]; + + UNUSED(sub_api); + + for (i = 0; i < SUB_API_MAX + 1; i++) + available[i] = false; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + switch (priv->usb_interface[i].apib->id) { + case USB_API_WINUSBX: + if (priv->usb_interface[i].sub_api != SUB_API_NOTSET) + available[priv->usb_interface[i].sub_api] = true; + break; + case USB_API_HID: + available[SUB_API_MAX] = true; + break; + default: + break; + } + } + + for (i = 0; i < SUB_API_MAX; i++) { // WinUSB-like drivers + if (available[i]) + usb_api_backend[USB_API_WINUSBX].close(i, dev_handle); + } + + if (available[SUB_API_MAX]) // HID driver + hid_close(SUB_API_NOTSET, dev_handle); +} + +static int composite_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + UNUSED(sub_api); + CHECK_SUPPORTED_API(priv->usb_interface[iface].apib, claim_interface); + + return priv->usb_interface[iface].apib-> + claim_interface(priv->usb_interface[iface].sub_api, dev_handle, iface); +} + +static int composite_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + UNUSED(sub_api); + CHECK_SUPPORTED_API(priv->usb_interface[iface].apib, set_interface_altsetting); + + return priv->usb_interface[iface].apib-> + set_interface_altsetting(priv->usb_interface[iface].sub_api, dev_handle, iface, altsetting); +} + +static int composite_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + + UNUSED(sub_api); + CHECK_SUPPORTED_API(priv->usb_interface[iface].apib, release_interface); + + return priv->usb_interface[iface].apib-> + release_interface(priv->usb_interface[iface].sub_api, dev_handle, iface); +} + +static int composite_submit_control_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + struct libusb_config_descriptor *conf_desc; + WINUSB_SETUP_PACKET *setup = (WINUSB_SETUP_PACKET *)transfer->buffer; + int iface, pass, r; + + UNUSED(sub_api); + + // Interface shouldn't matter for control, but it does in practice, with Windows' + // restrictions with regards to accessing HID keyboards and mice. Try to target + // a specific interface first, if possible. + switch (LIBUSB_REQ_RECIPIENT(setup->RequestType)) { + case LIBUSB_RECIPIENT_INTERFACE: + iface = setup->Index & 0xFF; + break; + case LIBUSB_RECIPIENT_ENDPOINT: + r = libusb_get_active_config_descriptor(transfer->dev_handle->dev, &conf_desc); + if (r == LIBUSB_SUCCESS) { + iface = get_interface_by_endpoint(conf_desc, (setup->Index & 0xFF)); + libusb_free_config_descriptor(conf_desc); + break; + } + // No break if not able to determine interface + // Fall through + default: + iface = -1; + break; + } + + // Try and target a specific interface if the control setup indicates such + if ((iface >= 0) && (iface < USB_MAXINTERFACES)) { + usbi_dbg(TRANSFER_CTX(transfer), "attempting control transfer targeted to interface %d", iface); + if ((priv->usb_interface[iface].path != NULL) + && (priv->usb_interface[iface].apib->submit_control_transfer != NULL)) { + r = priv->usb_interface[iface].apib->submit_control_transfer(priv->usb_interface[iface].sub_api, itransfer); + if (r == LIBUSB_SUCCESS) + return r; + } + } + + // Either not targeted to a specific interface or no luck in doing so. + // Try a 2 pass approach with all interfaces. + for (pass = 0; pass < 2; pass++) { + for (iface = 0; iface < USB_MAXINTERFACES; iface++) { + if ((priv->usb_interface[iface].path != NULL) + && (priv->usb_interface[iface].apib->submit_control_transfer != NULL)) { + if ((pass == 0) && (priv->usb_interface[iface].restricted_functionality)) { + usbi_dbg(TRANSFER_CTX(transfer), "trying to skip restricted interface #%d (HID keyboard or mouse?)", iface); + continue; + } + usbi_dbg(TRANSFER_CTX(transfer), "using interface %d", iface); + r = priv->usb_interface[iface].apib->submit_control_transfer(priv->usb_interface[iface].sub_api, itransfer); + // If not supported on this API, it may be supported on another, so don't give up yet!! + if (r == LIBUSB_ERROR_NOT_SUPPORTED) + continue; + return r; + } + } + } + + usbi_err(TRANSFER_CTX(transfer), "no libusb supported interfaces to complete request"); + return LIBUSB_ERROR_NOT_FOUND; +} + +static int composite_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int current_interface; + + UNUSED(sub_api); + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(TRANSFER_CTX(transfer), "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + CHECK_SUPPORTED_API(priv->usb_interface[current_interface].apib, submit_bulk_transfer); + + return priv->usb_interface[current_interface].apib-> + submit_bulk_transfer(priv->usb_interface[current_interface].sub_api, itransfer); +} + +static int composite_submit_iso_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int current_interface; + + UNUSED(sub_api); + + current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint); + if (current_interface < 0) { + usbi_err(TRANSFER_CTX(transfer), "unable to match endpoint to an open interface - cancelling transfer"); + return LIBUSB_ERROR_NOT_FOUND; + } + + CHECK_SUPPORTED_API(priv->usb_interface[current_interface].apib, submit_iso_transfer); + + return priv->usb_interface[current_interface].apib-> + submit_iso_transfer(priv->usb_interface[current_interface].sub_api, itransfer); +} + +static int composite_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int current_interface; + + UNUSED(sub_api); + + current_interface = interface_by_endpoint(priv, handle_priv, endpoint); + if (current_interface < 0) { + usbi_err(HANDLE_CTX(dev_handle), "unable to match endpoint to an open interface - cannot clear"); + return LIBUSB_ERROR_NOT_FOUND; + } + + CHECK_SUPPORTED_API(priv->usb_interface[current_interface].apib, clear_halt); + + return priv->usb_interface[current_interface].apib-> + clear_halt(priv->usb_interface[current_interface].sub_api, dev_handle, endpoint); +} + +static int composite_cancel_transfer(int sub_api, struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int current_interface = transfer_priv->interface_number; + + UNUSED(sub_api); + + if ((current_interface < 0) || (current_interface >= USB_MAXINTERFACES)) { + usbi_err(TRANSFER_CTX(transfer), "program assertion failed - invalid interface_number"); + return LIBUSB_ERROR_NOT_FOUND; + } + + CHECK_SUPPORTED_API(priv->usb_interface[current_interface].apib, cancel_transfer); + + return priv->usb_interface[current_interface].apib-> + cancel_transfer(priv->usb_interface[current_interface].sub_api, itransfer); +} + +static int composite_reset_device(int sub_api, struct libusb_device_handle *dev_handle) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); + int i, r; + bool available[SUB_API_MAX]; + + UNUSED(sub_api); + + for (i = 0; i < SUB_API_MAX; i++) + available[i] = false; + + for (i = 0; i < USB_MAXINTERFACES; i++) { + if ((priv->usb_interface[i].apib->id == USB_API_WINUSBX) + && (priv->usb_interface[i].sub_api != SUB_API_NOTSET)) + available[priv->usb_interface[i].sub_api] = true; + } + + for (i = 0; i < SUB_API_MAX; i++) { + if (available[i]) { + r = usb_api_backend[USB_API_WINUSBX].reset_device(i, dev_handle); + if (r != LIBUSB_SUCCESS) + return r; + } + } + + return LIBUSB_SUCCESS; +} + +static enum libusb_transfer_status composite_copy_transfer_data(int sub_api, struct usbi_transfer *itransfer, DWORD length) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer); + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + int current_interface = transfer_priv->interface_number; + + UNUSED(sub_api); + if (priv->usb_interface[current_interface].apib->copy_transfer_data == NULL) { + usbi_err(TRANSFER_CTX(transfer), "program assertion failed - no function to copy transfer data"); + return LIBUSB_TRANSFER_ERROR; + } + + return priv->usb_interface[current_interface].apib-> + copy_transfer_data(priv->usb_interface[current_interface].sub_api, itransfer, length); +} diff --git a/src/os/windows_winusb.h b/src/os/windows_winusb.h new file mode 100644 index 0000000..6afd5cb --- /dev/null +++ b/src/os/windows_winusb.h @@ -0,0 +1,783 @@ +/* + * Windows backend for libusb 1.0 + * Copyright © 2009-2012 Pete Batard <pete@akeo.ie> + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LIBUSB_WINDOWS_WINUSB_H +#define LIBUSB_WINDOWS_WINUSB_H + +#include <devioctl.h> +#include <guiddef.h> + +#include "windows_common.h" + +#define MAX_CTRL_BUFFER_LENGTH 4096 +#define MAX_USB_STRING_LENGTH 128 +#define MAX_HID_REPORT_SIZE 1024 +#define MAX_HID_DESCRIPTOR_SIZE 256 +#define MAX_GUID_STRING_LENGTH 40 +#define MAX_PATH_LENGTH 256 +#define MAX_KEY_LENGTH 256 +#define LIST_SEPARATOR ';' + +// Handle code for HID interface that have been claimed ("dibs") +#define INTERFACE_CLAIMED ((HANDLE)(intptr_t)0xD1B5) +// Additional return code for HID operations that completed synchronously +#define LIBUSB_COMPLETED (LIBUSB_SUCCESS + 1) + +// http://msdn.microsoft.com/en-us/library/ff545978.aspx +// http://msdn.microsoft.com/en-us/library/ff545972.aspx +// http://msdn.microsoft.com/en-us/library/ff545982.aspx +static const GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER = {0x3ABF6F2D, 0x71C4, 0x462A, {0x8A, 0x92, 0x1E, 0x68, 0x61, 0xE6, 0xAF, 0x27}}; +static const GUID GUID_DEVINTERFACE_USB_HUB = {0xF18A0E88, 0xC30C, 0x11D0, {0x88, 0x15, 0x00, 0xA0, 0xC9, 0x06, 0xBE, 0xD8}}; +static const GUID GUID_DEVINTERFACE_USB_DEVICE = {0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}; +static const GUID GUID_DEVINTERFACE_LIBUSB0_FILTER = {0xF9F3FF14, 0xAE21, 0x48A0, {0x8A, 0x25, 0x80, 0x11, 0xA7, 0xA9, 0x31, 0xD9}}; + +// The following define MUST be == sizeof(USB_DESCRIPTOR_REQUEST) +#define USB_DESCRIPTOR_REQUEST_SIZE 12U + +/* + * Multiple USB API backend support + */ +#define USB_API_UNSUPPORTED 0 +#define USB_API_HUB 1 +#define USB_API_COMPOSITE 2 +#define USB_API_WINUSBX 3 +#define USB_API_HID 4 +#define USB_API_MAX 5 + +// Sub-APIs for WinUSB-like driver APIs (WinUSB, libusbK, libusb-win32 through the libusbK DLL) +// Must have the same values as the KUSB_DRVID enum from libusbk.h +#define SUB_API_NOTSET -1 +#define SUB_API_LIBUSBK 0 +#define SUB_API_LIBUSB0 1 +#define SUB_API_WINUSB 2 +#define SUB_API_MAX 3 + +struct windows_usb_api_backend { + const uint8_t id; + const char * const designation; + const char * const * const driver_name_list; // Driver name, without .sys, e.g. "usbccgp" + const uint8_t nb_driver_names; + bool (*init)(struct libusb_context *ctx); + void (*exit)(void); + int (*open)(int sub_api, struct libusb_device_handle *dev_handle); + void (*close)(int sub_api, struct libusb_device_handle *dev_handle); + int (*configure_endpoints)(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); + int (*claim_interface)(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); + int (*set_interface_altsetting)(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting); + int (*release_interface)(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface); + int (*clear_halt)(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint); + int (*reset_device)(int sub_api, struct libusb_device_handle *dev_handle); + int (*submit_bulk_transfer)(int sub_api, struct usbi_transfer *itransfer); + int (*submit_iso_transfer)(int sub_api, struct usbi_transfer *itransfer); + int (*submit_control_transfer)(int sub_api, struct usbi_transfer *itransfer); + int (*cancel_transfer)(int sub_api, struct usbi_transfer *itransfer); + enum libusb_transfer_status (*copy_transfer_data)(int sub_api, struct usbi_transfer *itransfer, DWORD length); +}; + +extern const struct windows_usb_api_backend usb_api_backend[USB_API_MAX]; + +#define PRINT_UNSUPPORTED_API(fname) \ + usbi_dbg(NULL, "unsupported API call for '%s' " \ + "(unrecognized device driver)", #fname) + +#define CHECK_SUPPORTED_API(apip, fname) \ + do { \ + if ((apip)->fname == NULL) { \ + PRINT_UNSUPPORTED_API(fname); \ + return LIBUSB_ERROR_NOT_SUPPORTED; \ + } \ + } while (0) + +/* + * private structures definition + * with inline pseudo constructors/destructors + */ + +// TODO (v2+): move hid desc to libusb.h? +struct libusb_hid_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; + uint8_t bCountryCode; + uint8_t bNumDescriptors; + uint8_t bClassDescriptorType; + uint16_t wClassDescriptorLength; +}; + +#define LIBUSB_DT_HID_SIZE 9 +#define HID_MAX_CONFIG_DESC_SIZE (LIBUSB_DT_CONFIG_SIZE + LIBUSB_DT_INTERFACE_SIZE \ + + LIBUSB_DT_HID_SIZE + 2 * LIBUSB_DT_ENDPOINT_SIZE) +#define HID_MAX_REPORT_SIZE 1024 +#define HID_IN_EP 0x81 +#define HID_OUT_EP 0x02 +#define LIBUSB_REQ_RECIPIENT(request_type) ((request_type) & 0x1F) +#define LIBUSB_REQ_TYPE(request_type) ((request_type) & (0x03 << 5)) +#define LIBUSB_REQ_IN(request_type) ((request_type) & LIBUSB_ENDPOINT_IN) +#define LIBUSB_REQ_OUT(request_type) (!LIBUSB_REQ_IN(request_type)) + +// The following are used for HID reports IOCTLs +#define HID_IN_CTL_CODE(id) \ + CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS) +#define HID_OUT_CTL_CODE(id) \ + CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) + +#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) +#define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104) +#define IOCTL_HID_SET_FEATURE HID_IN_CTL_CODE(100) +#define IOCTL_HID_SET_OUTPUT_REPORT HID_IN_CTL_CODE(101) + +enum libusb_hid_request_type { + HID_REQ_GET_REPORT = 0x01, + HID_REQ_GET_IDLE = 0x02, + HID_REQ_GET_PROTOCOL = 0x03, + HID_REQ_SET_REPORT = 0x09, + HID_REQ_SET_IDLE = 0x0A, + HID_REQ_SET_PROTOCOL = 0x0B +}; + +enum libusb_hid_report_type { + HID_REPORT_TYPE_INPUT = 0x01, + HID_REPORT_TYPE_OUTPUT = 0x02, + HID_REPORT_TYPE_FEATURE = 0x03 +}; + +struct hid_device_priv { + uint16_t vid; + uint16_t pid; + uint8_t config; + uint8_t nb_interfaces; + bool uses_report_ids[3]; // input, ouptput, feature + uint16_t input_report_size; + uint16_t output_report_size; + uint16_t feature_report_size; + uint16_t usage; + uint16_t usagePage; + WCHAR string[3][MAX_USB_STRING_LENGTH]; + uint8_t string_index[3]; // man, prod, ser +}; + +static inline struct winusb_device_priv *winusb_device_priv_init(struct libusb_device *dev) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + int i; + + priv->apib = &usb_api_backend[USB_API_UNSUPPORTED]; + priv->sub_api = SUB_API_NOTSET; + for (i = 0; i < USB_MAXINTERFACES; i++) { + priv->usb_interface[i].apib = &usb_api_backend[USB_API_UNSUPPORTED]; + priv->usb_interface[i].sub_api = SUB_API_NOTSET; + } + + return priv; +} + +static inline void winusb_device_priv_release(struct libusb_device *dev) +{ + struct winusb_device_priv *priv = usbi_get_device_priv(dev); + int i; + + free(priv->dev_id); + free(priv->path); + if ((dev->device_descriptor.bNumConfigurations > 0) && (priv->config_descriptor != NULL)) { + for (i = 0; i < dev->device_descriptor.bNumConfigurations; i++) { + if (priv->config_descriptor[i] == NULL) + continue; + free((UCHAR *)priv->config_descriptor[i] - USB_DESCRIPTOR_REQUEST_SIZE); + } + } + free(priv->config_descriptor); + free(priv->hid); + for (i = 0; i < USB_MAXINTERFACES; i++) { + free(priv->usb_interface[i].path); + free(priv->usb_interface[i].endpoint); + } +} + +// used to match a device driver (including filter drivers) against a supported API +struct driver_lookup { + char list[MAX_KEY_LENGTH + 1]; // REG_MULTI_SZ list of services (driver) names + const DWORD reg_prop; // SPDRP registry key to use to retrieve list + const char *designation; // internal designation (for debug output) +}; + +/* + * Windows DDK API definitions. Most of it copied from MinGW's includes + */ +typedef DWORD DEVNODE, DEVINST; +typedef DEVNODE *PDEVNODE, *PDEVINST; +typedef DWORD RETURN_TYPE; +typedef RETURN_TYPE CONFIGRET; + +#define CR_SUCCESS 0x00000000 + +/* Cfgmgr32 dependencies */ +DLL_DECLARE_HANDLE(Cfgmgr32); +DLL_DECLARE_FUNC(WINAPI, CONFIGRET, CM_Get_Parent, (PDEVINST, DEVINST, ULONG)); +DLL_DECLARE_FUNC(WINAPI, CONFIGRET, CM_Get_Child, (PDEVINST, DEVINST, ULONG)); + +/* AdvAPI32 dependencies */ +DLL_DECLARE_HANDLE(AdvAPI32); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegQueryValueExA, (HKEY, LPCSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegCloseKey, (HKEY)); + +/* SetupAPI dependencies */ +DLL_DECLARE_HANDLE(SetupAPI); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, HDEVINFO, p, SetupDiGetClassDevsA, (LPCGUID, PCSTR, HWND, DWORD)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiEnumDeviceInfo, (HDEVINFO, DWORD, PSP_DEVINFO_DATA)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiEnumDeviceInterfaces, (HDEVINFO, PSP_DEVINFO_DATA, + LPCGUID, DWORD, PSP_DEVICE_INTERFACE_DATA)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceInstanceIdA, (HDEVINFO, PSP_DEVINFO_DATA, + PCSTR, DWORD, PDWORD)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceInterfaceDetailA, (HDEVINFO, PSP_DEVICE_INTERFACE_DATA, + PSP_DEVICE_INTERFACE_DETAIL_DATA_A, DWORD, PDWORD, PSP_DEVINFO_DATA)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceRegistryPropertyA, (HDEVINFO, + PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiDestroyDeviceInfoList, (HDEVINFO)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, HKEY, p, SetupDiOpenDevRegKey, (HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, HKEY, p, SetupDiOpenDeviceInterfaceRegKey, (HDEVINFO, PSP_DEVICE_INTERFACE_DATA, DWORD, DWORD)); + +#define FILE_DEVICE_USB FILE_DEVICE_UNKNOWN + +#define USB_GET_NODE_INFORMATION 258 +#define USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION 260 +#define USB_GET_NODE_CONNECTION_INFORMATION_EX 274 +#define USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 279 + +#define USB_CTL_CODE(id) \ + CTL_CODE(FILE_DEVICE_USB, (id), METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_USB_GET_NODE_INFORMATION \ + USB_CTL_CODE(USB_GET_NODE_INFORMATION) + +#define IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION \ + USB_CTL_CODE(USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION) + +#define IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX \ + USB_CTL_CODE(USB_GET_NODE_CONNECTION_INFORMATION_EX) + +#define IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 \ + USB_CTL_CODE(USB_GET_NODE_CONNECTION_INFORMATION_EX_V2) + +typedef enum _USB_CONNECTION_STATUS { + NoDeviceConnected, + DeviceConnected, + DeviceFailedEnumeration, + DeviceGeneralFailure, + DeviceCausedOvercurrent, + DeviceNotEnoughPower, + DeviceNotEnoughBandwidth, + DeviceHubNestedTooDeeply, + DeviceInLegacyHub, + DeviceEnumerating, + DeviceReset +} USB_CONNECTION_STATUS; + +typedef enum _USB_DEVICE_SPEED { + UsbLowSpeed = 0, + UsbFullSpeed, + UsbHighSpeed, + UsbSuperSpeed, + UsbSuperSpeedPlus // Not in Microsoft headers +} USB_DEVICE_SPEED; + +typedef enum _USB_HUB_NODE { + UsbHub, + UsbMIParent +} USB_HUB_NODE; + +#if defined(_MSC_VER) +// disable /W4 MSVC warnings that are benign +#pragma warning(push) +#pragma warning(disable:4214) // bit field types other than int +#endif + +// Most of the structures below need to be packed +#include <pshpack1.h> + +typedef struct _USB_HUB_DESCRIPTOR { + UCHAR bDescriptorLength; + UCHAR bDescriptorType; + UCHAR bNumberOfPorts; + USHORT wHubCharacteristics; + UCHAR bPowerOnToPowerGood; + UCHAR bHubControlCurrent; + UCHAR bRemoveAndPowerMask[64]; +} USB_HUB_DESCRIPTOR, *PUSB_HUB_DESCRIPTOR; + +typedef struct _USB_HUB_INFORMATION { + USB_HUB_DESCRIPTOR HubDescriptor; + BOOLEAN HubIsBusPowered; +} USB_HUB_INFORMATION, *PUSB_HUB_INFORMATION; + +typedef struct _USB_NODE_INFORMATION { + USB_HUB_NODE NodeType; + union { + USB_HUB_INFORMATION HubInformation; +// USB_MI_PARENT_INFORMATION MiParentInformation; + } u; +} USB_NODE_INFORMATION, *PUSB_NODE_INFORMATION; + +typedef struct _USB_DESCRIPTOR_REQUEST { + ULONG ConnectionIndex; + struct { + UCHAR bmRequest; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + } SetupPacket; +// UCHAR Data[0]; +} USB_DESCRIPTOR_REQUEST, *PUSB_DESCRIPTOR_REQUEST; + +typedef struct _USB_CONFIGURATION_DESCRIPTOR_SHORT { + USB_DESCRIPTOR_REQUEST req; + USB_CONFIGURATION_DESCRIPTOR desc; +} USB_CONFIGURATION_DESCRIPTOR_SHORT; + +typedef struct USB_INTERFACE_DESCRIPTOR { + UCHAR bLength; + UCHAR bDescriptorType; + UCHAR bInterfaceNumber; + UCHAR bAlternateSetting; + UCHAR bNumEndpoints; + UCHAR bInterfaceClass; + UCHAR bInterfaceSubClass; + UCHAR bInterfaceProtocol; + UCHAR iInterface; +} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR; + +typedef struct _USB_NODE_CONNECTION_INFORMATION_EX { + ULONG ConnectionIndex; + USB_DEVICE_DESCRIPTOR DeviceDescriptor; + UCHAR CurrentConfigurationValue; + UCHAR Speed; + BOOLEAN DeviceIsHub; + USHORT DeviceAddress; + ULONG NumberOfOpenPipes; + USB_CONNECTION_STATUS ConnectionStatus; +// USB_PIPE_INFO PipeList[0]; +} USB_NODE_CONNECTION_INFORMATION_EX, *PUSB_NODE_CONNECTION_INFORMATION_EX; + +typedef union _USB_PROTOCOLS { + ULONG ul; + struct { + ULONG Usb110:1; + ULONG Usb200:1; + ULONG Usb300:1; + ULONG ReservedMBZ:29; + }; +} USB_PROTOCOLS, *PUSB_PROTOCOLS; + +typedef union _USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS { + ULONG ul; + struct { + ULONG DeviceIsOperatingAtSuperSpeedOrHigher:1; + ULONG DeviceIsSuperSpeedCapableOrHigher:1; + ULONG DeviceIsOperatingAtSuperSpeedPlusOrHigher:1; + ULONG DeviceIsSuperSpeedPlusCapableOrHigher:1; + ULONG ReservedMBZ:28; + }; +} USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS, *PUSB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS; + +typedef struct _USB_NODE_CONNECTION_INFORMATION_EX_V2 { + ULONG ConnectionIndex; + ULONG Length; + USB_PROTOCOLS SupportedUsbProtocols; + USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS Flags; +} USB_NODE_CONNECTION_INFORMATION_EX_V2, *PUSB_NODE_CONNECTION_INFORMATION_EX_V2; + +#include <poppack.h> + +#if defined(_MSC_VER) +// Restore original warnings +#pragma warning(pop) +#endif + +/* winusb.dll interface */ + +/* pipe policies */ +#define SHORT_PACKET_TERMINATE 0x01 +#define AUTO_CLEAR_STALL 0x02 +#define PIPE_TRANSFER_TIMEOUT 0x03 +#define IGNORE_SHORT_PACKETS 0x04 +#define ALLOW_PARTIAL_READS 0x05 +#define AUTO_FLUSH 0x06 +#define RAW_IO 0x07 +#define MAXIMUM_TRANSFER_SIZE 0x08 +/* libusbK */ +#define ISO_ALWAYS_START_ASAP 0x21 + +typedef struct _USBD_ISO_PACKET_DESCRIPTOR { + ULONG Offset; + ULONG Length; + USBD_STATUS Status; +} USBD_ISO_PACKET_DESCRIPTOR, *PUSBD_ISO_PACKET_DESCRIPTOR; + +typedef enum _USBD_PIPE_TYPE { + UsbdPipeTypeControl, + UsbdPipeTypeIsochronous, + UsbdPipeTypeBulk, + UsbdPipeTypeInterrupt +} USBD_PIPE_TYPE; + +typedef struct { + USBD_PIPE_TYPE PipeType; + UCHAR PipeId; + USHORT MaximumPacketSize; + UCHAR Interval; + ULONG MaximumBytesPerInterval; +} WINUSB_PIPE_INFORMATION_EX, *PWINUSB_PIPE_INFORMATION_EX; + +#include <pshpack1.h> + +typedef struct _WINUSB_SETUP_PACKET { + UCHAR RequestType; + UCHAR Request; + USHORT Value; + USHORT Index; + USHORT Length; +} WINUSB_SETUP_PACKET, *PWINUSB_SETUP_PACKET; + +#include <poppack.h> + +typedef PVOID WINUSB_INTERFACE_HANDLE, *PWINUSB_INTERFACE_HANDLE; +typedef PVOID WINUSB_ISOCH_BUFFER_HANDLE, *PWINUSB_ISOCH_BUFFER_HANDLE; + +typedef BOOL (WINAPI *WinUsb_AbortPipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID +); +typedef BOOL (WINAPI *WinUsb_ControlTransfer_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + WINUSB_SETUP_PACKET SetupPacket, + PUCHAR Buffer, + ULONG BufferLength, + PULONG LengthTransferred, + LPOVERLAPPED Overlapped +); +typedef BOOL (WINAPI *WinUsb_FlushPipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID +); +typedef BOOL (WINAPI *WinUsb_Free_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle +); +typedef BOOL (WINAPI *WinUsb_GetAssociatedInterface_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR AssociatedInterfaceIndex, + PWINUSB_INTERFACE_HANDLE AssociatedInterfaceHandle +); +typedef BOOL (WINAPI *WinUsb_Initialize_t)( + HANDLE DeviceHandle, + PWINUSB_INTERFACE_HANDLE InterfaceHandle +); +typedef BOOL (WINAPI *WinUsb_QueryPipeEx_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR AlternateInterfaceHandle, + UCHAR PipeIndex, + PWINUSB_PIPE_INFORMATION_EX PipeInformationEx +); +typedef BOOL (WINAPI *WinUsb_ReadIsochPipeAsap_t)( + PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle, + ULONG Offset, + ULONG Length, + BOOL ContinueStream, + ULONG NumberOfPackets, + PUSBD_ISO_PACKET_DESCRIPTOR IsoPacketDescriptors, + LPOVERLAPPED Overlapped +); +typedef BOOL (WINAPI *WinUsb_ReadPipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID, + PUCHAR Buffer, + ULONG BufferLength, + PULONG LengthTransferred, + LPOVERLAPPED Overlapped +); +typedef BOOL (WINAPI *WinUsb_RegisterIsochBuffer_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID, + PVOID Buffer, + ULONG BufferLength, + PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle +); +typedef BOOL (WINAPI *WinUsb_ResetPipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID +); +typedef BOOL (WINAPI *WinUsb_SetCurrentAlternateSetting_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR AlternateSetting +); +typedef BOOL (WINAPI *WinUsb_SetPipePolicy_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID, + ULONG PolicyType, + ULONG ValueLength, + PVOID Value +); +typedef BOOL (WINAPI *WinUsb_UnregisterIsochBuffer_t)( + WINUSB_ISOCH_BUFFER_HANDLE BufferHandle +); +typedef BOOL (WINAPI *WinUsb_WriteIsochPipeAsap_t)( + WINUSB_ISOCH_BUFFER_HANDLE BufferHandle, + ULONG Offset, + ULONG Length, + BOOL ContinueStream, + LPOVERLAPPED Overlapped +); +typedef BOOL (WINAPI *WinUsb_WritePipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID, + PUCHAR Buffer, + ULONG BufferLength, + PULONG LengthTransferred, + LPOVERLAPPED Overlapped +); + +/* /!\ These must match the ones from the official libusbk.h */ +typedef enum _KUSB_FNID { + KUSB_FNID_Init, + KUSB_FNID_Free, + KUSB_FNID_ClaimInterface, + KUSB_FNID_ReleaseInterface, + KUSB_FNID_SetAltInterface, + KUSB_FNID_GetAltInterface, + KUSB_FNID_GetDescriptor, + KUSB_FNID_ControlTransfer, + KUSB_FNID_SetPowerPolicy, + KUSB_FNID_GetPowerPolicy, + KUSB_FNID_SetConfiguration, + KUSB_FNID_GetConfiguration, + KUSB_FNID_ResetDevice, + KUSB_FNID_Initialize, + KUSB_FNID_SelectInterface, + KUSB_FNID_GetAssociatedInterface, + KUSB_FNID_Clone, + KUSB_FNID_QueryInterfaceSettings, + KUSB_FNID_QueryDeviceInformation, + KUSB_FNID_SetCurrentAlternateSetting, + KUSB_FNID_GetCurrentAlternateSetting, + KUSB_FNID_QueryPipe, + KUSB_FNID_SetPipePolicy, + KUSB_FNID_GetPipePolicy, + KUSB_FNID_ReadPipe, + KUSB_FNID_WritePipe, + KUSB_FNID_ResetPipe, + KUSB_FNID_AbortPipe, + KUSB_FNID_FlushPipe, + KUSB_FNID_IsoReadPipe, + KUSB_FNID_IsoWritePipe, + KUSB_FNID_GetCurrentFrameNumber, + KUSB_FNID_GetOverlappedResult, + KUSB_FNID_GetProperty, + KUSB_FNID_COUNT, +} KUSB_FNID; + +typedef struct _KLIB_VERSION { + INT Major; + INT Minor; + INT Micro; + INT Nano; +} KLIB_VERSION, *PKLIB_VERSION; + +typedef BOOL (WINAPI *LibK_GetProcAddress_t)( + PVOID ProcAddress, + INT DriverID, + INT FunctionID +); + +typedef VOID (WINAPI *LibK_GetVersion_t)( + PKLIB_VERSION Version +); + +typedef BOOL (WINAPI *LibK_ResetDevice_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle +); + +//KISO_PACKET is equivalent of libusb_iso_packet_descriptor except uses absolute "offset" field instead of sequential Lengths +typedef struct _KISO_PACKET { + UINT offset; + USHORT actual_length; //changed from libusbk_shared.h "Length" for clarity + USHORT status; +} KISO_PACKET, *PKISO_PACKET; + +typedef enum _KISO_FLAG { + KISO_FLAG_NONE = 0, + KISO_FLAG_SET_START_FRAME = 0x00000001, +} KISO_FLAG; + +//KISO_CONTEXT is the conceptual equivalent of libusb_transfer except is isochronous-specific and must match libusbk's version +typedef struct _KISO_CONTEXT { + KISO_FLAG Flags; + UINT StartFrame; + SHORT ErrorCount; + SHORT NumberOfPackets; + UINT UrbHdrStatus; + KISO_PACKET IsoPackets[0]; +} KISO_CONTEXT, *PKISO_CONTEXT; + +typedef BOOL(WINAPI *LibK_IsoReadPipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID, + PUCHAR Buffer, + ULONG BufferLength, + LPOVERLAPPED Overlapped, + PKISO_CONTEXT IsoContext +); + +typedef BOOL(WINAPI *LibK_IsoWritePipe_t)( + WINUSB_INTERFACE_HANDLE InterfaceHandle, + UCHAR PipeID, + PUCHAR Buffer, + ULONG BufferLength, + LPOVERLAPPED Overlapped, + PKISO_CONTEXT IsoContext +); + +struct winusb_interface { + HMODULE hDll; + WinUsb_AbortPipe_t AbortPipe; + WinUsb_ControlTransfer_t ControlTransfer; + WinUsb_FlushPipe_t FlushPipe; + WinUsb_Free_t Free; + WinUsb_GetAssociatedInterface_t GetAssociatedInterface; + WinUsb_Initialize_t Initialize; + WinUsb_ReadPipe_t ReadPipe; + WinUsb_ResetPipe_t ResetPipe; + WinUsb_SetCurrentAlternateSetting_t SetCurrentAlternateSetting; + WinUsb_SetPipePolicy_t SetPipePolicy; + WinUsb_WritePipe_t WritePipe; + union { + struct { + // Isochoronous functions for libusbK sub api: + LibK_IsoReadPipe_t IsoReadPipe; + LibK_IsoWritePipe_t IsoWritePipe; + // Reset device function for libusbK sub api: + LibK_ResetDevice_t ResetDevice; + }; + struct { + // Isochronous functions for WinUSB sub api: + WinUsb_QueryPipeEx_t QueryPipeEx; + WinUsb_ReadIsochPipeAsap_t ReadIsochPipeAsap; + WinUsb_RegisterIsochBuffer_t RegisterIsochBuffer; + WinUsb_UnregisterIsochBuffer_t UnregisterIsochBuffer; + WinUsb_WriteIsochPipeAsap_t WriteIsochPipeAsap; + }; + }; +}; + +/* hid.dll interface */ + +#define HIDP_STATUS_SUCCESS 0x110000 +typedef void * PHIDP_PREPARSED_DATA; + +#include <pshpack1.h> + +typedef struct _HIDD_ATTIRBUTES { + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; +} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + +#include <poppack.h> + +typedef USHORT USAGE; +typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT NumberLinkCollectionNodes; + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + +typedef enum _HIDP_REPORT_TYPE { + HidP_Input, + HidP_Output, + HidP_Feature +} HIDP_REPORT_TYPE; + +typedef struct _HIDP_VALUE_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + BOOLEAN HasNull; + UCHAR Reserved; + USHORT BitSize; + USHORT ReportCount; + USHORT Reserved2[5]; + ULONG UnitsExp; + ULONG Units; + LONG LogicalMin, LogicalMax; + LONG PhysicalMin, PhysicalMax; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + } u; +} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS; + +DLL_DECLARE_HANDLE(hid); +DLL_DECLARE_FUNC(WINAPI, VOID, HidD_GetHidGuid, (LPGUID)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetAttributes, (HANDLE, PHIDD_ATTRIBUTES)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetPreparsedData, (HANDLE, PHIDP_PREPARSED_DATA *)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_FreePreparsedData, (PHIDP_PREPARSED_DATA)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetManufacturerString, (HANDLE, PVOID, ULONG)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetProductString, (HANDLE, PVOID, ULONG)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetSerialNumberString, (HANDLE, PVOID, ULONG)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetIndexedString, (HANDLE, ULONG, PVOID, ULONG)); +DLL_DECLARE_FUNC(WINAPI, LONG, HidP_GetCaps, (PHIDP_PREPARSED_DATA, PHIDP_CAPS)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_SetNumInputBuffers, (HANDLE, ULONG)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_GetPhysicalDescriptor, (HANDLE, PVOID, ULONG)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidD_FlushQueue, (HANDLE)); +DLL_DECLARE_FUNC(WINAPI, BOOL, HidP_GetValueCaps, (HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PULONG, PHIDP_PREPARSED_DATA)); + +#endif |