From 696f50906f0455312e3c5e4b0d7130782ffd7b3a Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Jun 2022 13:59:45 +0200 Subject: [PATCH 01/17] update hidapi to v0.12.0 and libusb to v1.0.26 --- hidapi/hidapi/hidapi.h | 32 +- hidapi/libusb/hid.c | 15 +- hidapi/libusb/hidapi_libusb.h | 2 + hidapi/linux/hid.c | 9 +- hidapi/mac/hid.c | 140 ++-- hidapi/mac/hidapi_darwin.h | 98 +++ hidapi/windows/hid.c | 927 ++++++++++++------------- hidapi/windows/hidapi_cfgmgr32.h | 69 ++ hidapi/windows/hidapi_hidclass.h | 38 + hidapi/windows/hidapi_hidpi.h | 65 ++ hidapi/windows/hidapi_hidsdi.h | 60 ++ hidapi/windows/hidapi_winapi.h | 58 ++ libusb/AUTHORS | 39 +- libusb/libusb/core.c | 454 ++++++------ libusb/libusb/descriptor.c | 26 +- libusb/libusb/hotplug.c | 256 ++++--- libusb/libusb/io.c | 146 ++-- libusb/libusb/libusb.h | 55 +- libusb/libusb/libusbi.h | 153 +++- libusb/libusb/os/darwin_usb.c | 668 ++++++++++++++---- libusb/libusb/os/darwin_usb.h | 52 +- libusb/libusb/os/events_posix.c | 6 +- libusb/libusb/os/events_windows.c | 4 +- libusb/libusb/os/haiku_pollfs.cpp | 10 +- libusb/libusb/os/haiku_usb_backend.cpp | 6 +- libusb/libusb/os/linux_netlink.c | 18 +- libusb/libusb/os/linux_udev.c | 8 +- libusb/libusb/os/linux_usbfs.c | 269 +++---- libusb/libusb/os/netbsd_usb.c | 40 +- libusb/libusb/os/openbsd_usb.c | 38 +- libusb/libusb/os/sunos_usb.c | 218 +++--- libusb/libusb/os/windows_common.c | 155 +++-- libusb/libusb/os/windows_common.h | 35 +- libusb/libusb/os/windows_usbdk.c | 3 +- libusb/libusb/os/windows_winusb.c | 475 ++++++++----- libusb/libusb/os/windows_winusb.h | 69 +- libusb/libusb/sync.c | 2 +- libusb/libusb/version.h | 2 +- libusb/libusb/version_nano.h | 2 +- 39 files changed, 2950 insertions(+), 1772 deletions(-) create mode 100644 hidapi/mac/hidapi_darwin.h create mode 100644 hidapi/windows/hidapi_cfgmgr32.h create mode 100644 hidapi/windows/hidapi_hidclass.h create mode 100644 hidapi/windows/hidapi_hidpi.h create mode 100644 hidapi/windows/hidapi_hidsdi.h create mode 100644 hidapi/windows/hidapi_winapi.h diff --git a/hidapi/hidapi/hidapi.h b/hidapi/hidapi/hidapi.h index 3a2dab4..959c912 100644 --- a/hidapi/hidapi/hidapi.h +++ b/hidapi/hidapi/hidapi.h @@ -5,9 +5,9 @@ Alan Ott Signal 11 Software - 8/22/2009 + libusb/hidapi Team - Copyright 2009, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -48,18 +48,40 @@ @ingroup API */ -#define HID_API_VERSION_MINOR 11 +#define HID_API_VERSION_MINOR 12 /** @brief Static/compile-time patch version of the library. @ingroup API */ -#define HID_API_VERSION_PATCH 2 +#define HID_API_VERSION_PATCH 0 /* Helper macros */ #define HID_API_AS_STR_IMPL(x) #x #define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) #define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) +/** @brief Coverts a version as Major/Minor/Patch into a number: + <8 bit major><16 bit minor><8 bit patch>. + + This macro was added in version 0.12.0. + + Convenient function to be used for compile-time checks, like: + #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API +*/ +#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p)) + +/** @brief Static/compile-time version of the library. + + This macro was added in version 0.12.0. + + @see @ref HID_API_MAKE_VERSION. + + @ingroup API +*/ +#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + /** @brief Static/compile-time string version of the library. @ingroup API @@ -369,6 +391,8 @@ extern "C" { /** @brief Get a input report from a HID device. + Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) + Set the first byte of @p data[] to the Report ID of the report to be read. Make sure to allow space for this extra byte in @p data[]. Upon return, the first byte will diff --git a/hidapi/libusb/hid.c b/hidapi/libusb/hid.c index c9e86c0..3103f03 100644 --- a/hidapi/libusb/hid.c +++ b/hidapi/libusb/hid.c @@ -5,12 +5,9 @@ Alan Ott Signal 11 Software - 8/22/2009 - Linux Version - 6/2/2010 - Libusb Version - 8/13/2010 - FreeBSD Version - 11/1/2011 + libusb/hidapi Team - Copyright 2009, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -47,6 +44,9 @@ #include #if !defined(__ANDROID__) && !defined(NO_ICONV) #include +#ifndef ICONV_CONST +#define ICONV_CONST +#endif #endif #include "hidapi_libusb.h" @@ -408,7 +408,7 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) size_t inbytes; size_t outbytes; size_t res; - char *inptr; + ICONV_CONST char *inptr; char *outptr; #endif @@ -424,7 +424,7 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) lang, (unsigned char*)buf, sizeof(buf)); - if (len < 0) + if (len < 2) /* we always skip first 2 bytes */ return NULL; #if defined(__ANDROID__) || defined(NO_ICONV) @@ -1411,6 +1411,7 @@ void HID_API_EXPORT hid_close(hid_device *dev) /* Clean up the Transfer objects allocated in read_thread(). */ free(dev->transfer->buffer); + dev->transfer->buffer = NULL; libusb_free_transfer(dev->transfer); /* release the interface */ diff --git a/hidapi/libusb/hidapi_libusb.h b/hidapi/libusb/hidapi_libusb.h index 1f56c2c..2920768 100644 --- a/hidapi/libusb/hidapi_libusb.h +++ b/hidapi/libusb/hidapi_libusb.h @@ -19,6 +19,8 @@ /** @file * @defgroup API hidapi API + + * Since version 0.11.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 11, 0). */ #ifndef HIDAPI_LIBUSB_H__ diff --git a/hidapi/linux/hid.c b/hidapi/linux/hid.c index c211fae..1d79117 100644 --- a/hidapi/linux/hid.c +++ b/hidapi/linux/hid.c @@ -5,10 +5,9 @@ Alan Ott Signal 11 Software - 8/22/2009 - Linux Version - 6/2/2009 + libusb/hidapi Team - Copyright 2009, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -271,7 +270,7 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) return 0; /* malformed report */ - /* Skip over this key and it's associated data */ + /* Skip over this key and its associated data */ i += data_len + key_size; } @@ -368,7 +367,7 @@ static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int break; } - /* Skip over this key and it's associated data */ + /* Skip over this key and its associated data */ *pos += data_len + key_size; /* Return usage pair */ diff --git a/hidapi/mac/hid.c b/hidapi/mac/hid.c index 12648d9..d78ac9b 100644 --- a/hidapi/mac/hid.c +++ b/hidapi/mac/hid.c @@ -5,9 +5,9 @@ Alan Ott Signal 11 Software - 2010-07-03 + libusb/hidapi Team - Copyright 2010, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -34,7 +34,7 @@ #include #include -#include "hidapi.h" +#include "hidapi_darwin.h" /* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */ extern const double NSAppKitVersionNumber; @@ -108,8 +108,21 @@ struct input_report { struct input_report *next; }; +static struct hid_api_version api_version = { + .major = HID_API_VERSION_MAJOR, + .minor = HID_API_VERSION_MINOR, + .patch = HID_API_VERSION_PATCH +}; + +/* - Run context - */ +static IOHIDManagerRef hid_mgr = 0x0; +static int is_macos_10_10_or_greater = 0; +static IOOptionBits device_open_options = 0; +/* --- */ + struct hid_device_ { IOHIDDeviceRef device_handle; + IOOptionBits open_options; int blocking; int uses_numbered_reports; int disconnected; @@ -132,6 +145,7 @@ static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); dev->device_handle = NULL; + dev->open_options = device_open_options; dev->blocking = 1; dev->uses_numbered_reports = 0; dev->disconnected = 0; @@ -184,23 +198,6 @@ static void free_hid_device(hid_device *dev) free(dev); } -static struct hid_api_version api_version = { - .major = HID_API_VERSION_MAJOR, - .minor = HID_API_VERSION_MINOR, - .patch = HID_API_VERSION_PATCH -}; - -static IOHIDManagerRef hid_mgr = 0x0; -static int is_macos_10_10_or_greater = 0; - - -#if 0 -static void register_error(hid_device *dev, const char *op) -{ - -} -#endif - static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref = IOHIDDeviceGetProperty(device, key); @@ -345,6 +342,7 @@ int HID_API_EXPORT hid_init(void) { if (!hid_mgr) { is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ + hid_darwin_set_open_exclusive(1); /* Backward compatibility */ return init_hid_manager(); } @@ -381,7 +379,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, struct hid_device_info *cur_dev; io_object_t iokit_dev; kern_return_t res; - uint64_t entry_id; + uint64_t entry_id = 0; if (dev == NULL) { return NULL; @@ -792,12 +790,12 @@ static io_registry_entry_t hid_open_service_registry_from_path(const char *path) char *endptr; uint64_t entry_id = strtoull(path + 10, &endptr, 10); if (*endptr == '\0') { - return IOServiceGetMatchingService(kIOMasterPortDefault, IORegistryEntryIDMatching(entry_id)); + return IOServiceGetMatchingService((mach_port_t) 0, IORegistryEntryIDMatching(entry_id)); } } else { /* Fallback to older format of the path */ - return IORegistryEntryFromPath(kIOMasterPortDefault, path); + return IORegistryEntryFromPath((mach_port_t) 0, path); } return MACH_PORT_NULL; @@ -830,7 +828,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) } /* Open the IOHIDDevice */ - ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); + ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); if (ret == kIOReturnSuccess) { char str[32]; @@ -905,7 +903,7 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char data_to_send, length_to_send); if (res == kIOReturnSuccess) { - return length; + return (int) length; } return -1; @@ -939,7 +937,7 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data if (report_id == 0x0) { /* 0 report number still present at the beginning */ report_length++; } - return report_length; + return (int) report_length; } return -1; @@ -961,7 +959,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) dev->input_reports = rpt->next; free(rpt->data); free(rpt); - return len; + return (int) len; } static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) @@ -1147,7 +1145,7 @@ void HID_API_EXPORT hid_close(hid_device *dev) Not leaking a resource in all tested environments. */ if (is_macos_10_10_or_greater || !dev->disconnected) { - IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); + IOHIDDeviceClose(dev->device_handle, dev->open_options); } /* Clear out the queue of received reports. */ @@ -1188,85 +1186,39 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index return 0; } - -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id) { - (void) dev; - /* TODO: */ - - return L"hid_error is not implemented yet"; + int res = get_int_property(dev->device_handle, CFSTR(kIOHIDLocationIDKey)); + if (res != 0) { + *location_id = (uint32_t) res; + return 0; + } else { + return -1; + } } - - - - - - -#if 0 -static int32_t get_location_id(IOHIDDeviceRef device) +void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive) { - return get_int_property(device, CFSTR(kIOHIDLocationIDKey)); + device_open_options = (open_exclusive == 0) ? kIOHIDOptionsTypeNone : kIOHIDOptionsTypeSeizeDevice; } -static int32_t get_usage(IOHIDDeviceRef device) +int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void) { - int32_t res; - res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); - if (!res) - res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); - return res; + return (device_open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; } -static int32_t get_usage_page(IOHIDDeviceRef device) +int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) { - int32_t res; - res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); - if (!res) - res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); - return res; -} + if (!dev) + return -1; -static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len) -{ - return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len); + return (dev->open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; } - -int main(void) +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - IOHIDManagerRef mgr; - int i; - - mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - IOHIDManagerSetDeviceMatching(mgr, NULL); - IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone); - - CFSetRef device_set = IOHIDManagerCopyDevices(mgr); - - CFIndex num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); - - for (i = 0; i < num_devices; i++) { - IOHIDDeviceRef dev = device_array[i]; - printf("Device: %p\n", dev); - printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev)); - - wchar_t serial[256], buf[256]; - char cbuf[256]; - get_serial_number(dev, serial, 256); - - - printf(" Serial: %ls\n", serial); - printf(" Loc: %ld\n", get_location_id(dev)); - get_transport(dev, buf, 256); - printf(" Trans: %ls\n", buf); - make_path(dev, cbuf, 256); - printf(" Path: %s\n", cbuf); - - } + (void) dev; + /* TODO: */ - return 0; + return L"hid_error is not implemented yet"; } -#endif diff --git a/hidapi/mac/hidapi_darwin.h b/hidapi/mac/hidapi_darwin.h new file mode 100644 index 0000000..34c30a0 --- /dev/null +++ b/hidapi/mac/hidapi_darwin.h @@ -0,0 +1,98 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + + * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + */ + +#ifndef HIDAPI_DARWIN_H__ +#define HIDAPI_DARWIN_H__ + +#include + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** @brief Get the location ID for a HID device. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + @param location_id The device's location ID on return. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id); + + + /** @brief Changes the behavior of all further calls to @ref hid_open or @ref hid_open_path. + + By default on Darwin platform all devices opened by HIDAPI with @ref hid_open or @ref hid_open_path + are opened in exclusive mode (see kIOHIDOptionsTypeSeizeDevice). + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @param open_exclusive When set to 0 - all further devices will be opened + in non-exclusive mode. Otherwise - all further devices will be opened + in exclusive mode. + + @note During the initialisation by @ref hid_init - this property is set to 1 (TRUE). + This is done to preserve full backward compatibility with previous behavior. + + @note Calling this function before @ref hid_init or after @ref hid_exit has no effect. + */ + void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive); + + /** @brief Getter for option set by @ref hid_darwin_set_open_exclusive. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @return 1 if all further devices will be opened in exclusive mode. + + @note Value returned by this function before calling to @ref hid_init or after @ref hid_exit + is not reliable. + */ + int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void); + + /** @brief Check how the device was opened. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + @ingroup API + @param dev A device to get property from. + + @return 1 if the device is opened in exclusive mode, 0 - opened in non-exclusive, + -1 - if dev is invalid. + */ + int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hidapi/windows/hid.c b/hidapi/windows/hid.c index 9ed9870..da47ff6 100644 --- a/hidapi/windows/hid.c +++ b/hidapi/windows/hid.c @@ -5,9 +5,9 @@ Alan Ott Signal 11 Software - 8/22/2009 + libusb/hidapi Team - Copyright 2009, All Rights Reserved. + Copyright 2022, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -21,11 +21,17 @@ ********************************************************/ #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -// Do not warn about mbsrtowcs and wcsncpy usage. -// https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt +/* Do not warn about wcsncpy usage. + https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */ #define _CRT_SECURE_NO_WARNINGS #endif +#ifdef __cplusplus +extern "C" { +#endif + +#include "hidapi.h" + #include #ifndef _NTDEF_ @@ -33,54 +39,36 @@ typedef LONG NTSTATUS; #endif #ifdef __MINGW32__ -#include #include #include +#define WC_ERR_INVALID_CHARS 0x00000080 #endif #ifdef __CYGWIN__ #include +#include #define _wcsdup wcsdup #endif -/* The maximum number of characters that can be passed into the - HidD_Get*String() functions without it failing.*/ -#define MAX_STRING_WCHARS 0xFFF - /*#define HIDAPI_USE_DDK*/ -#ifdef __cplusplus -extern "C" { -#endif - #include - #include - #ifdef HIDAPI_USE_DDK - #include - #endif - - /* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */ - #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) - -#ifdef __cplusplus -} /* extern "C" */ -#endif +#include +#include "hidapi_cfgmgr32.h" +#include "hidapi_hidclass.h" +#include "hidapi_hidsdi.h" #include #include #include -#include - -#include "hidapi.h" +#ifdef MIN #undef MIN +#endif #define MIN(x,y) ((x) < (y)? (x): (y)) -#ifdef __cplusplus -extern "C" { -#endif +/* MAXIMUM_USB_STRING_LENGTH from usbspec.h is 255 */ +/* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */ +#define MAX_STRING_WCHARS 256 static struct hid_api_version api_version = { .major = HID_API_VERSION_MAJOR, @@ -89,88 +77,97 @@ static struct hid_api_version api_version = { }; #ifndef HIDAPI_USE_DDK - /* Since we're not building with the DDK, and the HID header - files aren't part of the SDK, we have to define all this - stuff here. In lookup_functions(), the function pointers - defined below are set. */ - typedef struct _HIDD_ATTRIBUTES{ - ULONG Size; - USHORT VendorID; - USHORT ProductID; - USHORT VersionNumber; - } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; - - typedef USHORT USAGE; - typedef struct _HIDP_CAPS { - USAGE Usage; - USAGE UsagePage; - USHORT InputReportByteLength; - USHORT OutputReportByteLength; - USHORT FeatureReportByteLength; - USHORT Reserved[17]; - USHORT fields_not_used_by_hidapi[10]; - } HIDP_CAPS, *PHIDP_CAPS; - typedef void* PHIDP_PREPARSED_DATA; - #define HIDP_STATUS_SUCCESS 0x110000 - - typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid); - typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); - typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); - typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); - typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length); - typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data); - typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data); - typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps); - typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); - - static HidD_GetHidGuid_ HidD_GetHidGuid; - static HidD_GetAttributes_ HidD_GetAttributes; - static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; - static HidD_GetManufacturerString_ HidD_GetManufacturerString; - static HidD_GetProductString_ HidD_GetProductString; - static HidD_SetFeature_ HidD_SetFeature; - static HidD_GetFeature_ HidD_GetFeature; - static HidD_GetInputReport_ HidD_GetInputReport; - static HidD_GetIndexedString_ HidD_GetIndexedString; - static HidD_GetPreparsedData_ HidD_GetPreparsedData; - static HidD_FreePreparsedData_ HidD_FreePreparsedData; - static HidP_GetCaps_ HidP_GetCaps; - static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers; - - static HMODULE lib_handle = NULL; - static BOOLEAN initialized = FALSE; - - typedef DWORD RETURN_TYPE; - typedef RETURN_TYPE CONFIGRET; - typedef DWORD DEVNODE, DEVINST; - typedef DEVNODE* PDEVNODE, * PDEVINST; - typedef WCHAR* DEVNODEID_W, * DEVINSTID_W; - -#define CR_SUCCESS (0x00000000) -#define CR_BUFFER_SMALL (0x0000001A) - -#define CM_LOCATE_DEVNODE_NORMAL 0x00000000 - -#define DEVPROP_TYPEMOD_LIST 0x00002000 - -#define DEVPROP_TYPE_STRING 0x00000012 -#define DEVPROP_TYPE_STRING_LIST (DEVPROP_TYPE_STRING|DEVPROP_TYPEMOD_LIST) - - typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags); - typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); - typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); - typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); - - static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL; - static CM_Get_Parent_ CM_Get_Parent = NULL; - static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; - static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; - - static HMODULE cfgmgr32_lib_handle = NULL; +/* Since we're not building with the DDK, and the HID header + files aren't part of the Windows SDK, we define what we need ourselves. + In lookup_functions(), the function pointers + defined below are set. */ + +static HidD_GetHidGuid_ HidD_GetHidGuid; +static HidD_GetAttributes_ HidD_GetAttributes; +static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; +static HidD_GetManufacturerString_ HidD_GetManufacturerString; +static HidD_GetProductString_ HidD_GetProductString; +static HidD_SetFeature_ HidD_SetFeature; +static HidD_GetFeature_ HidD_GetFeature; +static HidD_GetInputReport_ HidD_GetInputReport; +static HidD_GetIndexedString_ HidD_GetIndexedString; +static HidD_GetPreparsedData_ HidD_GetPreparsedData; +static HidD_FreePreparsedData_ HidD_FreePreparsedData; +static HidP_GetCaps_ HidP_GetCaps; +static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers; + +static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL; +static CM_Get_Parent_ CM_Get_Parent = NULL; +static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; +static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; +static CM_Get_Device_Interface_List_SizeW_ CM_Get_Device_Interface_List_SizeW = NULL; +static CM_Get_Device_Interface_ListW_ CM_Get_Device_Interface_ListW = NULL; + +static HMODULE hid_lib_handle = NULL; +static HMODULE cfgmgr32_lib_handle = NULL; +static BOOLEAN hidapi_initialized = FALSE; + +static void free_library_handles() +{ + if (hid_lib_handle) + FreeLibrary(hid_lib_handle); + hid_lib_handle = NULL; + if (cfgmgr32_lib_handle) + FreeLibrary(cfgmgr32_lib_handle); + cfgmgr32_lib_handle = NULL; +} + +static int lookup_functions() +{ + hid_lib_handle = LoadLibraryW(L"hid.dll"); + if (hid_lib_handle == NULL) { + goto err; + } + + cfgmgr32_lib_handle = LoadLibraryW(L"cfgmgr32.dll"); + if (cfgmgr32_lib_handle == NULL) { + goto err; + } + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +#endif +#define RESOLVE(lib_handle, x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) goto err; + + RESOLVE(hid_lib_handle, HidD_GetHidGuid); + RESOLVE(hid_lib_handle, HidD_GetAttributes); + RESOLVE(hid_lib_handle, HidD_GetSerialNumberString); + RESOLVE(hid_lib_handle, HidD_GetManufacturerString); + RESOLVE(hid_lib_handle, HidD_GetProductString); + RESOLVE(hid_lib_handle, HidD_SetFeature); + RESOLVE(hid_lib_handle, HidD_GetFeature); + RESOLVE(hid_lib_handle, HidD_GetInputReport); + RESOLVE(hid_lib_handle, HidD_GetIndexedString); + RESOLVE(hid_lib_handle, HidD_GetPreparsedData); + RESOLVE(hid_lib_handle, HidD_FreePreparsedData); + RESOLVE(hid_lib_handle, HidP_GetCaps); + RESOLVE(hid_lib_handle, HidD_SetNumInputBuffers); + + RESOLVE(cfgmgr32_lib_handle, CM_Locate_DevNodeW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Parent); + RESOLVE(cfgmgr32_lib_handle, CM_Get_DevNode_PropertyW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_PropertyW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_List_SizeW); + RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_ListW); + +#undef RESOLVE +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + return 0; + +err: + free_library_handles(); + return -1; +} + #endif /* HIDAPI_USE_DDK */ struct hid_device_ { @@ -181,8 +178,7 @@ struct hid_device_ { size_t input_report_length; USHORT feature_report_length; unsigned char *feature_buf; - void *last_error_str; - DWORD last_error_num; + wchar_t *last_error_str; BOOL read_pending; char *read_buf; OVERLAPPED ol; @@ -201,7 +197,6 @@ static hid_device *new_hid_device() dev->feature_report_length = 0; dev->feature_buf = NULL; dev->last_error_str = NULL; - dev->last_error_num = 0; dev->read_pending = FALSE; dev->read_buf = NULL; memset(&dev->ol, 0, sizeof(dev->ol)); @@ -218,7 +213,8 @@ static void free_hid_device(hid_device *dev) CloseHandle(dev->ol.hEvent); CloseHandle(dev->write_ol.hEvent); CloseHandle(dev->device_handle); - LocalFree(dev->last_error_str); + free(dev->last_error_str); + dev->last_error_str = NULL; free(dev->write_buf); free(dev->feature_buf); free(dev->read_buf); @@ -226,101 +222,101 @@ static void free_hid_device(hid_device *dev) free(dev); } -static void register_error(hid_device *dev, const char *op) +static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op) { - WCHAR *ptr, *msg; - (void)op; // unreferenced param - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, + if (!error_buffer) + return; + + free(*error_buffer); + *error_buffer = NULL; + + /* Only clear out error messages if NULL is passed into op */ + if (!op) { + return; + } + + WCHAR system_err_buf[1024]; + DWORD error_code = GetLastError(); + + DWORD system_err_len = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, - GetLastError(), + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&msg, 0/*sz*/, + system_err_buf, ARRAYSIZE(system_err_buf), NULL); + DWORD op_len = (DWORD)wcslen(op); + + DWORD op_prefix_len = + op_len + + 15 /*: (0x00000000) */ + ; + DWORD msg_len = + + op_prefix_len + + system_err_len + ; + + *error_buffer = (WCHAR *)calloc(msg_len + 1, sizeof (WCHAR)); + WCHAR *msg = *error_buffer; + + if (!msg) + return; + + int printf_written = swprintf(msg, msg_len + 1, L"%.*ls: (0x%08X) %.*ls", op_len, op, error_code, system_err_len, system_err_buf); + + if (printf_written < 0) + { + /* Highly unlikely */ + msg[0] = L'\0'; + return; + } + /* Get rid of the CR and LF that FormatMessage() sticks at the end of the message. Thanks Microsoft! */ - ptr = msg; - while (*ptr) { - if (*ptr == L'\r') { - *ptr = L'\0'; - break; - } - ptr++; + while(msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ') + { + msg[msg_len-1] = L'\0'; + msg_len--; } +} + +static void register_winapi_error(hid_device *dev, const WCHAR *op) +{ + if (!dev) + return; - /* Store the message off in the Device entry so that - the hid_error() function can pick it up. */ - LocalFree(dev->last_error_str); - dev->last_error_str = msg; + register_winapi_error_to_buffer(&dev->last_error_str, op); } -#ifndef HIDAPI_USE_DDK -static int lookup_functions() +static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error) { - lib_handle = LoadLibraryA("hid.dll"); - if (lib_handle) { -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -#endif -#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; - RESOLVE(HidD_GetHidGuid); - RESOLVE(HidD_GetAttributes); - RESOLVE(HidD_GetSerialNumberString); - RESOLVE(HidD_GetManufacturerString); - RESOLVE(HidD_GetProductString); - RESOLVE(HidD_SetFeature); - RESOLVE(HidD_GetFeature); - RESOLVE(HidD_GetInputReport); - RESOLVE(HidD_GetIndexedString); - RESOLVE(HidD_GetPreparsedData); - RESOLVE(HidD_FreePreparsedData); - RESOLVE(HidP_GetCaps); - RESOLVE(HidD_SetNumInputBuffers); -#undef RESOLVE -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - } - else - return -1; + if (!error_buffer) + return; - cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); - if (cfgmgr32_lib_handle) { -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -#endif -#define RESOLVE(x) x = (x##_)GetProcAddress(cfgmgr32_lib_handle, #x); - RESOLVE(CM_Locate_DevNodeW); - RESOLVE(CM_Get_Parent); - RESOLVE(CM_Get_DevNode_PropertyW); - RESOLVE(CM_Get_Device_Interface_PropertyW); -#undef RESOLVE -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - } - else { - CM_Locate_DevNodeW = NULL; - CM_Get_Parent = NULL; - CM_Get_DevNode_PropertyW = NULL; - CM_Get_Device_Interface_PropertyW = NULL; + free(*error_buffer); + *error_buffer = NULL; + + if (string_error) { + *error_buffer = _wcsdup(string_error); } +} - return 0; +static void register_string_error(hid_device *dev, const WCHAR *string_error) +{ + if (!dev) + return; + + register_string_error_to_buffer(&dev->last_error_str, string_error); } -#endif -static HANDLE open_device(const char *path, BOOL open_rw) +static HANDLE open_device(const wchar_t *path, BOOL open_rw) { HANDLE handle; DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0; DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; - handle = CreateFileA(path, + handle = CreateFileW(path, desired_access, share_mode, NULL, @@ -344,12 +340,11 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str() int HID_API_EXPORT hid_init(void) { #ifndef HIDAPI_USE_DDK - if (!initialized) { + if (!hidapi_initialized) { if (lookup_functions() < 0) { - hid_exit(); return -1; } - initialized = TRUE; + hidapi_initialized = TRUE; } #endif return 0; @@ -358,57 +353,80 @@ int HID_API_EXPORT hid_init(void) int HID_API_EXPORT hid_exit(void) { #ifndef HIDAPI_USE_DDK - if (lib_handle) - FreeLibrary(lib_handle); - lib_handle = NULL; - if (cfgmgr32_lib_handle) - FreeLibrary(cfgmgr32_lib_handle); - cfgmgr32_lib_handle = NULL; - initialized = FALSE; + free_library_handles(); + hidapi_initialized = FALSE; #endif return 0; } -static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) +static void* hid_internal_get_devnode_property(DEVINST dev_node, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type) { - ULONG len; + ULONG len = 0; + CONFIGRET cr; + DEVPROPTYPE property_type; + PBYTE property_value = NULL; + + cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, NULL, &len, 0); + if (cr != CR_BUFFER_SMALL || property_type != expected_property_type) + return NULL; + + property_value = (PBYTE)calloc(len, sizeof(BYTE)); + cr = CM_Get_DevNode_PropertyW(dev_node, property_key, &property_type, property_value, &len, 0); + if (cr != CR_SUCCESS) { + free(property_value); + return NULL; + } + + return property_value; +} + +static void* hid_internal_get_device_interface_property(const wchar_t* interface_path, const DEVPROPKEY* property_key, DEVPROPTYPE expected_property_type) +{ + ULONG len = 0; CONFIGRET cr; DEVPROPTYPE property_type; + PBYTE property_value = NULL; + + cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, NULL, &len, 0); + if (cr != CR_BUFFER_SMALL || property_type != expected_property_type) + return NULL; + + property_value = (PBYTE)calloc(len, sizeof(BYTE)); + cr = CM_Get_Device_Interface_PropertyW(interface_path, property_key, &property_type, property_value, &len, 0); + if (cr != CR_SUCCESS) { + free(property_value); + return NULL; + } - static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac }, 10 }; // DEVPROP_TYPE_STRING - static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 1 }; // DEVPROP_TYPE_STRING - static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 4 }; // DEVPROP_TYPE_STRING + return property_value; +} +static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) +{ + wchar_t *manufacturer_string, *serial_number, *product_string; /* Manufacturer String */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING); + if (manufacturer_string) { free(dev->manufacturer_string); - dev->manufacturer_string = (wchar_t*)calloc(len, sizeof(BYTE)); - CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, (PBYTE)dev->manufacturer_string, &len, 0); + dev->manufacturer_string = manufacturer_string; } /* Serial Number String (MAC Address) */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING); + if (serial_number) { free(dev->serial_number); - dev->serial_number = (wchar_t*)calloc(len, sizeof(BYTE)); - CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, (PBYTE)dev->serial_number, &len, 0); + dev->serial_number = serial_number; } /* Get devnode grandparent to reach out Bluetooth LE device node */ - cr = CM_Get_Parent(&dev_node, dev_node, 0); - if (cr != CR_SUCCESS) + if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS) return; /* Product String */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + product_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING); + if (product_string) { free(dev->product_string); - dev->product_string = (wchar_t*)calloc(len, sizeof(BYTE)); - CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, (PBYTE)dev->product_string, &len, 0); + dev->product_string = product_string; } } @@ -437,43 +455,15 @@ static int hid_internal_get_interface_number(const wchar_t* hardware_id) return interface_number; } -static void hid_internal_get_info(struct hid_device_info* dev) +static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev) { - const char *tmp = NULL; - wchar_t *interface_path = NULL, *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL; - mbstate_t state; - ULONG len; + wchar_t *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL; CONFIGRET cr; - DEVPROPTYPE property_type; DEVINST dev_node; - static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57 }, 256 }; // DEVPROP_TYPE_STRING - static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 3 }; // DEVPROP_TYPE_STRING_LIST - static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 4 }; // DEVPROP_TYPE_STRING_LIST - - if (!CM_Get_Device_Interface_PropertyW || - !CM_Locate_DevNodeW || - !CM_Get_Parent || - !CM_Get_DevNode_PropertyW) - goto end; - - tmp = dev->path; - - len = (ULONG)strlen(tmp); - interface_path = (wchar_t*)calloc(len + 1, sizeof(wchar_t)); - memset(&state, 0, sizeof(state)); - - if (mbsrtowcs(interface_path, &tmp, len, &state) == (size_t)-1) - goto end; - /* Get the device id from interface path */ - len = 0; - cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { - device_id = (wchar_t*)calloc(len, sizeof(BYTE)); - cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, (PBYTE)device_id, &len, 0); - } - if (cr != CR_SUCCESS) + device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); + if (!device_id) goto end; /* Open devnode from device id */ @@ -482,16 +472,11 @@ static void hid_internal_get_info(struct hid_device_info* dev) goto end; /* Get the hardware ids from devnode */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_HardwareIds, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) { - hardware_ids = (wchar_t*)calloc(len, sizeof(BYTE)); - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_HardwareIds, &property_type, (PBYTE)hardware_ids, &len, 0); - } - if (cr != CR_SUCCESS) + hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST); + if (!hardware_ids) goto end; - // Search for interface number in hardware ids + /* Search for interface number in hardware ids */ for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) { /* Normalize to upper case */ for (wchar_t* p = hardware_id; *p; ++p) *p = towupper(*p); @@ -508,13 +493,8 @@ static void hid_internal_get_info(struct hid_device_info* dev) goto end; /* Get the compatible ids from parent devnode */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) { - compatible_ids = (wchar_t*)calloc(len, sizeof(BYTE)); - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, (PBYTE)compatible_ids, &len, 0); - } - if (cr != CR_SUCCESS) + compatible_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_CompatibleIds, DEVPROP_TYPE_STRING_LIST); + if (!compatible_ids) goto end; /* Now we can parse parent's compatible IDs to find out the device bus type */ @@ -532,41 +512,52 @@ static void hid_internal_get_info(struct hid_device_info* dev) } } end: - free(interface_path); free(device_id); free(hardware_ids); free(compatible_ids); } -static struct hid_device_info *hid_get_device_info(const char *path, HANDLE handle) +static char *hid_internal_UTF16toUTF8(const wchar_t *src) { - struct hid_device_info *dev = NULL; /* return object */ + char *dst = NULL; + int len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); + if (len) { + dst = (char*)calloc(len, sizeof(char)); + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dst, len, NULL, NULL); + } - BOOL res; + return dst; +} + +static wchar_t *hid_internal_UTF8toUTF16(const char *src) +{ + wchar_t *dst = NULL; + int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); + if (len) { + dst = (wchar_t*)calloc(len, sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dst, len); + } + + return dst; +} + +static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, HANDLE handle) +{ + struct hid_device_info *dev = NULL; /* return object */ HIDD_ATTRIBUTES attrib; PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps; - - #define WSTR_LEN 512 - wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ + wchar_t string[MAX_STRING_WCHARS]; /* Create the record. */ dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); /* Fill out the record */ dev->next = NULL; - - if (path) { - size_t len = strlen(path); - dev->path = (char*)calloc(len + 1, sizeof(char)); - memcpy(dev->path, path, len + 1); - } - else - dev->path = NULL; + dev->path = hid_internal_UTF16toUTF8(path); attrib.Size = sizeof(HIDD_ATTRIBUTES); - res = HidD_GetAttributes(handle, &attrib); - if (res) { + if (HidD_GetAttributes(handle, &attrib)) { /* VID/PID */ dev->vendor_id = attrib.VendorID; dev->product_id = attrib.ProductID; @@ -576,10 +567,8 @@ static struct hid_device_info *hid_get_device_info(const char *path, HANDLE hand } /* Get the Usage Page and Usage for this device. */ - res = HidD_GetPreparsedData(handle, &pp_data); - if (res) { - NTSTATUS nt_res = HidP_GetCaps(pp_data, &caps); - if (nt_res == HIDP_STATUS_SUCCESS) { + if (HidD_GetPreparsedData(handle, &pp_data)) { + if (HidP_GetCaps(pp_data, &caps) == HIDP_STATUS_SUCCESS) { dev->usage_page = caps.UsagePage; dev->usage = caps.Usage; } @@ -588,42 +577,36 @@ static struct hid_device_info *hid_get_device_info(const char *path, HANDLE hand } /* Serial Number */ - wstr[0] = L'\0'; - res = HidD_GetSerialNumberString(handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN - 1] = L'\0'; - dev->serial_number = _wcsdup(wstr); + string[0] = L'\0'; + HidD_GetSerialNumberString(handle, string, sizeof(string)); + string[MAX_STRING_WCHARS - 1] = L'\0'; + dev->serial_number = _wcsdup(string); /* Manufacturer String */ - wstr[0] = L'\0'; - res = HidD_GetManufacturerString(handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN - 1] = L'\0'; - dev->manufacturer_string = _wcsdup(wstr); + string[0] = L'\0'; + HidD_GetManufacturerString(handle, string, sizeof(string)); + string[MAX_STRING_WCHARS - 1] = L'\0'; + dev->manufacturer_string = _wcsdup(string); /* Product String */ - wstr[0] = L'\0'; - res = HidD_GetProductString(handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN - 1] = L'\0'; - dev->product_string = _wcsdup(wstr); + string[0] = L'\0'; + HidD_GetProductString(handle, string, sizeof(string)); + string[MAX_STRING_WCHARS - 1] = L'\0'; + dev->product_string = _wcsdup(string); - hid_internal_get_info(dev); + hid_internal_get_info(path, dev); return dev; } struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) { - BOOL res; struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; GUID interface_class_guid; - - /* Windows objects for interacting with the driver. */ - SP_DEVINFO_DATA devinfo_data; - SP_DEVICE_INTERFACE_DATA device_interface_data; - SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; - HDEVINFO device_info_set = INVALID_HANDLE_VALUE; - char driver_name[256]; - int device_index = 0; + CONFIGRET cr; + wchar_t* device_interface_list = NULL; + DWORD len; if (hid_init() < 0) return NULL; @@ -632,92 +615,49 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ HidD_GetHidGuid(&interface_class_guid); - /* Initialize the Windows objects. */ - memset(&devinfo_data, 0x0, sizeof(devinfo_data)); - devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); - device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - - /* Get information for all the devices belonging to the HID class. */ - device_info_set = SetupDiGetClassDevsA(&interface_class_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - - /* Iterate over each device in the HID class, looking for the right one. */ - - for (;;) { - HANDLE read_handle = INVALID_HANDLE_VALUE; - DWORD required_size = 0; - HIDD_ATTRIBUTES attrib; - - res = SetupDiEnumDeviceInterfaces(device_info_set, - NULL, - &interface_class_guid, - device_index, - &device_interface_data); - - if (!res) { - /* A return of FALSE from this function means that - there are no more devices. */ + /* Get the list of all device interfaces belonging to the HID class. */ + /* Retry in case of list was changed between calls to + CM_Get_Device_Interface_List_SizeW and CM_Get_Device_Interface_ListW */ + do { + cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (cr != CR_SUCCESS) { break; } - /* Call with 0-sized detail size, and let the function - tell us how long the detail struct needs to be. The - size is put in &required_size. */ - res = SetupDiGetDeviceInterfaceDetailA(device_info_set, - &device_interface_data, - NULL, - 0, - &required_size, - NULL); - - /* Allocate a long enough structure for device_interface_detail_data. */ - device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size); - device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); - - /* Get the detailed data for this device. The detail data gives us - the device path for this device, which is then passed into - CreateFile() to get a handle to the device. */ - res = SetupDiGetDeviceInterfaceDetailA(device_info_set, - &device_interface_data, - device_interface_detail_data, - required_size, - NULL, - NULL); - - if (!res) { - /* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail"); - Continue to the next device. */ - goto cont; + if (device_interface_list != NULL) { + free(device_interface_list); } - /* Populate devinfo_data. This function will return failure - when the device with such index doesn't exist. We've already checked it does. */ - res = SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data); - if (!res) - goto cont; - + device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); + if (device_interface_list == NULL) { + return NULL; + } + cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + } while (cr == CR_BUFFER_SMALL); - /* Make sure this device has a driver bound to it. */ - res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, - SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); - if (!res) - goto cont; + if (cr != CR_SUCCESS) { + goto end_of_function; + } - //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); + /* Iterate over each device interface in the HID class, looking for the right one. */ + for (wchar_t* device_interface = device_interface_list; *device_interface; device_interface += wcslen(device_interface) + 1) { + HANDLE device_handle = INVALID_HANDLE_VALUE; + HIDD_ATTRIBUTES attrib; /* Open read-only handle to the device */ - read_handle = open_device(device_interface_detail_data->DevicePath, FALSE); + device_handle = open_device(device_interface, FALSE); - /* Check validity of read_handle. */ - if (read_handle == INVALID_HANDLE_VALUE) { + /* Check validity of device_handle. */ + if (device_handle == INVALID_HANDLE_VALUE) { /* Unable to open the device. */ - //register_error(dev, "CreateFile"); - goto cont; + continue; } /* Get the Vendor ID and Product ID for this device. */ attrib.Size = sizeof(HIDD_ATTRIBUTES); - HidD_GetAttributes(read_handle, &attrib); - //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); + if (!HidD_GetAttributes(device_handle, &attrib)) { + goto cont_close; + } /* Check the VID/PID to see if we should add this device to the enumeration list. */ @@ -725,7 +665,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor (product_id == 0x0 || attrib.ProductID == product_id)) { /* VID/PID match. Create the record. */ - struct hid_device_info *tmp = hid_get_device_info(device_interface_detail_data->DevicePath, read_handle); + struct hid_device_info *tmp = hid_internal_get_device_info(device_interface, device_handle); if (tmp == NULL) { goto cont_close; @@ -741,17 +681,11 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor } cont_close: - CloseHandle(read_handle); -cont: - /* We no longer need the detail data. It can be freed */ - free(device_interface_detail_data); - - device_index++; - + CloseHandle(device_handle); } - /* Close the device information handle. */ - SetupDiDestroyDeviceInfoList(device_info_set); +end_of_function: + free(device_interface_list); return root; } @@ -771,7 +705,6 @@ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *d } } - HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* TODO: Merge this functions with the Linux version. This function should be platform independent. */ @@ -780,6 +713,10 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi hid_device *handle = NULL; devs = hid_enumerate(vendor_id, product_id); + if (!devs) { + return NULL; + } + cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && @@ -811,6 +748,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) { hid_device *dev = NULL; + wchar_t* interface_path = NULL; HANDLE device_handle = INVALID_HANDLE_VALUE; PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps; @@ -818,8 +756,12 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) if (hid_init() < 0) goto end_of_function; + interface_path = hid_internal_UTF8toUTF16(path); + if (!interface_path) + goto end_of_function; + /* Open a handle to the device */ - device_handle = open_device(path, TRUE); + device_handle = open_device(interface_path, TRUE); /* Check validity of write_handle. */ if (device_handle == INVALID_HANDLE_VALUE) { @@ -828,7 +770,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) them. This is to prevent keyloggers. However, feature reports can still be sent and received. Retry opening the device, but without read/write access. */ - device_handle = open_device(path, FALSE); + device_handle = open_device(interface_path, FALSE); /* Check the validity of the limited device_handle. */ if (device_handle == INVALID_HANDLE_VALUE) @@ -855,11 +797,15 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) dev->input_report_length = caps.InputReportByteLength; dev->feature_report_length = caps.FeatureReportByteLength; dev->read_buf = (char*) malloc(dev->input_report_length); - dev->device_info = hid_get_device_info(path, dev->device_handle); + dev->device_info = hid_internal_get_device_info(interface_path, dev->device_handle); end_of_function: + free(interface_path); CloseHandle(device_handle); - HidD_FreePreparsedData(pp_data); + + if (pp_data) { + HidD_FreePreparsedData(pp_data); + } return dev; } @@ -874,7 +820,7 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * unsigned char *buf; if (!data || (length==0)) { - register_error(dev, "Zero length buffer"); + register_string_error(dev, L"Zero buffer/length"); return function_result; } @@ -901,7 +847,7 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * if (!res) { if (GetLastError() != ERROR_IO_PENDING) { /* WriteFile() failed. Return error. */ - register_error(dev, "WriteFile"); + register_winapi_error(dev, L"WriteFile"); goto end_of_function; } overlapped = TRUE; @@ -913,7 +859,7 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * res = WaitForSingleObject(dev->write_ol.hEvent, 1000); if (res != WAIT_OBJECT_0) { /* There was a Timeout. */ - register_error(dev, "WriteFile/WaitForSingleObject Timeout"); + register_winapi_error(dev, L"hid_write/WaitForSingleObject"); goto end_of_function; } @@ -924,7 +870,7 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * } else { /* The Write operation failed. */ - register_error(dev, "WriteFile"); + register_winapi_error(dev, L"hid_write/GetOverlappedResult"); goto end_of_function; } } @@ -955,6 +901,7 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char if (GetLastError() != ERROR_IO_PENDING) { /* ReadFile() has failed. Clean up and return error. */ + register_winapi_error(dev, L"ReadFile"); CancelIo(dev->device_handle); dev->read_pending = FALSE; goto end_of_function; @@ -1001,10 +948,12 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char memcpy(data, dev->read_buf, copy_len); } } + if (!res) { + register_winapi_error(dev, L"hid_read_timeout/GetOverlappedResult"); + } end_of_function: if (!res) { - register_error(dev, "GetOverlappedResult"); return -1; } @@ -1048,7 +997,7 @@ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const u res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send); if (!res) { - register_error(dev, "HidD_SetFeature"); + register_winapi_error(dev, L"HidD_SetFeature"); return -1; } @@ -1072,7 +1021,7 @@ static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *dat if (!res) { if (GetLastError() != ERROR_IO_PENDING) { /* DeviceIoControl() failed. Return error. */ - register_error(dev, "Get Input/Feature Report DeviceIoControl"); + register_winapi_error(dev, L"Get Input/Feature Report DeviceIoControl"); return -1; } } @@ -1082,7 +1031,7 @@ static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *dat res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); if (!res) { /* The operation failed. */ - register_error(dev, "Get Input/Feature Report GetOverLappedResult"); + register_winapi_error(dev, L"Get Input/Feature Report GetOverLappedResult"); return -1; } @@ -1112,39 +1061,69 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) { if (!dev) return; + CancelIo(dev->device_handle); free_hid_device(dev); } int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info || !string || !maxlen) + if (!dev->device_info) + { + register_string_error(dev, L"NULL device/info"); return -1; + } + + if (!string || !maxlen) + { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } wcsncpy(string, dev->device_info->manufacturer_string, maxlen); - string[maxlen] = L'\0'; + string[maxlen - 1] = L'\0'; return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info || !string || !maxlen) + if (!dev->device_info) + { + register_string_error(dev, L"NULL device/info"); return -1; + } + + if (!string || !maxlen) + { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + wcsncpy(string, dev->device_info->product_string, maxlen); - string[maxlen] = L'\0'; + string[maxlen - 1] = L'\0'; return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info || !string || !maxlen) + if (!dev->device_info) + { + register_string_error(dev, L"NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + register_string_error(dev, L"Zero buffer/length"); return -1; + } + wcsncpy(string, dev->device_info->serial_number, maxlen); - string[maxlen] = L'\0'; + string[maxlen - 1] = L'\0'; return 0; } @@ -1155,92 +1134,78 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); if (!res) { - register_error(dev, "HidD_GetIndexedString"); + register_winapi_error(dev, L"HidD_GetIndexedString"); return -1; } return 0; } - -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id) { - if (dev) { - if (dev->last_error_str == NULL) - return L"Success"; - return (wchar_t*)dev->last_error_str; - } - - // Global error messages are not (yet) implemented on Windows. - return L"hid_error for global errors is not implemented yet"; -} - - -/*#define PICPGM*/ -/*#define S11*/ -#define P32 -#ifdef S11 - unsigned short VendorID = 0xa0a0; - unsigned short ProductID = 0x0001; -#endif - -#ifdef P32 - unsigned short VendorID = 0x04d8; - unsigned short ProductID = 0x3f; -#endif - - -#ifdef PICPGM - unsigned short VendorID = 0x04d8; - unsigned short ProductID = 0x0033; -#endif - + wchar_t *interface_path = NULL, *device_id = NULL; + CONFIGRET cr = CR_FAILURE; + DEVINST dev_node; + DEVPROPTYPE property_type; + ULONG len; -#if 0 -int __cdecl main(int argc, char* argv[]) -{ - int res; - unsigned char buf[65]; + if (!container_id) + { + register_string_error(dev, L"Invalid Container ID"); + return -1; + } - UNREFERENCED_PARAMETER(argc); - UNREFERENCED_PARAMETER(argv); + interface_path = hid_internal_UTF8toUTF16(dev->device_info->path); + if (!interface_path) + { + register_string_error(dev, L"Path conversion failure"); + goto end; + } - /* Set up the command buffer. */ - memset(buf,0x00,sizeof(buf)); - buf[0] = 0; - buf[1] = 0x81; + /* Get the device id from interface path */ + device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); + if (!device_id) + { + register_string_error(dev, L"Failed to get device interface property InstanceId"); + goto end; + } + /* Open devnode from device id */ + cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + { + register_string_error(dev, L"Failed to locate device node"); + goto end; + } - /* Open the device. */ - int handle = open(VendorID, ProductID, L"12345"); - if (handle < 0) - printf("unable to open device\n"); + /* Get the container id from devnode */ + len = sizeof(*container_id); + cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_ContainerId, &property_type, (PBYTE)container_id, &len, 0); + if (cr == CR_SUCCESS && property_type != DEVPROP_TYPE_GUID) + cr = CR_FAILURE; + if (cr != CR_SUCCESS) + register_string_error(dev, L"Failed to read ContainerId property from device node"); - /* Toggle LED (cmd 0x80) */ - buf[1] = 0x80; - res = write(handle, buf, 65); - if (res < 0) - printf("Unable to write()\n"); +end: + free(interface_path); + free(device_id); - /* Request state (cmd 0x81) */ - buf[1] = 0x81; - write(handle, buf, 65); - if (res < 0) - printf("Unable to write() (2)\n"); + return cr == CR_SUCCESS ? 0 : -1; +} - /* Read requested state */ - read(handle, buf, 65); - if (res < 0) - printf("Unable to read()\n"); - /* Print out the returned buffer. */ - for (int i = 0; i < 4; i++) - printf("buf[%d]: %d\n", i, buf[i]); +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return (wchar_t*)dev->last_error_str; + } - return 0; + /* Global error messages are not (yet) implemented on Windows. */ + return L"hid_error for global errors is not implemented yet"; } -#endif #ifdef __cplusplus } /* extern "C" */ diff --git a/hidapi/windows/hidapi_cfgmgr32.h b/hidapi/windows/hidapi_cfgmgr32.h new file mode 100644 index 0000000..720906e --- /dev/null +++ b/hidapi/windows/hidapi_cfgmgr32.h @@ -0,0 +1,69 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +#ifndef HIDAPI_CFGMGR32_H +#define HIDAPI_CFGMGR32_H + +#ifdef HIDAPI_USE_DDK + +#include +#include +#include +#include + +#else + +/* This part of the header mimics cfgmgr32.h, + but only what is used by HIDAPI */ + +typedef DWORD RETURN_TYPE; +typedef RETURN_TYPE CONFIGRET; +typedef DWORD DEVNODE, DEVINST; +typedef DEVNODE* PDEVNODE, * PDEVINST; +typedef WCHAR* DEVNODEID_W, * DEVINSTID_W; + +#define CR_SUCCESS (0x00000000) +#define CR_BUFFER_SMALL (0x0000001A) +#define CR_FAILURE (0x00000013) + +#define CM_LOCATE_DEVNODE_NORMAL 0x00000000 + +#define CM_GET_DEVICE_INTERFACE_LIST_PRESENT (0x00000000) + +typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags); +typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags); + +// from devpkey.h +static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, {0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac} }, 10 }; // DEVPROP_TYPE_STRING +static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, {0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57} }, 256 }; // DEVPROP_TYPE_STRING +static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 3 }; // DEVPROP_TYPE_STRING_LIST +static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 4 }; // DEVPROP_TYPE_STRING_LIST +static DEVPROPKEY DEVPKEY_Device_ContainerId = { { 0x8c7ed206, 0x3f8a, 0x4827, {0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c} }, 2 }; // DEVPROP_TYPE_GUID + +// from propkey.h +static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 1 }; // DEVPROP_TYPE_STRING +static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 4 }; // DEVPROP_TYPE_STRING + +#endif + +#endif /* HIDAPI_CFGMGR32_H */ diff --git a/hidapi/windows/hidapi_hidclass.h b/hidapi/windows/hidapi_hidclass.h new file mode 100644 index 0000000..13bd6f2 --- /dev/null +++ b/hidapi/windows/hidapi_hidclass.h @@ -0,0 +1,38 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +#ifndef HIDAPI_HIDCLASS_H +#define HIDAPI_HIDCLASS_H + +#ifdef HIDAPI_USE_DDK + +#include + +#else + +/* This part of the header mimics hidclass.h, + but only what is used by HIDAPI */ + +#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) + +#endif + +#endif /* HIDAPI_HIDCLASS_H */ diff --git a/hidapi/windows/hidapi_hidpi.h b/hidapi/windows/hidapi_hidpi.h new file mode 100644 index 0000000..343ddeb --- /dev/null +++ b/hidapi/windows/hidapi_hidpi.h @@ -0,0 +1,65 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +#ifndef HIDAPI_HIDPI_H +#define HIDAPI_HIDPI_H + +#ifdef HIDAPI_USE_DDK + +#include + +#else + +/* This part of the header mimics hidpi.h, + but only what is used by HIDAPI */ + +typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; + +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; + +#define HIDP_STATUS_SUCCESS 0x00110000 +#define HIDP_STATUS_INVALID_PREPARSED_DATA 0xc0110001 + +typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, PHIDP_CAPS caps); + +#endif + +#endif /* HIDAPI_HIDPI_H */ diff --git a/hidapi/windows/hidapi_hidsdi.h b/hidapi/windows/hidapi_hidsdi.h new file mode 100644 index 0000000..453f899 --- /dev/null +++ b/hidapi/windows/hidapi_hidsdi.h @@ -0,0 +1,60 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +#ifndef HIDAPI_HIDSDI_H +#define HIDAPI_HIDSDI_H + +#ifdef HIDAPI_USE_DDK + +#include + +#else + +/* This part of the header mimics hidsdi.h, + but only what is used by HIDAPI */ + +typedef USHORT USAGE; + +#include "hidapi_hidpi.h" + +typedef struct _HIDD_ATTRIBUTES{ + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; +} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + +typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; + +typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid); +typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); +typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); +typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); +typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); +typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); +typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); +typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length); +typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); +typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data); +typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data); +typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); + +#endif + +#endif /* HIDAPI_HIDSDI_H */ diff --git a/hidapi/windows/hidapi_winapi.h b/hidapi/windows/hidapi_winapi.h new file mode 100644 index 0000000..884712a --- /dev/null +++ b/hidapi/windows/hidapi_winapi.h @@ -0,0 +1,58 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + * + * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + */ + +#ifndef HIDAPI_WINAPI_H__ +#define HIDAPI_WINAPI_H__ + +#include + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** @brief Get the container ID for a HID device. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + This function returns the `DEVPKEY_Device_ContainerId` property of + the given device. This can be used to correlate different + interfaces/ports on the same hardware device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param guid The device's container ID on return. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libusb/AUTHORS b/libusb/AUTHORS index a366189..8f91512 100644 --- a/libusb/AUTHORS +++ b/libusb/AUTHORS @@ -12,6 +12,7 @@ Copyright © 2013-2018 Chris Dickens Other contributors: Aaron Luft +Adam Korcz Adrian Bunk Adrien Destugues Akshay Jaggi @@ -24,10 +25,12 @@ Alexander Stein Alex Vatchenko Andrew Aldridge Andrew Fernandes +Andrew Goodney Andy Chunyu Andy McFadden Angus Gratton Anil Nair +Ankur Verma Anthony Clay Antonio Ospite Artem Egorkine @@ -38,21 +41,28 @@ Baruch Siach Bastien Nocera Bei Zhang Bence Csokas +Benjamin Berg Benjamin Dobell +Bohdan Tymkiv Brent Rector +Bruno Harbulot Carl Karsten -Chris Zhu Christophe Zeitouny +Chris Zhu Chunyu Xie Colin Walters +Craig Hutchinson Dave Camarillo David Engraf Davidlohr Bueso David Moore Dmitry Fleytman Dmitry Kostjuchenko +Dmitry Zakablukov Doug Johnston Evan Hunter +Evan Miller +Fabrice Fontaine Federico Manzan Felipe Balbi Florian Albrechtskirchinger @@ -60,10 +70,12 @@ Francesco Montorsi Francisco Facioni Frank Li Frederik Carlier +Freek Dijkstra Gaurav Gupta Graeme Gill Greg Kroah-Hartman Gustavo Zacarias +Haidong Zheng Hans Ulrich Niedermann Harry Mallon Hector Martin @@ -76,6 +88,7 @@ Jakub Klama James Hanko Jeffrey Nichols Jie Zhang +Jim Chen Johann Richard John Keeping John Sheu @@ -86,35 +99,48 @@ Josh Gao Joshua Blake Joshua Hou Juan Cruz Viotti +Julian Scheel Justin Bischoff Karsten Koenig +Keith Ahluwalia Kenjiro Tsuji -KIMURA Masaru +Kimura Masaru Konrad Rzepecki Kuangye Guo Lars Kanis Lars Wirzenius Lei Chen Léo Lam +Liang Yunwang Luca Longinotti +Luz Paz +Mac Wang +Marco Trevisan (Treviño) Marcus Meissner +Mark Kuo Markus Heidelberg Martin Ettl Martin Koegler +Martin Ling Martin Thierer +Mathias Hjärtström Matthew Stapleton Matthias Bolte +Michael Dickens Michel Zou Mike Frysinger Mikhail Gusarov Mikolaj Kucharski Morgan Leborgne Moritz Fischer +Nancy Li Nia Alarie Nicholas Corgan Omri Iluz Orin Eman +Ozkan Sezer Patrick Stewart +Paul Cercueil Paul Fertser Paul Qureshi Pekka Nikander @@ -123,12 +149,15 @@ Pino Toscano Rob Walker Romain Vimont Roman Kalashnikov +Ryan Hileman +Ryan Schmidt Saleem Rashid Sameeh Jubran Sean McBride Sebastian Pipping Sebastian von Ohr Sergey Serb +Shawn Hoffman Simon Haggett Simon Newton Slash Gordon @@ -157,13 +186,17 @@ Vladimir Beloborodov William Orr William Skellenger Xiaofan Chen +Yegor Yefremov Zhiqiang Liu Zoltán Kovács Сергей Валерьевич Ларионов Даниил Роман Донченко -liangyunwang +jonner +orbitcowboy +osy parafin RipleyTom +Seneral saur0n winterrace diff --git a/libusb/libusb/core.c b/libusb/libusb/core.c index 07d459c..ec429b7 100644 --- a/libusb/libusb/core.c +++ b/libusb/libusb/core.c @@ -21,7 +21,6 @@ */ #include "libusbi.h" -#include "hotplug.h" #include "version.h" #ifdef __ANDROID__ @@ -33,17 +32,21 @@ #include #endif -struct libusb_context *usbi_default_context; static const struct libusb_version libusb_version_internal = { LIBUSB_MAJOR, LIBUSB_MINOR, LIBUSB_MICRO, LIBUSB_NANO, LIBUSB_RC, "http://libusb.info" }; -static int default_context_refcnt; -static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER; static struct timespec timestamp_origin; #if defined(ENABLE_LOGGING) && !defined(USE_SYSTEM_LOGGING_FACILITY) static libusb_log_cb log_handler; #endif +struct libusb_context *usbi_default_context; +struct libusb_context *usbi_fallback_context; +static int default_context_refcnt; +static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER; +static struct usbi_option default_context_options[LIBUSB_OPTION_MAX]; + + usbi_mutex_static_t active_contexts_lock = USBI_MUTEX_INITIALIZER; struct list_head active_contexts_list; @@ -310,7 +313,8 @@ if (cfg != desired) * - libusb is able to send a packet of zero length to an endpoint simply by * submitting a transfer of zero length. * - The \ref libusb_transfer_flags::LIBUSB_TRANSFER_ADD_ZERO_PACKET - * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently only supported on Linux. + * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently supported on Linux, + * Darwin and Windows (WinUSB). */ /** @@ -577,7 +581,9 @@ libusb_free_device_list(list, 1); * * The libusb_get_device_list() function can be used to obtain a list of * devices currently connected to the system. This is known as device - * discovery. + * discovery. Devices can also be discovered with the hotplug mechanism, + * whereby a callback function registered with libusb_hotplug_register_callback() + * will be called when a device of interest is connected or disconnected. * * Just because you have a reference to a device does not mean it is * necessarily usable. The device may have been unplugged, you may not have @@ -608,7 +614,7 @@ libusb_free_device_list(list, 1); * * With the above information in mind, the process of opening a device can * be viewed as follows: - * -# Discover devices using libusb_get_device_list(). + * -# Discover devices using libusb_get_device_list() or libusb_hotplug_register_callback(). * -# Choose the device that you want to operate, and call libusb_open(). * -# Unref all devices in the discovered device list. * -# Free the discovered device list. @@ -633,7 +639,7 @@ libusb_free_device_list(list, 1); * which grows when required. it can be freed once discovery has completed, * eliminating the need for a list node in the libusb_device structure * itself. */ -#define DISCOVERED_DEVICES_SIZE_STEP 8 +#define DISCOVERED_DEVICES_SIZE_STEP 16 static struct discovered_devs *discovered_devs_alloc(void) { @@ -674,7 +680,7 @@ struct discovered_devs *discovered_devs_append( } /* exceeded capacity, need to grow */ - usbi_dbg("need to increase capacity"); + usbi_dbg(DEVICE_CTX(dev), "need to increase capacity"); capacity = discdevs->capacity + DISCOVERED_DEVICES_SIZE_STEP; /* can't use usbi_reallocf here because in failure cases it would * free the existing discdevs without unreferencing its devices. */ @@ -704,16 +710,14 @@ struct libusb_device *usbi_alloc_device(struct libusb_context *ctx, if (!dev) return NULL; - usbi_mutex_init(&dev->lock); + usbi_atomic_store(&dev->refcnt, 1); dev->ctx = ctx; - dev->refcnt = 1; dev->session_data = session_id; dev->speed = LIBUSB_SPEED_UNKNOWN; - if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { - usbi_connect_device (dev); - } + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + usbi_connect_device(dev); return dev; } @@ -722,39 +726,26 @@ void usbi_connect_device(struct libusb_device *dev) { struct libusb_context *ctx = DEVICE_CTX(dev); - dev->attached = 1; + usbi_atomic_store(&dev->attached, 1); usbi_mutex_lock(&dev->ctx->usb_devs_lock); list_add(&dev->list, &dev->ctx->usb_devs); usbi_mutex_unlock(&dev->ctx->usb_devs_lock); - /* Signal that an event has occurred for this device if we support hotplug AND - * the hotplug message list is ready. This prevents an event from getting raised - * during initial enumeration. */ - if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_msgs.next) { - usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); - } + usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); } void usbi_disconnect_device(struct libusb_device *dev) { struct libusb_context *ctx = DEVICE_CTX(dev); - usbi_mutex_lock(&dev->lock); - dev->attached = 0; - usbi_mutex_unlock(&dev->lock); + usbi_atomic_store(&dev->attached, 0); usbi_mutex_lock(&ctx->usb_devs_lock); list_del(&dev->list); usbi_mutex_unlock(&ctx->usb_devs_lock); - /* Signal that an event has occurred for this device if we support hotplug AND - * the hotplug message list is ready. This prevents an event from getting raised - * during initial enumeration. libusb_handle_events will take care of dereferencing - * the device. */ - if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_msgs.next) { - usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); - } + usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); } /* Perform some final sanity checks on a newly discovered device. If this @@ -775,7 +766,7 @@ int usbi_sanitize_device(struct libusb_device *dev) usbi_err(DEVICE_CTX(dev), "too many configurations"); return LIBUSB_ERROR_IO; } else if (0 == num_configurations) { - usbi_dbg("zero configurations, maybe an unauthorized device"); + usbi_dbg(DEVICE_CTX(dev), "zero configurations, maybe an unauthorized device"); } return 0; @@ -830,7 +821,7 @@ ssize_t API_EXPORTED libusb_get_device_list(libusb_context *ctx, int r = 0; ssize_t i, len; - usbi_dbg(" "); + usbi_dbg(ctx, " "); if (!discdevs) return LIBUSB_ERROR_NO_MEM; @@ -1173,9 +1164,11 @@ int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev, DEFAULT_VISIBILITY libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev) { - usbi_mutex_lock(&dev->lock); - dev->refcnt++; - usbi_mutex_unlock(&dev->lock); + long refcnt; + + refcnt = usbi_atomic_inc(&dev->refcnt); + assert(refcnt >= 2); + return dev; } @@ -1186,17 +1179,16 @@ libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev) */ void API_EXPORTED libusb_unref_device(libusb_device *dev) { - int refcnt; + long refcnt; if (!dev) return; - usbi_mutex_lock(&dev->lock); - refcnt = --dev->refcnt; - usbi_mutex_unlock(&dev->lock); + refcnt = usbi_atomic_dec(&dev->refcnt); + assert(refcnt >= 0); if (refcnt == 0) { - usbi_dbg("destroy device %d.%d", dev->bus_number, dev->device_address); + usbi_dbg(DEVICE_CTX(dev), "destroy device %d.%d", dev->bus_number, dev->device_address); libusb_unref_device(dev->parent_dev); @@ -1208,7 +1200,6 @@ void API_EXPORTED libusb_unref_device(libusb_device *dev) usbi_disconnect_device(dev); } - usbi_mutex_destroy(&dev->lock); free(dev); } } @@ -1218,8 +1209,10 @@ void API_EXPORTED libusb_unref_device(libusb_device *dev) * handle for the underlying device. The handle allows you to use libusb to * perform I/O on the device in question. * - * Must call libusb_set_option(NULL, LIBUSB_OPTION_WEAK_AUTHORITY) - * before libusb_init if don't have authority to access the usb device directly. + * Call libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY) before + * libusb_init() if you want to skip enumeration of USB devices. In particular, + * this might be needed on Android if you don't have authority to access USB + * devices in general. * * On Linux, the system device handle must be a valid file descriptor opened * on the device node. @@ -1233,6 +1226,8 @@ void API_EXPORTED libusb_unref_device(libusb_device *dev) * * This is a non-blocking function; no requests are sent over the bus. * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * * \param ctx the context to operate on, or NULL for the default context * \param sys_dev the platform-specific system device handle * \param dev_handle output location for the returned device handle pointer. Only @@ -1251,7 +1246,7 @@ int API_EXPORTED libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev, size_t priv_size = usbi_backend.device_handle_priv_size; int r; - usbi_dbg("wrap_sys_device 0x%" PRIxPTR, (uintptr_t)sys_dev); + usbi_dbg(ctx, "wrap_sys_device 0x%" PRIxPTR, (uintptr_t)sys_dev); ctx = usbi_get_context(ctx); @@ -1266,7 +1261,7 @@ int API_EXPORTED libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev, r = usbi_backend.wrap_sys_device(ctx, _dev_handle, sys_dev); if (r < 0) { - usbi_dbg("wrap_sys_device 0x%" PRIxPTR " returns %d", (uintptr_t)sys_dev, r); + usbi_dbg(ctx, "wrap_sys_device 0x%" PRIxPTR " returns %d", (uintptr_t)sys_dev, r); usbi_mutex_destroy(&_dev_handle->lock); free(_dev_handle); return r; @@ -1306,11 +1301,11 @@ int API_EXPORTED libusb_open(libusb_device *dev, struct libusb_device_handle *_dev_handle; size_t priv_size = usbi_backend.device_handle_priv_size; int r; - usbi_dbg("open %d.%d", dev->bus_number, dev->device_address); - if (!dev->attached) { + usbi_dbg(DEVICE_CTX(dev), "open %d.%d", dev->bus_number, dev->device_address); + + if (!usbi_atomic_load(&dev->attached)) return LIBUSB_ERROR_NO_DEVICE; - } _dev_handle = calloc(1, PTR_ALIGN(sizeof(*_dev_handle)) + priv_size); if (!_dev_handle) @@ -1322,7 +1317,7 @@ int API_EXPORTED libusb_open(libusb_device *dev, r = usbi_backend.open(_dev_handle); if (r < 0) { - usbi_dbg("open %d.%d returns %d", dev->bus_number, dev->device_address, r); + usbi_dbg(DEVICE_CTX(dev), "open %d.%d returns %d", dev->bus_number, dev->device_address, r); libusb_unref_device(dev); usbi_mutex_destroy(&_dev_handle->lock); free(_dev_handle); @@ -1428,7 +1423,7 @@ static void do_close(struct libusb_context *ctx, * just making sure that we don't attempt to process the transfer after * the device handle is invalid */ - usbi_dbg("Removed transfer %p from the in-flight list because device handle %p closed", + usbi_dbg(ctx, "Removed transfer %p from the in-flight list because device handle %p closed", transfer, dev_handle); } usbi_mutex_unlock(&ctx->flying_transfers_lock); @@ -1462,9 +1457,9 @@ void API_EXPORTED libusb_close(libusb_device_handle *dev_handle) if (!dev_handle) return; - usbi_dbg(" "); - ctx = HANDLE_CTX(dev_handle); + usbi_dbg(ctx, " "); + handling_events = usbi_handling_events(ctx); /* Similarly to libusb_open(), we want to interrupt all event handlers @@ -1546,27 +1541,28 @@ int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev_handle, { int r = LIBUSB_ERROR_NOT_SUPPORTED; uint8_t tmp = 0; + struct libusb_context *ctx = HANDLE_CTX(dev_handle); - usbi_dbg(" "); + usbi_dbg(ctx, " "); if (usbi_backend.get_configuration) r = usbi_backend.get_configuration(dev_handle, &tmp); if (r == LIBUSB_ERROR_NOT_SUPPORTED) { - usbi_dbg("falling back to control message"); + usbi_dbg(ctx, "falling back to control message"); r = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_CONFIGURATION, 0, 0, &tmp, 1, 1000); if (r == 1) { r = 0; } else if (r == 0) { - usbi_err(HANDLE_CTX(dev_handle), "zero bytes returned in ctrl transfer?"); + usbi_err(ctx, "zero bytes returned in ctrl transfer?"); r = LIBUSB_ERROR_IO; } else { - usbi_dbg("control failed, error %d", r); + usbi_dbg(ctx, "control failed, error %d", r); } } if (r == 0) { - usbi_dbg("active config %u", tmp); + usbi_dbg(ctx, "active config %u", tmp); *config = (int)tmp; } @@ -1630,7 +1626,7 @@ int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev_handle, int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev_handle, int configuration) { - usbi_dbg("configuration %d", configuration); + usbi_dbg(HANDLE_CTX(dev_handle), "configuration %d", configuration); if (configuration < -1 || configuration > (int)UINT8_MAX) return LIBUSB_ERROR_INVALID_PARAM; return usbi_backend.set_configuration(dev_handle, configuration); @@ -1669,11 +1665,11 @@ int API_EXPORTED libusb_claim_interface(libusb_device_handle *dev_handle, { int r = 0; - usbi_dbg("interface %d", interface_number); + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) return LIBUSB_ERROR_INVALID_PARAM; - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; usbi_mutex_lock(&dev_handle->lock); @@ -1713,7 +1709,7 @@ int API_EXPORTED libusb_release_interface(libusb_device_handle *dev_handle, { int r; - usbi_dbg("interface %d", interface_number); + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) return LIBUSB_ERROR_INVALID_PARAM; @@ -1756,19 +1752,19 @@ int API_EXPORTED libusb_release_interface(libusb_device_handle *dev_handle, int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, int interface_number, int alternate_setting) { - usbi_dbg("interface %d altsetting %d", + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d altsetting %d", interface_number, alternate_setting); if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) return LIBUSB_ERROR_INVALID_PARAM; if (alternate_setting < 0 || alternate_setting > (int)UINT8_MAX) return LIBUSB_ERROR_INVALID_PARAM; - usbi_mutex_lock(&dev_handle->lock); - if (!dev_handle->dev->attached) { + if (!usbi_atomic_load(&dev_handle->dev->attached)) { usbi_mutex_unlock(&dev_handle->lock); return LIBUSB_ERROR_NO_DEVICE; } + usbi_mutex_lock(&dev_handle->lock); if (!(dev_handle->claimed_interfaces & (1U << interface_number))) { usbi_mutex_unlock(&dev_handle->lock); return LIBUSB_ERROR_NOT_FOUND; @@ -1798,8 +1794,8 @@ int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_hand int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev_handle, unsigned char endpoint) { - usbi_dbg("endpoint %x", endpoint); - if (!dev_handle->dev->attached) + usbi_dbg(HANDLE_CTX(dev_handle), "endpoint 0x%x", endpoint); + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; return usbi_backend.clear_halt(dev_handle, endpoint); @@ -1826,8 +1822,8 @@ int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev_handle, */ int API_EXPORTED libusb_reset_device(libusb_device_handle *dev_handle) { - usbi_dbg(" "); - if (!dev_handle->dev->attached) + usbi_dbg(HANDLE_CTX(dev_handle), " "); + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; if (usbi_backend.reset_device) @@ -1860,12 +1856,12 @@ int API_EXPORTED libusb_reset_device(libusb_device_handle *dev_handle) int API_EXPORTED libusb_alloc_streams(libusb_device_handle *dev_handle, uint32_t num_streams, unsigned char *endpoints, int num_endpoints) { - usbi_dbg("streams %u eps %d", (unsigned)num_streams, num_endpoints); + usbi_dbg(HANDLE_CTX(dev_handle), "streams %u eps %d", (unsigned)num_streams, num_endpoints); if (!num_streams || !endpoints || num_endpoints <= 0) return LIBUSB_ERROR_INVALID_PARAM; - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; if (usbi_backend.alloc_streams) @@ -1890,12 +1886,12 @@ int API_EXPORTED libusb_alloc_streams(libusb_device_handle *dev_handle, int API_EXPORTED libusb_free_streams(libusb_device_handle *dev_handle, unsigned char *endpoints, int num_endpoints) { - usbi_dbg("eps %d", num_endpoints); + usbi_dbg(HANDLE_CTX(dev_handle), "eps %d", num_endpoints); if (!endpoints || num_endpoints <= 0) return LIBUSB_ERROR_INVALID_PARAM; - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; if (usbi_backend.free_streams) @@ -1933,7 +1929,7 @@ DEFAULT_VISIBILITY unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle, size_t length) { - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return NULL; if (usbi_backend.dev_mem_alloc) @@ -1979,12 +1975,12 @@ int API_EXPORTED libusb_dev_mem_free(libusb_device_handle *dev_handle, int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev_handle, int interface_number) { - usbi_dbg("interface %d", interface_number); + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) return LIBUSB_ERROR_INVALID_PARAM; - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; if (usbi_backend.kernel_driver_active) @@ -1997,7 +1993,7 @@ int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev_handle, * Detach a kernel driver from an interface. If successful, you will then be * able to claim the interface and perform I/O. * - * This functionality is not available on Darwin or Windows. + * This functionality is not available on Windows. * * Note that libusb itself also talks to the device through a special kernel * driver, if this driver is already attached to the device, this call will @@ -2017,12 +2013,12 @@ int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev_handle, int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev_handle, int interface_number) { - usbi_dbg("interface %d", interface_number); + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) return LIBUSB_ERROR_INVALID_PARAM; - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; if (usbi_backend.detach_kernel_driver) @@ -2033,10 +2029,9 @@ int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev_handle, /** \ingroup libusb_dev * Re-attach an interface's kernel driver, which was previously detached - * using libusb_detach_kernel_driver(). This call is only effective on - * Linux and returns LIBUSB_ERROR_NOT_SUPPORTED on all other platforms. + * using libusb_detach_kernel_driver(). * - * This functionality is not available on Darwin or Windows. + * This functionality is not available on Windows. * * \param dev_handle a device handle * \param interface_number the interface to attach the driver from @@ -2054,12 +2049,12 @@ int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev_handle, int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev_handle, int interface_number) { - usbi_dbg("interface %d", interface_number); + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) return LIBUSB_ERROR_INVALID_PARAM; - if (!dev_handle->dev->attached) + if (!usbi_atomic_load(&dev_handle->dev->attached)) return LIBUSB_ERROR_NO_DEVICE; if (usbi_backend.attach_kernel_driver) @@ -2130,6 +2125,8 @@ void API_EXPORTED libusb_set_debug(libusb_context *ctx, int level) * If ENABLE_DEBUG_LOGGING is defined then per context callback function will * never be called. * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * * \param ctx context on which to assign log handler, or NULL for the default * context. Parameter ignored if only LIBUSB_LOG_CB_GLOBAL mode is requested. * \param cb pointer to the callback function, or NULL to stop log @@ -2170,6 +2167,9 @@ void API_EXPORTED libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb, * Some options require one or more arguments to be provided. Consult each * option's documentation for specific requirements. * + * If the context ctx is NULL, the option will be added to a list of default + * options that will be applied to all subsequently created contexts. + * * Since version 1.0.22, \ref LIBUSB_API_VERSION >= 0x01000106 * * \param ctx context on which to operate @@ -2185,40 +2185,63 @@ void API_EXPORTED libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb, int API_EXPORTED libusb_set_option(libusb_context *ctx, enum libusb_option option, ...) { - int arg, r = LIBUSB_SUCCESS; + int arg = 0, r = LIBUSB_SUCCESS; va_list ap; - ctx = usbi_get_context(ctx); - va_start(ap, option); - switch (option) { - case LIBUSB_OPTION_LOG_LEVEL: + if (LIBUSB_OPTION_LOG_LEVEL == option) { arg = va_arg(ap, int); if (arg < LIBUSB_LOG_LEVEL_NONE || arg > LIBUSB_LOG_LEVEL_DEBUG) { r = LIBUSB_ERROR_INVALID_PARAM; - break; } + } + va_end(ap); + + if (LIBUSB_SUCCESS != r) { + return r; + } + + if (option >= LIBUSB_OPTION_MAX) { + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (NULL == ctx) { + usbi_mutex_static_lock(&default_context_lock); + default_context_options[option].is_set = 1; + if (LIBUSB_OPTION_LOG_LEVEL == option) { + default_context_options[option].arg.ival = arg; + } + usbi_mutex_static_unlock(&default_context_lock); + } + + ctx = usbi_get_context(ctx); + if (NULL == ctx) { + return LIBUSB_SUCCESS; + } + + switch (option) { + case LIBUSB_OPTION_LOG_LEVEL: #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) if (!ctx->debug_fixed) ctx->debug = (enum libusb_log_level)arg; #endif break; - /* Handle all backend-specific options here */ + /* Handle all backend-specific options here */ case LIBUSB_OPTION_USE_USBDK: - case LIBUSB_OPTION_WEAK_AUTHORITY: + case LIBUSB_OPTION_NO_DEVICE_DISCOVERY: if (usbi_backend.set_option) - r = usbi_backend.set_option(ctx, option, ap); - else - r = LIBUSB_ERROR_NOT_SUPPORTED; + return usbi_backend.set_option(ctx, option, ap); + + return LIBUSB_ERROR_NOT_SUPPORTED; break; + case LIBUSB_OPTION_MAX: default: - r = LIBUSB_ERROR_INVALID_PARAM; + return LIBUSB_ERROR_INVALID_PARAM; } - va_end(ap); - return r; + return LIBUSB_SUCCESS;; } #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) @@ -2249,113 +2272,125 @@ static enum libusb_log_level get_env_debug_level(void) * context will be created. If there was already a default context, it will * be reused (and nothing will be initialized/reinitialized). * - * \param context Optional output location for context pointer. + * \param ctx Optional output location for context pointer. * Only valid on return code 0. * \returns 0 on success, or a LIBUSB_ERROR code on failure * \see libusb_contexts */ -int API_EXPORTED libusb_init(libusb_context **context) +int API_EXPORTED libusb_init(libusb_context **ctx) { - struct libusb_device *dev, *next; size_t priv_size = usbi_backend.context_priv_size; - struct libusb_context *ctx; - static int first_init = 1; - int r = 0; + struct libusb_context *_ctx; + int r; usbi_mutex_static_lock(&default_context_lock); - if (!timestamp_origin.tv_sec) - usbi_get_monotonic_time(×tamp_origin); - - if (!context && usbi_default_context) { - usbi_dbg("reusing default context"); + if (!ctx && default_context_refcnt > 0) { + usbi_dbg(usbi_default_context, "reusing default context"); default_context_refcnt++; usbi_mutex_static_unlock(&default_context_lock); return 0; } - ctx = calloc(1, PTR_ALIGN(sizeof(*ctx)) + priv_size); - if (!ctx) { - r = LIBUSB_ERROR_NO_MEM; - goto err_unlock; + /* check for first init */ + if (!active_contexts_list.next) { + list_init(&active_contexts_list); + usbi_get_monotonic_time(×tamp_origin); + } + + _ctx = calloc(1, PTR_ALIGN(sizeof(*_ctx)) + priv_size); + if (!_ctx) { + usbi_mutex_static_unlock(&default_context_lock); + return LIBUSB_ERROR_NO_MEM; } #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) - ctx->debug = get_env_debug_level(); - if (ctx->debug != LIBUSB_LOG_LEVEL_NONE) - ctx->debug_fixed = 1; + if (NULL == ctx && default_context_options[LIBUSB_OPTION_LOG_LEVEL].is_set) { + _ctx->debug = default_context_options[LIBUSB_OPTION_LOG_LEVEL].arg.ival; + } else { + _ctx->debug = get_env_debug_level(); + } + if (_ctx->debug != LIBUSB_LOG_LEVEL_NONE) + _ctx->debug_fixed = 1; #endif - /* default context should be initialized before calling usbi_dbg */ - if (!usbi_default_context) { - usbi_default_context = ctx; - default_context_refcnt++; - usbi_dbg("created default context"); + usbi_mutex_init(&_ctx->usb_devs_lock); + usbi_mutex_init(&_ctx->open_devs_lock); + list_init(&_ctx->usb_devs); + list_init(&_ctx->open_devs); + + /* apply default options to all new contexts */ + for (enum libusb_option option = 0 ; option < LIBUSB_OPTION_MAX ; option++) { + if (LIBUSB_OPTION_LOG_LEVEL == option || !default_context_options[option].is_set) { + continue; + } + r = libusb_set_option(_ctx, option); + if (LIBUSB_SUCCESS != r) + goto err_free_ctx; + } + + /* default context must be initialized before calling usbi_dbg */ + if (!ctx) { + usbi_default_context = _ctx; + default_context_refcnt = 1; + usbi_dbg(usbi_default_context, "created default context"); } - usbi_dbg("libusb v%u.%u.%u.%u%s", libusb_version_internal.major, libusb_version_internal.minor, + usbi_dbg(_ctx, "libusb v%u.%u.%u.%u%s", libusb_version_internal.major, libusb_version_internal.minor, libusb_version_internal.micro, libusb_version_internal.nano, libusb_version_internal.rc); - usbi_mutex_init(&ctx->usb_devs_lock); - usbi_mutex_init(&ctx->open_devs_lock); - usbi_mutex_init(&ctx->hotplug_cbs_lock); - list_init(&ctx->usb_devs); - list_init(&ctx->open_devs); - list_init(&ctx->hotplug_cbs); - ctx->next_hotplug_cb_handle = 1; + r = usbi_io_init(_ctx); + if (r < 0) + goto err_free_ctx; usbi_mutex_static_lock(&active_contexts_lock); - if (first_init) { - first_init = 0; - list_init(&active_contexts_list); - } - list_add (&ctx->list, &active_contexts_list); + list_add(&_ctx->list, &active_contexts_list); usbi_mutex_static_unlock(&active_contexts_lock); if (usbi_backend.init) { - r = usbi_backend.init(ctx); + r = usbi_backend.init(_ctx); if (r) - goto err_free_ctx; + goto err_io_exit; } - r = usbi_io_init(ctx); - if (r < 0) - goto err_backend_exit; + /* Initialize hotplug after the initial enumeration is done. */ + usbi_hotplug_init(_ctx); - usbi_mutex_static_unlock(&default_context_lock); + if (ctx) { + *ctx = _ctx; - if (context) - *context = ctx; + if (!usbi_fallback_context) { + usbi_fallback_context = _ctx; + usbi_warn(usbi_fallback_context, "installing new context as implicit default"); + } + } - return 0; + usbi_mutex_static_unlock(&default_context_lock); -err_backend_exit: - if (usbi_backend.exit) - usbi_backend.exit(ctx); -err_free_ctx: - if (ctx == usbi_default_context) { - usbi_default_context = NULL; - default_context_refcnt--; - } + return 0; +err_io_exit: usbi_mutex_static_lock(&active_contexts_lock); - list_del(&ctx->list); + list_del(&_ctx->list); usbi_mutex_static_unlock(&active_contexts_lock); - usbi_mutex_lock(&ctx->usb_devs_lock); - for_each_device_safe(ctx, dev, next) { - list_del(&dev->list); - libusb_unref_device(dev); + usbi_hotplug_exit(_ctx); + usbi_io_exit(_ctx); + +err_free_ctx: + if (!ctx) { + /* clear default context that was not fully initialized */ + usbi_default_context = NULL; + default_context_refcnt = 0; } - usbi_mutex_unlock(&ctx->usb_devs_lock); - usbi_mutex_destroy(&ctx->open_devs_lock); - usbi_mutex_destroy(&ctx->usb_devs_lock); - usbi_mutex_destroy(&ctx->hotplug_cbs_lock); + usbi_mutex_destroy(&_ctx->open_devs_lock); + usbi_mutex_destroy(&_ctx->usb_devs_lock); + + free(_ctx); - free(ctx); -err_unlock: usbi_mutex_static_unlock(&default_context_lock); + return r; } @@ -2366,90 +2401,66 @@ int API_EXPORTED libusb_init(libusb_context **context) */ void API_EXPORTED libusb_exit(libusb_context *ctx) { - struct libusb_device *dev, *next; - struct timeval tv = { 0, 0 }; - int destroying_default_context = 0; - - usbi_dbg(" "); + struct libusb_context *_ctx; + struct libusb_device *dev; - ctx = usbi_get_context(ctx); + usbi_mutex_static_lock(&default_context_lock); /* if working with default context, only actually do the deinitialization * if we're the last user */ - usbi_mutex_static_lock(&default_context_lock); - if (ctx == usbi_default_context) { + if (!ctx) { if (!usbi_default_context) { - usbi_dbg("no default context, not initialized?"); + usbi_dbg(ctx, "no default context, not initialized?"); usbi_mutex_static_unlock(&default_context_lock); return; } if (--default_context_refcnt > 0) { - usbi_dbg("not destroying default context"); + usbi_dbg(ctx, "not destroying default context"); usbi_mutex_static_unlock(&default_context_lock); return; } - usbi_dbg("destroying default context"); - /* - * Setting this flag without unlocking the default context, as - * we are actually destroying the default context. - * usbi_default_context is not set to NULL yet, as all activities - * would only stop after usbi_backend->exit() returns. - */ - destroying_default_context = 1; + usbi_dbg(ctx, "destroying default context"); + _ctx = usbi_default_context; } else { - /* Unlock default context, as we're not modifying it. */ - usbi_mutex_static_unlock(&default_context_lock); + usbi_dbg(ctx, " "); + _ctx = ctx; } usbi_mutex_static_lock(&active_contexts_lock); - list_del(&ctx->list); + list_del(&_ctx->list); usbi_mutex_static_unlock(&active_contexts_lock); - if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { - usbi_hotplug_deregister(ctx, 1); - - /* - * Ensure any pending unplug events are read from the hotplug - * pipe. The usb_device-s hold in the events are no longer part - * of usb_devs, but the events still hold a reference! - * - * Note we don't do this if the application has left devices - * open (which implies a buggy app) to avoid packet completion - * handlers running when the app does not expect them to run. - */ - if (list_empty(&ctx->open_devs)) - libusb_handle_events_timeout(ctx, &tv); + if (usbi_backend.exit) + usbi_backend.exit(_ctx); - usbi_mutex_lock(&ctx->usb_devs_lock); - for_each_device_safe(ctx, dev, next) { - list_del(&dev->list); - libusb_unref_device(dev); - } - usbi_mutex_unlock(&ctx->usb_devs_lock); - } + if (!ctx) + usbi_default_context = NULL; + if (ctx == usbi_fallback_context) + usbi_fallback_context = NULL; - /* a few sanity checks. don't bother with locking because unless - * there is an application bug, nobody will be accessing these. */ - if (!list_empty(&ctx->usb_devs)) - usbi_warn(ctx, "some libusb_devices were leaked"); - if (!list_empty(&ctx->open_devs)) - usbi_warn(ctx, "application left some devices open"); + usbi_mutex_static_unlock(&default_context_lock); - usbi_io_exit(ctx); - if (usbi_backend.exit) - usbi_backend.exit(ctx); + /* Don't bother with locking after this point because unless there is + * an application bug, nobody will be accessing the context. */ - usbi_mutex_destroy(&ctx->open_devs_lock); - usbi_mutex_destroy(&ctx->usb_devs_lock); - usbi_mutex_destroy(&ctx->hotplug_cbs_lock); - free(ctx); + usbi_hotplug_exit(_ctx); + usbi_io_exit(_ctx); - if (destroying_default_context) { - usbi_default_context = NULL; - usbi_mutex_static_unlock(&default_context_lock); + for_each_device(_ctx, dev) { + usbi_warn(_ctx, "device %d.%d still referenced", + dev->bus_number, dev->device_address); + DEVICE_CTX(dev) = NULL; } + + if (!list_empty(&_ctx->open_devs)) + usbi_warn(_ctx, "application left some devices open"); + + usbi_mutex_destroy(&_ctx->open_devs_lock); + usbi_mutex_destroy(&_ctx->usb_devs_lock); + + free(_ctx); } /** \ingroup libusb_misc @@ -2574,7 +2585,8 @@ static void log_v(struct libusb_context *ctx, enum libusb_log_level level, #else enum libusb_log_level ctx_level; - ctx = usbi_get_context(ctx); + ctx = ctx ? ctx : usbi_default_context; + ctx = ctx ? ctx : usbi_fallback_context; if (ctx) ctx_level = ctx->debug; else diff --git a/libusb/libusb/descriptor.c b/libusb/libusb/descriptor.c index ecd9441..253ef1c 100644 --- a/libusb/libusb/descriptor.c +++ b/libusb/libusb/descriptor.c @@ -140,7 +140,7 @@ static int parse_endpoint(struct libusb_context *ctx, header->bDescriptorType == LIBUSB_DT_DEVICE) break; - usbi_dbg("skipping descriptor 0x%x", header->bDescriptorType); + usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType); buffer += header->bLength; size -= header->bLength; parsed += header->bLength; @@ -414,7 +414,7 @@ static int parse_configuration(struct libusb_context *ctx, header->bDescriptorType == LIBUSB_DT_DEVICE) break; - usbi_dbg("skipping descriptor 0x%x", header->bDescriptorType); + usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType); buffer += header->bLength; size -= header->bLength; } @@ -531,7 +531,7 @@ static int get_config_descriptor(struct libusb_device *dev, uint8_t config_idx, int API_EXPORTED libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc) { - usbi_dbg(" "); + usbi_dbg(DEVICE_CTX(dev), " "); static_assert(sizeof(dev->device_descriptor) == LIBUSB_DT_DEVICE_SIZE, "struct libusb_device_descriptor is not expected size"); *desc = dev->device_descriptor; @@ -601,7 +601,7 @@ int API_EXPORTED libusb_get_config_descriptor(libusb_device *dev, uint8_t *buf; int r; - usbi_dbg("index %u", config_index); + usbi_dbg(DEVICE_CTX(dev), "index %u", config_index); if (config_index >= dev->device_descriptor.bNumConfigurations) return LIBUSB_ERROR_NOT_FOUND; @@ -656,7 +656,7 @@ int API_EXPORTED libusb_get_config_descriptor_by_value(libusb_device *dev, return raw_desc_to_config(DEVICE_CTX(dev), buf, r, config); } - usbi_dbg("value %u", bConfigurationValue); + usbi_dbg(DEVICE_CTX(dev), "value %u", bConfigurationValue); for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { union usbi_config_desc_buf _config; @@ -850,23 +850,24 @@ int API_EXPORTED libusb_get_bos_descriptor(libusb_device_handle *dev_handle, uint16_t bos_len; uint8_t *bos_data; int r; + struct libusb_context *ctx = HANDLE_CTX(dev_handle); /* Read the BOS. This generates 2 requests on the bus, * one for the header, and one for the full BOS */ r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, _bos.buf, sizeof(_bos.buf)); if (r < 0) { if (r != LIBUSB_ERROR_PIPE) - usbi_err(HANDLE_CTX(dev_handle), "failed to read BOS (%d)", r); + usbi_err(ctx, "failed to read BOS (%d)", r); return r; } if (r < LIBUSB_DT_BOS_SIZE) { - usbi_err(HANDLE_CTX(dev_handle), "short BOS read %d/%d", + usbi_err(ctx, "short BOS read %d/%d", r, LIBUSB_DT_BOS_SIZE); return LIBUSB_ERROR_IO; } bos_len = libusb_le16_to_cpu(_bos.desc.wTotalLength); - usbi_dbg("found BOS descriptor: size %u bytes, %u capabilities", + usbi_dbg(ctx, "found BOS descriptor: size %u bytes, %u capabilities", bos_len, _bos.desc.bNumDeviceCaps); bos_data = calloc(1, bos_len); if (!bos_data) @@ -875,11 +876,10 @@ int API_EXPORTED libusb_get_bos_descriptor(libusb_device_handle *dev_handle, r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, bos_data, bos_len); if (r >= 0) { if (r != (int)bos_len) - usbi_warn(HANDLE_CTX(dev_handle), "short BOS read %d/%u", - r, bos_len); + usbi_warn(ctx, "short BOS read %d/%u", r, bos_len); r = parse_bos(HANDLE_CTX(dev_handle), bos, bos_data, r); } else { - usbi_err(HANDLE_CTX(dev_handle), "failed to read BOS (%d)", r); + usbi_err(ctx, "failed to read BOS (%d)", r); } free(bos_data); @@ -1109,7 +1109,7 @@ int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_ha else if (str.desc.bDescriptorType != LIBUSB_DT_STRING) return LIBUSB_ERROR_IO; else if (str.desc.bLength & 1) - usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor", str.desc.bLength); + usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for language ID string descriptor", str.desc.bLength); langid = libusb_le16_to_cpu(str.desc.wData[0]); r = libusb_get_string_descriptor(dev_handle, desc_index, langid, str.buf, sizeof(str.buf)); @@ -1120,7 +1120,7 @@ int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_ha else if (str.desc.bDescriptorType != LIBUSB_DT_STRING) return LIBUSB_ERROR_IO; else if ((str.desc.bLength & 1) || str.desc.bLength != r) - usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor", str.desc.bLength); + usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor (read %d)", str.desc.bLength, r); di = 0; for (si = 2; si < str.desc.bLength; si += 2) { diff --git a/libusb/libusb/hotplug.c b/libusb/libusb/hotplug.c index e3e5e76..6b743c7 100644 --- a/libusb/libusb/hotplug.c +++ b/libusb/libusb/hotplug.c @@ -1,7 +1,7 @@ /* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ /* * Hotplug functions for libusb - * Copyright © 2012-2013 Nathan Hjelm + * Copyright © 2012-2021 Nathan Hjelm * Copyright © 2012-2013 Peter Stuge * * This library is free software; you can redistribute it and/or @@ -20,7 +20,6 @@ */ #include "libusbi.h" -#include "hotplug.h" /** * @defgroup libusb_hotplug Device hotplug event notification @@ -63,7 +62,7 @@ * A hotplug event can listen for either or both of these events. * * Note: If you receive notification that a device has left and you have any - * a libusb_device_handles for the device it is up to you to call libusb_close() + * libusb_device_handles for the device it is up to you to call libusb_close() * on each device handle to free up any remaining resources associated with the device. * Once a device has left any libusb_device_handle associated with the device * are invalid and will remain so even if the device comes back. @@ -144,15 +143,79 @@ int main (void) { */ #define VALID_HOTPLUG_EVENTS \ - (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | \ - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | \ + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) #define VALID_HOTPLUG_FLAGS \ - (LIBUSB_HOTPLUG_ENUMERATE) + (LIBUSB_HOTPLUG_ENUMERATE) -static int usbi_hotplug_match_cb(struct libusb_context *ctx, - struct libusb_device *dev, libusb_hotplug_event event, - struct libusb_hotplug_callback *hotplug_cb) +void usbi_hotplug_init(struct libusb_context *ctx) +{ + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return; + + usbi_mutex_init(&ctx->hotplug_cbs_lock); + list_init(&ctx->hotplug_cbs); + ctx->next_hotplug_cb_handle = 1; + usbi_atomic_store(&ctx->hotplug_ready, 1); +} + +void usbi_hotplug_exit(struct libusb_context *ctx) +{ + struct usbi_hotplug_callback *hotplug_cb, *next_cb; + struct usbi_hotplug_message *msg; + struct libusb_device *dev, *next_dev; + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return; + + if (!usbi_atomic_load(&ctx->hotplug_ready)) + return; + + /* free all registered hotplug callbacks */ + for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) { + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + + /* free all pending hotplug messages */ + while (!list_empty(&ctx->hotplug_msgs)) { + msg = list_first_entry(&ctx->hotplug_msgs, struct usbi_hotplug_message, list); + + /* if the device left, the message holds a reference + * and we must drop it */ + if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + libusb_unref_device(msg->device); + + list_del(&msg->list); + free(msg); + } + + /* free all discovered devices. due to parent references loop until no devices are freed. */ + for_each_device_safe(ctx, dev, next_dev) { + /* remove the device from the usb_devs list only if there are no + * references held, otherwise leave it on the list so that a + * warning message will be shown */ + if (usbi_atomic_load(&dev->refcnt) == 1) { + list_del(&dev->list); + } + if (dev->parent_dev && usbi_atomic_load(&dev->parent_dev->refcnt) == 1) { + /* the parent was before this device in the list and will be released. + remove it from the list. this is safe as parent_dev can not be + equal to next_dev. */ + assert (dev->parent_dev != next_dev); + list_del(&dev->parent_dev->list); + } + libusb_unref_device(dev); + } + + usbi_mutex_destroy(&ctx->hotplug_cbs_lock); +} + +static int usbi_hotplug_match_cb(struct libusb_device *dev, + libusb_hotplug_event event, struct usbi_hotplug_callback *hotplug_cb) { if (!(hotplug_cb->flags & event)) { return 0; @@ -173,71 +236,100 @@ static int usbi_hotplug_match_cb(struct libusb_context *ctx, return 0; } - return hotplug_cb->cb(ctx, dev, event, hotplug_cb->user_data); -} - -void usbi_hotplug_match(struct libusb_context *ctx, struct libusb_device *dev, - libusb_hotplug_event event) -{ - struct libusb_hotplug_callback *hotplug_cb, *next; - int ret; - - usbi_mutex_lock(&ctx->hotplug_cbs_lock); - - for_each_hotplug_cb_safe(ctx, hotplug_cb, next) { - if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) { - /* process deregistration in usbi_hotplug_deregister() */ - continue; - } - - usbi_mutex_unlock(&ctx->hotplug_cbs_lock); - ret = usbi_hotplug_match_cb(ctx, dev, event, hotplug_cb); - usbi_mutex_lock(&ctx->hotplug_cbs_lock); - - if (ret) { - list_del(&hotplug_cb->list); - free(hotplug_cb); - } - } - - usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + return hotplug_cb->cb(DEVICE_CTX(dev), dev, event, hotplug_cb->user_data); } void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, libusb_hotplug_event event) { - struct libusb_hotplug_message *message = calloc(1, sizeof(*message)); + struct usbi_hotplug_message *msg; unsigned int event_flags; - if (!message) { + /* Only generate a notification if hotplug is ready. This prevents hotplug + * notifications from being generated during initial enumeration or if the + * backend does not support hotplug. */ + if (!usbi_atomic_load(&ctx->hotplug_ready)) + return; + + msg = calloc(1, sizeof(*msg)); + if (!msg) { usbi_err(ctx, "error allocating hotplug message"); return; } - message->event = event; - message->device = dev; + msg->event = event; + msg->device = dev; /* Take the event data lock and add this message to the list. * Only signal an event if there are no prior pending events. */ usbi_mutex_lock(&ctx->event_data_lock); event_flags = ctx->event_flags; ctx->event_flags |= USBI_EVENT_HOTPLUG_MSG_PENDING; - list_add_tail(&message->list, &ctx->hotplug_msgs); + list_add_tail(&msg->list, &ctx->hotplug_msgs); if (!event_flags) usbi_signal_event(&ctx->event); usbi_mutex_unlock(&ctx->event_data_lock); } +void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_msgs) +{ + struct usbi_hotplug_callback *hotplug_cb, *next_cb; + struct usbi_hotplug_message *msg; + int r; + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + /* dispatch all pending hotplug messages */ + while (!list_empty(hotplug_msgs)) { + msg = list_first_entry(hotplug_msgs, struct usbi_hotplug_message, list); + + for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) { + /* skip callbacks that have unregistered */ + if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) + continue; + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + r = usbi_hotplug_match_cb(msg->device, msg->event, hotplug_cb); + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + if (r) { + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + } + + /* if the device left, the message holds a reference + * and we must drop it */ + if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + libusb_unref_device(msg->device); + + list_del(&msg->list); + free(msg); + } + + /* free any callbacks that have unregistered */ + for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) { + if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) { + usbi_dbg(ctx, "freeing hotplug cb %p with handle %d", + hotplug_cb, hotplug_cb->handle); + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + } + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); +} + int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, int events, int flags, int vendor_id, int product_id, int dev_class, libusb_hotplug_callback_fn cb_fn, void *user_data, libusb_hotplug_callback_handle *callback_handle) { - struct libusb_hotplug_callback *new_callback; + struct usbi_hotplug_callback *hotplug_cb; /* check for sane values */ - if ((!events || (~VALID_HOTPLUG_EVENTS & events)) || + if (!events || (~VALID_HOTPLUG_EVENTS & events) || (~VALID_HOTPLUG_FLAGS & flags) || (LIBUSB_HOTPLUG_MATCH_ANY != vendor_id && (~0xffff & vendor_id)) || (LIBUSB_HOTPLUG_MATCH_ANY != product_id && (~0xffff & product_id)) || @@ -247,47 +339,45 @@ int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, } /* check for hotplug support */ - if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) return LIBUSB_ERROR_NOT_SUPPORTED; - } ctx = usbi_get_context(ctx); - new_callback = calloc(1, sizeof(*new_callback)); - if (!new_callback) { + hotplug_cb = calloc(1, sizeof(*hotplug_cb)); + if (!hotplug_cb) return LIBUSB_ERROR_NO_MEM; - } - new_callback->flags = (uint8_t)events; + hotplug_cb->flags = (uint8_t)events; if (LIBUSB_HOTPLUG_MATCH_ANY != vendor_id) { - new_callback->flags |= USBI_HOTPLUG_VENDOR_ID_VALID; - new_callback->vendor_id = (uint16_t)vendor_id; + hotplug_cb->flags |= USBI_HOTPLUG_VENDOR_ID_VALID; + hotplug_cb->vendor_id = (uint16_t)vendor_id; } if (LIBUSB_HOTPLUG_MATCH_ANY != product_id) { - new_callback->flags |= USBI_HOTPLUG_PRODUCT_ID_VALID; - new_callback->product_id = (uint16_t)product_id; + hotplug_cb->flags |= USBI_HOTPLUG_PRODUCT_ID_VALID; + hotplug_cb->product_id = (uint16_t)product_id; } if (LIBUSB_HOTPLUG_MATCH_ANY != dev_class) { - new_callback->flags |= USBI_HOTPLUG_DEV_CLASS_VALID; - new_callback->dev_class = (uint8_t)dev_class; + hotplug_cb->flags |= USBI_HOTPLUG_DEV_CLASS_VALID; + hotplug_cb->dev_class = (uint8_t)dev_class; } - new_callback->cb = cb_fn; - new_callback->user_data = user_data; + hotplug_cb->cb = cb_fn; + hotplug_cb->user_data = user_data; usbi_mutex_lock(&ctx->hotplug_cbs_lock); /* protect the handle by the context hotplug lock */ - new_callback->handle = ctx->next_hotplug_cb_handle++; + hotplug_cb->handle = ctx->next_hotplug_cb_handle++; /* handle the unlikely case of overflow */ if (ctx->next_hotplug_cb_handle < 0) ctx->next_hotplug_cb_handle = 1; - list_add(&new_callback->list, &ctx->hotplug_cbs); + list_add(&hotplug_cb->list, &ctx->hotplug_cbs); usbi_mutex_unlock(&ctx->hotplug_cbs_lock); - usbi_dbg("new hotplug cb %p with handle %d", new_callback, new_callback->handle); + usbi_dbg(ctx, "new hotplug cb %p with handle %d", hotplug_cb, hotplug_cb->handle); if ((flags & LIBUSB_HOTPLUG_ENUMERATE) && (events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)) { ssize_t i, len; @@ -295,23 +385,21 @@ int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, len = libusb_get_device_list(ctx, &devs); if (len < 0) { - libusb_hotplug_deregister_callback(ctx, - new_callback->handle); + libusb_hotplug_deregister_callback(ctx, hotplug_cb->handle); return (int)len; } for (i = 0; i < len; i++) { - usbi_hotplug_match_cb(ctx, devs[i], + usbi_hotplug_match_cb(devs[i], LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, - new_callback); + hotplug_cb); } libusb_free_device_list(devs, 1); } - if (callback_handle) - *callback_handle = new_callback->handle; + *callback_handle = hotplug_cb->handle; return LIBUSB_SUCCESS; } @@ -319,24 +407,24 @@ int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, void API_EXPORTED libusb_hotplug_deregister_callback(libusb_context *ctx, libusb_hotplug_callback_handle callback_handle) { - struct libusb_hotplug_callback *hotplug_cb; + struct usbi_hotplug_callback *hotplug_cb; int deregistered = 0; /* check for hotplug support */ - if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) return; - } - usbi_dbg("deregister hotplug cb %d", callback_handle); + usbi_dbg(ctx, "deregister hotplug cb %d", callback_handle); ctx = usbi_get_context(ctx); usbi_mutex_lock(&ctx->hotplug_cbs_lock); for_each_hotplug_cb(ctx, hotplug_cb) { if (callback_handle == hotplug_cb->handle) { - /* Mark this callback for deregistration */ + /* mark this callback for deregistration */ hotplug_cb->flags |= USBI_HOTPLUG_NEEDS_FREE; deregistered = 1; + break; } } usbi_mutex_unlock(&ctx->hotplug_cbs_lock); @@ -357,15 +445,14 @@ DEFAULT_VISIBILITY void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx, libusb_hotplug_callback_handle callback_handle) { - struct libusb_hotplug_callback *hotplug_cb; + struct usbi_hotplug_callback *hotplug_cb; void *user_data = NULL; /* check for hotplug support */ - if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) return NULL; - } - usbi_dbg("get hotplug user data %d", callback_handle); + usbi_dbg(ctx, "get hotplug cb %d user data", callback_handle); ctx = usbi_get_context(ctx); @@ -373,25 +460,10 @@ void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx, for_each_hotplug_cb(ctx, hotplug_cb) { if (callback_handle == hotplug_cb->handle) { user_data = hotplug_cb->user_data; + break; } } usbi_mutex_unlock(&ctx->hotplug_cbs_lock); return user_data; } - -void usbi_hotplug_deregister(struct libusb_context *ctx, int forced) -{ - struct libusb_hotplug_callback *hotplug_cb, *next; - - usbi_mutex_lock(&ctx->hotplug_cbs_lock); - for_each_hotplug_cb_safe(ctx, hotplug_cb, next) { - if (forced || (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE)) { - usbi_dbg("freeing hotplug cb %p with handle %d", hotplug_cb, - hotplug_cb->handle); - list_del(&hotplug_cb->list); - free(hotplug_cb); - } - } - usbi_mutex_unlock(&ctx->hotplug_cbs_lock); -} diff --git a/libusb/libusb/io.c b/libusb/libusb/io.c index 0e960dd..9e3146c 100644 --- a/libusb/libusb/io.c +++ b/libusb/libusb/io.c @@ -22,7 +22,6 @@ */ #include "libusbi.h" -#include "hotplug.h" /** * \page libusb_io Synchronous and asynchronous device I/O @@ -73,7 +72,7 @@ * a single function call. When the function call returns, the transfer has * completed and you can parse the results. * - * If you have used the libusb-0.1 before, this I/O style will seem familiar to + * If you have used libusb-0.1 before, this I/O style will seem familiar to * you. libusb-0.1 only offered a synchronous interface. * * In our input device example, to read button presses you might write code @@ -1181,12 +1180,12 @@ int usbi_io_init(struct libusb_context *ctx) #ifdef HAVE_OS_TIMER r = usbi_create_timer(&ctx->timer); if (r == 0) { - usbi_dbg("using timer for timeouts"); + usbi_dbg(ctx, "using timer for timeouts"); r = usbi_add_event_source(ctx, USBI_TIMER_OS_HANDLE(&ctx->timer), USBI_TIMER_POLL_EVENTS); if (r < 0) goto err_destroy_timer; } else { - usbi_dbg("timer not available for timeouts"); + usbi_dbg(ctx, "timer not available for timeouts"); } #endif @@ -1310,7 +1309,6 @@ struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer( itransfer->priv = ptr; usbi_mutex_init(&itransfer->lock); transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); - usbi_dbg("transfer %p", transfer); return transfer; } @@ -1340,12 +1338,14 @@ void API_EXPORTED libusb_free_transfer(struct libusb_transfer *transfer) if (!transfer) return; - usbi_dbg("transfer %p", transfer); + usbi_dbg(TRANSFER_CTX(transfer), "transfer %p", transfer); if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER) free(transfer->buffer); itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); usbi_mutex_destroy(&itransfer->lock); + if (itransfer->dev) + libusb_unref_device(itransfer->dev); priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size); ptr = (unsigned char *)itransfer - priv_size; @@ -1376,12 +1376,12 @@ static int arm_timer_for_next_timeout(struct libusb_context *ctx) /* act on first transfer that has not already been handled */ if (!(itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))) { - usbi_dbg("next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); + usbi_dbg(ctx, "next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); return usbi_arm_timer(&ctx->timer, cur_ts); } } - usbi_dbg("no timeouts, disarming timer"); + usbi_dbg(ctx, "no timeouts, disarming timer"); return usbi_disarm_timer(&ctx->timer); } #else @@ -1438,7 +1438,7 @@ static int add_to_flying_list(struct usbi_transfer *itransfer) if (first && usbi_using_timer(ctx) && TIMESPEC_IS_SET(timeout)) { /* if this transfer has the lowest timeout of all active transfers, * rearm the timer with this transfer's timeout */ - usbi_dbg("arm timer for timeout in %ums (first in line)", + usbi_dbg(ctx, "arm timer for timeout in %ums (first in line)", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); r = usbi_arm_timer(&ctx->timer, timeout); } @@ -1491,10 +1491,16 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer) { struct usbi_transfer *itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); - struct libusb_context *ctx = TRANSFER_CTX(transfer); + struct libusb_context *ctx; int r; - usbi_dbg("transfer %p", transfer); + assert(transfer->dev_handle); + if (itransfer->dev) + libusb_unref_device(itransfer->dev); + itransfer->dev = libusb_ref_device(transfer->dev_handle->dev); + + ctx = HANDLE_CTX(transfer->dev_handle); + usbi_dbg(ctx, "transfer %p", transfer); /* * Important note on locking, this function takes / releases locks @@ -1546,15 +1552,13 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer) } /* * We must release the flying transfers lock here, because with - * some backends the submit_transfer method is synchroneous. + * some backends the submit_transfer method is synchronous. */ usbi_mutex_unlock(&ctx->flying_transfers_lock); r = usbi_backend.submit_transfer(itransfer); if (r == LIBUSB_SUCCESS) { itransfer->state_flags |= USBI_TRANSFER_IN_FLIGHT; - /* keep a reference to this device */ - libusb_ref_device(transfer->dev_handle->dev); } usbi_mutex_unlock(&itransfer->lock); @@ -1572,6 +1576,26 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer) * \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED * "LIBUSB_TRANSFER_CANCELLED." * + * This function behaves differently on Darwin-based systems (macOS and iOS): + * + * - Calling this function for one transfer will cause all transfers on the + * same endpoint to be cancelled. Your callback function will be invoked with + * a transfer status of + * \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED + * "LIBUSB_TRANSFER_CANCELLED" for each transfer that was cancelled. + + * - Calling this function also sends a \c ClearFeature(ENDPOINT_HALT) request + * for the transfer's endpoint. If the device does not handle this request + * correctly, the data toggle bits for the endpoint can be left out of sync + * between host and device, which can have unpredictable results when the + * next data is sent on the endpoint, including data being silently lost. + * A call to \ref libusb_clear_halt will not resolve this situation, since + * that function uses the same request. Therefore, if your program runs on + * Darwin and uses a device that does not correctly implement + * \c ClearFeature(ENDPOINT_HALT) requests, it may only be safe to cancel + * transfers when followed by a device reset using + * \ref libusb_reset_device. + * * \param transfer the transfer to cancel * \returns 0 on success * \returns LIBUSB_ERROR_NOT_FOUND if the transfer is not in progress, @@ -1582,9 +1606,10 @@ int API_EXPORTED libusb_cancel_transfer(struct libusb_transfer *transfer) { struct usbi_transfer *itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); int r; - usbi_dbg("transfer %p", transfer ); + usbi_dbg(ctx, "transfer %p", transfer ); usbi_mutex_lock(&itransfer->lock); if (!(itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT) || (itransfer->state_flags & USBI_TRANSFER_CANCELLING)) { @@ -1595,10 +1620,9 @@ int API_EXPORTED libusb_cancel_transfer(struct libusb_transfer *transfer) if (r < 0) { if (r != LIBUSB_ERROR_NOT_FOUND && r != LIBUSB_ERROR_NO_DEVICE) - usbi_err(TRANSFER_CTX(transfer), - "cancel transfer failed error %d", r); + usbi_err(ctx, "cancel transfer failed error %d", r); else - usbi_dbg("cancel transfer failed error %d", r); + usbi_dbg(ctx, "cancel transfer failed error %d", r); if (r == LIBUSB_ERROR_NO_DEVICE) itransfer->state_flags |= USBI_TRANSFER_DEVICE_DISAPPEARED; @@ -1661,13 +1685,13 @@ int usbi_handle_transfer_completion(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 = ITRANSFER_CTX(itransfer); uint8_t flags; int r; r = remove_from_flying_list(itransfer); if (r < 0) - usbi_err(ITRANSFER_CTX(itransfer), "failed to set timer for next timeout"); + usbi_err(ctx, "failed to set timer for next timeout"); usbi_mutex_lock(&itransfer->lock); itransfer->state_flags &= ~USBI_TRANSFER_IN_FLIGHT; @@ -1679,7 +1703,7 @@ int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL) rqlen -= LIBUSB_CONTROL_SETUP_SIZE; if (rqlen != itransfer->transferred) { - usbi_dbg("interpreting short transfer as error"); + usbi_dbg(ctx, "interpreting short transfer as error"); status = LIBUSB_TRANSFER_ERROR; } } @@ -1687,14 +1711,13 @@ int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, flags = transfer->flags; transfer->status = status; transfer->actual_length = itransfer->transferred; - usbi_dbg("transfer %p has callback %p", transfer, transfer->callback); + usbi_dbg(ctx, "transfer %p has callback %p", transfer, transfer->callback); if (transfer->callback) transfer->callback(transfer); /* transfer might have been freed by the above call, do not use from * this point. */ if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) libusb_free_transfer(transfer); - libusb_unref_device(dev_handle->dev); return r; } @@ -1715,7 +1738,7 @@ int usbi_handle_transfer_cancellation(struct usbi_transfer *itransfer) /* if the URB was cancelled due to timeout, report timeout to the user */ if (timed_out) { - usbi_dbg("detected timeout cancellation"); + usbi_dbg(ctx, "detected timeout cancellation"); return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_TIMED_OUT); } @@ -1728,10 +1751,10 @@ int usbi_handle_transfer_cancellation(struct usbi_transfer *itransfer) * function will be called the next time an event handler runs. */ void usbi_signal_transfer_completion(struct usbi_transfer *itransfer) { - libusb_device_handle *dev_handle = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->dev_handle; + struct libusb_device *dev = itransfer->dev; - if (dev_handle) { - struct libusb_context *ctx = HANDLE_CTX(dev_handle); + if (dev) { + struct libusb_context *ctx = DEVICE_CTX(dev); unsigned int event_flags; usbi_mutex_lock(&ctx->event_data_lock); @@ -1776,7 +1799,7 @@ int API_EXPORTED libusb_try_lock_events(libusb_context *ctx) ru = ctx->device_close; usbi_mutex_unlock(&ctx->event_data_lock); if (ru) { - usbi_dbg("someone else is closing a device"); + usbi_dbg(ctx, "someone else is closing a device"); return 1; } @@ -1868,7 +1891,7 @@ int API_EXPORTED libusb_event_handling_ok(libusb_context *ctx) r = ctx->device_close; usbi_mutex_unlock(&ctx->event_data_lock); if (r) { - usbi_dbg("someone else is closing a device"); + usbi_dbg(ctx, "someone else is closing a device"); return 0; } @@ -1897,7 +1920,7 @@ int API_EXPORTED libusb_event_handler_active(libusb_context *ctx) r = ctx->device_close; usbi_mutex_unlock(&ctx->event_data_lock); if (r) { - usbi_dbg("someone else is closing a device"); + usbi_dbg(ctx, "someone else is closing a device"); return 1; } @@ -1918,7 +1941,7 @@ void API_EXPORTED libusb_interrupt_event_handler(libusb_context *ctx) { unsigned int event_flags; - usbi_dbg(" "); + usbi_dbg(ctx, " "); ctx = usbi_get_context(ctx); usbi_mutex_lock(&ctx->event_data_lock); @@ -2073,9 +2096,10 @@ static void handle_timeouts(struct libusb_context *ctx) static int handle_event_trigger(struct libusb_context *ctx) { struct list_head hotplug_msgs; + int hotplug_event = 0; int r = 0; - usbi_dbg("event triggered"); + usbi_dbg(ctx, "event triggered"); list_init(&hotplug_msgs); @@ -2084,21 +2108,28 @@ static int handle_event_trigger(struct libusb_context *ctx) /* check if someone modified the event sources */ if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) - usbi_dbg("someone updated the event sources"); + usbi_dbg(ctx, "someone updated the event sources"); if (ctx->event_flags & USBI_EVENT_USER_INTERRUPT) { - usbi_dbg("someone purposefully interrupted"); + usbi_dbg(ctx, "someone purposefully interrupted"); ctx->event_flags &= ~USBI_EVENT_USER_INTERRUPT; } + if (ctx->event_flags & USBI_EVENT_HOTPLUG_CB_DEREGISTERED) { + usbi_dbg(ctx, "someone unregistered a hotplug cb"); + ctx->event_flags &= ~USBI_EVENT_HOTPLUG_CB_DEREGISTERED; + hotplug_event = 1; + } + /* check if someone is closing a device */ if (ctx->event_flags & USBI_EVENT_DEVICE_CLOSE) - usbi_dbg("someone is closing a device"); + usbi_dbg(ctx, "someone is closing a device"); /* check for any pending hotplug messages */ if (ctx->event_flags & USBI_EVENT_HOTPLUG_MSG_PENDING) { - usbi_dbg("hotplug message received"); + usbi_dbg(ctx, "hotplug message received"); ctx->event_flags &= ~USBI_EVENT_HOTPLUG_MSG_PENDING; + hotplug_event = 1; assert(!list_empty(&ctx->hotplug_msgs)); list_cut(&hotplug_msgs, &ctx->hotplug_msgs); } @@ -2136,20 +2167,9 @@ static int handle_event_trigger(struct libusb_context *ctx) usbi_mutex_unlock(&ctx->event_data_lock); - /* process the hotplug messages, if any */ - while (!list_empty(&hotplug_msgs)) { - struct libusb_hotplug_message *message = - list_first_entry(&hotplug_msgs, struct libusb_hotplug_message, list); - - usbi_hotplug_match(ctx, message->device, message->event); - - /* the device left, dereference the device */ - if (message->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) - libusb_unref_device(message->device); - - list_del(&message->list); - free(message); - } + /* process the hotplug events, if any */ + if (hotplug_event) + usbi_hotplug_process(ctx, &hotplug_msgs); return r; } @@ -2190,7 +2210,7 @@ static int handle_events(struct libusb_context *ctx, struct timeval *tv) * save the additional overhead */ usbi_mutex_lock(&ctx->event_data_lock); if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) { - usbi_dbg("event sources modified, reallocating event data"); + usbi_dbg(ctx, "event sources modified, reallocating event data"); /* free anything removed since we last ran */ cleanup_removed_event_sources(ctx); @@ -2337,7 +2357,7 @@ int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx, if (libusb_try_lock_events(ctx) == 0) { if (completed == NULL || !*completed) { /* we obtained the event lock: do our own event handling */ - usbi_dbg("doing our own event handling"); + usbi_dbg(ctx, "doing our own event handling"); r = handle_events(ctx, &poll_timeout); } libusb_unlock_events(ctx); @@ -2355,11 +2375,11 @@ int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx, /* we hit a race: whoever was event handling earlier finished in the * time it took us to reach this point. try the cycle again. */ libusb_unlock_event_waiters(ctx); - usbi_dbg("event handler was active but went away, retrying"); + usbi_dbg(ctx, "event handler was active but went away, retrying"); goto retry; } - usbi_dbg("another thread is doing event handling"); + usbi_dbg(ctx, "another thread is doing event handling"); r = libusb_wait_for_event(ctx, &poll_timeout); already_done: @@ -2554,7 +2574,7 @@ int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx, usbi_mutex_lock(&ctx->flying_transfers_lock); if (list_empty(&ctx->flying_transfers)) { usbi_mutex_unlock(&ctx->flying_transfers_lock); - usbi_dbg("no URBs, no timeout!"); + usbi_dbg(ctx, "no URBs, no timeout!"); return 0; } @@ -2573,19 +2593,19 @@ int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx, usbi_mutex_unlock(&ctx->flying_transfers_lock); if (!TIMESPEC_IS_SET(&next_timeout)) { - usbi_dbg("no URB with timeout or all handled by OS; no timeout!"); + usbi_dbg(ctx, "no URB with timeout or all handled by OS; no timeout!"); return 0; } usbi_get_monotonic_time(&systime); if (!TIMESPEC_CMP(&systime, &next_timeout, <)) { - usbi_dbg("first timeout already expired"); + usbi_dbg(ctx, "first timeout already expired"); timerclear(tv); } else { TIMESPEC_SUB(&next_timeout, &systime, &next_timeout); TIMESPEC_TO_TIMEVAL(tv, &next_timeout); - usbi_dbg("next timeout in %ld.%06lds", (long)tv->tv_sec, (long)tv->tv_usec); + usbi_dbg(ctx, "next timeout in %ld.%06lds", (long)tv->tv_sec, (long)tv->tv_usec); } return 1; @@ -2656,7 +2676,7 @@ int usbi_add_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle if (!ievent_source) return LIBUSB_ERROR_NO_MEM; - usbi_dbg("add " USBI_OS_HANDLE_FORMAT_STRING " events %d", os_handle, poll_events); + usbi_dbg(ctx, "add " USBI_OS_HANDLE_FORMAT_STRING " events %d", os_handle, poll_events); ievent_source->data.os_handle = os_handle; ievent_source->data.poll_events = poll_events; usbi_mutex_lock(&ctx->event_data_lock); @@ -2678,7 +2698,7 @@ void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_ha struct usbi_event_source *ievent_source; int found = 0; - usbi_dbg("remove " USBI_OS_HANDLE_FORMAT_STRING, os_handle); + usbi_dbg(ctx, "remove " USBI_OS_HANDLE_FORMAT_STRING, os_handle); usbi_mutex_lock(&ctx->event_data_lock); for_each_event_source(ctx, ievent_source) { if (ievent_source->data.os_handle == os_handle) { @@ -2688,7 +2708,7 @@ void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_ha } if (!found) { - usbi_dbg("couldn't find " USBI_OS_HANDLE_FORMAT_STRING " to remove", os_handle); + usbi_dbg(ctx, "couldn't find " USBI_OS_HANDLE_FORMAT_STRING " to remove", os_handle); usbi_mutex_unlock(&ctx->event_data_lock); return; } @@ -2787,7 +2807,7 @@ void usbi_handle_disconnect(struct libusb_device_handle *dev_handle) struct usbi_transfer *cur; struct usbi_transfer *to_cancel; - usbi_dbg("device %d.%d", + usbi_dbg(ctx, "device %d.%d", dev_handle->dev->bus_number, dev_handle->dev->device_address); /* terminate all pending transfers with the LIBUSB_TRANSFER_NO_DEVICE @@ -2822,7 +2842,7 @@ void usbi_handle_disconnect(struct libusb_device_handle *dev_handle) if (!to_cancel) break; - usbi_dbg("cancelling transfer %p from disconnect", + usbi_dbg(ctx, "cancelling transfer %p from disconnect", USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel)); usbi_mutex_lock(&to_cancel->lock); diff --git a/libusb/libusb/libusb.h b/libusb/libusb/libusb.h index 1308571..2592ea7 100644 --- a/libusb/libusb/libusb.h +++ b/libusb/libusb/libusb.h @@ -26,13 +26,19 @@ #define LIBUSB_H #if defined(_MSC_VER) +#pragma warning(push) +/* Disable: warning C4200: nonstandard extension used : zero-sized array in struct/union */ +#pragma warning(disable:4200) /* on MS environments, the inline keyword is available in C++ only */ #if !defined(__cplusplus) #define inline __inline #endif /* ssize_t is also not available */ +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED #include typedef SSIZE_T ssize_t; +#endif /* _SSIZE_T_DEFINED */ #endif /* _MSC_VER */ #include @@ -136,7 +142,7 @@ typedef SSIZE_T ssize_t; * Internally, LIBUSB_API_VERSION is defined as follows: * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental) */ -#define LIBUSB_API_VERSION 0x01000108 +#define LIBUSB_API_VERSION 0x01000109 /* The following is kept for compatibility, but will be deprecated in the future */ #define LIBUSBX_API_VERSION LIBUSB_API_VERSION @@ -904,7 +910,7 @@ struct libusb_container_id_descriptor { /** \ingroup libusb_asyncio * Setup packet for control transfers. */ -#if defined(_MSC_VER) +#if defined(_MSC_VER) || defined(__WATCOMC__) #pragma pack(push, 1) #endif struct libusb_control_setup { @@ -932,7 +938,7 @@ struct libusb_control_setup { /** Number of bytes to transfer */ uint16_t wLength; } LIBUSB_PACKED; -#if defined(_MSC_VER) +#if defined(_MSC_VER) || defined(__WATCOMC__) #pragma pack(pop) #endif @@ -979,8 +985,9 @@ struct libusb_version { * Sessions are created by libusb_init() and destroyed through libusb_exit(). * If your application is guaranteed to only ever include a single libusb * user (i.e. you), you do not have to worry about contexts: pass NULL in - * every function call where a context is required. The default context - * will be used. + * every function call where a context is required, and the default context + * will be used. Note that libusb_set_option(NULL, ...) is special, and adds + * an option to a list of default options for new contexts. * * For more information, see \ref libusb_contexts. */ @@ -989,7 +996,7 @@ typedef struct libusb_context libusb_context; /** \ingroup libusb_dev * Structure representing a USB device detected on the system. This is an * opaque type for which you are only ever provided with a pointer, usually - * originating from libusb_get_device_list(). + * originating from libusb_get_device_list() or libusb_hotplug_register_callback(). * * Certain operations can be performed on a device, but in order to do any * I/O you will have to first obtain a device handle using libusb_open(). @@ -1325,6 +1332,9 @@ enum libusb_log_level { /** \ingroup libusb_lib * Log callback mode. + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * * \see libusb_set_log_cb() */ enum libusb_log_cb_mode { @@ -1341,6 +1351,9 @@ enum libusb_log_cb_mode { * is a global log message * \param level the log level, see \ref libusb_log_level for a description * \param str the log message + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * * \see libusb_set_log_cb() */ typedef void (LIBUSB_CALL *libusb_log_cb)(libusb_context *ctx, @@ -2092,20 +2105,36 @@ enum libusb_option { */ LIBUSB_OPTION_USE_USBDK = 1, - /** Set libusb has weak authority. With this option, libusb will skip - * scan devices in libusb_init. + /** Do not scan for devices + * + * With this option set, libusb will skip scanning devices in + * libusb_init(). Must be set before calling libusb_init(). * - * This option should be set before calling libusb_init(), otherwise - * libusb_init will failed. Normally libusb_wrap_sys_device need set - * this option. + * Hotplug functionality will also be deactivated. * - * Only valid on Linux-based operating system, such as Android. + * The option is useful in combination with libusb_wrap_sys_device(), + * which can access a device directly without prior device scanning. + * + * This is typically needed on Android, where access to USB devices + * is limited. + * + * For LIBUSB_API_VERSION 0x01000108 it was called LIBUSB_OPTION_WEAK_AUTHORITY + * + * Only valid on Linux. */ - LIBUSB_OPTION_WEAK_AUTHORITY = 2 + LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2, + +#define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY + + LIBUSB_OPTION_MAX = 3 }; int LIBUSB_CALL libusb_set_option(libusb_context *ctx, enum libusb_option option, ...); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #if defined(__cplusplus) } #endif diff --git a/libusb/libusb/libusbi.h b/libusb/libusb/libusbi.h index 491114b..b1fc88c 100644 --- a/libusb/libusb/libusbi.h +++ b/libusb/libusb/libusbi.h @@ -83,6 +83,34 @@ #define PTR_ALIGN(v) \ (((v) + (sizeof(void *) - 1)) & ~(sizeof(void *) - 1)) +/* Atomic operations + * + * Useful for reference counting or when accessing a value without a lock + * + * The following atomic operations are defined: + * usbi_atomic_load() - Atomically read a variable's value + * usbi_atomic_store() - Atomically write a new value value to a variable + * usbi_atomic_inc() - Atomically increment a variable's value and return the new value + * usbi_atomic_dec() - Atomically decrement a variable's value and return the new value + * + * All of these operations are ordered with each other, thus the effects of + * any one operation is guaranteed to be seen by any other operation. + */ +#ifdef _MSC_VER +typedef volatile LONG usbi_atomic_t; +#define usbi_atomic_load(a) (*(a)) +#define usbi_atomic_store(a, v) (*(a)) = (v) +#define usbi_atomic_inc(a) InterlockedIncrement((a)) +#define usbi_atomic_dec(a) InterlockedDecrement((a)) +#else +#include +typedef atomic_long usbi_atomic_t; +#define usbi_atomic_load(a) atomic_load((a)) +#define usbi_atomic_store(a, v) atomic_store((a), (v)) +#define usbi_atomic_inc(a) (atomic_fetch_add((a), 1) + 1) +#define usbi_atomic_dec(a) (atomic_fetch_add((a), -1) - 1) +#endif + /* Internal abstractions for event handling and thread synchronization */ #if defined(PLATFORM_POSIX) #include "os/events_posix.h" @@ -289,22 +317,23 @@ void usbi_log(struct libusb_context *ctx, enum libusb_log_level level, #define usbi_err(ctx, ...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_ERROR, __VA_ARGS__) #define usbi_warn(ctx, ...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_WARNING, __VA_ARGS__) #define usbi_info(ctx, ...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_INFO, __VA_ARGS__) -#define usbi_dbg(...) _usbi_log(NULL, LIBUSB_LOG_LEVEL_DEBUG, __VA_ARGS__) +#define usbi_dbg(ctx ,...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_DEBUG, __VA_ARGS__) #else /* ENABLE_LOGGING */ #define usbi_err(ctx, ...) UNUSED(ctx) #define usbi_warn(ctx, ...) UNUSED(ctx) #define usbi_info(ctx, ...) UNUSED(ctx) -#define usbi_dbg(...) do {} while (0) +#define usbi_dbg(ctx, ...) do {} while (0) #endif /* ENABLE_LOGGING */ #define DEVICE_CTX(dev) ((dev)->ctx) -#define HANDLE_CTX(handle) (DEVICE_CTX((handle)->dev)) -#define TRANSFER_CTX(transfer) (HANDLE_CTX((transfer)->dev_handle)) +#define HANDLE_CTX(handle) ((handle) ? DEVICE_CTX((handle)->dev) : NULL) #define ITRANSFER_CTX(itransfer) \ - (TRANSFER_CTX(USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer))) + ((itransfer)->dev ? DEVICE_CTX((itransfer)->dev) : NULL) +#define TRANSFER_CTX(transfer) \ + (ITRANSFER_CTX(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer))) #define IS_EPIN(ep) (0 != ((ep) & LIBUSB_ENDPOINT_IN)) #define IS_EPOUT(ep) (!IS_EPIN(ep)) @@ -340,6 +369,9 @@ struct libusb_context { libusb_hotplug_callback_handle next_hotplug_cb_handle; usbi_mutex_t hotplug_cbs_lock; + /* A flag to indicate that the context is ready for hotplug notifications */ + usbi_atomic_t hotplug_ready; + /* this is a list of in-flight transfer handles, sorted by timeout * expiration. URBs to timeout the soonest are placed at the beginning of * the list, URBs that will time out later are placed after, and urbs with @@ -404,13 +436,26 @@ struct libusb_context { }; extern struct libusb_context *usbi_default_context; +extern struct libusb_context *usbi_fallback_context; extern struct list_head active_contexts_list; extern usbi_mutex_static_t active_contexts_lock; static inline struct libusb_context *usbi_get_context(struct libusb_context *ctx) { - return ctx ? ctx : usbi_default_context; + static int warned = 0; + + if (!ctx) { + ctx = usbi_default_context; + } + if (!ctx) { + ctx = usbi_fallback_context; + if (ctx && warned == 0) { + usbi_err(ctx, "API misuse! Using non-default context as implicit default."); + warned = 1; + } + } + return ctx; } enum usbi_event_flags { @@ -450,10 +495,7 @@ static inline void usbi_end_event_handling(struct libusb_context *ctx) } struct libusb_device { - /* lock protects refcnt, everything else is finalized at initialization - * time */ - usbi_mutex_t lock; - int refcnt; + usbi_atomic_t refcnt; struct libusb_context *ctx; struct libusb_device *parent_dev; @@ -467,7 +509,7 @@ struct libusb_device { unsigned long session_data; struct libusb_device_descriptor device_descriptor; - int attached; + usbi_atomic_t attached; }; struct libusb_device_handle { @@ -534,6 +576,10 @@ struct usbi_transfer { uint32_t state_flags; /* Protected by usbi_transfer->lock */ uint32_t timeout_flags; /* Protected by the flying_stransfers_lock */ + /* The device reference is held until destruction for logging + * even after dev_handle is set to NULL. */ + struct libusb_device *dev; + /* this lock is held during libusb_submit_transfer() and * libusb_cancel_transfer() (allowing the OS backend to prevent duplicate * cancellation, submission-during-cancellation, etc). the OS backend @@ -664,8 +710,75 @@ union usbi_bos_desc_buf { uint16_t align; /* Force 2-byte alignment */ }; +enum usbi_hotplug_flags { + /* This callback is interested in device arrivals */ + USBI_HOTPLUG_DEVICE_ARRIVED = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, + + /* This callback is interested in device removals */ + USBI_HOTPLUG_DEVICE_LEFT = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + + /* IMPORTANT: The values for the below entries must start *after* + * the highest value of the above entries!!! + */ + + /* The vendor_id field is valid for matching */ + USBI_HOTPLUG_VENDOR_ID_VALID = (1U << 3), + + /* The product_id field is valid for matching */ + USBI_HOTPLUG_PRODUCT_ID_VALID = (1U << 4), + + /* The dev_class field is valid for matching */ + USBI_HOTPLUG_DEV_CLASS_VALID = (1U << 5), + + /* This callback has been unregistered and needs to be freed */ + USBI_HOTPLUG_NEEDS_FREE = (1U << 6), +}; + +struct usbi_hotplug_callback { + /* Flags that control how this callback behaves */ + uint8_t flags; + + /* Vendor ID to match (if flags says this is valid) */ + uint16_t vendor_id; + + /* Product ID to match (if flags says this is valid) */ + uint16_t product_id; + + /* Device class to match (if flags says this is valid) */ + uint8_t dev_class; + + /* Callback function to invoke for matching event/device */ + libusb_hotplug_callback_fn cb; + + /* Handle for this callback (used to match on deregister) */ + libusb_hotplug_callback_handle handle; + + /* User data that will be passed to the callback function */ + void *user_data; + + /* List this callback is registered in (ctx->hotplug_cbs) */ + struct list_head list; +}; + +struct usbi_hotplug_message { + /* The hotplug event that occurred */ + libusb_hotplug_event event; + + /* The device for which this hotplug event occurred */ + struct libusb_device *device; + + /* List this message is contained in (ctx->hotplug_msgs) */ + struct list_head list; +}; + /* shared data and functions */ +void usbi_hotplug_init(struct libusb_context *ctx); +void usbi_hotplug_exit(struct libusb_context *ctx); +void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event); +void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_msgs); + int usbi_io_init(struct libusb_context *ctx); void usbi_io_exit(struct libusb_context *ctx); @@ -696,6 +809,13 @@ int usbi_add_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle short poll_events); void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle); +struct usbi_option { + int is_set; + union { + int ival; + } arg; +}; + /* OS event abstraction */ int usbi_create_event(usbi_event_t *event); @@ -793,7 +913,8 @@ struct usbi_os_backend { * data structures for later, etc. * * This function is called when a libusb user initializes the library - * prior to use. + * prior to use. Mutual exclusion with other init and exit calls is + * guaranteed when this function is called. * * Return 0 on success, or a LIBUSB_ERROR code on failure. */ @@ -803,6 +924,8 @@ struct usbi_os_backend { * that was set up by init. * * This function is called when the user deinitializes the library. + * Mutual exclusion with other init and exit calls is guaranteed when + * this function is called. */ void (*exit)(struct libusb_context *ctx); @@ -1365,6 +1488,12 @@ extern const struct usbi_os_backend usbi_backend; #define for_each_removed_event_source_safe(ctx, e, n) \ for_each_safe_helper(e, n, &(ctx)->removed_event_sources, struct usbi_event_source) +#define for_each_hotplug_cb(ctx, c) \ + for_each_helper(c, &(ctx)->hotplug_cbs, struct usbi_hotplug_callback) + +#define for_each_hotplug_cb_safe(ctx, c, n) \ + for_each_safe_helper(c, n, &(ctx)->hotplug_cbs, struct usbi_hotplug_callback) + #ifdef __cplusplus } #endif diff --git a/libusb/libusb/os/darwin_usb.c b/libusb/libusb/os/darwin_usb.c index e415589..388dbca 100644 --- a/libusb/libusb/os/darwin_usb.c +++ b/libusb/libusb/os/darwin_usb.c @@ -1,8 +1,8 @@ /* -*- Mode: C; indent-tabs-mode:nil -*- */ /* * darwin backend for libusb 1.0 - * Copyright © 2008-2020 Nathan Hjelm - * Copyright © 2019-2020 Google LLC. All rights reserved. + * Copyright © 2008-2021 Nathan Hjelm + * 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 @@ -41,6 +41,10 @@ * 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 (10 * USEC_PER_SEC) + #include #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200 #include @@ -48,9 +52,11 @@ #include "darwin_usb.h" -static pthread_mutex_t libusb_darwin_init_mutex = PTHREAD_MUTEX_INITIALIZER; 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; @@ -77,14 +83,17 @@ 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 int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface); 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(io_service_t service, struct darwin_cached_device **cached_out, +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) @@ -102,6 +111,9 @@ static const char *darwin_error_str (IOReturn result) { 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"; @@ -141,16 +153,20 @@ static enum libusb_error darwin_to_libusb (IOReturn result) { 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: - case kIOUSBUnknownPipeErr: default: return LIBUSB_ERROR_OTHER; } @@ -167,6 +183,7 @@ static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) (*(cached_dev->device))->Release(cached_dev->device); cached_dev->device = NULL; } + IOObjectRelease (cached_dev->service); free (cached_dev); } } @@ -183,7 +200,9 @@ static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, ui uint8_t i, iface; - usbi_dbg ("converting ep address 0x%02x to pipeRef and interface", ep); + 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]; @@ -199,7 +218,7 @@ static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, ui if (interface_out) *interface_out = cInterface; - usbi_dbg ("pipe %d on interface %d matches", *pipep, iface); + usbi_dbg (ctx, "pipe %d on interface %d matches", *pipep, iface); return LIBUSB_SUCCESS; } } @@ -240,7 +259,7 @@ static IOReturn usb_setup_device_iterator (io_iterator_t *deviceIterator, UInt32 CFRelease (locationCF); } - return IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, deviceIterator); + return IOServiceGetMatchingServices(darwin_default_master_port, matchingDict, deviceIterator); } /* Returns 1 on success, 0 on failure. */ @@ -281,7 +300,7 @@ static bool get_ioregistry_value_data (io_service_t service, CFStringRef propert return success; } -static usb_device_t **darwin_device_from_service (io_service_t service) +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; @@ -300,14 +319,14 @@ static usb_device_t **darwin_device_from_service (io_service_t service) break; } - usbi_dbg ("set up plugin for service retry: %s", darwin_error_str (kresult)); + 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 ("could not set up plugin for service: %s", darwin_error_str (kresult)); + usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult)); return NULL; } @@ -330,7 +349,7 @@ static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { usbi_mutex_lock(&active_contexts_lock); while ((service = IOIteratorNext(add_devices))) { - ret = darwin_get_cached_device (service, &cached_device, &old_session_id); + ret = darwin_get_cached_device (NULL, service, &cached_device, &old_session_id); if (ret < 0 || !cached_device->can_enumerate) { continue; } @@ -341,7 +360,7 @@ static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { } if (cached_device->in_reenumerate) { - usbi_dbg ("cached device in reset state. reset complete..."); + usbi_dbg (NULL, "cached device in reset state. reset complete..."); cached_device->in_reenumerate = false; } @@ -358,7 +377,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { struct darwin_cached_device *old_device; io_service_t device; - UInt64 session; + UInt64 session, locationID; int ret; usbi_mutex_lock(&active_contexts_lock); @@ -368,6 +387,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { /* 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; @@ -380,7 +400,8 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { 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 ("detected device detached due to re-enumeration"); + 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) { @@ -403,7 +424,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { } for_each_context(ctx) { - usbi_dbg ("notifying context %p of device disconnect", ctx); + usbi_dbg (ctx, "notifying context %p of device disconnect", ctx); dev = usbi_get_device_by_session_id(ctx, (unsigned long) session); if (dev) { @@ -426,7 +447,7 @@ static void darwin_hotplug_poll (void) /* 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 (kIOMasterPortDefault, &timeout); + (void) IOKitWaitQuiet (darwin_default_master_port, &timeout); } static void darwin_clear_iterator (io_iterator_t iter) { @@ -473,7 +494,8 @@ static void *darwin_event_thread_main (void *arg0) { io_iterator_t libusb_rem_device_iterator; io_iterator_t libusb_add_device_iterator; - usbi_dbg ("creating hotplug event source"); + /* ctx must only be used for logging during thread startup */ + usbi_dbg (ctx, "creating hotplug event source"); runloop = CFRunLoopGetCurrent (); CFRetain (runloop); @@ -486,7 +508,7 @@ static void *darwin_event_thread_main (void *arg0) { CFRunLoopAddSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode); /* add the notification port to the run loop */ - libusb_notification_port = IONotificationPortCreate (kIOMasterPortDefault); + libusb_notification_port = IONotificationPortCreate (darwin_default_master_port); libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port); CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); @@ -494,7 +516,7 @@ static void *darwin_event_thread_main (void *arg0) { kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification, IOServiceMatching(darwin_device_class), darwin_devices_detached, - ctx, &libusb_rem_device_iterator); + NULL, &libusb_rem_device_iterator); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); @@ -507,7 +529,7 @@ static void *darwin_event_thread_main (void *arg0) { kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification, IOServiceMatching(darwin_device_class), darwin_devices_attached, - ctx, &libusb_add_device_iterator); + NULL, &libusb_add_device_iterator); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); @@ -520,7 +542,7 @@ static void *darwin_event_thread_main (void *arg0) { darwin_clear_iterator (libusb_rem_device_iterator); darwin_clear_iterator (libusb_add_device_iterator); - usbi_dbg ("darwin event thread ready to receive events"); + 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); @@ -532,7 +554,7 @@ static void *darwin_event_thread_main (void *arg0) { /* run the runloop */ CFRunLoopRun(); - usbi_dbg ("darwin event thread exiting"); + usbi_dbg (NULL, "darwin event thread exiting"); /* signal the main thread that the hotplug runloop has finished. */ pthread_mutex_lock (&libusb_darwin_at_mutex); @@ -567,23 +589,20 @@ static void darwin_cleanup_devices(void) { list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) { darwin_deref_cached_device(dev); } - - darwin_cached_devices.prev = darwin_cached_devices.next = NULL; } static int darwin_init(struct libusb_context *ctx) { bool first_init; int rc; - pthread_mutex_lock (&libusb_darwin_init_mutex); - first_init = (1 == ++init_count); do { if (first_init) { - assert (NULL == darwin_cached_devices.next); - list_init (&darwin_cached_devices); - + 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; @@ -632,16 +651,12 @@ static int darwin_init(struct libusb_context *ctx) { --init_count; } - pthread_mutex_unlock (&libusb_darwin_init_mutex); - return rc; } static void darwin_exit (struct libusb_context *ctx) { UNUSED(ctx); - pthread_mutex_lock (&libusb_darwin_init_mutex); - if (0 == --init_count) { /* stop the event runloop and wait for the thread to terminate. */ pthread_mutex_lock (&libusb_darwin_at_mutex); @@ -659,8 +674,6 @@ static void darwin_exit (struct libusb_context *ctx) { mach_port_deallocate(mach_task_self(), clock_monotonic); #endif } - - pthread_mutex_unlock (&libusb_darwin_init_mutex); } static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) { @@ -744,7 +757,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, not usable anyway */ if (0x05ac == libusb_le16_to_cpu (dev->dev_descriptor.idVendor) && 0x8005 == libusb_le16_to_cpu (dev->dev_descriptor.idProduct)) { - usbi_dbg ("ignoring configuration on root hub simulation"); + usbi_dbg (ctx, "ignoring configuration on root hub simulation"); dev->active_config = 0; return LIBUSB_SUCCESS; } @@ -787,7 +800,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, /* not configured */ dev->active_config = 0; - usbi_dbg ("active config: %u, first config: %u", dev->active_config, dev->first_config); + usbi_dbg (ctx, "active config: %u, first config: %u", dev->active_config, dev->first_config); return LIBUSB_SUCCESS; } @@ -812,7 +825,7 @@ static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UI return (*device)->DeviceRequestTO (device, &req); } -static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_device *dev) { +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 @@ -850,7 +863,7 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de 0 == dev->dev_descriptor.bcdUSB)) { /* work around for incorrectly configured devices */ if (try_reconfigure && is_open) { - usbi_dbg("descriptor appears to be invalid. resetting configuration before trying again..."); + usbi_dbg(ctx, "descriptor appears to be invalid. resetting configuration before trying again..."); /* set the first configuration */ (*device)->SetConfiguration(device, 1); @@ -881,7 +894,7 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de if (kIOReturnSuccess != ret2) { /* prevent log spew from poorly behaving devices. this indicates the os actually had trouble communicating with the device */ - usbi_dbg("could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); + usbi_dbg(ctx, "could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); } else unsuspended = 1; @@ -890,7 +903,7 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de } if (kIOReturnSuccess != ret) { - usbi_dbg("kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000); + 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); } @@ -906,10 +919,10 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de if (ret != kIOReturnSuccess) { /* a debug message was already printed out for this error */ if (LIBUSB_CLASS_HUB == bDeviceClass) - usbi_dbg ("could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + usbi_dbg (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", idVendor, idProduct, darwin_error_str (ret), ret); else - usbi_warn (NULL, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + 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); } @@ -922,20 +935,20 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de return LIBUSB_ERROR_NO_DEVICE; } - usbi_dbg ("cached device descriptor:"); - usbi_dbg (" bDescriptorType: 0x%02x", dev->dev_descriptor.bDescriptorType); - usbi_dbg (" bcdUSB: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB)); - usbi_dbg (" bDeviceClass: 0x%02x", dev->dev_descriptor.bDeviceClass); - usbi_dbg (" bDeviceSubClass: 0x%02x", dev->dev_descriptor.bDeviceSubClass); - usbi_dbg (" bDeviceProtocol: 0x%02x", dev->dev_descriptor.bDeviceProtocol); - usbi_dbg (" bMaxPacketSize0: 0x%02x", dev->dev_descriptor.bMaxPacketSize0); - usbi_dbg (" idVendor: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor)); - usbi_dbg (" idProduct: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct)); - usbi_dbg (" bcdDevice: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice)); - usbi_dbg (" iManufacturer: 0x%02x", dev->dev_descriptor.iManufacturer); - usbi_dbg (" iProduct: 0x%02x", dev->dev_descriptor.iProduct); - usbi_dbg (" iSerialNumber: 0x%02x", dev->dev_descriptor.iSerialNumber); - usbi_dbg (" bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations); + 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; @@ -979,7 +992,7 @@ static bool get_device_parent_sessionID(io_service_t service, UInt64 *parent_ses return false; } -static enum libusb_error darwin_get_cached_device(io_service_t service, struct darwin_cached_device **cached_out, +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; @@ -996,28 +1009,28 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d (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("could not get connected port number"); + usbi_dbg(ctx, "could not get connected port number"); } - usbi_dbg("finding cached device for sessionID 0x%" PRIx64, sessionID); + usbi_dbg(ctx, "finding cached device for sessionID 0x%" PRIx64, sessionID); if (get_device_parent_sessionID(service, &parent_sessionID)) { - usbi_dbg("parent sessionID: 0x%" PRIx64, 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("matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x", + 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 ("found cached device with matching location that is being re-enumerated"); + 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("using cached device for device"); + usbi_dbg(ctx, "using cached device for device"); *cached_out = new_device; break; } @@ -1026,9 +1039,9 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d if (*cached_out) break; - usbi_dbg("caching new device with sessionID 0x%" PRIx64, sessionID); + usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID); - device = darwin_device_from_service (service); + device = darwin_device_from_service (ctx, service); if (!device) { ret = LIBUSB_ERROR_NO_DEVICE; break; @@ -1052,6 +1065,9 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d (*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 @@ -1060,9 +1076,13 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d 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(new_device); + ret = darwin_cache_device_descriptor(ctx, new_device); if (ret) break; @@ -1094,14 +1114,14 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct break; if (0 != old_session_id) { - usbi_dbg ("re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64, + 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 ("allocating new device in context %p for with session 0x%" PRIx64, + 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); @@ -1114,6 +1134,8 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct 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; @@ -1127,10 +1149,13 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct 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); - } else { - dev->parent_dev = NULL; } (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed); @@ -1139,7 +1164,7 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct 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 >= 1070 +#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 @@ -1153,7 +1178,7 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct if (ret < 0) break; - usbi_dbg ("found device with address %d port = %d parent = %p at %p", dev->device_address, + 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); @@ -1180,7 +1205,7 @@ static enum libusb_error darwin_scan_devices(struct libusb_context *ctx) { return darwin_to_libusb (kresult); while ((service = IOIteratorNext (deviceIterator))) { - ret = darwin_get_cached_device (service, &cached_device, &old_session_id); + ret = darwin_get_cached_device (ctx, service, &cached_device, &old_session_id); if (ret < 0 || !cached_device->can_enumerate) { continue; } @@ -1232,14 +1257,14 @@ static int darwin_open (struct libusb_device_handle *dev_handle) { CFRetain (libusb_darwin_acfl); - /* add the cfSource to the aync run loop */ + /* add the cfSource to the async run loop */ CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes); } /* device opened successfully */ dpriv->open_count++; - usbi_dbg ("device open for access"); + usbi_dbg (HANDLE_CTX(dev_handle), "device open for access"); return 0; } @@ -1257,6 +1282,10 @@ static void darwin_close (struct libusb_device_handle *dev_handle) { } 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++) @@ -1362,28 +1391,39 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, /* current interface */ struct darwin_interface *cInterface = &priv->interfaces[iface]; +#if InterfaceVersion >= 550 + IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; +#else + UInt8 dont_care1, dont_care3; + UInt16 dont_care2; +#endif 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 ("building table of endpoints."); + 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 (HANDLE_CTX (dev_handle), "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); + 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++) { +#if InterfaceVersion >= 550 + kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, i, &pipeProperties); + number = pipeProperties.bEndpointNumber; + direction = pipeProperties.bDirection; +#else kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1, &dont_care2, &dont_care3); - +#endif if (kresult != kIOReturnSuccess) { /* probably a buggy device. try to get the endpoint address from the descriptors */ struct libusb_config_descriptor *config; @@ -1401,6 +1441,10 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, return rc; } + if (iface >= config->bNumInterfaces) { + usbi_err (HANDLE_CTX (dev_handle), "interface %d out of range for device", iface); + return LIBUSB_ERROR_NOT_FOUND; + } endpoint_desc = config->interface[iface].altsetting[alt_setting].endpoint + i - 1; cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress; @@ -1408,7 +1452,7 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK)); } - usbi_dbg ("interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift, + 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); } @@ -1429,30 +1473,32 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* 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 (HANDLE_CTX (dev_handle), "no interface found; setting configuration: %d", dpriv->first_config); + 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 (HANDLE_CTX (dev_handle), "could not set configuration"); + usbi_err (ctx, "could not set configuration"); return ret; } kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult)); + usbi_err (ctx, "darwin_get_interface: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } } if (!usbInterface) { - usbi_err (HANDLE_CTX (dev_handle), "interface not found"); + usbi_info (ctx, "interface not found"); return LIBUSB_ERROR_NOT_FOUND; } @@ -1464,12 +1510,12 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 (void)IOObjectRelease (usbInterface); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); + usbi_err (ctx, "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } if (!plugInInterface) { - usbi_err (HANDLE_CTX (dev_handle), "plugin interface not found"); + usbi_err (ctx, "plugin interface not found"); return LIBUSB_ERROR_NOT_FOUND; } @@ -1481,14 +1527,14 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ (*plugInInterface)->Release (plugInInterface); if (kresult != kIOReturnSuccess || !cInterface->interface) { - usbi_err (HANDLE_CTX (dev_handle), "QueryInterface: %s", darwin_error_str(kresult)); + 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_err (HANDLE_CTX (dev_handle), "USBInterfaceOpen: %s", darwin_error_str(kresult)); + usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } @@ -1497,7 +1543,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 if (ret) { /* this should not happen */ darwin_release_interface (dev_handle, iface); - usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + usbi_err (ctx, "could not build endpoint table"); return ret; } @@ -1506,7 +1552,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* create async event source */ kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "could not create async event source"); + usbi_err (ctx, "could not create async event source"); /* can't continue without an async event source */ (void)darwin_release_interface (dev_handle, iface); @@ -1517,7 +1563,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* add the cfSource to the async thread's run loop */ CFRunLoopAddSource(libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); - usbi_dbg ("interface opened"); + usbi_dbg (ctx, "interface opened"); return LIBUSB_SUCCESS; } @@ -1540,6 +1586,7 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin if (cInterface->cfSource) { CFRunLoopRemoveSource (libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); CFRelease (cInterface->cfSource); + cInterface->cfSource = NULL; } kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface); @@ -1555,6 +1602,30 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin 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; @@ -1567,19 +1638,41 @@ static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_hand return LIBUSB_ERROR_NO_DEVICE; kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting); - if (kresult != kIOReturnSuccess) - darwin_reset_device (dev_handle); + 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; + } - /* update 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"); + usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult)); + + ret = darwin_to_libusb(kresult); + if (ret != LIBUSB_ERROR_PIPE) { return ret; } - return darwin_to_libusb (kresult); + /* 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) { @@ -1610,6 +1703,8 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t 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; @@ -1629,16 +1724,16 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t } if (dpriv->active_config != active_config) { - usbi_dbg ("darwin/restore_state: restoring configuration %d...", 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 ("darwin/restore_state: could not restore configuration"); + usbi_dbg (ctx, "darwin/restore_state: could not restore configuration"); return LIBUSB_ERROR_NOT_FOUND; } } - usbi_dbg ("darwin/restore_state: reclaiming interfaces"); + usbi_dbg (ctx, "darwin/restore_state: reclaiming interfaces"); if (claimed_interfaces) { for (uint8_t iface = 0 ; iface < USB_MAXINTERFACES ; ++iface) { @@ -1646,11 +1741,11 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t continue; } - usbi_dbg ("darwin/restore_state: re-claiming interface %u", iface); + usbi_dbg (ctx, "darwin/restore_state: re-claiming interface %u", iface); ret = darwin_claim_interface (dev_handle, iface); if (LIBUSB_SUCCESS != ret) { - usbi_dbg ("darwin/restore_state: could not claim interface %u", iface); + usbi_dbg (ctx, "darwin/restore_state: could not claim interface %u", iface); return LIBUSB_ERROR_NOT_FOUND; } @@ -1658,21 +1753,24 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t } } - usbi_dbg ("darwin/restore_state: device state restored"); + usbi_dbg (ctx, "darwin/restore_state: device state restored"); return LIBUSB_SUCCESS; } -static int darwin_reset_device(struct libusb_device_handle *dev_handle) { +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; @@ -1689,62 +1787,152 @@ static int darwin_reset_device(struct libusb_device_handle *dev_handle) { 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, 0); + kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); + usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); dpriv->in_reenumerate = false; return darwin_to_libusb (kresult); } - usbi_dbg ("darwin/reset_device: waiting for re-enumeration to complete..."); + /* 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; + usbi_get_monotonic_time(&start); while (dpriv->in_reenumerate) { struct timespec delay = {.tv_sec = 0, .tv_nsec = 1000}; nanosleep (&delay, NULL); + + struct timespec now; + usbi_get_monotonic_time(&now); + unsigned long elapsed_us = (now.tv_sec - start.tv_sec) * USEC_PER_SEC + + (now.tv_nsec - start.tv_nsec) / 1000; + + if (elapsed_us >= 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 ("darwin/reset_device: checking whether descriptors changed"); + 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 ("darwin/reset_device: device descriptor changed"); + 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 ("darwin/reset_device: configuration descriptor %d changed", i); + usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i); return LIBUSB_ERROR_NOT_FOUND; } } - usbi_dbg ("darwin/reset_device: device reset complete. restoring state..."); + usbi_dbg (ctx, "darwin/reenumerate_device: device reset complete. restoring state..."); return darwin_restore_state (dev_handle, active_config, claimed_interfaces); } -static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) { +static int darwin_reset_device (struct libusb_device_handle *dev_handle) { struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); - io_service_t usbInterface; - CFTypeRef driver; IOReturn kresult; + enum libusb_error ret; - kresult = darwin_get_interface (dpriv->device, interface, &usbInterface); - if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult)); - - return darwin_to_libusb (kresult); +#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1 + if (dpriv->capture_count > 0) { + /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */ + kresult = (*(dpriv->device))->ResetDevice (dpriv->device); + ret = darwin_to_libusb (kresult); + } else { + ret = darwin_reenumerate_device (dev_handle, false); + } +#else + /* ResetDevice() is missing on non-macOS platforms */ + ret = darwin_reenumerate_device (dev_handle, false); + if ((ret == LIBUSB_SUCCESS || ret == LIBUSB_ERROR_NOT_FOUND) && dpriv->capture_count > 0) { + int capture_count; + int8_t active_config = dpriv->active_config; + unsigned long claimed_interfaces = dev_handle->claimed_interfaces; + + /* save old capture_count */ + capture_count = dpriv->capture_count; + /* reset capture count */ + dpriv->capture_count = 0; + /* attempt to detach kernel driver again as it is now re-attached */ + ret = darwin_detach_kernel_driver (dev_handle, 0); + if (ret != LIBUSB_SUCCESS) { + return ret; + } + /* restore capture_count */ + dpriv->capture_count = capture_count; + /* restore configuration */ + ret = darwin_restore_state (dev_handle, active_config, claimed_interfaces); } +#endif + return ret; +} + +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); - driver = IORegistryEntryCreateCFProperty (usbInterface, kIOBundleIdentifierKey, kCFAllocatorDefault, 0); - IOObjectRelease (usbInterface); + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBHostMatchingPropertyInterfaceNumber), interfaceCF); - if (driver) { - CFRelease (driver); + 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; } @@ -1873,11 +2061,17 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); IOReturn kresult; - uint8_t direction, number, interval, pipeRef, transferType; - uint16_t maxPacketSize; + uint8_t pipeRef, interval; UInt64 frame; AbsoluteTime atTime; int i; +#if InterfaceVersion >= 550 + IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; +#else + /* None of the values below are used in libusb for iso transfers */ + uint8_t direction, number, transferType; + uint16_t maxPacketSize; +#endif struct darwin_interface *cInterface; @@ -1909,8 +2103,20 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { } /* determine the properties of this endpoint and the speed of the device */ - (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, +#if InterfaceVersion >= 550 + kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties); + interval = pipeProperties.bInterval; +#else + kresult = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval); +#endif + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "failed to get pipe properties: %d", kresult); + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + + return darwin_to_libusb (kresult); + } /* Last but not least we need the bus frame number */ kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime); @@ -1922,9 +2128,6 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { 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; @@ -2051,8 +2254,10 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { 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 (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + usbi_err (ctx, "endpoint not found on any open interface"); return LIBUSB_ERROR_NOT_FOUND; } @@ -2060,7 +2265,7 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { if (!dpriv->device) return LIBUSB_ERROR_NO_DEVICE; - usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions on interface %d pipe %d", iface, pipeRef); + usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef); /* abort transactions */ #if InterfaceVersion >= 550 @@ -2070,7 +2275,7 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { #endif (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef); - usbi_dbg ("calling clear pipe stall to clear the data toggle bit"); + 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); @@ -2099,7 +2304,7 @@ static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0) struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); - usbi_dbg ("an async io operation has completed"); + 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) { @@ -2122,6 +2327,8 @@ static enum libusb_transfer_status darwin_transfer_status (struct usbi_transfer if (itransfer->timeout_flags & USBI_TRANSFER_TIMED_OUT) result = kIOUSBTransactionTimeout; + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + switch (result) { case kIOReturnUnderrun: case kIOReturnSuccess: @@ -2129,17 +2336,17 @@ static enum libusb_transfer_status darwin_transfer_status (struct usbi_transfer case kIOReturnAborted: return LIBUSB_TRANSFER_CANCELLED; case kIOUSBPipeStalled: - usbi_dbg ("transfer error: pipe is stalled"); + usbi_dbg (ctx, "transfer error: pipe is stalled"); return LIBUSB_TRANSFER_STALL; case kIOReturnOverrun: - usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: data overrun"); + usbi_warn (ctx, "transfer error: data overrun"); return LIBUSB_TRANSFER_OVERFLOW; case kIOUSBTransactionTimeout: - usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: timed out"); + usbi_warn (ctx, "transfer error: timed out"); itransfer->timeout_flags |= USBI_TRANSFER_TIMED_OUT; return LIBUSB_TRANSFER_TIMED_OUT; default: - usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); + usbi_warn (ctx, "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); return LIBUSB_TRANSFER_ERROR; } } @@ -2148,22 +2355,23 @@ 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[max_transfer_type + 1] = {"control", "isoc", "bulk", "interrupt", "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 (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + usbi_err (ctx, "unknown endpoint type %d", transfer->type); return LIBUSB_ERROR_INVALID_PARAM; } if (NULL == tpriv) { - usbi_err (TRANSFER_CTX(transfer), "malformed request is missing transfer priv"); + usbi_err (ctx, "malformed request is missing transfer priv"); return LIBUSB_ERROR_INVALID_PARAM; } - usbi_dbg ("handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result); + 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) { + if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result || kIOUSBTransactionTimeout == tpriv->result) { if (is_isoc && tpriv->isoc_framelist) { /* copy isochronous results back */ @@ -2262,9 +2470,159 @@ static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigne } #endif +#if InterfaceVersion >= 700 + +/* macOS APIs for getting entitlement values */ + +#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1 +#include +#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 = 0, + .caps = USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, .init = darwin_init, .exit = darwin_exit, .get_active_config_descriptor = darwin_get_active_config_descriptor, @@ -2275,8 +2633,6 @@ const struct usbi_os_backend usbi_backend = { .close = darwin_close, .get_configuration = darwin_get_configuration, .set_configuration = darwin_set_configuration, - .claim_interface = darwin_claim_interface, - .release_interface = darwin_release_interface, .set_interface_altsetting = darwin_set_interface_altsetting, .clear_halt = darwin_clear_halt, @@ -2289,6 +2645,16 @@ const struct usbi_os_backend usbi_backend = { .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, diff --git a/libusb/libusb/os/darwin_usb.h b/libusb/libusb/os/darwin_usb.h index b799bfd..7b72fff 100644 --- a/libusb/libusb/os/darwin_usb.h +++ b/libusb/libusb/os/darwin_usb.h @@ -30,59 +30,63 @@ #include #include +#if defined(HAVE_IOKIT_USB_IOUSBHOSTFAMILYDEFINITIONS_H) +#include +#endif + /* IOUSBInterfaceInferface */ /* New in OS 10.12.0. */ -#if defined (kIOUSBInterfaceInterfaceID800) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) +#if defined (kIOUSBInterfaceInterfaceID800) #define usb_interface_t IOUSBInterfaceInterface800 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID800 #define InterfaceVersion 800 /* New in OS 10.10.0. */ -#elif defined (kIOUSBInterfaceInterfaceID700) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 101000) +#elif defined (kIOUSBInterfaceInterfaceID700) #define usb_interface_t IOUSBInterfaceInterface700 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700 #define InterfaceVersion 700 /* New in OS 10.9.0. */ -#elif defined (kIOUSBInterfaceInterfaceID650) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1090) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1090) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) +#elif defined (kIOUSBInterfaceInterfaceID500) #define usb_interface_t IOUSBInterfaceInterface500 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500 #define InterfaceVersion 500 /* New in OS 10.5.0. */ -#elif defined (kIOUSBInterfaceInterfaceID300) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) +#elif defined (kIOUSBInterfaceInterfaceID245) #define usb_interface_t IOUSBInterfaceInterface245 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245 #define InterfaceVersion 245 /* New in OS 10.4.0. */ -#elif defined (kIOUSBInterfaceInterfaceID220) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1040) +#elif defined (kIOUSBInterfaceInterfaceID220) #define usb_interface_t IOUSBInterfaceInterface220 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220 @@ -97,42 +101,42 @@ /* IOUSBDeviceInterface */ /* New in OS 10.9.0. */ -#if defined (kIOUSBDeviceInterfaceID650) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1090) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060) +#elif defined (kIOUSBDeviceInterfaceID320) #define usb_device_t IOUSBDeviceInterface320 #define DeviceInterfaceID kIOUSBDeviceInterfaceID320 #define DeviceVersion 320 /* New in OS 10.5.0. */ -#elif defined (kIOUSBDeviceInterfaceID300) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050) +#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1030) +#elif defined (kIOUSBDeviceInterfaceID197) #define usb_device_t IOUSBDeviceInterface197 #define DeviceInterfaceID kIOUSBDeviceInterfaceID197 @@ -144,10 +148,28 @@ #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; @@ -161,11 +183,13 @@ struct darwin_cached_device { 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 { diff --git a/libusb/libusb/os/events_posix.c b/libusb/libusb/os/events_posix.c index b74189b..715a2d5 100644 --- a/libusb/libusb/os/events_posix.c +++ b/libusb/libusb/os/events_posix.c @@ -222,9 +222,9 @@ int usbi_wait_for_events(struct libusb_context *ctx, usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt; int internal_fds, num_ready; - usbi_dbg("poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); + usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); num_ready = poll(fds, nfds, timeout_ms); - usbi_dbg("poll() returned %d", num_ready); + usbi_dbg(ctx, "poll() returned %d", num_ready); if (num_ready == 0) { if (usbi_using_timer(ctx)) goto done; @@ -279,7 +279,7 @@ int usbi_wait_for_events(struct libusb_context *ctx, continue; /* pollfd was removed between the creation of the fds array and * here. remove triggered revent as it is no longer relevant. */ - usbi_dbg("fd %d was removed, ignoring raised events", fds[n].fd); + usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd); fds[n].revents = 0; num_ready--; break; diff --git a/libusb/libusb/os/events_windows.c b/libusb/libusb/os/events_windows.c index 81d8b87..f22bebc 100644 --- a/libusb/libusb/os/events_windows.c +++ b/libusb/libusb/os/events_windows.c @@ -171,9 +171,9 @@ int usbi_wait_for_events(struct libusb_context *ctx, DWORD num_handles = (DWORD)ctx->event_data_cnt; DWORD result; - usbi_dbg("WaitForMultipleObjects() for %lu HANDLEs with timeout in %dms", ULONG_CAST(num_handles), timeout_ms); + 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("WaitForMultipleObjects() returned %lu", ULONG_CAST(result)); + usbi_dbg(ctx, "WaitForMultipleObjects() returned %lu", ULONG_CAST(result)); if (result == WAIT_TIMEOUT) { if (usbi_using_timer(ctx)) goto done; diff --git a/libusb/libusb/os/haiku_pollfs.cpp b/libusb/libusb/os/haiku_pollfs.cpp index cb4fda8..b85edf7 100644 --- a/libusb/libusb/os/haiku_pollfs.cpp +++ b/libusb/libusb/os/haiku_pollfs.cpp @@ -100,14 +100,14 @@ WatchedEntry::WatchedEntry(BMessenger *messenger, entry_ref *ref) for_each_context(ctx) { struct libusb_device *dev = usbi_get_device_by_session_id(ctx, session_id); if (dev) { - usbi_dbg("using previously allocated device with location %lu", session_id); + usbi_dbg(NULL, "using previously allocated device with location %lu", session_id); libusb_unref_device(dev); continue; } - usbi_dbg("allocating new device with location %lu", session_id); + usbi_dbg(NULL, "allocating new device with location %lu", session_id); dev = usbi_alloc_device(ctx, session_id); if (!dev) { - usbi_dbg("device allocation failed"); + usbi_dbg(NULL, "device allocation failed"); continue; } *((USBDevice **)usbi_get_device_priv(dev)) = fDevice; @@ -134,7 +134,7 @@ WatchedEntry::WatchedEntry(BMessenger *messenger, entry_ref *ref) usbi_localize_device_descriptor(&dev->device_descriptor); if (usbi_sanitize_device(dev) < 0) { - usbi_dbg("device sanitization failed"); + usbi_dbg(NULL, "device sanitization failed"); libusb_unref_device(dev); continue; } @@ -178,7 +178,7 @@ WatchedEntry::~WatchedEntry() usbi_disconnect_device(dev); libusb_unref_device(dev); } else { - usbi_dbg("device with location %lu not found", session_id); + usbi_dbg(ctx, "device with location %lu not found", session_id); } } usbi_mutex_static_unlock(&active_contexts_lock); diff --git a/libusb/libusb/os/haiku_usb_backend.cpp b/libusb/libusb/os/haiku_usb_backend.cpp index 8bbf3e0..2fcefdd 100644 --- a/libusb/libusb/os/haiku_usb_backend.cpp +++ b/libusb/libusb/os/haiku_usb_backend.cpp @@ -296,16 +296,16 @@ USBDeviceHandle::SetAltSetting(uint8 inumber, uint8 alt) return _errno_to_libusb(command.alternate.status); } if (command.alternate.alternate_info == (uint32)alt) { - usbi_dbg("Setting alternate interface successful"); + 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 DISONNECTED PROBABLY + 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("Setting alternate interface successful"); + usbi_dbg(NULL, "Setting alternate interface successful"); return LIBUSB_SUCCESS; } diff --git a/libusb/libusb/os/linux_netlink.c b/libusb/libusb/os/linux_netlink.c index 77c83c5..899084f 100644 --- a/libusb/libusb/os/linux_netlink.c +++ b/libusb/libusb/os/linux_netlink.c @@ -34,8 +34,8 @@ #ifdef HAVE_ASM_TYPES_H #include #endif -#include #include +#include #define NL_GROUP_KERNEL 1 @@ -99,7 +99,7 @@ int linux_netlink_start_event_monitor(void) linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT); if (linux_netlink_socket == -1 && errno == EINVAL) { - usbi_dbg("failed to create netlink socket of type %d, attempting SOCK_RAW", socktype); + 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); } @@ -204,7 +204,7 @@ static int linux_netlink_parse(const char *buffer, size_t len, int *detached, } else if (strcmp(tmp, "remove") == 0) { *detached = 1; } else if (strcmp(tmp, "add") != 0) { - usbi_dbg("unknown device action %s", tmp); + usbi_dbg(NULL, "unknown device action %s", tmp); return -1; } @@ -311,20 +311,20 @@ static int linux_netlink_read_message(void) } if (sa_nl.nl_groups != NL_GROUP_KERNEL || sa_nl.nl_pid != 0) { - usbi_dbg("ignoring netlink message from unknown group/PID (%u/%u)", + 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("ignoring netlink message with no sender 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("ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid); + usbi_dbg(NULL, "ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid); return -1; } @@ -332,7 +332,7 @@ static int linux_netlink_read_message(void) if (r) return r; - usbi_dbg("netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s", + 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 */ @@ -362,7 +362,7 @@ static void *linux_netlink_event_thread_main(void *arg) usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r); #endif - usbi_dbg("netlink event thread entering"); + usbi_dbg(NULL, "netlink event thread entering"); while (1) { r = poll(fds, 2, -1); @@ -384,7 +384,7 @@ static void *linux_netlink_event_thread_main(void *arg) } } - usbi_dbg("netlink event thread exiting"); + usbi_dbg(NULL, "netlink event thread exiting"); return NULL; } diff --git a/libusb/libusb/os/linux_udev.c b/libusb/libusb/os/linux_udev.c index beb2f05..9ec9eb1 100644 --- a/libusb/libusb/os/linux_udev.c +++ b/libusb/libusb/os/linux_udev.c @@ -177,7 +177,7 @@ static void *linux_udev_event_thread_main(void *arg) usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r); #endif - usbi_dbg("udev event thread entering"); + usbi_dbg(NULL, "udev event thread entering"); while (1) { r = poll(fds, 2, -1); @@ -201,7 +201,7 @@ static void *linux_udev_event_thread_main(void *arg) } } - usbi_dbg("udev event thread exiting"); + usbi_dbg(NULL, "udev event thread exiting"); return NULL; } @@ -246,7 +246,7 @@ static void udev_hotplug_event(struct udev_device *udev_dev) break; } - usbi_dbg("udev hotplug event. action: %s.", udev_action); + usbi_dbg(NULL, "udev hotplug event. action: %s.", udev_action); if (strncmp(udev_action, "add", 3) == 0) { linux_hotplug_enumerate(busnum, devaddr, sys_name); @@ -313,7 +313,7 @@ void linux_udev_hotplug_poll(void) do { udev_dev = udev_monitor_receive_device(udev_monitor); if (udev_dev) { - usbi_dbg("Handling hotplug event from hotplug_poll"); + usbi_dbg(NULL, "Handling hotplug event from hotplug_poll"); udev_hotplug_event(udev_dev); } } while (udev_dev); diff --git a/libusb/libusb/os/linux_usbfs.c b/libusb/libusb/os/linux_usbfs.c index fb2ed53..285d9ca 100644 --- a/libusb/libusb/os/linux_usbfs.c +++ b/libusb/libusb/os/linux_usbfs.c @@ -95,13 +95,9 @@ static int sysfs_available = -1; /* how many times have we initted (and not exited) ? */ static int init_count = 0; -#ifdef __ANDROID__ /* have no authority to operate usb device directly */ -static int weak_authority = 0; -#endif +static int no_enumeration = 0; -/* Serialize hotplug start/stop */ -static usbi_mutex_static_t linux_hotplug_startstop_lock = USBI_MUTEX_INITIALIZER; /* Serialize scan-devices, event-thread, and poll */ usbi_mutex_static_t linux_hotplug_lock = USBI_MUTEX_INITIALIZER; @@ -128,7 +124,7 @@ struct linux_device_priv { void *descriptors; size_t descriptors_len; struct config_descriptor *config_descriptors; - uint8_t active_config; /* cache val for !sysfs_available */ + int active_config; /* cache val for !sysfs_available */ }; struct linux_device_handle_priv { @@ -169,6 +165,21 @@ struct linux_transfer_priv { 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); @@ -223,11 +234,11 @@ static int is_usbdev_entry(const char *name, uint8_t *bus_p, uint8_t *dev_p) if (sscanf(name, "usbdev%d.%d", &busnum, &devnum) != 2) return 0; if (busnum < 0 || busnum > UINT8_MAX || devnum < 0 || devnum > UINT8_MAX) { - usbi_dbg("invalid usbdev format '%s'", name); + usbi_dbg(NULL, "invalid usbdev format '%s'", name); return 0; } - usbi_dbg("found: %s", name); + usbi_dbg(NULL, "found: %s", name); if (bus_p) *bus_p = (uint8_t)busnum; if (dev_p) @@ -312,7 +323,7 @@ static int get_kernel_version(struct libusb_context *ctx, if (atoms < 3) ver->sublevel = -1; - usbi_dbg("reported kernel version is %s", uts.release); + usbi_dbg(ctx, "reported kernel version is %s", uts.release); return 0; } @@ -360,7 +371,7 @@ static int op_init(struct libusb_context *ctx) return LIBUSB_ERROR_OTHER; } - usbi_dbg("found usbfs at %s", usbfs_path); + usbi_dbg(ctx, "found usbfs at %s", usbfs_path); if (!max_iso_packet_len) { if (kernel_version_ge(&kversion, 5, 2, 0)) @@ -371,14 +382,14 @@ static int op_init(struct libusb_context *ctx) max_iso_packet_len = 8192; } - usbi_dbg("max iso packet length is (likely) %u bytes", max_iso_packet_len); + 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("sysfs is available"); + usbi_dbg(ctx, "sysfs is available"); sysfs_available = 1; } else { usbi_warn(ctx, "sysfs not mounted"); @@ -386,13 +397,10 @@ static int op_init(struct libusb_context *ctx) } } -#ifdef __ANDROID__ - if (weak_authority) { + if (no_enumeration) { return LIBUSB_SUCCESS; } -#endif - usbi_mutex_static_lock(&linux_hotplug_startstop_lock); r = LIBUSB_SUCCESS; if (init_count == 0) { /* start up hotplug event handler */ @@ -407,7 +415,6 @@ static int op_init(struct libusb_context *ctx) } else { usbi_err(ctx, "error starting hotplug event monitor"); } - usbi_mutex_static_unlock(&linux_hotplug_startstop_lock); return r; } @@ -415,13 +422,16 @@ static int op_init(struct libusb_context *ctx) static void op_exit(struct libusb_context *ctx) { UNUSED(ctx); - usbi_mutex_static_lock(&linux_hotplug_startstop_lock); + + if (no_enumeration) { + return; + } + assert(init_count != 0); if (!--init_count) { /* tear down event handler */ linux_stop_event_monitor(); } - usbi_mutex_static_unlock(&linux_hotplug_startstop_lock); } static int op_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) @@ -429,15 +439,11 @@ static int op_set_option(struct libusb_context *ctx, enum libusb_option option, UNUSED(ctx); UNUSED(ap); -#ifdef __ANDROID__ - if (option == LIBUSB_OPTION_WEAK_AUTHORITY) { - usbi_dbg("set libusb has weak authority"); - weak_authority = 1; + if (option == LIBUSB_OPTION_NO_DEVICE_DISCOVERY) { + usbi_dbg(ctx, "no enumeration will be performed"); + no_enumeration = 1; return LIBUSB_SUCCESS; } -#else - UNUSED(option); -#endif return LIBUSB_ERROR_NOT_SUPPORTED; } @@ -498,7 +504,7 @@ static int read_sysfs_attr(struct libusb_context *ctx, if (fd < 0) return fd; - r = read(fd, buf, sizeof(buf)); + r = read(fd, buf, sizeof(buf) - 1); if (r < 0) { r = errno; close(fd); @@ -516,16 +522,18 @@ static int read_sysfs_attr(struct libusb_context *ctx, return 0; } - /* The kernel does *not* NULL-terminate the string, but every attribute + /* 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_err(ctx, "attribute %s doesn't end with newline?", attr); - return LIBUSB_ERROR_IO; + usbi_warn(ctx, "attribute %s doesn't end with newline?", attr); + } else { + /* Remove the terminating newline character */ + r--; } - buf[r - 1] = '\0'; + buf[r] = '\0'; errno = 0; value = strtol(buf, &endptr, 10); @@ -565,22 +573,12 @@ static int sysfs_scan_device(struct libusb_context *ctx, const char *devname) } /* read the bConfigurationValue for a device */ -static int sysfs_get_active_config(struct libusb_device *dev, uint8_t *config) +static int sysfs_get_active_config(struct libusb_device *dev, int *config) { struct linux_device_priv *priv = usbi_get_device_priv(dev); - int ret, tmp; - - ret = read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue", - UINT8_MAX, &tmp); - if (ret < 0) - return ret; - - if (tmp == -1) - tmp = 0; /* unconfigured */ - *config = (uint8_t)tmp; - - return 0; + 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, @@ -590,7 +588,7 @@ int linux_get_device_address(struct libusb_context *ctx, int detached, int sysfs_val; int r; - usbi_dbg("getting address for device: %s detached: %d", sys_name, detached); + 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) { @@ -619,7 +617,7 @@ int linux_get_device_address(struct libusb_context *ctx, int detached, return LIBUSB_SUCCESS; } - usbi_dbg("scan %s", sys_name); + usbi_dbg(ctx, "scan %s", sys_name); r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val); if (r < 0) @@ -631,7 +629,7 @@ int linux_get_device_address(struct libusb_context *ctx, int detached, return r; *devaddr = (uint8_t)sysfs_val; - usbi_dbg("bus=%u dev=%u", *busnum, *devaddr); + usbi_dbg(ctx, "bus=%u dev=%u", *busnum, *devaddr); return LIBUSB_SUCCESS; } @@ -641,7 +639,12 @@ static int seek_to_next_config(struct libusb_context *ctx, uint8_t *buffer, size_t len) { struct usbi_descriptor_header *header; - int offset = 0; + 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) { @@ -718,7 +721,7 @@ static int parse_config_descriptors(struct libusb_device *dev) } 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. @@ -727,8 +730,7 @@ static int parse_config_descriptors(struct libusb_device *dev) int offset; if (num_configs > 1 && idx < num_configs - 1) { - offset = seek_to_next_config(ctx, buffer + LIBUSB_DT_CONFIG_SIZE, - remaining - LIBUSB_DT_CONFIG_SIZE); + offset = seek_to_next_config(ctx, buffer, remaining); if (offset < 0) return offset; sysfs_config_len = (uint16_t)offset; @@ -752,6 +754,9 @@ static int parse_config_descriptors(struct libusb_device *dev) } } + 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; @@ -785,7 +790,7 @@ static int op_get_active_config_descriptor(struct libusb_device *dev, { struct linux_device_priv *priv = usbi_get_device_priv(dev); void *config_desc; - uint8_t active_config; + int active_config; int r; if (priv->sysfs_dir) { @@ -797,12 +802,12 @@ static int op_get_active_config_descriptor(struct libusb_device *dev, active_config = priv->active_config; } - if (active_config == 0) { + if (active_config == -1) { usbi_err(DEVICE_CTX(dev), "device unconfigured"); return LIBUSB_ERROR_NOT_FOUND; } - r = op_get_config_descriptor_by_value(dev, active_config, &config_desc); + r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc); if (r < 0) return r; @@ -850,17 +855,26 @@ static int usbfs_get_active_config(struct libusb_device *dev, int fd) /* 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) { - /* some buggy devices have a configuration 0, but we're - * reaching into the corner of a corner case here, so let's - * not support buggy devices in these circumstances. - * stick to the specs: a configuration value of 0 means - * unconfigured. */ - usbi_warn(DEVICE_CTX(dev), "active cfg 0? assuming unconfigured device"); + 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; } - priv->active_config = active_config; - return LIBUSB_SUCCESS; } @@ -991,9 +1005,9 @@ static int initialize_device(struct libusb_device *dev, uint8_t busnum, usbi_warn(ctx, "Missing rw usbfs access; cannot determine " "active configuration descriptor"); if (priv->config_descriptors) - priv->active_config = priv->config_descriptors[0].desc->bConfigurationValue; + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; else - priv->active_config = 0; /* No config dt */ + priv->active_config = -1; /* No config dt */ return LIBUSB_SUCCESS; } @@ -1058,14 +1072,14 @@ static int linux_get_parent_info(struct libusb_device *dev, const char *sysfs_di usbi_mutex_unlock(&ctx->usb_devs_lock); if (!dev->parent_dev && add_parent) { - usbi_dbg("parent_dev %s not enumerated yet, enumerating now", + 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("dev %p (%s) has parent %p (%s) port %u", dev, sysfs_dir, + 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); @@ -1084,17 +1098,17 @@ int linux_enumerate_device(struct libusb_context *ctx, * will be reused. instead we should add a simple sysfs attribute with * a session ID. */ session_id = busnum << 8 | devaddr; - usbi_dbg("busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id); + 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("session_id %lu already exists", session_id); + usbi_dbg(ctx, "session_id %lu already exists", session_id); libusb_unref_device(dev); return LIBUSB_SUCCESS; } - usbi_dbg("allocating new device for %u/%u (session %lu)", + usbi_dbg(ctx, "allocating new device for %u/%u (session %lu)", busnum, devaddr, session_id); dev = usbi_alloc_device(ctx, session_id); if (!dev) @@ -1143,7 +1157,7 @@ void linux_device_disconnected(uint8_t busnum, uint8_t devaddr) usbi_disconnect_device(dev); libusb_unref_device(dev); } else { - usbi_dbg("device not found for session %lx", session_id); + usbi_dbg(ctx, "device not found for session %lx", session_id); } } usbi_mutex_static_unlock(&active_contexts_lock); @@ -1175,7 +1189,7 @@ static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum) int r = LIBUSB_ERROR_IO; sprintf(dirpath, USB_DEVTMPFS_PATH "/%03u", busnum); - usbi_dbg("%s", dirpath); + usbi_dbg(ctx, "%s", dirpath); dir = opendir(dirpath); if (!dir) { usbi_err(ctx, "opendir '%s' failed, errno=%d", dirpath, errno); @@ -1191,12 +1205,12 @@ static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum) continue; if (!parse_u8(entry->d_name, &devaddr)) { - usbi_dbg("unknown dir entry %s", entry->d_name); + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); continue; } if (linux_enumerate_device(ctx, busnum, devaddr, NULL)) { - usbi_dbg("failed to enumerate dir entry %s", entry->d_name); + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); continue; } @@ -1234,12 +1248,12 @@ static int usbfs_get_device_list(struct libusb_context *ctx) r = linux_enumerate_device(ctx, busnum, devaddr, NULL); if (r < 0) { - usbi_dbg("failed to enumerate dir entry %s", entry->d_name); + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); continue; } } else { if (!parse_u8(entry->d_name, &busnum)) { - usbi_dbg("unknown dir entry %s", entry->d_name); + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); continue; } @@ -1274,7 +1288,7 @@ static int sysfs_get_device_list(struct libusb_context *ctx) num_devices++; if (sysfs_scan_device(ctx, entry->d_name)) { - usbi_dbg("failed to enumerate dir entry %s", entry->d_name); + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); continue; } @@ -1314,7 +1328,7 @@ static int initialize_handle(struct libusb_device_handle *handle, int fd) r = ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps); if (r < 0) { if (errno == ENOTTY) - usbi_dbg("getcap not available"); + 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; @@ -1348,7 +1362,7 @@ static int op_wrap_sys_device(struct libusb_context *ctx, /* Session id is unused as we do not add the device to the list of * connected devices. */ - usbi_dbg("allocating new device for fd %d", fd); + usbi_dbg(ctx, "allocating new device for fd %d", fd); dev = usbi_alloc_device(ctx, 0); if (!dev) return LIBUSB_ERROR_NO_MEM; @@ -1361,7 +1375,7 @@ static int op_wrap_sys_device(struct libusb_context *ctx, goto out; /* Consider the device as connected, but do not add it to the managed * device list. */ - dev->attached = 1; + usbi_atomic_store(&dev->attached, 1); handle->dev = dev; r = initialize_handle(handle, fd); @@ -1383,8 +1397,8 @@ static int op_open(struct libusb_device_handle *handle) /* 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 (handle->dev->attached) { - usbi_dbg("open failed with no device, but device still attached"); + 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); } @@ -1415,22 +1429,27 @@ 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 = -1; /* to please compiler */ int r; if (priv->sysfs_dir) { - r = sysfs_get_active_config(handle->dev, config); + 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) - *config = priv->active_config; + active_config = priv->active_config; } if (r < 0) return r; - if (*config == 0) - usbi_err(HANDLE_CTX(handle), "device unconfigured"); + if (active_config == -1) { + usbi_warn(HANDLE_CTX(handle), "device unconfigured"); + active_config = 0; + } + + *config = (uint8_t)active_config; return 0; } @@ -1454,11 +1473,13 @@ static int op_set_configuration(struct libusb_device_handle *handle, int config) return LIBUSB_ERROR_OTHER; } - if (config == -1) - config = 0; + /* if necessary, update our cached active config descriptor */ + if (!priv->sysfs_dir) { + if (config == 0 && !dev_has_config0(handle->dev)) + config = -1; - /* update our cached active config descriptor */ - priv->active_config = (uint8_t)config; + priv->active_config = config; + } return LIBUSB_SUCCESS; } @@ -1847,11 +1868,11 @@ static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plu continue; if (errno == EINVAL) { - usbi_dbg("URB not found --> assuming ready to be reaped"); + 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("Device not found for URB --> assuming ready to be reaped"); + 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); @@ -1942,7 +1963,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) last_urb_partial = 1; num_urbs++; } - usbi_dbg("need %d urbs for new transfer with length %d", num_urbs, transfer->length); + 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; @@ -2007,7 +2028,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) /* if the first URB submission fails, we can simply free up and * return failure immediately. */ if (i == 0) { - usbi_dbg("first URB failed, easy peasy"); + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); free(urbs); tpriv->urbs = NULL; return r; @@ -2041,7 +2062,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) discard_urbs(itransfer, 0, i); - usbi_dbg("reporting successful submission but waiting for %d " + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " "discards before reporting error", i); return 0; } @@ -2092,7 +2113,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) /* 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("need %d urbs for new transfer with length %d", num_urbs, transfer->length); + 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) @@ -2163,7 +2184,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) /* if the first URB submission fails, we can simply free up and * return failure immediately. */ if (i == 0) { - usbi_dbg("first URB failed, easy peasy"); + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); free_iso_urbs(tpriv); return r; } @@ -2188,7 +2209,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) tpriv->num_retired = num_urbs - i; discard_urbs(itransfer, 0, i); - usbi_dbg("reporting successful submission but waiting for %d " + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " "discards before reporting error", i); return 0; } @@ -2318,14 +2339,14 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, int urb_idx = urb - tpriv->urbs; usbi_mutex_lock(&itransfer->lock); - usbi_dbg("handling completion status %d of bulk urb %d/%d", urb->status, + 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("abnormal reap: urb status %d", urb->status); + 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. @@ -2346,9 +2367,9 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, if (urb->actual_length > 0) { unsigned char *target = transfer->buffer + itransfer->transferred; - usbi_dbg("received %d bytes of surplus data", urb->actual_length); + usbi_dbg(TRANSFER_CTX(transfer), "received %d bytes of surplus data", urb->actual_length); if (urb->buffer != target) { - usbi_dbg("moving surplus data from offset %zu to offset %zu", + 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); @@ -2357,7 +2378,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, } if (tpriv->num_retired == tpriv->num_urbs) { - usbi_dbg("abnormal reap: last URB handled, reporting"); + 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; @@ -2381,17 +2402,17 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, break; case -ENODEV: case -ESHUTDOWN: - usbi_dbg("device removed"); + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE; goto cancel_remaining; case -EPIPE: - usbi_dbg("detected endpoint stall"); + 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("overflow, actual_length=%d", urb->actual_length); + 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; @@ -2400,7 +2421,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, case -EILSEQ: case -ECOMM: case -ENOSR: - usbi_dbg("low-level bus error %d", urb->status); + usbi_dbg(TRANSFER_CTX(transfer), "low-level bus error %d", urb->status); tpriv->reap_action = ERROR; goto cancel_remaining; default: @@ -2412,10 +2433,10 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, /* 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("all URBs in transfer reaped --> complete!"); + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); goto completed; } else if (urb->actual_length < urb->buffer_length) { - usbi_dbg("short transfer %d/%d --> complete!", + 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; @@ -2471,7 +2492,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("handling completion status %d of iso urb %d/%d", urb->status, + 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 */ @@ -2490,15 +2511,15 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, break; case -ENODEV: case -ESHUTDOWN: - usbi_dbg("packet %d - device removed", i); + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - device removed", i); lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE; break; case -EPIPE: - usbi_dbg("packet %d - detected endpoint stall", i); + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - detected endpoint stall", i); lib_desc->status = LIBUSB_TRANSFER_STALL; break; case -EOVERFLOW: - usbi_dbg("packet %d - overflow error", i); + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - overflow error", i); lib_desc->status = LIBUSB_TRANSFER_OVERFLOW; break; case -ETIME: @@ -2507,7 +2528,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, case -ECOMM: case -ENOSR: case -EXDEV: - usbi_dbg("packet %d - low-level USB error %d", i, urb_desc->status); + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - low-level USB error %d", i, urb_desc->status); lib_desc->status = LIBUSB_TRANSFER_ERROR; break; default: @@ -2522,10 +2543,10 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, tpriv->num_retired++; if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */ - usbi_dbg("CANCEL: urb status %d", urb->status); + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: urb status %d", urb->status); if (tpriv->num_retired == num_urbs) { - usbi_dbg("CANCEL: last URB handled, reporting"); + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: last URB handled, reporting"); free_iso_urbs(tpriv); if (tpriv->reap_action == CANCELLED) { usbi_mutex_unlock(&itransfer->lock); @@ -2545,7 +2566,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, case -ECONNRESET: break; case -ESHUTDOWN: - usbi_dbg("device removed"); + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); status = LIBUSB_TRANSFER_NO_DEVICE; break; default: @@ -2556,7 +2577,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, /* if we've reaped all urbs then we're done */ if (tpriv->num_retired == num_urbs) { - usbi_dbg("all URBs in transfer reaped --> complete!"); + 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); @@ -2574,7 +2595,7 @@ static int handle_control_completion(struct usbi_transfer *itransfer, int status; usbi_mutex_lock(&itransfer->lock); - usbi_dbg("handling completion status %d", urb->status); + usbi_dbg(ITRANSFER_CTX(itransfer), "handling completion status %d", urb->status); itransfer->transferred += urb->actual_length; @@ -2597,15 +2618,15 @@ static int handle_control_completion(struct usbi_transfer *itransfer, break; case -ENODEV: case -ESHUTDOWN: - usbi_dbg("device removed"); + usbi_dbg(ITRANSFER_CTX(itransfer), "device removed"); status = LIBUSB_TRANSFER_NO_DEVICE; break; case -EPIPE: - usbi_dbg("unsupported control request"); + usbi_dbg(ITRANSFER_CTX(itransfer), "unsupported control request"); status = LIBUSB_TRANSFER_STALL; break; case -EOVERFLOW: - usbi_dbg("overflow, actual_length=%d", urb->actual_length); + usbi_dbg(ITRANSFER_CTX(itransfer), "overflow, actual_length=%d", urb->actual_length); status = LIBUSB_TRANSFER_OVERFLOW; break; case -ETIME: @@ -2613,7 +2634,7 @@ static int handle_control_completion(struct usbi_transfer *itransfer, case -EILSEQ: case -ECOMM: case -ENOSR: - usbi_dbg("low-level bus error %d", urb->status); + usbi_dbg(ITRANSFER_CTX(itransfer), "low-level bus error %d", urb->status); status = LIBUSB_TRANSFER_ERROR; break; default: @@ -2650,7 +2671,7 @@ static int reap_for_handle(struct libusb_device_handle *handle) itransfer = urb->usercontext; transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); - usbi_dbg("urb type=%u status=%d transferred=%d", urb->type, urb->status, urb->actual_length); + 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: @@ -2707,7 +2728,7 @@ static int op_handle_events(struct libusb_context *ctx, /* 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 (handle->dev->attached) + 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); diff --git a/libusb/libusb/os/netbsd_usb.c b/libusb/libusb/os/netbsd_usb.c index 7a36209..74833f6 100644 --- a/libusb/libusb/os/netbsd_usb.c +++ b/libusb/libusb/os/netbsd_usb.c @@ -122,7 +122,7 @@ netbsd_get_device_list(struct libusb_context * ctx, char devnode[16]; int fd, err, i; - usbi_dbg(" "); + usbi_dbg(ctx, " "); /* Only ugen(4) is supported */ for (i = 0; i < USB_MAX_DEVICES; i++) { @@ -205,7 +205,7 @@ netbsd_open(struct libusb_device_handle *handle) for (i = 0; i < USB_MAX_ENDPOINTS; i++) hpriv->endpoints[i] = -1; - usbi_dbg("open %s: fd %d", dpriv->devnode, dpriv->fd); + usbi_dbg(HANDLE_CTX(handle), "open %s: fd %d", dpriv->devnode, dpriv->fd); return (LIBUSB_SUCCESS); } @@ -215,7 +215,7 @@ netbsd_close(struct libusb_device_handle *handle) { struct device_priv *dpriv = usbi_get_device_priv(handle->dev); - usbi_dbg("close: fd %d", dpriv->fd); + usbi_dbg(HANDLE_CTX(handle), "close: fd %d", dpriv->fd); close(dpriv->fd); dpriv->fd = -1; @@ -229,7 +229,7 @@ netbsd_get_active_config_descriptor(struct libusb_device *dev, len = MIN(len, (size_t)UGETW(dpriv->cdesc->wTotalLength)); - usbi_dbg("len %zu", len); + usbi_dbg(DEVICE_CTX(dev), "len %zu", len); memcpy(buf, dpriv->cdesc, len); @@ -244,7 +244,7 @@ netbsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, struct usb_full_desc ufd; int fd, err; - usbi_dbg("index %u, len %zu", idx, len); + 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) { @@ -278,12 +278,12 @@ 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(" "); + usbi_dbg(HANDLE_CTX(handle), " "); if (ioctl(dpriv->fd, USB_GET_CONFIG, &tmp) < 0) return _errno_to_libusb(errno); - usbi_dbg("configuration %d", tmp); + usbi_dbg(HANDLE_CTX(handle), "configuration %d", tmp); *config = (uint8_t)tmp; return (LIBUSB_SUCCESS); @@ -294,7 +294,7 @@ netbsd_set_configuration(struct libusb_device_handle *handle, int config) { struct device_priv *dpriv = usbi_get_device_priv(handle->dev); - usbi_dbg("configuration %d", config); + usbi_dbg(HANDLE_CTX(handle), "configuration %d", config); if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) return _errno_to_libusb(errno); @@ -338,7 +338,7 @@ netbsd_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t ifa struct device_priv *dpriv = usbi_get_device_priv(handle->dev); struct usb_alt_interface intf; - usbi_dbg("iface %u, setting %u", iface, altsetting); + usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting); memset(&intf, 0, sizeof(intf)); @@ -357,7 +357,7 @@ 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(" "); + usbi_dbg(HANDLE_CTX(handle), " "); req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; req.ucr_request.bRequest = UR_CLEAR_FEATURE; @@ -376,7 +376,7 @@ netbsd_destroy_device(struct libusb_device *dev) { struct device_priv *dpriv = usbi_get_device_priv(dev); - usbi_dbg(" "); + usbi_dbg(DEVICE_CTX(dev), " "); free(dpriv->cdesc); } @@ -387,7 +387,7 @@ netbsd_submit_transfer(struct usbi_transfer *itransfer) struct libusb_transfer *transfer; int err = 0; - usbi_dbg(" "); + usbi_dbg(ITRANSFER_CTX(itransfer), " "); transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); @@ -430,7 +430,7 @@ netbsd_cancel_transfer(struct usbi_transfer *itransfer) { UNUSED(itransfer); - usbi_dbg(" "); + usbi_dbg(ITRANSFER_CTX(itransfer), " "); return (LIBUSB_ERROR_NOT_SUPPORTED); } @@ -458,7 +458,7 @@ _errno_to_libusb(int err) return (LIBUSB_ERROR_TIMEOUT); } - usbi_dbg("error: %s", strerror(err)); + usbi_dbg(NULL, "error: %s", strerror(err)); return (LIBUSB_ERROR_OTHER); } @@ -472,14 +472,14 @@ _cache_active_config_descriptor(struct libusb_device *dev, int fd) void *buf; int len; - usbi_dbg("fd %d", fd); + 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("active bLength %d", ucd.ucd_desc.bLength); + usbi_dbg(DEVICE_CTX(dev), "active bLength %d", ucd.ucd_desc.bLength); len = UGETW(ucd.ucd_desc.wTotalLength); buf = malloc((size_t)len); @@ -490,7 +490,7 @@ _cache_active_config_descriptor(struct libusb_device *dev, int fd) ufd.ufd_size = len; ufd.ufd_data = buf; - usbi_dbg("index %d, len %d", ufd.ufd_config_index, len); + 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); @@ -516,7 +516,7 @@ _sync_control_transfer(struct usbi_transfer *itransfer) dpriv = usbi_get_device_priv(transfer->dev_handle->dev); setup = (struct libusb_control_setup *)transfer->buffer; - usbi_dbg("type %d request %d value %d index %d length %d timeout %d", + 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), @@ -541,7 +541,7 @@ _sync_control_transfer(struct usbi_transfer *itransfer) itransfer->transferred = req.ucr_actlen; - usbi_dbg("transferred %d", itransfer->transferred); + usbi_dbg(ITRANSFER_CTX(itransfer), "transferred %d", itransfer->transferred); return (0); } @@ -561,7 +561,7 @@ _access_endpoint(struct libusb_transfer *transfer) endpt = UE_GET_ADDR(transfer->endpoint); mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; - usbi_dbg("endpoint %d mode %d", endpt, mode); + usbi_dbg(TRANSFER_CTX(transfer), "endpoint %d mode %d", endpt, mode); if (hpriv->endpoints[endpt] < 0) { /* Pick the right node given the control one */ diff --git a/libusb/libusb/os/openbsd_usb.c b/libusb/libusb/os/openbsd_usb.c index e05610e..9a5c604 100644 --- a/libusb/libusb/os/openbsd_usb.c +++ b/libusb/libusb/os/openbsd_usb.c @@ -129,7 +129,7 @@ obsd_get_device_list(struct libusb_context * ctx, char *udevname; int fd, addr, i, j; - usbi_dbg(" "); + usbi_dbg(ctx, " "); for (i = 0; i < 8; i++) { snprintf(busnode, sizeof(busnode), USBDEV "%d", i); @@ -238,7 +238,7 @@ obsd_open(struct libusb_device_handle *handle) return _errno_to_libusb(errno); dpriv->fd = fd; - usbi_dbg("open %s: fd %d", devnode, dpriv->fd); + usbi_dbg(HANDLE_CTX(handle), "open %s: fd %d", devnode, dpriv->fd); } return (LIBUSB_SUCCESS); @@ -250,7 +250,7 @@ obsd_close(struct libusb_device_handle *handle) struct device_priv *dpriv = usbi_get_device_priv(handle->dev); if (dpriv->devname) { - usbi_dbg("close: fd %d", dpriv->fd); + usbi_dbg(HANDLE_CTX(handle), "close: fd %d", dpriv->fd); close(dpriv->fd); dpriv->fd = -1; @@ -265,7 +265,7 @@ obsd_get_active_config_descriptor(struct libusb_device *dev, len = MIN(len, (size_t)UGETW(dpriv->cdesc->wTotalLength)); - usbi_dbg("len %zu", len); + usbi_dbg(DEVICE_CTX(dev), "len %zu", len); memcpy(buf, dpriv->cdesc, len); @@ -288,7 +288,7 @@ obsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, udf.udf_size = len; udf.udf_data = buf; - usbi_dbg("index %d, len %zu", udf.udf_config_index, len); + 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; @@ -307,7 +307,7 @@ obsd_get_configuration(struct libusb_device_handle *handle, uint8_t *config) *config = dpriv->cdesc->bConfigurationValue; - usbi_dbg("bConfigurationValue %u", *config); + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %u", *config); return (LIBUSB_SUCCESS); } @@ -320,7 +320,7 @@ obsd_set_configuration(struct libusb_device_handle *handle, int config) if (dpriv->devname == NULL) return (LIBUSB_ERROR_NOT_SUPPORTED); - usbi_dbg("bConfigurationValue %d", config); + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %d", config); if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) return _errno_to_libusb(errno); @@ -367,7 +367,7 @@ obsd_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t iface if (dpriv->devname == NULL) return (LIBUSB_ERROR_NOT_SUPPORTED); - usbi_dbg("iface %u, setting %u", iface, altsetting); + usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting); memset(&intf, 0, sizeof(intf)); @@ -389,7 +389,7 @@ obsd_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) if ((fd = _bus_open(handle->dev->bus_number)) < 0) return _errno_to_libusb(errno); - usbi_dbg(" "); + usbi_dbg(HANDLE_CTX(handle), " "); req.ucr_addr = handle->dev->device_address; req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; @@ -413,7 +413,7 @@ obsd_destroy_device(struct libusb_device *dev) { struct device_priv *dpriv = usbi_get_device_priv(dev); - usbi_dbg(" "); + usbi_dbg(DEVICE_CTX(dev), " "); free(dpriv->cdesc); free(dpriv->devname); @@ -425,7 +425,7 @@ obsd_submit_transfer(struct usbi_transfer *itransfer) struct libusb_transfer *transfer; int err = 0; - usbi_dbg(" "); + usbi_dbg(ITRANSFER_CTX(itransfer), " "); transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); @@ -468,7 +468,7 @@ obsd_cancel_transfer(struct usbi_transfer *itransfer) { UNUSED(itransfer); - usbi_dbg(" "); + usbi_dbg(ITRANSFER_CTX(itransfer), " "); return (LIBUSB_ERROR_NOT_SUPPORTED); } @@ -482,7 +482,7 @@ obsd_handle_transfer_completion(struct usbi_transfer *itransfer) int _errno_to_libusb(int err) { - usbi_dbg("error: %s (%d)", strerror(err), err); + usbi_dbg(NULL, "error: %s (%d)", strerror(err), err); switch (err) { case EIO: @@ -512,7 +512,7 @@ _cache_active_config_descriptor(struct libusb_device *dev) if ((fd = _bus_open(dev->bus_number)) < 0) return _errno_to_libusb(errno); - usbi_dbg("fd %d, addr %d", fd, dev->device_address); + 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; @@ -523,7 +523,7 @@ _cache_active_config_descriptor(struct libusb_device *dev) return _errno_to_libusb(errno); } - usbi_dbg("active bLength %d", udc.udc_desc.bLength); + usbi_dbg(DEVICE_CTX(dev), "active bLength %d", udc.udc_desc.bLength); len = UGETW(udc.udc_desc.wTotalLength); buf = malloc((size_t)len); @@ -536,7 +536,7 @@ _cache_active_config_descriptor(struct libusb_device *dev) udf.udf_size = len; udf.udf_data = buf; - usbi_dbg("index %d, len %d", udf.udf_config_index, len); + 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; @@ -565,7 +565,7 @@ _sync_control_transfer(struct usbi_transfer *itransfer) dpriv = usbi_get_device_priv(transfer->dev_handle->dev); setup = (struct libusb_control_setup *)transfer->buffer; - usbi_dbg("type %x request %x value %x index %d length %d timeout %d", + 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), @@ -610,7 +610,7 @@ _sync_control_transfer(struct usbi_transfer *itransfer) itransfer->transferred = req.ucr_actlen; - usbi_dbg("transferred %d", itransfer->transferred); + usbi_dbg(ITRANSFER_CTX(itransfer), "transferred %d", itransfer->transferred); return (0); } @@ -630,7 +630,7 @@ _access_endpoint(struct libusb_transfer *transfer) endpt = UE_GET_ADDR(transfer->endpoint); mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; - usbi_dbg("endpoint %d mode %d", endpt, mode); + usbi_dbg(TRANSFER_CTX(transfer), "endpoint %d mode %d", endpt, mode); if (hpriv->endpoints[endpt] < 0) { /* Pick the right endpoint node */ diff --git a/libusb/libusb/os/sunos_usb.c b/libusb/libusb/os/sunos_usb.c index 46866fb..28b167f 100644 --- a/libusb/libusb/os/sunos_usb.c +++ b/libusb/libusb/os/sunos_usb.c @@ -90,7 +90,7 @@ static int sunos_get_link(di_devlink_t devlink, void *arg) char *content = (char *)di_devlink_content(devlink); char *start = strstr(content, "/devices/"); start += strlen("/devices"); - usbi_dbg("%s", start); + usbi_dbg(NULL, "%s", start); /* line content must have minor node */ if (start == NULL || @@ -101,7 +101,7 @@ static int sunos_get_link(di_devlink_t devlink, void *arg) p = di_devlink_path(devlink); q = strrchr(p, '/'); - usbi_dbg("%s", q); + usbi_dbg(NULL, "%s", q); *(larg->linkpp) = strndup(p, strlen(p) - strlen(q)); @@ -118,7 +118,7 @@ static int sunos_physpath_to_devlink( *link_path = NULL; larg.linkpp = link_path; if ((hdl = di_devlink_init(NULL, 0)) == NULL) { - usbi_dbg("di_devlink_init failure"); + usbi_dbg(NULL, "di_devlink_init failure"); return (-1); } @@ -131,7 +131,7 @@ static int sunos_physpath_to_devlink( (void) di_devlink_fini(&hdl); if (*link_path == NULL) { - usbi_dbg("there is no devlink for this path"); + usbi_dbg(NULL, "there is no devlink for this path"); return (-1); } @@ -167,13 +167,13 @@ sunos_usb_ioctl(struct libusb_device *dev, int cmd) return (-1); } end++; - usbi_dbg("unitaddr: %s", 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("ioctl hub path: %s", path_arg); + usbi_dbg(DEVICE_CTX(dev), "ioctl hub path: %s", path_arg); fd = open(path_arg, O_RDONLY); if (fd < 0) { @@ -193,15 +193,15 @@ sunos_usb_ioctl(struct libusb_device *dev, int cmd) iocdata.c_nodename = (char *)"hub"; iocdata.c_unitaddr = end; iocdata.cpyout_buf = &devctl_ap_state; - usbi_dbg("%p, %" PRIuPTR, iocdata.nvl_user, iocdata.nvl_usersz); + 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("dev rstate: %d", devctl_ap_state.ap_rstate); - usbi_dbg("dev ostate: %d", devctl_ap_state.ap_ostate); + 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; @@ -227,7 +227,7 @@ sunos_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t inte UNUSED(interface); - usbi_dbg("%s", dpriv->ugenpath); + usbi_dbg(HANDLE_CTX(dev_handle), "%s", dpriv->ugenpath); return (dpriv->ugenpath == NULL); } @@ -236,7 +236,7 @@ sunos_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t inte * Private functions */ static int _errno_to_libusb(int); -static int sunos_usb_get_status(int fd); +static int sunos_usb_get_status(struct libusb_context *ctx, int fd); static string_list_t * sunos_new_string_list(void) @@ -358,7 +358,7 @@ sunos_detach_kernel_driver(struct libusb_device_handle *dev_handle, dpriv = usbi_get_device_priv(dev_handle->dev); snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath); - usbi_dbg("%s", path_arg); + usbi_dbg(HANDLE_CTX(dev_handle), "%s", path_arg); list = sunos_new_string_list(); if (list == NULL) @@ -418,7 +418,7 @@ sunos_attach_kernel_driver(struct libusb_device_handle *dev_handle, dpriv = usbi_get_device_priv(dev_handle->dev); snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath); - usbi_dbg("%s", path_arg); + usbi_dbg(HANDLE_CTX(dev_handle), "%s", path_arg); list = sunos_new_string_list(); if (list == NULL) @@ -474,7 +474,7 @@ sunos_fill_in_dev_info(di_node_t node, struct libusb_device *dev) proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, "usb-raw-cfg-descriptors", &rdata); if (proplen <= 0) { - usbi_dbg("can't find raw config descriptors"); + usbi_dbg(DEVICE_CTX(dev), "can't find raw config descriptors"); return (LIBUSB_ERROR_IO); } @@ -501,7 +501,7 @@ sunos_fill_in_dev_info(di_node_t node, struct libusb_device *dev) snprintf(match_str, sizeof(match_str), "^usb/%x.%x", dev->device_descriptor.idVendor, dev->device_descriptor.idProduct); - usbi_dbg("match is %s", match_str); + 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); @@ -514,7 +514,7 @@ sunos_fill_in_dev_info(di_node_t node, struct libusb_device *dev) /* address */ n = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "assigned-address", &addr); if (n != 1 || *addr == 0) { - usbi_dbg("can't get address"); + usbi_dbg(DEVICE_CTX(dev), "can't get address"); } else { dev->device_address = *addr; } @@ -530,7 +530,7 @@ sunos_fill_in_dev_info(di_node_t node, struct libusb_device *dev) dev->speed = LIBUSB_SPEED_SUPER; } - usbi_dbg("vid=%x pid=%x, path=%s, bus_nmber=0x%x, port_number=%d, speed=%d", + 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); @@ -571,7 +571,7 @@ sunos_add_devices(di_devlink_t link, void *arg) dn = myself; /* find the root hub */ while (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "root-hub", &j) != 0) { - usbi_dbg("find_root_hub:%s", di_devfs_path(dn)); + 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); @@ -586,13 +586,13 @@ sunos_add_devices(di_devlink_t link, void *arg) session_id |= (bdf << i * 8); bus_number = (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg); - usbi_dbg("device bus address=%s:%x, name:%s", + usbi_dbg(NULL, "device bus address=%s:%x, name:%s", di_bus_addr(myself), bus_number, di_node_name(dn)); - usbi_dbg("session id org:%" PRIx64, session_id); + 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("device path:%s", di_devfs_path(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; @@ -600,18 +600,18 @@ sunos_add_devices(di_devlink_t link, void *arg) n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "assigned-address", &addr_prop); if ((n != 1) || (addr_prop[0] == 0)) { - usbi_dbg("cannot get valid usb_addr"); + usbi_dbg(NULL, "cannot get valid usb_addr"); continue; } sid = (session_id << 8) | (addr_prop[0] & 0xff) ; - usbi_dbg("session id %" PRIX64, sid); + 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("can't alloc device"); + usbi_dbg(NULL, "can't alloc device"); continue; } devpriv = usbi_get_device_priv(dev); @@ -619,21 +619,21 @@ sunos_add_devices(di_devlink_t link, void *arg) if (sunos_fill_in_dev_info(dn, dev) != LIBUSB_SUCCESS) { libusb_unref_device(dev); - usbi_dbg("get information fail"); + usbi_dbg(NULL, "get information fail"); continue; } if (usbi_sanitize_device(dev) < 0) { libusb_unref_device(dev); - usbi_dbg("sanatize failed: "); + usbi_dbg(NULL, "sanatize failed: "); return (DI_WALK_TERMINATE); } } else { devpriv = usbi_get_device_priv(dev); - usbi_dbg("Dev %s exists", devpriv->ugenpath); + usbi_dbg(NULL, "Dev %s exists", devpriv->ugenpath); } if (discovered_devs_append(*(nargs->discdevs), dev) == NULL) { - usbi_dbg("cannot append device"); + usbi_dbg(NULL, "cannot append device"); } /* @@ -642,7 +642,7 @@ sunos_add_devices(di_devlink_t link, void *arg) */ libusb_unref_device(dev); - usbi_dbg("Device %s %s id=0x%" PRIx64 ", devcount:%" PRIuPTR + 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); @@ -690,13 +690,13 @@ sunos_get_device_list(struct libusb_context * ctx, args.discdevs = discdevs; args.last_ugenpath = NULL; if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) { - usbi_dbg("di_int() failed: errno %d (%s)", errno, strerror(errno)); + 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("di_devlink_init() failed: errno %d (%s)", errno, strerror(errno)); + usbi_dbg(ctx, "di_devlink_init() failed: errno %d (%s)", errno, strerror(errno)); return (LIBUSB_ERROR_IO); } @@ -705,7 +705,7 @@ sunos_get_device_list(struct libusb_context * ctx, /* 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("di_walk_node() failed: errno %d (%s)", errno, strerror(errno)); + usbi_dbg(ctx, "di_walk_node() failed: errno %d (%s)", errno, strerror(errno)); di_fini(root_node); return (LIBUSB_ERROR_IO); @@ -714,7 +714,7 @@ sunos_get_device_list(struct libusb_context * ctx, di_fini(root_node); di_devlink_fini(&devlink_hdl); - usbi_dbg("%zu devices", (*discdevs)->len); + usbi_dbg(ctx, "%zu devices", (*discdevs)->len); return ((*discdevs)->len); } @@ -729,7 +729,7 @@ sunos_usb_open_ep0(sunos_dev_handle_priv_t *hpriv, sunos_dev_priv_t *dpriv) } snprintf(filename, PATH_MAX, "%s/cntrl0", dpriv->ugenpath); - usbi_dbg("opening %s", filename); + 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)); @@ -836,20 +836,20 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, uint8_t ep_index; sunos_dev_handle_priv_t *hpriv; - usbi_dbg("open ep 0x%02x", ep_addr); + 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("ep 0x%02x already opened, return success", + 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("can't find interface for endpoint 0x%02x", + usbi_dbg(HANDLE_CTX(hdl), "can't find interface for endpoint 0x%02x", ep_addr); return (EACCES); @@ -896,7 +896,7 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, } /* Open the xfer endpoint first */ if ((fd = open(filename, mode)) == -1) { - usbi_dbg("can't open %s: errno %d (%s)", filename, errno, + usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", filename, errno, strerror(errno)); return (errno); @@ -917,7 +917,7 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, /* Open the status endpoint with RDWR */ if ((fdstat = open(statfilename, O_RDWR)) == -1) { - usbi_dbg("can't open %s RDWR: errno %d (%s)", + usbi_dbg(HANDLE_CTX(hdl), "can't open %s RDWR: errno %d (%s)", statfilename, errno, strerror(errno)); return (errno); @@ -925,7 +925,7 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, count = write(fdstat, &control, sizeof(control)); if (count != 1) { /* this should have worked */ - usbi_dbg("can't write to %s: errno %d (%s)", + usbi_dbg(HANDLE_CTX(hdl), "can't write to %s: errno %d (%s)", statfilename, errno, strerror(errno)); (void) close(fdstat); @@ -934,7 +934,7 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, } } else { if ((fdstat = open(statfilename, O_RDONLY)) == -1) { - usbi_dbg("can't open %s: errno %d (%s)", statfilename, errno, + usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", statfilename, errno, strerror(errno)); return (errno); @@ -943,7 +943,7 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, /* Re-open the xfer endpoint */ if ((fd = open(filename, mode)) == -1) { - usbi_dbg("can't open %s: errno %d (%s)", filename, errno, + usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", filename, errno, strerror(errno)); (void) close(fdstat); @@ -952,7 +952,7 @@ sunos_check_device_and_status_open(struct libusb_device_handle *hdl, hpriv->eps[ep_index].datafd = fd; hpriv->eps[ep_index].statfd = fdstat; - usbi_dbg("ep=0x%02x datafd=%d, statfd=%d", ep_addr, fd, fdstat); + usbi_dbg(HANDLE_CTX(hdl), "ep=0x%02x datafd=%d, statfd=%d", ep_addr, fd, fdstat); return (0); } @@ -981,7 +981,7 @@ sunos_open(struct libusb_device_handle *handle) } if ((ret = sunos_usb_open_ep0(hpriv, dpriv)) != LIBUSB_SUCCESS) { - usbi_dbg("fail: %d", ret); + usbi_dbg(HANDLE_CTX(handle), "fail: %d", ret); return (ret); } @@ -993,7 +993,7 @@ sunos_close(struct libusb_device_handle *handle) { sunos_dev_handle_priv_t *hpriv; - usbi_dbg(" "); + usbi_dbg(HANDLE_CTX(handle), " "); hpriv = usbi_get_device_handle_priv(handle); @@ -1016,14 +1016,14 @@ sunos_get_active_config_descriptor(struct libusb_device *dev, * has ever been changed through setCfg. */ if ((node = di_init(dpriv->phypath, DINFOCPYALL)) == DI_NODE_NIL) { - usbi_dbg("di_int() failed: errno %d (%s)", errno, + 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("can't find raw config descriptors"); + usbi_dbg(DEVICE_CTX(dev), "can't find raw config descriptors"); return (LIBUSB_ERROR_IO); } @@ -1040,7 +1040,7 @@ sunos_get_active_config_descriptor(struct libusb_device *dev, 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("path:%s len %zu", dpriv->phypath, len); + usbi_dbg(DEVICE_CTX(dev), "path:%s len %zu", dpriv->phypath, len); return (len); } @@ -1061,7 +1061,7 @@ sunos_get_configuration(struct libusb_device_handle *handle, uint8_t *config) *config = dpriv->cfgvalue; - usbi_dbg("bConfigurationValue %u", *config); + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %u", *config); return (LIBUSB_SUCCESS); } @@ -1072,7 +1072,7 @@ 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("bConfigurationValue %d", config); + usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %d", config); hpriv = usbi_get_device_handle_priv(handle); if (dpriv->ugenpath == NULL) @@ -1092,7 +1092,7 @@ sunos_claim_interface(struct libusb_device_handle *handle, uint8_t iface) { UNUSED(handle); - usbi_dbg("iface %u", iface); + usbi_dbg(HANDLE_CTX(handle), "iface %u", iface); return (LIBUSB_SUCCESS); } @@ -1102,7 +1102,7 @@ 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("iface %u", iface); + usbi_dbg(HANDLE_CTX(handle), "iface %u", iface); /* XXX: can we release it? */ hpriv->altsetting[iface] = 0; @@ -1117,7 +1117,7 @@ sunos_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t ifac 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("iface %u, setting %u", iface, altsetting); + usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting); if (dpriv->ugenpath == NULL) return (LIBUSB_ERROR_NOT_FOUND); @@ -1169,7 +1169,7 @@ sunos_async_callback(union sigval arg) ret = aio_error(aiocb); if (ret != 0) { - xfer->status = sunos_usb_get_status(hpriv->eps[ep].statfd); + xfer->status = sunos_usb_get_status(TRANSFER_CTX(xfer), hpriv->eps[ep].statfd); } else { xfer->actual_length = LIBUSB_TRANSFER_TO_USBI_TRANSFER(xfer)->transferred = @@ -1178,7 +1178,7 @@ sunos_async_callback(union sigval arg) usb_dump_data(xfer->buffer, xfer->actual_length); - usbi_dbg("ret=%d, len=%d, actual_len=%d", ret, xfer->length, + usbi_dbg(TRANSFER_CTX(xfer), "ret=%d, len=%d, actual_len=%d", ret, xfer->length, xfer->actual_length); /* async notification */ @@ -1195,7 +1195,7 @@ sunos_do_async_io(struct libusb_transfer *transfer) uint8_t ep; struct sunos_transfer_priv *tpriv; - usbi_dbg(" "); + 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); @@ -1225,12 +1225,12 @@ sunos_do_async_io(struct libusb_transfer *transfer) /* return the number of bytes read/written */ static ssize_t -usb_do_io(int fd, int stat_fd, void *data, size_t size, int flag, int *status) +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("usb_do_io(): datafd=%d statfd=%d size=0x%zx flag=%s", + usbi_dbg(ctx, "usb_do_io(): datafd=%d statfd=%d size=0x%zx flag=%s", fd, stat_fd, size, flag? "WRITE":"READ"); switch (flag) { @@ -1246,17 +1246,17 @@ usb_do_io(int fd, int stat_fd, void *data, size_t size, int flag, int *status) break; } - usbi_dbg("usb_do_io(): amount=%zd", ret); + usbi_dbg(ctx, "usb_do_io(): amount=%zd", ret); if (ret < 0) { int save_errno = errno; - usbi_dbg("TID=%x io %s errno %d (%s)", pthread_self(), + 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(stat_fd); - usbi_dbg("io status=%d errno %d (%s)", error, + 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) { @@ -1286,26 +1286,26 @@ solaris_submit_ctrl_on_default(struct libusb_transfer *transfer) wLength = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; if (hpriv->eps[0].datafd == -1) { - usbi_dbg("ep0 not opened"); + 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("IN request"); - ret = usb_do_io(hpriv->eps[0].datafd, + 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("OUT request"); - ret = usb_do_io(hpriv->eps[0].datafd, hpriv->eps[0].statfd, + 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("error sending control msg: %zd", ret); + usbi_dbg(TRANSFER_CTX(transfer), "error sending control msg: %zd", ret); return (LIBUSB_ERROR_IO); } @@ -1315,8 +1315,8 @@ solaris_submit_ctrl_on_default(struct libusb_transfer *transfer) /* Read the remaining bytes for IN request */ if ((wLength) && ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN)) { - usbi_dbg("DATA: %d", transfer->length - (int)setup_ret); - ret = usb_do_io(hpriv->eps[0].datafd, + 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); @@ -1325,7 +1325,7 @@ solaris_submit_ctrl_on_default(struct libusb_transfer *transfer) if (ret >= 0) { LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->transferred = ret; } - usbi_dbg("Done: ctrl data bytes %zd", ret); + usbi_dbg(TRANSFER_CTX(transfer), "Done: ctrl data bytes %zd", ret); /** * Sync transfer handling. @@ -1345,13 +1345,13 @@ sunos_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) { int ret; - usbi_dbg("endpoint=0x%02x", endpoint); + 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("ret=%d", ret); + usbi_dbg(HANDLE_CTX(handle), "ret=%d", ret); return (ret); } @@ -1361,7 +1361,7 @@ sunos_destroy_device(struct libusb_device *dev) { sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); - usbi_dbg("destroy everything"); + usbi_dbg(DEVICE_CTX(dev), "destroy everything"); free(dpriv->raw_cfgdescr); free(dpriv->ugenpath); free(dpriv->phypath); @@ -1387,7 +1387,7 @@ sunos_submit_transfer(struct usbi_transfer *itransfer) switch (transfer->type) { case LIBUSB_TRANSFER_TYPE_CONTROL: /* sync transfer */ - usbi_dbg("CTRL transfer: %d", transfer->length); + usbi_dbg(ITRANSFER_CTX(itransfer), "CTRL transfer: %d", transfer->length); err = solaris_submit_ctrl_on_default(transfer); break; @@ -1395,9 +1395,9 @@ sunos_submit_transfer(struct usbi_transfer *itransfer) /* fallthru */ case LIBUSB_TRANSFER_TYPE_INTERRUPT: if (transfer->type == LIBUSB_TRANSFER_TYPE_BULK) - usbi_dbg("BULK transfer: %d", transfer->length); + usbi_dbg(ITRANSFER_CTX(itransfer), "BULK transfer: %d", transfer->length); else - usbi_dbg("INTR transfer: %d", transfer->length); + usbi_dbg(ITRANSFER_CTX(itransfer), "INTR transfer: %d", transfer->length); err = sunos_do_async_io(transfer); break; @@ -1407,9 +1407,9 @@ sunos_submit_transfer(struct usbi_transfer *itransfer) /* fallthru */ case LIBUSB_TRANSFER_TYPE_BULK_STREAM: if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) - usbi_dbg("ISOC transfer: %d", transfer->length); + usbi_dbg(ITRANSFER_CTX(itransfer), "ISOC transfer: %d", transfer->length); else - usbi_dbg("BULK STREAM transfer: %d", transfer->length); + usbi_dbg(ITRANSFER_CTX(itransfer), "BULK STREAM transfer: %d", transfer->length); err = LIBUSB_ERROR_NOT_SUPPORTED; break; } @@ -1435,7 +1435,7 @@ sunos_cancel_transfer(struct usbi_transfer *itransfer) ret = aio_cancel(hpriv->eps[ep].datafd, aiocb); - usbi_dbg("aio->fd=%d fd=%d ret = %d, %s", aiocb->aio_fildes, + 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)); @@ -1461,7 +1461,7 @@ sunos_handle_transfer_completion(struct usbi_transfer *itransfer) int _errno_to_libusb(int err) { - usbi_dbg("error: %s (%d)", strerror(err), err); + usbi_dbg(NULL, "error: %s (%d)", strerror(err), err); switch (err) { case EIO: @@ -1486,96 +1486,96 @@ _errno_to_libusb(int err) * Returns: ugen's last cmd status */ static int -sunos_usb_get_status(int fd) +sunos_usb_get_status(struct libusb_context *ctx, int fd) { int status; ssize_t ret; - usbi_dbg("sunos_usb_get_status(): fd=%d", fd); + 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("No Error"); + usbi_dbg(ctx, "No Error"); break; case USB_LC_STAT_CRC: - usbi_dbg("CRC Timeout Detected\n"); + usbi_dbg(ctx, "CRC Timeout Detected\n"); break; case USB_LC_STAT_BITSTUFFING: - usbi_dbg("Bit Stuffing Violation\n"); + usbi_dbg(ctx, "Bit Stuffing Violation\n"); break; case USB_LC_STAT_DATA_TOGGLE_MM: - usbi_dbg("Data Toggle Mismatch\n"); + usbi_dbg(ctx, "Data Toggle Mismatch\n"); break; case USB_LC_STAT_STALL: - usbi_dbg("End Point Stalled\n"); + usbi_dbg(ctx, "End Point Stalled\n"); break; case USB_LC_STAT_DEV_NOT_RESP: - usbi_dbg("Device is Not Responding\n"); + usbi_dbg(ctx, "Device is Not Responding\n"); break; case USB_LC_STAT_PID_CHECKFAILURE: - usbi_dbg("PID Check Failure\n"); + usbi_dbg(ctx, "PID Check Failure\n"); break; case USB_LC_STAT_UNEXP_PID: - usbi_dbg("Unexpected PID\n"); + usbi_dbg(ctx, "Unexpected PID\n"); break; case USB_LC_STAT_DATA_OVERRUN: - usbi_dbg("Data Exceeded Size\n"); + usbi_dbg(ctx, "Data Exceeded Size\n"); break; case USB_LC_STAT_DATA_UNDERRUN: - usbi_dbg("Less data received\n"); + usbi_dbg(ctx, "Less data received\n"); break; case USB_LC_STAT_BUFFER_OVERRUN: - usbi_dbg("Buffer Size Exceeded\n"); + usbi_dbg(ctx, "Buffer Size Exceeded\n"); break; case USB_LC_STAT_BUFFER_UNDERRUN: - usbi_dbg("Buffer Underrun\n"); + usbi_dbg(ctx, "Buffer Underrun\n"); break; case USB_LC_STAT_TIMEOUT: - usbi_dbg("Command Timed Out\n"); + usbi_dbg(ctx, "Command Timed Out\n"); break; case USB_LC_STAT_NOT_ACCESSED: - usbi_dbg("Not Accessed by h/w\n"); + usbi_dbg(ctx, "Not Accessed by h/w\n"); break; case USB_LC_STAT_UNSPECIFIED_ERR: - usbi_dbg("Unspecified Error\n"); + usbi_dbg(ctx, "Unspecified Error\n"); break; case USB_LC_STAT_NO_BANDWIDTH: - usbi_dbg("No Bandwidth\n"); + usbi_dbg(ctx, "No Bandwidth\n"); break; case USB_LC_STAT_HW_ERR: - usbi_dbg("Host Controller h/w Error\n"); + usbi_dbg(ctx, "Host Controller h/w Error\n"); break; case USB_LC_STAT_SUSPENDED: - usbi_dbg("Device was Suspended\n"); + usbi_dbg(ctx, "Device was Suspended\n"); break; case USB_LC_STAT_DISCONNECTED: - usbi_dbg("Device was Disconnected\n"); + usbi_dbg(ctx, "Device was Disconnected\n"); break; case USB_LC_STAT_INTR_BUF_FULL: - usbi_dbg("Interrupt buffer was full\n"); + usbi_dbg(ctx, "Interrupt buffer was full\n"); break; case USB_LC_STAT_INVALID_REQ: - usbi_dbg("Request was Invalid\n"); + usbi_dbg(ctx, "Request was Invalid\n"); break; case USB_LC_STAT_INTERRUPTED: - usbi_dbg("Request was Interrupted\n"); + usbi_dbg(ctx, "Request was Interrupted\n"); break; case USB_LC_STAT_NO_RESOURCES: - usbi_dbg("No resources available for " + usbi_dbg(ctx, "No resources available for " "request\n"); break; case USB_LC_STAT_INTR_POLLING_FAILED: - usbi_dbg("Failed to Restart Poll"); + usbi_dbg(ctx, "Failed to Restart Poll"); break; default: - usbi_dbg("Error Not Determined %d\n", + usbi_dbg(ctx, "Error Not Determined %d\n", status); break; } } else { - usbi_dbg("read stat error: %s",strerror(errno)); + usbi_dbg(ctx, "read stat error: %s",strerror(errno)); status = -1; } diff --git a/libusb/libusb/os/windows_common.c b/libusb/libusb/os/windows_common.c index 119ed49..24ac095 100644 --- a/libusb/libusb/os/windows_common.c +++ b/libusb/libusb/os/windows_common.c @@ -24,7 +24,6 @@ #include -#include #include #include "libusbi.h" @@ -153,7 +152,7 @@ static bool htab_create(struct libusb_context *ctx) // Create a mutex usbi_mutex_init(&htab_mutex); - usbi_dbg("using %lu entries hash table", HTAB_SIZE); + usbi_dbg(ctx, "using %lu entries hash table", HTAB_SIZE); htab_filled = 0; // allocate memory and zero out. @@ -222,7 +221,7 @@ unsigned long htab_hash(const char *str) if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0)) goto out_unlock; // existing hash - usbi_dbg("hash collision ('%s' vs '%s')", str, htab_table[idx].str); + 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); @@ -284,7 +283,7 @@ enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS st case USBD_STATUS_DEVICE_GONE: return LIBUSB_TRANSFER_NO_DEVICE; default: - usbi_dbg("USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status)); + usbi_dbg(NULL, "USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status)); return LIBUSB_TRANSFER_ERROR; } } @@ -294,15 +293,18 @@ enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS st */ 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 %p, length %lu", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(size)); + usbi_dbg(TRANSFER_CTX(transfer), "transfer %p, length %lu", transfer, ULONG_CAST(size)); overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS; overlapped->InternalHigh = (ULONG_PTR)size; - usbi_signal_transfer_completion(itransfer); + 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 */ @@ -344,6 +346,8 @@ static enum windows_version get_windows_version(void) 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 + // And starting with Windows 10 Preview 2, Windows enforces the use of the application/supportedOS + // manifest in order for VerSetConditionMask() to report the ACTUAL OS major and minor... major_equal = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL); for (major = vi.dwMajorVersion; major <= 9; major++) { @@ -379,6 +383,7 @@ static enum windows_version get_windows_version(void) 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; @@ -388,22 +393,30 @@ static enum windows_version get_windows_version(void) 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; + case 0xA0: winver = WINDOWS_10; w = (ws ? "10" : "2016"); + if (vi.dwBuildNumber < 20000) + break; + // fallthrough + case 0xB0: winver = WINDOWS_11; w = (ws ? "11" : "2022"); break; default: if (version < 0x50) return WINDOWS_UNDEFINED; - winver = WINDOWS_11_OR_LATER; - w = "11 or later"; + winver = WINDOWS_12_OR_LATER; + w = "12 or later"; } + // We cannot tell if we are on 8, 10, or 11 without "app manifest" + if (version == 0x62 && vi.dwBuildNumber == 9200) + w = "8 (or later)"; + arch = is_x64() ? "64-bit" : "32-bit"; if (vi.wServicePackMinor) - usbi_dbg("Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch); + usbi_dbg(NULL, "Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch); else if (vi.wServicePackMajor) - usbi_dbg("Windows %s SP%u %s", w, vi.wServicePackMajor, arch); + usbi_dbg(NULL, "Windows %s SP%u %s", w, vi.wServicePackMajor, arch); else - usbi_dbg("Windows %s %s", w, arch); + usbi_dbg(NULL, "Windows %s %s", w, arch); return winver; } @@ -416,10 +429,14 @@ static unsigned __stdcall windows_iocp_thread(void *arg) 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("I/O completion thread started"); + usbi_dbg(ctx, "I/O completion thread started"); while (true) { overlapped = NULL; @@ -435,14 +452,48 @@ static unsigned __stdcall windows_iocp_thread(void *arg) break; } - transfer_priv = container_of(overlapped, struct windows_transfer_priv, overlapped); + // 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("transfer %p completed, length %lu", + 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("I/O completion thread exiting"); + usbi_dbg(ctx, "I/O completion thread exiting"); return 0; } @@ -450,26 +501,9 @@ static unsigned __stdcall windows_iocp_thread(void *arg) static int windows_init(struct libusb_context *ctx) { struct windows_context_priv *priv = usbi_get_context_priv(ctx); - char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0' - HANDLE mutex; bool winusb_backend_init = false; int r; - sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU)); - mutex = CreateMutexA(NULL, FALSE, mutex_name); - if (mutex == NULL) { - usbi_err(ctx, "could not create mutex: %s", windows_error_str(0)); - return LIBUSB_ERROR_NO_MEM; - } - - // A successful wait gives this thread ownership of the mutex - // => any concurrent wait stalls until the mutex is released - if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) { - usbi_err(ctx, "failure to access mutex: %s", windows_error_str(0)); - CloseHandle(mutex); - return LIBUSB_ERROR_NO_MEM; - } - // 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? @@ -496,7 +530,7 @@ static int windows_init(struct libusb_context *ctx) r = usbdk_backend.init(ctx); if (r == LIBUSB_SUCCESS) { - usbi_dbg("UsbDk backend is available"); + usbi_dbg(ctx, "UsbDk backend is available"); usbdk_available = true; } else { usbi_info(ctx, "UsbDk backend is not available"); @@ -538,29 +572,12 @@ static int windows_init(struct libusb_context *ctx) --init_count; } - ReleaseMutex(mutex); - CloseHandle(mutex); return r; } static void windows_exit(struct libusb_context *ctx) { struct windows_context_priv *priv = usbi_get_context_priv(ctx); - char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0' - HANDLE mutex; - - sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU)); - mutex = CreateMutexA(NULL, FALSE, mutex_name); - if (mutex == NULL) - return; - - // A successful wait gives this thread ownership of the mutex - // => any concurrent wait stalls until the mutex is released - if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) { - usbi_err(ctx, "failed to access mutex: %s", windows_error_str(0)); - CloseHandle(mutex); - return; - } // 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)) @@ -581,9 +598,6 @@ static void windows_exit(struct libusb_context *ctx) winusb_backend.exit(ctx); htab_destroy(); } - - ReleaseMutex(mutex); - CloseHandle(mutex); } static int windows_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) @@ -597,7 +611,7 @@ static int windows_set_option(struct libusb_context *ctx, enum libusb_option opt usbi_err(ctx, "UsbDk backend not available"); return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("switching context %p to use UsbDk backend", ctx); + usbi_dbg(ctx, "switching context %p to use UsbDk backend", ctx); priv->backend = &usbdk_backend; return LIBUSB_SUCCESS; } @@ -614,6 +628,9 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered 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); } @@ -698,8 +715,10 @@ static void windows_destroy_device(struct libusb_device *dev) static int windows_submit_transfer(struct usbi_transfer *itransfer) { struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); - struct libusb_context *ctx = TRANSFER_CTX(transfer); + 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; @@ -722,8 +741,18 @@ static int windows_submit_transfer(struct usbi_transfer *itransfer) 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; @@ -772,7 +801,7 @@ static int windows_handle_transfer_completion(struct usbi_transfer *itransfer) else result = GetLastError(); - usbi_dbg("handling transfer %p completion with errcode %lu, length %lu", + 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) { @@ -780,25 +809,25 @@ static int windows_handle_transfer_completion(struct usbi_transfer *itransfer) status = backend->copy_transfer_data(itransfer, bytes_transferred); break; case ERROR_GEN_FAILURE: - usbi_dbg("detected endpoint stall"); + usbi_dbg(ctx, "detected endpoint stall"); status = LIBUSB_TRANSFER_STALL; break; case ERROR_SEM_TIMEOUT: - usbi_dbg("detected semaphore 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("failed to copy partial data in aborted operation: %d", (int)istatus); + usbi_dbg(ctx, "failed to copy partial data in aborted operation: %d", (int)istatus); - usbi_dbg("detected operation aborted"); + 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("detected device removed"); + usbi_dbg(ctx, "detected device removed"); status = LIBUSB_TRANSFER_NO_DEVICE; break; default: @@ -881,6 +910,6 @@ const struct usbi_os_backend usbi_backend = { windows_handle_transfer_completion, sizeof(struct windows_context_priv), sizeof(union windows_device_priv), - sizeof(union windows_device_handle_priv), + sizeof(struct windows_device_handle_priv), sizeof(struct windows_transfer_priv), }; diff --git a/libusb/libusb/os/windows_common.h b/libusb/libusb/os/windows_common.h index 0c4b94c..4582ce4 100644 --- a/libusb/libusb/os/windows_common.h +++ b/libusb/libusb/os/windows_common.h @@ -52,6 +52,8 @@ #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 #endif #define safe_free(p) do {if (p != NULL) {free((void *)p); p = NULL;}} while (0) @@ -151,7 +153,8 @@ enum windows_version { WINDOWS_8, WINDOWS_8_1, WINDOWS_10, - WINDOWS_11_OR_LATER + WINDOWS_11, + WINDOWS_12_OR_LATER }; extern enum windows_version windows_version; @@ -257,18 +260,26 @@ struct winusb_device_priv { } 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 }; @@ -336,20 +347,36 @@ union windows_device_priv { struct winusb_device_priv winusb_priv; }; -union windows_device_handle_priv { - struct usbdk_device_handle_priv usbdk_priv; - struct winusb_device_handle_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); diff --git a/libusb/libusb/os/windows_usbdk.c b/libusb/libusb/os/windows_usbdk.c index c9ebfcf..9f52b48 100644 --- a/libusb/libusb/os/windows_usbdk.c +++ b/libusb/libusb/os/windows_usbdk.c @@ -27,7 +27,6 @@ #include #include "libusbi.h" -#include "windows_common.h" #include "windows_usbdk.h" #if !defined(STATUS_SUCCESS) @@ -414,7 +413,7 @@ static int usbdk_open(struct libusb_device_handle *dev_handle) device_priv->system_handle = usbdk_helper.GetRedirectorSystemHandle(device_priv->redirector_handle); - if (CreateIoCompletionPort(device_priv->system_handle, priv->completion_port, 0, 0) == NULL) { + 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; diff --git a/libusb/libusb/os/windows_winusb.c b/libusb/libusb/os/windows_winusb.c index f291b8e..ffc1612 100644 --- a/libusb/libusb/os/windows_winusb.c +++ b/libusb/libusb/os/windows_winusb.c @@ -28,14 +28,9 @@ #include #include #include -#include -#include #include -#include -#include #include "libusbi.h" -#include "windows_common.h" #include "windows_winusb.h" #define HANDLE_VALID(h) (((h) != NULL) && ((h) != INVALID_HANDLE_VALUE)) @@ -109,12 +104,12 @@ static struct winusb_interface WinUSBX[SUB_API_MAX]; } while (0) #if defined(ENABLE_LOGGING) -static const char *guid_to_string(const GUID *guid) +static const char *guid_to_string(const GUID *guid, char guid_string[MAX_GUID_STRING_LENGTH]) { - static char guid_string[MAX_GUID_STRING_LENGTH]; - - if (guid == NULL) - return ""; + 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, @@ -125,6 +120,37 @@ static const char *guid_to_string(const GUID *guid) } #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 @@ -154,12 +180,9 @@ static bool init_dlls(struct libusb_context *ctx) // Prefixed to avoid conflict with header files DLL_GET_HANDLE(ctx, AdvAPI32); - DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegQueryValueExW, true); + DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegQueryValueExA, true); DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegCloseKey, true); - DLL_GET_HANDLE(ctx, OLE32); - DLL_LOAD_FUNC_PREFIXED(OLE32, p, IIDFromString, true); - DLL_GET_HANDLE(ctx, SetupAPI); DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiGetClassDevsA, true); DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiEnumDeviceInfo, true); @@ -177,7 +200,6 @@ static bool init_dlls(struct libusb_context *ctx) static void exit_dlls(void) { DLL_FREE_HANDLE(SetupAPI); - DLL_FREE_HANDLE(OLE32); DLL_FREE_HANDLE(AdvAPI32); DLL_FREE_HANDLE(Cfgmgr32); } @@ -238,6 +260,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, { 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); @@ -246,7 +269,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, 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), ULONG_CAST(*_index), windows_error_str(0)); + guid_to_string(guid, guid_string), ULONG_CAST(*_index), windows_error_str(0)); return LIBUSB_ERROR_OTHER; } @@ -262,7 +285,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, if (GetLastError() != ERROR_NO_MORE_ITEMS) { usbi_err(ctx, "Could not obtain interface data for %s devInst %lX: %s", - guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); return LIBUSB_ERROR_OTHER; } @@ -274,7 +297,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, // 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), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); return LIBUSB_ERROR_OTHER; } } else { @@ -285,7 +308,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, 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), ULONG_CAST(dev_info_data->DevInst)); + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst)); return LIBUSB_ERROR_NO_MEM; } @@ -293,7 +316,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, 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), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0)); free(dev_interface_details); return LIBUSB_ERROR_OTHER; } @@ -303,7 +326,7 @@ static int get_interface_details(struct libusb_context *ctx, HDEVINFO dev_info, if (*dev_interface_path == NULL) { usbi_err(ctx, "could not allocate interface path for %s devInst %lX", - guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst)); + guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst)); return LIBUSB_ERROR_NO_MEM; } @@ -386,14 +409,14 @@ static int get_interface_details_filter(struct libusb_context *ctx, HDEVINFO *de DWORD value_length = sizeof(DWORD); LONG status; - status = pRegQueryValueExW(hkey_dev_interface, L"LUsb0", NULL, NULL, + 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("assigned libusb0 symbolic link %s", filter_path); + 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. } @@ -451,23 +474,23 @@ static int get_interface_by_endpoint(struct libusb_config_descriptor *conf_desc, intf_desc = &intf->altsetting[j]; for (k = 0; k < intf_desc->bNumEndpoints; k++) { if (intf_desc->endpoint[k].bEndpointAddress == ep) { - usbi_dbg("found endpoint %02X on interface %d", intf_desc->bInterfaceNumber, i); + usbi_dbg(NULL, "found endpoint %02X on interface %d", intf_desc->bInterfaceNumber, i); return intf_desc->bInterfaceNumber; } } } } - usbi_dbg("endpoint %02X not found on any interface", ep); + 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 *dev, const char *path, DWORD access) +static HANDLE windows_open(struct libusb_device_handle *dev_handle, const char *path, DWORD access) { - struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_context *ctx = HANDLE_CTX(dev_handle); struct windows_context_priv *priv = usbi_get_context_priv(ctx); HANDLE handle; @@ -475,7 +498,7 @@ static HANDLE windows_open(struct libusb_device *dev, const char *path, DWORD ac if (handle == INVALID_HANDLE_VALUE) return handle; - if (CreateIoCompletionPort(handle, priv->completion_port, 0, 0) == NULL) { + 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; @@ -500,26 +523,26 @@ static int windows_assign_endpoints(struct libusb_device_handle *dev_handle, uin return r; } + if (iface >= conf_desc->bNumInterfaces) { + usbi_err(HANDLE_CTX(dev_handle), "interface %d out of range for device", iface); + return LIBUSB_ERROR_NOT_FOUND; + } if_desc = &conf_desc->interface[iface].altsetting[altsetting]; safe_free(priv->usb_interface[iface].endpoint); if (if_desc->bNumEndpoints == 0) { - usbi_dbg("no endpoints found for interface %u", iface); - libusb_free_config_descriptor(conf_desc); - priv->usb_interface[iface].current_altsetting = altsetting; - return LIBUSB_SUCCESS; - } - - 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("(re)assigned endpoint %02X to interface %u", priv->usb_interface[iface].endpoint[i], iface); + 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); @@ -570,7 +593,7 @@ static int get_sub_api(char *driver, int api) static int auto_claim(struct libusb_transfer *transfer, int *interface_number, int api_type) { struct winusb_device_handle_priv *handle_priv = - usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -589,7 +612,7 @@ static int auto_claim(struct libusb_transfer *transfer, int *interface_number, i // 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("auto-claimed interface %d for control request", current_interface); + 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]++; @@ -617,7 +640,7 @@ 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 = usbi_get_device_handle_priv(dev_handle); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); int r; usbi_mutex_lock(&autoclaim_lock); @@ -626,9 +649,9 @@ static void auto_release(struct usbi_transfer *itransfer) 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("auto-released interface %d", transfer_priv->interface_number); + usbi_dbg(ITRANSFER_CTX(itransfer), "auto-released interface %d", transfer_priv->interface_number); else - usbi_dbg("failed to auto-release interface %d (%s)", + usbi_dbg(ITRANSFER_CTX(itransfer), "failed to auto-release interface %d (%s)", transfer_priv->interface_number, libusb_error_name((enum libusb_error)r)); } } @@ -769,7 +792,7 @@ static void cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handl continue; } - usbi_dbg("cached config descriptor %u (bConfigurationValue=%u, %u bytes)", + usbi_dbg(ctx, "cached config descriptor %u (bConfigurationValue=%u, %u bytes)", i, cd_data->bConfigurationValue, cd_data->wTotalLength); // Cache the descriptor @@ -886,7 +909,7 @@ static int init_root_hub(struct libusb_device *dev) } num_ports = hub_info.u.HubInformation.HubDescriptor.bNumberOfPorts; - usbi_dbg("root hub '%s' reports %lu ports", priv->dev_id, ULONG_CAST(num_ports)); + 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, @@ -1021,7 +1044,7 @@ static int init_root_hub(struct libusb_device *dev) static int init_device(struct libusb_device *dev, struct libusb_device *parent_dev, uint8_t port_number, DEVINST devinst) { - struct libusb_context *ctx; + 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; @@ -1120,7 +1143,7 @@ static int init_device(struct libusb_device *dev, struct libusb_device *parent_d priv->active_config = conn_info.CurrentConfigurationValue; if (priv->active_config == 0) { - usbi_dbg("0x%x:0x%x found %u configurations (not configured)", + usbi_dbg(ctx, "0x%x:0x%x found %u configurations (not configured)", dev->device_descriptor.idVendor, dev->device_descriptor.idProduct, dev->device_descriptor.bNumConfigurations); @@ -1143,7 +1166,7 @@ static int init_device(struct libusb_device *dev, struct libusb_device *parent_d dev->device_descriptor.bNumConfigurations); priv->active_config = 1; } else { - usbi_dbg("found %u configurations (current config: %u)", dev->device_descriptor.bNumConfigurations, priv->active_config); + usbi_dbg(ctx, "found %u configurations (current config: %u)", dev->device_descriptor.bNumConfigurations, priv->active_config); } // Cache as many config descriptors as we can @@ -1194,12 +1217,53 @@ static int init_device(struct libusb_device *dev, struct libusb_device *parent_d priv->initialized = true; - usbi_dbg("(bus: %u, addr: %u, depth: %u, port: %u): '%s'", + 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) { @@ -1222,7 +1286,7 @@ static int enumerate_hcd_root_hub(struct libusb_context *ctx, const char *dev_id if (dev->bus_number == 0) { // Only do this once - usbi_dbg("assigning HCD '%s' bus number %u", dev_id, bus_number); + 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) @@ -1266,10 +1330,10 @@ static void get_api_type(HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, if (lookup[k].list[l] == 0) lookup[k].list[l] = LIST_SEPARATOR; } - usbi_dbg("%s(s): %s", lookup[k].designation, lookup[k].list); + usbi_dbg(NULL, "%s(s): %s", lookup[k].designation, lookup[k].list); } else { if (GetLastError() != ERROR_INVALID_DATA) - usbi_dbg("could not access %s: %s", lookup[k].designation, windows_error_str(0)); + usbi_dbg(NULL, "could not access %s: %s", lookup[k].designation, windows_error_str(0)); lookup[k].list[0] = 0; } } @@ -1278,7 +1342,7 @@ static void get_api_type(HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, for (k = 0; k < 3; k++) { j = get_sub_api(lookup[k].list, i); if (j >= 0) { - usbi_dbg("matched %s name against %s", lookup[k].designation, + 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; @@ -1314,7 +1378,7 @@ static int set_composite_interface(struct libusb_context *ctx, struct libusb_dev 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("interface[%d] already set - ignoring HID collection: %s", + usbi_dbg(ctx, "interface[%d] already set - ignoring HID collection: %s", interface_number, device_id); return LIBUSB_ERROR_ACCESS; } @@ -1322,7 +1386,7 @@ static int set_composite_interface(struct libusb_context *ctx, struct libusb_dev safe_free(priv->usb_interface[interface_number].path); } - usbi_dbg("interface[%d] = %s", interface_number, dev_interface_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; @@ -1351,14 +1415,14 @@ static int set_hid_interface(struct libusb_context *ctx, struct libusb_device *d 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("interface[%u] already set to %s", i, dev_interface_path); + 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("interface[%u] = %s", priv->hid->nb_interfaces, dev_interface_path); + usbi_dbg(ctx, "interface[%u] = %s", priv->hid->nb_interfaces, dev_interface_path); priv->hid->nb_interfaces++; return LIBUSB_SUCCESS; } @@ -1384,7 +1448,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ unsigned long session_id; DWORD size, port_nr, reg_type, install_state; HKEY key; - WCHAR guid_string_w[MAX_GUID_STRING_LENGTH]; + char guid_string[MAX_GUID_STRING_LENGTH]; GUID *if_guid; LONG s; #define HUB_PASS 0 @@ -1454,7 +1518,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ //#define ENUM_DEBUG #if defined(ENABLE_LOGGING) && defined(ENUM_DEBUG) const char * const passname[] = {"HUB", "DEV", "HCD", "GEN", "HID", "EXT"}; - usbi_dbg("#### PROCESSING %ss %s", passname[MIN(pass, EXT_PASS)], guid_to_string(guid_list[pass])); + 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; @@ -1506,7 +1570,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ } #ifdef ENUM_DEBUG - usbi_dbg("PRO: %s", dev_id); + usbi_dbg(ctx, "PRO: %s", dev_id); #endif // Set API to use or get additional data from generic pass @@ -1529,7 +1593,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ break; } if (j == nb_usb_enumerators) { - usbi_dbg("found new PnP enumerator string '%s'", enumerator); + 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) { @@ -1555,16 +1619,25 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ if (key == INVALID_HANDLE_VALUE) break; // Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order - size = sizeof(guid_string_w); - s = pRegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, ®_type, - (LPBYTE)guid_string_w, &size); + // 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 = pRegQueryValueExW(key, L"DeviceInterfaceGUID", NULL, ®_type, - (LPBYTE)guid_string_w, &size); + s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, ®_type, + (LPBYTE)guid_string, &size); pRegCloseKey(key); - if ((s == ERROR_SUCCESS) && - (((reg_type == REG_SZ) && (size == (sizeof(guid_string_w) - sizeof(WCHAR)))) || - ((reg_type == REG_MULTI_SZ) && (size == sizeof(guid_string_w))))) { + 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) { @@ -1579,8 +1652,8 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ usbi_err(ctx, "failed to alloc if_guid"); LOOP_BREAK(LIBUSB_ERROR_NO_MEM); } - if (pIIDFromString(guid_string_w, if_guid) != 0) { - usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string, skipping", dev_id); + 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 @@ -1589,14 +1662,14 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ break; } if (j == nb_guids) { - usbi_dbg("extra GUID: %s", guid_to_string(if_guid)); + usbi_dbg(ctx, "extra GUID: %s", guid_string); guid_list[nb_guids++] = if_guid; } else { // Duplicate, ignore free(if_guid); } } - } else if (s == ERROR_SUCCESS) { + } else { usbi_warn(ctx, "unexpected type/size of DeviceInterfaceGUID for '%s'", dev_id); } break; @@ -1631,7 +1704,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ libusb_unref_device(dev); } - usbi_dbg("unlisted ancestor for '%s' (non USB HID, newly connected, etc.) - ignoring", dev_id); + usbi_dbg(ctx, "unlisted ancestor for '%s' (non USB HID, newly connected, etc.) - ignoring", dev_id); continue; } @@ -1650,23 +1723,30 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ dev = usbi_get_device_by_session_id(ctx, session_id); if (dev == NULL) { alloc_device: - usbi_dbg("allocating new device for session [%lX]", session_id); + 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("found existing device for session [%lX]", session_id); + 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("device instance ID for session [%lX] changed", session_id); + 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; @@ -1724,10 +1804,8 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ r = enumerate_hcd_root_hub(ctx, dev_id, (uint8_t)(i + 1), dev_info_data.DevInst); break; case GEN_PASS: - // The SPDRP_ADDRESS for USB devices is the device port number on the hub port_nr = 0; - if (!pSetupDiGetDeviceRegistryPropertyA(*dev_info, &dev_info_data, SPDRP_ADDRESS, - NULL, (PBYTE)&port_nr, sizeof(port_nr), &size) || (size != sizeof(port_nr))) + 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) { @@ -1747,10 +1825,10 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ 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("setting HID interface for [%lX]:", parent_dev->session_data); + 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("setting composite interface for [%lX]:", parent_dev->session_data); + 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) { @@ -2001,8 +2079,6 @@ static int winusb_submit_transfer(struct usbi_transfer *itransfer) break; 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; transfer_fn = priv->apib->submit_bulk_transfer; break; case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: @@ -2263,10 +2339,10 @@ static bool winusbx_init(struct libusb_context *ctx) KLIB_VERSION LibK_Version; pLibK_GetVersion(&LibK_Version); - usbi_dbg("libusbK DLL found, version: %d.%d.%d.%d", LibK_Version.Major, LibK_Version.Minor, + 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("libusbK DLL found, version unknown"); + usbi_dbg(ctx, "libusbK DLL found, version unknown"); } pLibK_GetProcAddress = (LibK_GetProcAddress_t)GetProcAddress(hlibusbK, "LibK_GetProcAddress"); @@ -2352,7 +2428,7 @@ static void winusbx_exit(void) 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 = usbi_get_device_handle_priv(dev_handle); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); HANDLE file_handle; int i; @@ -2362,7 +2438,7 @@ static int winusbx_open(int sub_api, struct libusb_device_handle *dev_handle) 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->dev, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE); + 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()) { @@ -2384,7 +2460,7 @@ 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) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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; @@ -2429,7 +2505,7 @@ 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) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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 = handle_priv->interface_handle[iface].api_handle; UCHAR policy; @@ -2445,35 +2521,36 @@ static int winusbx_configure_endpoints(int sub_api, struct libusb_device_handle 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("failed to set PIPE_TRANSFER_TIMEOUT for control endpoint %02X", endpoint_address); + 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("failed to disable SHORT_PACKET_TERMINATE for endpoint %02X", endpoint_address); + 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("failed to disable IGNORE_SHORT_PACKETS for endpoint %02X", endpoint_address); + 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("failed to enable ALLOW_PARTIAL_READS for endpoint %02X", endpoint_address); + 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("failed to enable AUTO_CLEAR_STALL for endpoint %02X", endpoint_address); + 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("failed to enable ISO_ALWAYS_START_ASAP for endpoint %02X", endpoint_address); + usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable ISO_ALWAYS_START_ASAP for endpoint %02X", endpoint_address); } } @@ -2483,7 +2560,7 @@ static int winusbx_configure_endpoints(int sub_api, struct libusb_device_handle 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 = usbi_get_device_handle_priv(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; @@ -2534,7 +2611,7 @@ static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev *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->dev, filter_path, GENERIC_READ | GENERIC_WRITE); + 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 @@ -2591,7 +2668,7 @@ static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev } handle_priv->interface_handle[iface].dev_handle = handle_priv->interface_handle[0].dev_handle; } - usbi_dbg("claimed interface %u", iface); + usbi_dbg(ctx, "claimed interface %u", iface); handle_priv->active_interface = iface; return LIBUSB_SUCCESS; @@ -2599,7 +2676,7 @@ static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev static int winusbx_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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; @@ -2620,12 +2697,12 @@ static int winusbx_release_interface(int sub_api, struct libusb_device_handle *d */ static int get_valid_interface(struct libusb_device_handle *dev_handle, int api_id) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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); int i; if ((api_id < USB_API_WINUSBX) || (api_id > USB_API_HID)) { - usbi_dbg("unsupported API ID"); + usbi_dbg(HANDLE_CTX(dev_handle), "unsupported API ID"); return -1; } @@ -2644,14 +2721,14 @@ static int get_valid_interface(struct libusb_device_handle *dev_handle, int api_ */ static int check_valid_interface(struct libusb_device_handle *dev_handle, unsigned short interface, int api_id) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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); if (interface >= USB_MAXINTERFACES) return -1; if ((api_id < USB_API_WINUSBX) || (api_id > USB_API_HID)) { - usbi_dbg("unsupported API ID"); + usbi_dbg(HANDLE_CTX(dev_handle), "unsupported API ID"); return -1; } @@ -2691,9 +2768,9 @@ static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *it 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; + ULONG size, transferred; HANDLE winusb_handle; OVERLAPPED *overlapped; int current_interface; @@ -2703,7 +2780,7 @@ static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *it size = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; // Windows places upper limits on the control transfer size - // See: https://msdn.microsoft.com/en-us/library/windows/hardware/ff538112.aspx + // 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; @@ -2716,8 +2793,9 @@ static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *it return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("will use interface %d", current_interface); + 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); @@ -2732,22 +2810,22 @@ static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *it } windows_force_sync_completion(itransfer, 0); } else { - if (!WinUSBX[sub_api].ControlTransfer(winusb_handle, *setup, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, size, NULL, overlapped)) { + 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); } } - transfer_priv->interface_number = (uint8_t)current_interface; - 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 = usbi_get_device_handle_priv(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; @@ -2803,7 +2881,7 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans { 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -2818,8 +2896,9 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + 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); @@ -2852,10 +2931,10 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans } if (IS_XFERIN(transfer)) { - usbi_dbg("reading %d iso packets", transfer->num_iso_packets); + 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("writing %d iso packets", transfer->num_iso_packets); + 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); } @@ -2864,8 +2943,6 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans return LIBUSB_ERROR_IO; } - transfer_priv->interface_number = (uint8_t)current_interface; - return LIBUSB_SUCCESS; } else if (sub_api == SUB_API_WINUSB) { WINUSB_PIPE_INFORMATION_EX pipe_info_ex = { 0 }; @@ -2913,7 +2990,11 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans // 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->dev_handle->dev->speed >= LIBUSB_SPEED_HIGH) // Microframes (125us) + iso_transfer_size_multiple = (pipe_info_ex.MaximumBytesPerInterval * 8) / interval; + else // Normal Frames (1ms) + iso_transfer_size_multiple = pipe_info_ex.MaximumBytesPerInterval / 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; @@ -2980,8 +3061,6 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans transfer_priv->isoch_buffer_handle = buffer_handle; - transfer_priv->interface_number = (uint8_t)current_interface; - return LIBUSB_SUCCESS; } else { PRINT_UNSUPPORTED_API(winusbx_submit_iso_transfer); @@ -2993,7 +3072,7 @@ static int winusbx_submit_bulk_transfer(int sub_api, struct usbi_transfer *itran { 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -3008,17 +3087,35 @@ static int winusbx_submit_bulk_transfer(int sub_api, struct usbi_transfer *itran return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + 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("reading %d bytes", transfer->length); + 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 { - usbi_dbg("writing %d bytes", transfer->length); + // 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); } @@ -3027,14 +3124,12 @@ static int winusbx_submit_bulk_transfer(int sub_api, struct usbi_transfer *itran return LIBUSB_ERROR_IO; } - transfer_priv->interface_number = (uint8_t)current_interface; - 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 = usbi_get_device_handle_priv(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 current_interface; @@ -3047,7 +3142,7 @@ static int winusbx_clear_halt(int sub_api, struct libusb_device_handle *dev_hand return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("matched endpoint %02X with interface %d", endpoint, current_interface); + 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)) { @@ -3061,7 +3156,7 @@ static int winusbx_clear_halt(int sub_api, struct libusb_device_handle *dev_hand 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -3069,7 +3164,7 @@ static int winusbx_cancel_transfer(int sub_api, struct usbi_transfer *itransfer) CHECK_WINUSBX_AVAILABLE(sub_api); - usbi_dbg("will use interface %d", current_interface); + 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)) { @@ -3091,7 +3186,7 @@ static int winusbx_cancel_transfer(int sub_api, struct usbi_transfer *itransfer) // 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 = usbi_get_device_handle_priv(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; @@ -3103,7 +3198,7 @@ static int winusbx_reset_device(int sub_api, struct libusb_device_handle *dev_ha 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("resetting ep %02X", priv->usb_interface[i].endpoint[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)); @@ -3138,12 +3233,25 @@ static enum libusb_transfer_status winusbx_copy_transfer_data(int sub_api, struc int i; if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev); + + if (sub_api == SUB_API_NOTSET) + sub_api = priv->sub_api; + if (WinUSBX[sub_api].hDll == NULL) + return LIBUSB_TRANSFER_ERROR; + // 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; + if (IS_XFERIN(transfer)) { + transfer->iso_packet_desc[i].actual_length = iso_context->IsoPackets[i].actual_length; + } else { + // On Windows the usbd Length field is not used for OUT transfers. + // Copy the requested value back for consistency with other platforms. + transfer->iso_packet_desc[i].actual_length = transfer->iso_packet_desc[i].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; } @@ -3164,6 +3272,9 @@ static enum libusb_transfer_status winusbx_copy_transfer_data(int sub_api, struc } else { for (i = 0; i < transfer->num_iso_packets; i++) { transfer->iso_packet_desc[i].status = LIBUSB_TRANSFER_COMPLETED; + // On Windows the usbd Length field is not used for OUT transfers. + // Copy the requested value back for consistency with other platforms. + transfer->iso_packet_desc[i].actual_length = transfer->iso_packet_desc[i].length; } } } else { @@ -3439,28 +3550,28 @@ static int _hid_get_descriptor(struct libusb_device *dev, HANDLE hid_handle, int switch (type) { case LIBUSB_DT_DEVICE: - usbi_dbg("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("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("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("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("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("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; @@ -3502,7 +3613,7 @@ static int _hid_get_report(struct libusb_device *dev, HANDLE hid_handle, int id, return LIBUSB_ERROR_NO_MEM; buf[0] = (uint8_t)id; // Must be set always - usbi_dbg("report ID: 0x%02X", buf[0]); + 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, @@ -3550,7 +3661,7 @@ static int _hid_set_report(struct libusb_device *dev, HANDLE hid_handle, int id, return LIBUSB_ERROR_INVALID_PARAM; } - usbi_dbg("report ID: 0x%02X", id); + 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) @@ -3644,7 +3755,7 @@ 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 = usbi_get_device_handle_priv(dev_handle); + 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; @@ -3669,7 +3780,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) 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, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE); + 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 @@ -3679,7 +3790,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) */ 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, priv->usb_interface[i].path, 0); + 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()) { @@ -3709,7 +3820,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) // Set the maximum available input buffer size for (i = 32; HidD_SetNumInputBuffers(hid_handle, i); i *= 2); - usbi_dbg("set maximum input buffer size to %d", 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) { @@ -3726,7 +3837,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) size[1] = capabilities.NumberOutputValueCaps; size[2] = capabilities.NumberFeatureValueCaps; for (j = HidP_Input; j <= HidP_Feature; j++) { - usbi_dbg("%lu HID %s report value(s) found", ULONG_CAST(size[j]), type[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)); @@ -3736,7 +3847,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) nb_ids[0] = 0; nb_ids[1] = 0; for (i = 0; i < (int)size[j]; i++) { - usbi_dbg(" Report ID: 0x%02X", value_caps[i].ReportID); + usbi_dbg(HANDLE_CTX(dev_handle), " Report ID: 0x%02X", value_caps[i].ReportID); if (value_caps[i].ReportID != 0) nb_ids[1]++; else @@ -3773,7 +3884,10 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle) 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])); + // Using HidD_GetIndexedString() instead of HidD_GetProductString(), as the latter would otherwise return the name + // of the interface instead of the iProduct string whenever the iInterface member of the USB_INTERFACE_DESCRIPTOR + // structure for the interface is nonzero (see Remarks section in the documentation of the HID API routines) + HidD_GetIndexedString(hid_handle, priv->hid->string_index[1], priv->hid->string[1], sizeof(priv->hid->string[1])); else priv->hid->string[1][0] = 0; @@ -3793,7 +3907,7 @@ 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) { struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev); - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); HANDLE file_handle; int i; @@ -3813,7 +3927,7 @@ 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) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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); UNUSED(sub_api); @@ -3829,7 +3943,7 @@ static int hid_claim_interface(int sub_api, struct libusb_device_handle *dev_han handle_priv->interface_handle[iface].dev_handle = INTERFACE_CLAIMED; - usbi_dbg("claimed interface %u", iface); + usbi_dbg(HANDLE_CTX(dev_handle), "claimed interface %u", iface); handle_priv->active_interface = iface; return LIBUSB_SUCCESS; @@ -3837,7 +3951,7 @@ static int hid_claim_interface(int sub_api, struct libusb_device_handle *dev_han static int hid_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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); UNUSED(sub_api); @@ -3874,7 +3988,7 @@ static int hid_submit_control_transfer(int sub_api, struct usbi_transfer *itrans 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 = usbi_get_device_handle_priv(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; @@ -3900,8 +4014,9 @@ static int hid_submit_control_transfer(int sub_api, struct usbi_transfer *itrans return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("will use interface %d", current_interface); + 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); @@ -3963,8 +4078,6 @@ static int hid_submit_control_transfer(int sub_api, struct usbi_transfer *itrans r = LIBUSB_SUCCESS; } - transfer_priv->interface_number = (uint8_t)current_interface; - return LIBUSB_SUCCESS; } @@ -3972,7 +4085,7 @@ 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -3983,6 +4096,9 @@ static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer 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); @@ -3992,8 +4108,9 @@ static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface); + 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); @@ -4015,7 +4132,7 @@ static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer if (direction_in) { transfer_priv->hid_dest = transfer->buffer; - usbi_dbg("reading %d bytes (report ID: 0x00)", length); + 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]) @@ -4024,7 +4141,7 @@ static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer // We could actually do without the calloc and memcpy in this case memcpy(transfer_priv->hid_buffer, transfer->buffer, transfer->length); - usbi_dbg("writing %d bytes (report ID: 0x%02X)", length, transfer_priv->hid_buffer[0]); + 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); } @@ -4034,14 +4151,12 @@ static int hid_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer return LIBUSB_ERROR_IO; } - transfer_priv->interface_number = (uint8_t)current_interface; - return LIBUSB_SUCCESS; } static int hid_reset_device(int sub_api, struct libusb_device_handle *dev_handle) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle); HANDLE hid_handle; int current_interface; @@ -4060,7 +4175,7 @@ static int hid_reset_device(int sub_api, struct libusb_device_handle *dev_handle static int hid_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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 hid_handle; int current_interface; @@ -4074,7 +4189,7 @@ static int hid_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, return LIBUSB_ERROR_NOT_FOUND; } - usbi_dbg("matched endpoint %02X with interface %d", endpoint, current_interface); + 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 @@ -4109,8 +4224,6 @@ static enum libusb_transfer_status hid_copy_transfer_data(int sub_api, struct us } if (transfer_priv->hid_buffer[0] == 0) { - // Discard the 1 byte report ID prefix - length--; memcpy(transfer_priv->hid_dest, transfer_priv->hid_buffer + 1, length); } else { memcpy(transfer_priv->hid_dest, transfer_priv->hid_buffer, length); @@ -4171,7 +4284,7 @@ static int composite_open(int sub_api, struct libusb_device_handle *dev_handle) // 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("ignoring access denied error while opening HID interface of composite device"); + usbi_dbg(HANDLE_CTX(dev_handle), "ignoring access denied error while opening HID interface of composite device"); r = LIBUSB_SUCCESS; } } @@ -4280,7 +4393,7 @@ static int composite_submit_control_transfer(int sub_api, struct usbi_transfer * // Try and target a specific interface if the control setup indicates such if ((iface >= 0) && (iface < USB_MAXINTERFACES)) { - usbi_dbg("attempting control transfer targeted to interface %d", iface); + 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); @@ -4296,10 +4409,10 @@ static int composite_submit_control_transfer(int sub_api, struct usbi_transfer * 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("trying to skip restricted interface #%d (HID keyboard or mouse?)", iface); + usbi_dbg(TRANSFER_CTX(transfer), "trying to skip restricted interface #%d (HID keyboard or mouse?)", iface); continue; } - usbi_dbg("using interface %d", iface); + 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) @@ -4316,7 +4429,7 @@ static int composite_submit_control_transfer(int sub_api, struct usbi_transfer * 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -4337,7 +4450,7 @@ static int composite_submit_bulk_transfer(int sub_api, struct usbi_transfer *itr 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 = usbi_get_device_handle_priv(transfer->dev_handle); + 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; @@ -4357,7 +4470,7 @@ static int composite_submit_iso_transfer(int sub_api, struct usbi_transfer *itra static int composite_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint) { - struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(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); int current_interface; diff --git a/libusb/libusb/os/windows_winusb.h b/libusb/libusb/os/windows_winusb.h index 49355d4..6afd5cb 100644 --- a/libusb/libusb/os/windows_winusb.h +++ b/libusb/libusb/os/windows_winusb.h @@ -23,20 +23,10 @@ #ifndef LIBUSB_WINDOWS_WINUSB_H #define LIBUSB_WINDOWS_WINUSB_H -#include "windows_common.h" - -#if defined(_MSC_VER) -// disable /W4 MSVC warnings that are benign -#pragma warning(disable:4214) // bit field types other than int -#endif +#include +#include -// Missing from MSVC6 setupapi.h -#ifndef SPDRP_ADDRESS -#define SPDRP_ADDRESS 28 -#endif -#ifndef SPDRP_INSTALL_STATE -#define SPDRP_INSTALL_STATE 34 -#endif +#include "windows_common.h" #define MAX_CTRL_BUFFER_LENGTH 4096 #define MAX_USB_STRING_LENGTH 128 @@ -55,18 +45,10 @@ // 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 -#ifndef GUID_DEVINTERFACE_USB_HOST_CONTROLLER -const GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER = {0x3ABF6F2D, 0x71C4, 0x462A, {0x8A, 0x92, 0x1E, 0x68, 0x61, 0xE6, 0xAF, 0x27}}; -#endif -#ifndef GUID_DEVINTERFACE_USB_DEVICE -const GUID GUID_DEVINTERFACE_USB_DEVICE = {0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}; -#endif -#ifndef GUID_DEVINTERFACE_USB_HUB -const GUID GUID_DEVINTERFACE_USB_HUB = {0xF18A0E88, 0xC30C, 0x11D0, {0x88, 0x15, 0x00, 0xA0, 0xC9, 0x06, 0xBE, 0xD8}}; -#endif -#ifndef GUID_DEVINTERFACE_LIBUSB0_FILTER -const GUID GUID_DEVINTERFACE_LIBUSB0_FILTER = {0xF9F3FF14, 0xAE21, 0x48A0, {0x8A, 0x25, 0x80, 0x11, 0xA7, 0xA9, 0x31, 0xD9}}; -#endif +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 @@ -114,7 +96,7 @@ struct windows_usb_api_backend { extern const struct windows_usb_api_backend usb_api_backend[USB_API_MAX]; #define PRINT_UNSUPPORTED_API(fname) \ - usbi_dbg("unsupported API call for '%s' " \ + usbi_dbg(NULL, "unsupported API call for '%s' " \ "(unrecognized device driver)", #fname) #define CHECK_SUPPORTED_API(apip, fname) \ @@ -152,11 +134,6 @@ struct libusb_hid_descriptor { #define LIBUSB_REQ_IN(request_type) ((request_type) & LIBUSB_ENDPOINT_IN) #define LIBUSB_REQ_OUT(request_type) (!LIBUSB_REQ_IN(request_type)) -#ifndef CTL_CODE -#define CTL_CODE(DeviceType, Function, Method, Access) \ - (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) -#endif - // 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) @@ -259,13 +236,9 @@ DLL_DECLARE_FUNC(WINAPI, CONFIGRET, CM_Get_Child, (PDEVINST, DEVINST, ULONG)); /* AdvAPI32 dependencies */ DLL_DECLARE_HANDLE(AdvAPI32); -DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegQueryValueExW, (HKEY, LPCWSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD)); +DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegQueryValueExA, (HKEY, LPCSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD)); DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegCloseKey, (HKEY)); -/* OLE32 dependency */ -DLL_DECLARE_HANDLE(OLE32); -DLL_DECLARE_FUNC_PREFIXED(WINAPI, HRESULT, p, IIDFromString, (LPCOLESTR, LPIID)); - /* SetupAPI dependencies */ DLL_DECLARE_HANDLE(SetupAPI); DLL_DECLARE_FUNC_PREFIXED(WINAPI, HDEVINFO, p, SetupDiGetClassDevsA, (LPCGUID, PCSTR, HWND, DWORD)); @@ -282,23 +255,12 @@ DLL_DECLARE_FUNC_PREFIXED(WINAPI, BOOL, p, SetupDiDestroyDeviceInfoList, (HDEVIN 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 -#ifndef USB_GET_NODE_INFORMATION #define USB_GET_NODE_INFORMATION 258 -#endif -#ifndef USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION #define USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION 260 -#endif -#ifndef USB_GET_NODE_CONNECTION_INFORMATION_EX #define USB_GET_NODE_CONNECTION_INFORMATION_EX 274 -#endif -#ifndef USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 #define USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 279 -#endif - -#ifndef FILE_DEVICE_USB -#define FILE_DEVICE_USB FILE_DEVICE_UNKNOWN -#endif #define USB_CTL_CODE(id) \ CTL_CODE(FILE_DEVICE_USB, (id), METHOD_BUFFERED, FILE_ANY_ACCESS) @@ -342,6 +304,12 @@ typedef enum _USB_HUB_NODE { 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 @@ -439,6 +407,11 @@ typedef struct _USB_NODE_CONNECTION_INFORMATION_EX_V2 { #include +#if defined(_MSC_VER) +// Restore original warnings +#pragma warning(pop) +#endif + /* winusb.dll interface */ /* pipe policies */ diff --git a/libusb/libusb/sync.c b/libusb/libusb/sync.c index adc95b4..1fa1f0b 100644 --- a/libusb/libusb/sync.c +++ b/libusb/libusb/sync.c @@ -36,7 +36,7 @@ static void LIBUSB_CALL sync_transfer_cb(struct libusb_transfer *transfer) { int *completed = transfer->user_data; *completed = 1; - usbi_dbg("actual_length=%d", transfer->actual_length); + usbi_dbg(TRANSFER_CTX(transfer), "actual_length=%d", transfer->actual_length); /* caller interprets result and frees transfer */ } diff --git a/libusb/libusb/version.h b/libusb/libusb/version.h index d8ebde4..fe95d84 100644 --- a/libusb/libusb/version.h +++ b/libusb/libusb/version.h @@ -7,7 +7,7 @@ #define LIBUSB_MINOR 0 #endif #ifndef LIBUSB_MICRO -#define LIBUSB_MICRO 24 +#define LIBUSB_MICRO 26 #endif #ifndef LIBUSB_NANO #define LIBUSB_NANO 0 diff --git a/libusb/libusb/version_nano.h b/libusb/libusb/version_nano.h index 0f100a8..dbd5d5f 100644 --- a/libusb/libusb/version_nano.h +++ b/libusb/libusb/version_nano.h @@ -1 +1 @@ -#define LIBUSB_NANO 11584 +#define LIBUSB_NANO 11724 From 16e7f0f33f62c145784f06d0f04683b3c595aef2 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Jun 2022 14:17:02 +0200 Subject: [PATCH 02/17] fix typos --- c_deps.go | 4 ++-- hid_disabled.go | 2 +- hid_enabled.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/c_deps.go b/c_deps.go index 7de6017..85a2329 100644 --- a/c_deps.go +++ b/c_deps.go @@ -2,10 +2,10 @@ // This file is part of a workaround for `go mod vendor` which won't vendor // C files if there's no Go file in the same directory. -// This would prevent the bundled c-libs from beeing removed when vendored. +// This would prevent the bundled c-libs from being removed when vendored. // // This Go file imports the c directory and subdirectories where there is -// another go (named after the direcory) file which is the second part +// another go (named after the directory) file which is the second part // of this workaround. // // These files combined make it so `go mod vendor` behaves correctly. diff --git a/hid_disabled.go b/hid_disabled.go index 5f69956..a533e0b 100644 --- a/hid_disabled.go +++ b/hid_disabled.go @@ -9,7 +9,7 @@ package hid // Supported returns whether this platform is supported by the HID library or not. -// The goal of this method is to allow programatically handling platforms that do +// The goal of this method is to allow programmatically handling platforms that do // not support USB HID and not having to fall back to build constraints. func Supported() bool { return false diff --git a/hid_enabled.go b/hid_enabled.go index 397baf6..4125500 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -67,7 +67,7 @@ import ( var enumerateLock sync.Mutex // Supported returns whether this platform is supported by the HID library or not. -// The goal of this method is to allow programatically handling platforms that do +// The goal of this method is to allow programmatically handling platforms that do // not support USB HID and not having to fall back to build constraints. func Supported() bool { return true @@ -249,7 +249,7 @@ func (dev *Device) SendFeatureReport(b []byte) (int, error) { // Read is a wrapper to ReadTimeout that will check if device blocking is enabled // and set timeout accordingly. // -// This reproduces C.hid_read() behaviour in wrapping hid_read_timeout: +// This reproduces C.hid_read() behavior in wrapping hid_read_timeout: // return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); func (dev *Device) Read(b []byte) (int, error) { var timeout int @@ -315,7 +315,7 @@ func (dev *Device) GetFeatureReport(b []byte) (int, error) { return 0, ErrDeviceClosed } - // Retrive the feature report + // Retrieve the feature report read := int(C.hid_get_feature_report(device, (*C.uchar)(&b[0]), C.size_t(len(b)))) if read == -1 { // If the read failed, verify if closed or other error @@ -358,7 +358,7 @@ func (dev *Device) GetInputReport(b []byte) (int, error) { return 0, ErrDeviceClosed } - // Retrive the feature report + // Retrieve the feature report read := int(C.hid_get_input_report(device, (*C.uchar)(&b[0]), C.size_t(len(b)))) if read == -1 { // If the read failed, verify if closed or other error From 88b60e64254a38cdb64e138cc625bdef3963ed5a Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Jun 2022 14:21:09 +0200 Subject: [PATCH 03/17] make go-staticcheck happy --- wchar.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wchar.go b/wchar.go index d103bef..660119b 100644 --- a/wchar.go +++ b/wchar.go @@ -43,7 +43,7 @@ func stringToWcharT(s string) (*C.wchar_t, C.size_t) { case 4: return stringToWchar4(s) // Unix default: - panic(fmt.Sprintf("Invalid sizeof(wchar_t) = %v", sizeofWcharT)) + panic(fmt.Sprintf("invalid sizeof(wchar_t) = %v", sizeofWcharT)) } } @@ -54,7 +54,7 @@ func wcharTToString(s *C.wchar_t) (string, error) { case 4: return wchar4ToString(s) // Unix default: - panic(fmt.Sprintf("Invalid sizeof(wchar_t) = %v", sizeofWcharT)) + panic(fmt.Sprintf("invalid sizeof(wchar_t) = %v", sizeofWcharT)) } } @@ -65,7 +65,7 @@ func wcharTNToString(s *C.wchar_t, size C.size_t) (string, error) { case 4: return wchar4NToString(s, size) // Unix default: - panic(fmt.Sprintf("Invalid sizeof(wchar_t) = %v", sizeofWcharT)) + panic(fmt.Sprintf("invalid sizeof(wchar_t) = %v", sizeofWcharT)) } } @@ -131,7 +131,7 @@ func wchar2ToString(s *C.wchar_t) (string, error) { i++ if !utf16.IsSurrogate(r) { if !utf8.ValidRune(r) { - err := fmt.Errorf("Invalid rune at position %v", i) + err := fmt.Errorf("invalid rune at position %v", i) return "", err } res += string(r) @@ -140,7 +140,7 @@ func wchar2ToString(s *C.wchar_t) (string, error) { r2 := rune(ch2) r12 := utf16.DecodeRune(r, r2) if r12 == '\uFFFD' { - err := fmt.Errorf("Invalid surrogate pair at position %v", i-1) + err := fmt.Errorf("invalid surrogate pair at position %v", i-1) return "", err } res += string(r12) @@ -161,7 +161,7 @@ func wchar4ToString(s *C.wchar_t) (string, error) { } r := rune(ch) if !utf8.ValidRune(r) { - err := fmt.Errorf("Invalid rune at position %v", i) + err := fmt.Errorf("invalid rune at position %v", i) return "", err } res += string(r) @@ -184,21 +184,21 @@ func wchar2NToString(s *C.wchar_t, size C.size_t) (string, error) { i++ if !utf16.IsSurrogate(r) { if !utf8.ValidRune(r) { - err := fmt.Errorf("Invalid rune at position %v", i) + err := fmt.Errorf("invalid rune at position %v", i) return "", err } res += string(r) } else { if i >= N { - err := fmt.Errorf("Invalid surrogate pair at position %v", i-1) + err := fmt.Errorf("invalid surrogate pair at position %v", i-1) return "", err } ch2 := C.gowchar_get(s, C.int(i)) r2 := rune(ch2) r12 := utf16.DecodeRune(r, r2) if r12 == '\uFFFD' { - err := fmt.Errorf("Invalid surrogate pair at position %v", i-1) + err := fmt.Errorf("invalid surrogate pair at position %v", i-1) return "", err } res += string(r12) @@ -217,7 +217,7 @@ func wchar4NToString(s *C.wchar_t, size C.size_t) (string, error) { ch := C.gowchar_get(s, C.int(i)) r := rune(ch) if !utf8.ValidRune(r) { - err := fmt.Errorf("Invalid rune at position %v", i) + err := fmt.Errorf("invalid rune at position %v", i) return "", err } res += string(r) From e9d30d891ff556378ae94e4ab108cda3125e1caf Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Jun 2022 14:27:50 +0200 Subject: [PATCH 04/17] req go1.17, migrate to go1.17+ build constraints --- c_deps.go | 2 +- go.mod | 2 +- hid_disabled.go | 2 +- hid_enabled.go | 2 +- hidapi/hidapi.go | 2 +- hidapi/hidapi/hidapi.go | 2 +- hidapi/libusb/libusb.go | 2 +- hidapi/linux/linux.go | 2 +- hidapi/mac/mac.go | 2 +- hidapi/windows/windows.go | 2 +- libusb/libusb.go | 2 +- libusb/libusb/libusb.go | 2 +- libusb/libusb/os/os.go | 2 +- wchar.go | 3 +-- wchar_test.go | 3 +-- 15 files changed, 15 insertions(+), 17 deletions(-) diff --git a/c_deps.go b/c_deps.go index 85a2329..8f556e2 100644 --- a/c_deps.go +++ b/c_deps.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This file is part of a workaround for `go mod vendor` which won't vendor // C files if there's no Go file in the same directory. diff --git a/go.mod b/go.mod index 6a2e97a..b80ef01 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/bearsh/hid -go 1.14 +go 1.17 diff --git a/hid_disabled.go b/hid_disabled.go index a533e0b..78eb56a 100644 --- a/hid_disabled.go +++ b/hid_disabled.go @@ -4,7 +4,7 @@ // This file is released under the 3-clause BSD license. Note however that Linux // support depends on libusb, released under GNU LGPL 2.1 or later. -// +build !linux,!darwin,!windows ios !cgo +//go:build (!linux && !darwin && !windows) || ios || !cgo package hid diff --git a/hid_enabled.go b/hid_enabled.go index 4125500..9959c6b 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -4,7 +4,7 @@ // This file is released under the 3-clause BSD license. Note however that Linux // support depends on libusb, released under LGNU GPL 2.1 or later. -// +build linux,cgo darwin,!ios,cgo windows,cgo +//go:build (linux && cgo) || (darwin && !ios && cgo) || (windows && cgo) package hid diff --git a/hidapi/hidapi.go b/hidapi/hidapi.go index 4da4aa1..38c411f 100644 --- a/hidapi/hidapi.go +++ b/hidapi/hidapi.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/hidapi/hidapi/hidapi.go b/hidapi/hidapi/hidapi.go index 4da4aa1..38c411f 100644 --- a/hidapi/hidapi/hidapi.go +++ b/hidapi/hidapi/hidapi.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/hidapi/libusb/libusb.go b/hidapi/libusb/libusb.go index 2cf46de..2f32277 100644 --- a/hidapi/libusb/libusb.go +++ b/hidapi/libusb/libusb.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/hidapi/linux/linux.go b/hidapi/linux/linux.go index 3ab09ec..a053e2a 100644 --- a/hidapi/linux/linux.go +++ b/hidapi/linux/linux.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/hidapi/mac/mac.go b/hidapi/mac/mac.go index 5e032a0..8a8677d 100644 --- a/hidapi/mac/mac.go +++ b/hidapi/mac/mac.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/hidapi/windows/windows.go b/hidapi/windows/windows.go index 2998bb6..655228a 100644 --- a/hidapi/windows/windows.go +++ b/hidapi/windows/windows.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/libusb/libusb.go b/libusb/libusb.go index 2cf46de..2f32277 100644 --- a/libusb/libusb.go +++ b/libusb/libusb.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/libusb/libusb/libusb.go b/libusb/libusb/libusb.go index 2cf46de..2f32277 100644 --- a/libusb/libusb/libusb.go +++ b/libusb/libusb/libusb.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/libusb/libusb/os/os.go b/libusb/libusb/os/os.go index 7f030df..abd2248 100644 --- a/libusb/libusb/os/os.go +++ b/libusb/libusb/os/os.go @@ -1,4 +1,4 @@ -// +build dummy +//go:build dummy // This Go file is part of a workaround for `go mod vendor`. // Please see the file `c_deps.go` at the root for more information. diff --git a/wchar.go b/wchar.go index 660119b..338adf3 100644 --- a/wchar.go +++ b/wchar.go @@ -6,8 +6,7 @@ // The vendored file is licensed under the 3-clause BSD license, according to: // https://github.com/orofarne/gowchar/blob/master/LICENSE -// +build !ios -// +build linux darwin windows +//go:build !ios && (linux || darwin || windows) package hid diff --git a/wchar_test.go b/wchar_test.go index b80ebb8..9624391 100644 --- a/wchar_test.go +++ b/wchar_test.go @@ -6,8 +6,7 @@ // The vendored file is licensed under the 3-clause BSD license, according to: // https://github.com/orofarne/gowchar/blob/master/LICENSE -// +build !ios -// +build linux darwin windows +//go:build !ios && (linux || darwin || windows) package hid From 35af594cb5a7c822f12a6affe65aafaec3165caf Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Jun 2022 14:30:55 +0200 Subject: [PATCH 05/17] appveyor: bump go version --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 2031a4e..6e9e4b7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,8 +22,8 @@ environment: install: - rmdir C:\go /s /q - - appveyor DownloadFile https://storage.googleapis.com/golang/go1.15.5.windows-%GOARCH%.zip - - 7z x go1.15.5.windows-%GOARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://storage.googleapis.com/golang/go1.17.11.windows-%GOARCH%.zip + - 7z x go1.17.11.windows-%GOARCH%.zip -y -oC:\ > NUL - go version - gcc --version From 79c697d2f8e175078e070815b83f2a31ed6b0266 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 13:14:33 +0200 Subject: [PATCH 06/17] bump hidapi to v0.13.1 --- hidapi/hidapi/hidapi.h | 163 ++++++-- hidapi/libusb/hid.c | 398 +++++++++++++------ hidapi/linux/hid.c | 877 +++++++++++++++++++++++------------------ hidapi/mac/hid.c | 348 +++++++++++++--- hidapi/windows/hid.c | 223 ++++++++--- 5 files changed, 1348 insertions(+), 661 deletions(-) diff --git a/hidapi/hidapi/hidapi.h b/hidapi/hidapi/hidapi.h index 959c912..22b3cd6 100644 --- a/hidapi/hidapi/hidapi.h +++ b/hidapi/hidapi/hidapi.h @@ -48,12 +48,12 @@ @ingroup API */ -#define HID_API_VERSION_MINOR 12 +#define HID_API_VERSION_MINOR 13 /** @brief Static/compile-time patch version of the library. @ingroup API */ -#define HID_API_VERSION_PATCH 0 +#define HID_API_VERSION_PATCH 1 /* Helper macros */ #define HID_API_AS_STR_IMPL(x) #x @@ -88,6 +88,14 @@ */ #define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) +/** @brief Maximum expected HID Report descriptor size in bytes. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API +*/ +#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 + #ifdef __cplusplus extern "C" { #endif @@ -100,6 +108,37 @@ extern "C" { struct hid_device_; typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + /** @brief HID underlying bus types. + + @ingroup API + */ + typedef enum { + /* Unknown bus type */ + HID_API_BUS_UNKNOWN = 0x00, + + /* USB bus + Specifications: + https://usb.org/hid */ + HID_API_BUS_USB = 0x01, + + /* Bluetooth or Bluetooth LE bus + Specifications: + https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ + https://www.bluetooth.com/specifications/specs/hid-service-1-0/ + https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ + HID_API_BUS_BLUETOOTH = 0x02, + + /* I2C bus + Specifications: + https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ + HID_API_BUS_I2C = 0x03, + + /* SPI bus + Specifications: + https://www.microsoft.com/download/details.aspx?id=103325 */ + HID_API_BUS_SPI = 0x04, + } hid_bus_type; + /** hidapi info structure */ struct hid_device_info { /** Platform-specific device path */ @@ -135,6 +174,11 @@ extern "C" { /** Pointer to the next device */ struct hid_device_info *next; + + /** Underlying bus type + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + */ + hid_bus_type bus_type; }; @@ -151,6 +195,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(NULL) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_init(void); @@ -162,7 +207,7 @@ extern "C" { @ingroup API - @returns + @returns This function returns 0 on success and -1 on error. */ int HID_API_EXPORT HID_API_CALL hid_exit(void); @@ -182,21 +227,25 @@ extern "C" { @param product_id The Product ID (PID) of the types of device to open. - @returns - This function returns a pointer to a linked list of type - struct #hid_device_info, containing information about the HID devices - attached to the system, or NULL in the case of failure. Free - this linked list by calling hid_free_enumeration(). + @returns + This function returns a pointer to a linked list of type + struct #hid_device_info, containing information about the HID devices + attached to the system, + or NULL in the case of failure or if no HID devices present in the system. + Call hid_error(NULL) to get the failure reason. + + @note The returned value by this function must to be freed by calling hid_free_enumeration(), + when not needed anymore. */ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); /** @brief Free an enumeration Linked List - This function frees a linked list created by hid_enumerate(). + This function frees a linked list created by hid_enumerate(). @ingroup API - @param devs Pointer to a list of struct_device returned from - hid_enumerate(). + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). */ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); @@ -206,17 +255,19 @@ extern "C" { If @p serial_number is NULL, the first device with the specified VID and PID is opened. - This function sets the return value of hid_error(). - @ingroup API @param vendor_id The Vendor ID (VID) of the device to open. @param product_id The Product ID (PID) of the device to open. @param serial_number The Serial Number of the device to open - (Optionally NULL). + (Optionally NULL). @returns This function returns a pointer to a #hid_device object on success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. */ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); @@ -226,14 +277,16 @@ extern "C" { platform-specific path name can be used (eg: /dev/hidraw0 on Linux). - This function sets the return value of hid_error(). - @ingroup API - @param path The path name of the device to open + @param path The path name of the device to open @returns This function returns a pointer to a #hid_device object on success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. */ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); @@ -253,8 +306,6 @@ extern "C" { one exists. If it does not, it will send the data through the Control Endpoint (Endpoint 0). - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as @@ -264,6 +315,7 @@ extern "C" { @returns This function returns the actual number of bytes written and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); @@ -273,8 +325,6 @@ extern "C" { to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @@ -285,7 +335,9 @@ extern "C" { @returns This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read within + -1 on error. + Call hid_error(dev) to get the failure reason. + If no packet was available to be read within the timeout period, this function returns 0. */ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); @@ -293,11 +345,9 @@ extern "C" { /** @brief Read an Input report from a HID device. Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will + to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into. @@ -307,7 +357,9 @@ extern "C" { @returns This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read and + -1 on error. + Call hid_error(dev) to get the failure reason. + If no packet was available to be read and the handle is in non-blocking mode, this function returns 0. */ int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); @@ -329,6 +381,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); @@ -347,8 +400,6 @@ extern "C" { report data (16 bytes). In this example, the length passed in would be 17. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data The data to send, including the report number as @@ -359,6 +410,7 @@ extern "C" { @returns This function returns the actual number of bytes written and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); @@ -370,8 +422,6 @@ extern "C" { still contain the Report ID, and the report data will start in data[1]. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into, including @@ -386,6 +436,7 @@ extern "C" { This function returns the number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); @@ -400,7 +451,7 @@ extern "C" { start in data[1]. @ingroup API - @param device A device handle returned from hid_open(). + @param dev A device handle returned from hid_open(). @param data A buffer to put the read data into, including the Report ID. Set the first byte of @p data[] to the Report ID of the report to be read, or set it to zero @@ -413,13 +464,12 @@ extern "C" { This function returns the number of bytes read plus one for the report ID (which is still in the first byte), or -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); /** @brief Close a HID device. - This function sets the return value of hid_error(). - @ingroup API @param dev A device handle returned from hid_open(). */ @@ -434,6 +484,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); @@ -446,6 +497,7 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); @@ -458,9 +510,27 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + /** @brief Get The struct #hid_device_info from a HID device. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + + @returns + This function returns a pointer to the struct #hid_device_info + for this hid_device, or NULL in the case of failure. + Call hid_error(dev) to get the failure reason. + This struct is valid until the device is closed with hid_close(). + + @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); + /** @brief Get a string from a HID device, based on its string index. @ingroup API @@ -471,32 +541,41 @@ extern "C" { @returns This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. */ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); /** @brief Get a string describing the last error which occurred. - Whether a function sets the last error is noted in its - documentation. These functions will reset the last error - to NULL before their execution. + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL. + If there was no error in the last function call - + the returned string clearly indicates that. - Strings returned from hid_error() must not be freed by the user! + Any HIDAPI function that can explicitly indicate an execution failure + (e.g. by an error code, or by returning NULL) - may set the error string, + to be returned by this function. - This function is thread-safe, and error messages are thread-local. + Strings returned from hid_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + Global error string may remain allocated at most until hid_exit() is called. @ingroup API @param dev A device handle returned from hid_open(), or NULL to get the last non-device-specific error - (e.g. for errors in hid_open() itself). + (e.g. for errors in hid_open() or hid_enumerate()). @returns - This function returns a string containing the last error - which occurred or NULL if none has occurred. + A string describing the last error (if any). */ HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); /** @brief Get a runtime version of the library. + This function is thread-safe. + @ingroup API @returns @@ -507,6 +586,8 @@ extern "C" { /** @brief Get a runtime version string of the library. + This function is thread-safe. + @ingroup API @returns diff --git a/hidapi/libusb/hid.c b/hidapi/libusb/hid.c index 3103f03..b46b1c7 100644 --- a/hidapi/libusb/hid.c +++ b/hidapi/libusb/hid.c @@ -146,18 +146,23 @@ struct hid_device_ { /* Handle to the actual device. */ libusb_device_handle *device_handle; + /* USB Configuration Number of the device */ + int config_number; + /* The interface number of the HID */ + int interface; + + uint16_t report_descriptor_size; + /* Endpoint information */ int input_endpoint; int output_endpoint; int input_ep_max_packet_size; - /* The interface number of the HID */ - int interface; - /* Indexes of Strings */ int manufacturer_index; int product_index; int serial_index; + struct hid_device_info* device_info; /* Whether blocking reads are used */ int blocking; /* boolean */ @@ -210,6 +215,8 @@ static void free_hid_device(hid_device *dev) pthread_cond_destroy(&dev->condition); pthread_mutex_destroy(&dev->mutex); + hid_free_enumeration(dev->device_info); + /* Free the device itself */ free(dev); } @@ -222,7 +229,6 @@ static void register_error(hid_device *dev, const char *op) } #endif -#ifdef INVASIVE_GET_USAGE /* Get bytes from a HID Report Descriptor. Only call with a num_bytes of 0, 1, 2, or 4. */ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) @@ -322,7 +328,6 @@ static int get_usage(uint8_t *report_descriptor, size_t size, return -1; /* failure */ } -#endif /* INVASIVE_GET_USAGE */ #if defined(__FreeBSD__) && __FreeBSD__ < 10 /* The libusb version included in FreeBSD < 10 doesn't have this function. In @@ -484,9 +489,14 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) return str; } -static char *make_path(libusb_device *dev, int interface_number, int config_number) +/** + Max length of the result: "000-000.000.000.000.000.000.000:000.000" (39 chars). + 64 is used for simplicity/alignment. +*/ +static void get_path(char (*result)[64], libusb_device *dev, int config_number, int interface_number) { - char str[64]; /* max length "000-000.000.000.000.000.000.000:000.000" */ + char *str = *result; + /* Note that USB3 port count limit is 7; use 8 here for alignment */ uint8_t port_numbers[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int num_ports = libusb_get_port_numbers(dev, port_numbers, 8); @@ -499,7 +509,7 @@ static char *make_path(libusb_device *dev, int interface_number, int config_numb n += snprintf(&str[n], sizeof(":000.000"), ":%u.%u", (uint8_t)config_number, (uint8_t)interface_number); str[n] = '\0'; } else { - /* USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ + /* Likely impossible, but check: USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ if (num_ports == LIBUSB_ERROR_OVERFLOW) { LOG("make_path() failed. buffer overflow error\n"); } else { @@ -507,6 +517,12 @@ static char *make_path(libusb_device *dev, int interface_number, int config_numb } str[0] = '\0'; } +} + +static char *make_path(libusb_device *dev, int config_number, int interface_number) +{ + char str[64]; + get_path(&str, dev, config_number, interface_number); return strdup(str); } @@ -548,11 +564,186 @@ int HID_API_EXPORT hid_exit(void) return 0; } +static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size, unsigned char *buf, size_t buf_size) +{ + unsigned char tmp[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + + if (expected_report_descriptor_size > HID_API_MAX_REPORT_DESCRIPTOR_SIZE) + expected_report_descriptor_size = HID_API_MAX_REPORT_DESCRIPTOR_SIZE; + + /* Get the HID Report Descriptor. + See USB HID Specificatin, sectin 7.1.1 + */ + int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); + if (res < 0) { + LOG("libusb_control_transfer() for getting the HID Report descriptor failed with %d: %s\n", res, libusb_error_name(res)); + return -1; + } + + if (res > (int)buf_size) + res = (int)buf_size; + + memcpy(buf, tmp, (size_t)res); + return res; +} + +/** + * Requires an opened device with *claimed interface*. + */ +static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size) +{ + unsigned char hid_report_descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; + unsigned short page = 0, usage = 0; + + int res = hid_get_report_descriptor_libusb(handle, interface_num, expected_report_descriptor_size, hid_report_descriptor, sizeof(hid_report_descriptor)); + if (res >= 0) { + /* Parse the usage and usage page + out of the report descriptor. */ + get_usage(hid_report_descriptor, res, &page, &usage); + } + + cur_dev->usage_page = page; + cur_dev->usage = usage; +} + +#ifdef INVASIVE_GET_USAGE +static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num, uint16_t report_descriptor_size) +{ + int res = 0; + +#ifdef DETACH_KERNEL_DRIVER + int detached = 0; + /* Usage Page and Usage */ + res = libusb_kernel_driver_active(handle, interface_num); + if (res == 1) { + res = libusb_detach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); + else + detached = 1; + } +#endif + + res = libusb_claim_interface(handle, interface_num); + if (res >= 0) { + fill_device_info_usage(cur_dev, handle, interface_num, report_descriptor_size); + + /* Release the interface */ + res = libusb_release_interface(handle, interface_num); + if (res < 0) + LOG("Can't release the interface.\n"); + } + else + LOG("Can't claim interface: (%d) %s\n", res, libusb_error_name(res)); + +#ifdef DETACH_KERNEL_DRIVER + /* Re-attach kernel driver if necessary. */ + if (detached) { + res = libusb_attach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't re-attach kernel driver.\n"); + } +#endif +} +#endif /* INVASIVE_GET_USAGE */ + +/** + * Create and fill up most of hid_device_info fields. + * usage_page/usage is not filled up. + */ +static struct hid_device_info * create_device_info_for_device(libusb_device *device, libusb_device_handle *handle, struct libusb_device_descriptor *desc, int config_number, int interface_num) +{ + struct hid_device_info *cur_dev = calloc(1, sizeof(struct hid_device_info)); + if (cur_dev == NULL) { + return NULL; + } + + /* VID/PID */ + cur_dev->vendor_id = desc->idVendor; + cur_dev->product_id = desc->idProduct; + + cur_dev->release_number = desc->bcdDevice; + + cur_dev->interface_number = interface_num; + + cur_dev->bus_type = HID_API_BUS_USB; + + cur_dev->path = make_path(device, config_number, interface_num); + + if (!handle) { + return cur_dev; + } + + if (desc->iSerialNumber > 0) + cur_dev->serial_number = get_usb_string(handle, desc->iSerialNumber); + + /* Manufacturer and Product strings */ + if (desc->iManufacturer > 0) + cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer); + if (desc->iProduct > 0) + cur_dev->product_string = get_usb_string(handle, desc->iProduct); + + return cur_dev; +} + +static uint16_t get_report_descriptor_size_from_interface_descriptors(const struct libusb_interface_descriptor *intf_desc) +{ + int i = 0; + int found_hid_report_descriptor = 0; + uint16_t result = HID_API_MAX_REPORT_DESCRIPTOR_SIZE; + const unsigned char *extra = intf_desc->extra; + int extra_length = intf_desc->extra_length; + + /* + "extra" contains a HID descriptor + See section 6.2.1 of HID 1.1 specification. + */ + + while (extra_length >= 2) { /* Descriptor header: bLength/bDescriptorType */ + if (extra[1] == LIBUSB_DT_HID) { /* bDescriptorType */ + if (extra_length < 6) { + LOG("Broken HID descriptor: not enough data\n"); + break; + } + unsigned char bNumDescriptors = extra[5]; + if (extra_length < (6 + 3 * bNumDescriptors)) { + LOG("Broken HID descriptor: not enough data for Report metadata\n"); + break; + } + for (i = 0; i < bNumDescriptors; i++) { + if (extra[6 + 3 * i] == LIBUSB_DT_REPORT) { + result = (uint16_t)extra[6 + 3 * i + 2] << 8 | extra[6 + 3 * i + 1]; + found_hid_report_descriptor = 1; + break; + } + } + + if (!found_hid_report_descriptor) { + /* We expect to find exactly 1 HID descriptor (LIBUSB_DT_HID) + which should contain exactly one HID Report Descriptor metadata (LIBUSB_DT_REPORT). */ + LOG("Broken HID descriptor: missing Report descriptor\n"); + } + break; + } + + if (extra[0] == 0) { /* bLength */ + LOG("Broken HID Interface descriptors: zero-sized descriptor\n"); + break; + } + + /* Iterate over to the next Descriptor */ + extra_length -= extra[0]; + extra += extra[0]; + } + + return result; +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { libusb_device **devs; libusb_device *dev; - libusb_device_handle *handle; + libusb_device_handle *handle = NULL; ssize_t num_devs; int i = 0; @@ -589,27 +780,12 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc; intf_desc = &intf->altsetting[k]; if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - int interface_num = intf_desc->bInterfaceNumber; struct hid_device_info *tmp; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue); - res = libusb_open(dev, &handle); - if (res >= 0) { #ifdef __ANDROID__ + if (handle) { /* There is (a potential) libusb Android backend, in which device descriptor is not accurate up until the device is opened. https://github.com/libusb/libusb/pull/874#discussion_r632801373 @@ -618,95 +794,47 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, having it here won't do any harm, since reading the device descriptor is as cheap as copy 18 bytes of data. */ libusb_get_device_descriptor(dev, &desc); + } #endif - /* Serial Number */ - if (desc.iSerialNumber > 0) - cur_dev->serial_number = - get_usb_string(handle, desc.iSerialNumber); - - /* Manufacturer and Product strings */ - if (desc.iManufacturer > 0) - cur_dev->manufacturer_string = - get_usb_string(handle, desc.iManufacturer); - if (desc.iProduct > 0) - cur_dev->product_string = - get_usb_string(handle, desc.iProduct); - + tmp = create_device_info_for_device(dev, handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); + if (tmp) { #ifdef INVASIVE_GET_USAGE -{ - /* - This section is removed because it is too - invasive on the system. Getting a Usage Page - and Usage requires parsing the HID Report - descriptor. Getting a HID Report descriptor - involves claiming the interface. Claiming the - interface involves detaching the kernel driver. - Detaching the kernel driver is hard on the system - because it will unclaim interfaces (if another - app has them claimed) and the re-attachment of - the driver will sometimes change /dev entry names. - It is for these reasons that this section is - #if 0. For composite devices, use the interface - field in the hid_device_info struct to distinguish - between interfaces. */ - unsigned char data[256]; -#ifdef DETACH_KERNEL_DRIVER - int detached = 0; - /* Usage Page and Usage */ - res = libusb_kernel_driver_active(handle, interface_num); - if (res == 1) { - res = libusb_detach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); - else - detached = 1; - } -#endif - res = libusb_claim_interface(handle, interface_num); - if (res >= 0) { - /* Get the HID Report Descriptor. */ - res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); - if (res >= 0) { - unsigned short page=0, usage=0; - /* Parse the usage and usage page - out of the report descriptor. */ - get_usage(data, res, &page, &usage); - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - else - LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res); - - /* Release the interface */ - res = libusb_release_interface(handle, interface_num); - if (res < 0) - LOG("Can't release the interface.\n"); - } - else - LOG("Can't claim interface %d\n", res); -#ifdef DETACH_KERNEL_DRIVER - /* Re-attach kernel driver if necessary. */ - if (detached) { - res = libusb_attach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't re-attach kernel driver.\n"); + /* TODO: have a runtime check for this section. */ + + /* + This section is removed because it is too + invasive on the system. Getting a Usage Page + and Usage requires parsing the HID Report + descriptor. Getting a HID Report descriptor + involves claiming the interface. Claiming the + interface involves detaching the kernel driver. + Detaching the kernel driver is hard on the system + because it will unclaim interfaces (if another + app has them claimed) and the re-attachment of + the driver will sometimes change /dev entry names. + It is for these reasons that this section is + optional. For composite devices, use the interface + field in the hid_device_info struct to distinguish + between interfaces. */ + if (handle) { + uint16_t report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); + + invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber, report_descriptor_size); } -#endif -} #endif /* INVASIVE_GET_USAGE */ - libusb_close(handle); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; } - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Release Number */ - cur_dev->release_number = desc.bcdDevice; - /* Interface Number */ - cur_dev->interface_number = interface_num; + if (res >= 0) + libusb_close(handle); } } /* altsettings */ } /* interfaces */ @@ -830,7 +958,7 @@ static void read_callback(struct libusb_transfer *transfer) /* Re-submit the transfer object. */ res = libusb_submit_transfer(transfer); if (res != 0) { - LOG("Unable to submit URB. libusb error code: %d\n", res); + LOG("Unable to submit URB: (%d) %s\n", res, libusb_error_name(res)); dev->shutdown_thread = 1; dev->transfer_loop_finished = 1; } @@ -839,6 +967,7 @@ static void read_callback(struct libusb_transfer *transfer) static void *read_thread(void *param) { + int res; hid_device *dev = param; uint8_t *buf; const size_t length = dev->input_ep_max_packet_size; @@ -857,18 +986,22 @@ static void *read_thread(void *param) /* Make the first submission. Further submissions are made from inside read_callback() */ - libusb_submit_transfer(dev->transfer); + res = libusb_submit_transfer(dev->transfer); + if(res < 0) { + LOG("libusb_submit_transfer failed: %d %s. Stopping read_thread from running\n", res, libusb_error_name(res)); + dev->shutdown_thread = 1; + dev->transfer_loop_finished = 1; + } /* Notify the main thread that the read thread is up and running. */ pthread_barrier_wait(&dev->barrier); /* Handle all the events. */ while (!dev->shutdown_thread) { - int res; res = libusb_handle_events(usb_context); if (res < 0) { /* There was an error. */ - LOG("read_thread(): libusb reports error # %d\n", res); + LOG("read_thread(): (%d) %s\n", res, libusb_error_name(res)); /* Break out of this loop only on fatal error.*/ if (res != LIBUSB_ERROR_BUSY && @@ -909,7 +1042,7 @@ static void *read_thread(void *param) } -static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc) +static int hidapi_initialize_device(hid_device *dev, int config_number, const struct libusb_interface_descriptor *intf_desc) { int i =0; int res = 0; @@ -923,7 +1056,7 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); if (res < 0) { - LOG("Unable to detach Kernel Driver\n"); + LOG("Unable to detach Kernel Driver: (%d) %s\n", res, libusb_error_name(res)); return 0; } else { @@ -934,7 +1067,15 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa #endif res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); if (res < 0) { - LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + LOG("can't claim interface %d: (%d) %s\n", intf_desc->bInterfaceNumber, res, libusb_error_name(res)); + +#ifdef DETACH_KERNEL_DRIVER + if (dev->is_driver_detached) { + res = libusb_attach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) + LOG("Failed to reattach the driver to kernel: (%d) %s\n", res, libusb_error_name(res)); + } +#endif return 0; } @@ -943,9 +1084,12 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa dev->product_index = desc.iProduct; dev->serial_index = desc.iSerialNumber; - /* Store off the interface number */ + /* Store off the USB information */ + dev->config_number = config_number; dev->interface = intf_desc->bInterfaceNumber; + dev->report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); + dev->input_endpoint = 0; dev->input_ep_max_packet_size = 0; dev->output_endpoint = 0; @@ -1017,7 +1161,8 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) for (k = 0; k < intf->num_altsetting && !good_open; k++) { const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); + char dev_path[64]; + get_path(&dev_path, usb_dev, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); if (!strcmp(dev_path, path)) { /* Matched Paths. Open this device */ @@ -1025,14 +1170,12 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) res = libusb_open(usb_dev, &dev->device_handle); if (res < 0) { LOG("can't open device\n"); - free(dev_path); break; } - good_open = hidapi_initialize_device(dev, intf_desc); + good_open = hidapi_initialize_device(dev, conf_desc->bConfigurationValue, intf_desc); if (!good_open) libusb_close(dev->device_handle); } - free(dev_path); } } } @@ -1107,7 +1250,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys goto err; } - if (!hidapi_initialize_device(dev, selected_intf_desc)) + if (!hidapi_initialize_device(dev, conf_desc->bConfigurationValue, selected_intf_desc)) goto err; return dev; @@ -1455,6 +1598,23 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); } +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + struct libusb_device_descriptor desc; + libusb_device *usb_device = libusb_get_device(dev->device_handle); + libusb_get_device_descriptor(usb_device, &desc); + + dev->device_info = create_device_info_for_device(usb_device, dev->device_handle, &desc, dev->config_number, dev->interface); + // device error already set by create_device_info_for_device, if any + + if (dev->device_info) { + fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface, dev->report_descriptor_size); + } + } + + return dev->device_info; +} + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { wchar_t *str; diff --git a/hidapi/linux/hid.c b/hidapi/linux/hid.c index 1d79117..1b1d2c0 100644 --- a/hidapi/linux/hid.c +++ b/hidapi/linux/hid.c @@ -68,27 +68,11 @@ #define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) #endif -/* USB HID device property names */ -const char *device_string_names[] = { - "manufacturer", - "product", - "serial", -}; - -/* Symbolic names for the properties above */ -enum device_string_id { - DEVICE_STRING_MANUFACTURER, - DEVICE_STRING_PRODUCT, - DEVICE_STRING_SERIAL, - - DEVICE_STRING_COUNT, -}; - struct hid_device_ { int device_handle; int blocking; - int uses_numbered_reports; wchar_t *last_error_str; + struct hid_device_info* device_info; }; static struct hid_api_version api_version = { @@ -97,17 +81,20 @@ static struct hid_api_version api_version = { .patch = HID_API_VERSION_PATCH }; -/* Global error message that is not specific to a device, e.g. for - hid_open(). It is thread-local like errno. */ -__thread wchar_t *last_global_error_str = NULL; +static wchar_t *last_global_error_str = NULL; + static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + dev->device_handle = -1; dev->blocking = 1; - dev->uses_numbered_reports = 0; dev->last_error_str = NULL; + dev->device_info = NULL; return dev; } @@ -124,6 +111,10 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) return wcsdup(L""); } ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } mbstowcs(ret, utf8, wlen+1); ret[wlen] = 0x0000; } @@ -132,6 +123,25 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) } +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Semilar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[256]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + /* Set the last global error to be reported by hid_error(NULL). * The given error message will be copied (and decoded according to the * currently locale, so do not pass in string constants). @@ -139,51 +149,35 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) * Use register_global_error(NULL) to indicate "no error". */ static void register_global_error(const char *msg) { - if (last_global_error_str) - free(last_global_error_str); - - last_global_error_str = utf8_to_wchar_t(msg); + register_error_str(&last_global_error_str, msg); } -/* See register_global_error, but you can pass a format string into this function. */ +/* Similar to register_global_error, but allows passing a format string into this function. */ static void register_global_error_format(const char *format, ...) { va_list args; va_start(args, format); - - char msg[100]; - vsnprintf(msg, sizeof(msg), format, args); - + register_error_str_vformat(&last_global_error_str, format, args); va_end(args); - - register_global_error(msg); } -/* Set the last error for a device to be reported by hid_error(device). +/* Set the last error for a device to be reported by hid_error(dev). * The given error message will be copied (and decoded according to the * currently locale, so do not pass in string constants). - * The last stored global error message is freed. - * Use register_device_error(device, NULL) to indicate "no error". */ + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ static void register_device_error(hid_device *dev, const char *msg) { - if (dev->last_error_str) - free(dev->last_error_str); - - dev->last_error_str = utf8_to_wchar_t(msg); + register_error_str(&dev->last_error_str, msg); } -/* See register_device_error, but you can pass a format string into this function. */ +/* Similar to register_device_error, but you can pass a format string into this function. */ static void register_device_error_format(hid_device *dev, const char *format, ...) { va_list args; va_start(args, format); - - char msg[100]; - vsnprintf(msg, sizeof(msg), format, args); - + register_error_str_vformat(&dev->last_error_str, format, args); va_end(args); - - register_device_error(dev, msg); } /* Get an attribute value from a udev_device and return it as a whar_t @@ -250,34 +244,6 @@ static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 si return 0; } -/* uses_numbered_reports() returns 1 if report_descriptor describes a device - which contains numbered reports. */ -static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { - unsigned int i = 0; - int data_len, key_size; - - while (i < size) { - int key = report_descriptor[i]; - - /* Check for the Report ID key */ - if (key == 0x85/*Report ID*/) { - /* This device has a Report ID, which means it uses - numbered reports. */ - return 1; - } - - /* Determine data_len and key_size */ - if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) - return 0; /* malformed report */ - - /* Skip over this key and its associated data */ - i += data_len + key_size; - } - - /* Didn't find a Report ID key. Device doesn't use numbered reports. */ - return 0; -} - /* * Get bytes from a HID Report Descriptor. * Only call with a num_bytes of 0, 1, 2, or 4. @@ -392,7 +358,7 @@ static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_ int rpt_handle; ssize_t res; - rpt_handle = open(rpt_path, O_RDONLY); + rpt_handle = open(rpt_path, O_RDONLY | O_CLOEXEC); if (rpt_handle < 0) { register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); return -1; @@ -415,6 +381,7 @@ static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_ return (int) res; } +/* return size of the descriptor, or -1 on failure */ static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) { int res = -1; @@ -429,16 +396,106 @@ static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct h return res; } +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, uevent_len); + tmp[uevent_len] = '\0'; + + char *saveptr = NULL; + char *line; + char *key; + char *value; + + line = strtok_r(tmp, "\n", &saveptr); + while (line != NULL) { + /* line: "KEY=value" */ + key = line; + value = strchr(line, '='); + if (!value) { + goto next_line; + } + *value = '\0'; + value++; + + if (strcmp(key, "HID_ID") == 0) { + /** + * type vendor product + * HID_ID=0003:000005AC:00008242 + **/ + int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); + if (ret == 3) { + return 1; + } + } + +next_line: + line = strtok_r(NULL, "\n", &saveptr); + } + + register_global_error("Couldn't find/parse HID_ID"); + return 0; +} + +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int handle; + ssize_t res; + + handle = open(uevent_path, O_RDONLY | O_CLOEXEC); + if (handle < 0) { + register_global_error_format("open failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + char buf[1024]; + res = read(handle, buf, sizeof(buf)); + close(handle); + + if (res < 0) { + register_global_error_format("read failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + buf[res] = '\0'; + return parse_hid_vid_pid_from_uevent(buf, bus_type, vendor_id, product_id); +} + +/* return non-zero if successfully read/parsed */ +static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int res = 0; + /* Construct /device/uevent */ + size_t uevent_path_len = strlen(sysfs_path) + 14 + 1; + char* uevent_path = (char*) calloc(1, uevent_path_len); + snprintf(uevent_path, uevent_path_len, "%s/device/uevent", sysfs_path); + + res = parse_hid_vid_pid_from_uevent_path(uevent_path, bus_type, vendor_id, product_id); + free(uevent_path); + + return res; +} + /* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. */ -static int -parse_uevent_info(const char *uevent, unsigned *bus_type, +static int parse_uevent_info(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id, char **serial_number_utf8, char **product_name_utf8) { - char *tmp = strdup(uevent); + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, uevent_len); + tmp[uevent_len] = '\0'; + char *saveptr = NULL; char *line; char *key; @@ -482,109 +539,203 @@ parse_uevent_info(const char *uevent, unsigned *bus_type, line = strtok_r(NULL, "\n", &saveptr); } - free(tmp); return (found_id && found_name && found_serial); } -static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen) +static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) { - struct udev *udev; - struct udev_device *udev_dev, *parent, *hid_dev; - struct stat s; - int ret = -1; + struct hid_device_info *root = NULL; + struct hid_device_info *cur_dev = NULL; + + const char *sysfs_path; + const char *dev_path; + const char *str; + struct udev_device *hid_dev; /* The device's HID udev node. */ + struct udev_device *usb_dev; /* The device's USB udev node. */ + struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ + unsigned short dev_vid; + unsigned short dev_pid; char *serial_number_utf8 = NULL; char *product_name_utf8 = NULL; + unsigned bus_type; + int result; + struct hidraw_report_descriptor report_desc; - /* Create the udev object */ - udev = udev_new(); - if (!udev) { - register_global_error("Couldn't create udev context"); - return -1; + sysfs_path = udev_device_get_syspath(raw_dev); + dev_path = udev_device_get_devnode(raw_dev); + + hid_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "hid", + NULL); + + if (!hid_dev) { + /* Unable to find parent hid device. */ + goto end; } - /* Get the dev_t (major/minor numbers) from the file handle. */ - ret = fstat(dev->device_handle, &s); - if (-1 == ret) - return ret; - /* Open a udev device from the dev_t. 'c' means character device. */ - udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); - if (udev_dev) { - hid_dev = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "hid", - NULL); - if (hid_dev) { - unsigned short dev_vid; - unsigned short dev_pid; - unsigned bus_type; - size_t retm; - - ret = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - /* Standard USB device */ - if (bus_type == BUS_USB) { - /* This is a USB device. Find its parent USB Device node. */ - parent = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "usb", - "usb_device"); - if (parent) { - const char *str; - const char *key_str = NULL; - - if (key >= 0 && key < DEVICE_STRING_COUNT) { - key_str = device_string_names[key]; - } else { - ret = -1; - goto end; - } - - str = udev_device_get_sysattr_value(parent, key_str); - if (str) { - /* Convert the string from UTF-8 to wchar_t */ - retm = mbstowcs(string, str, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - } - - /* USB information parsed */ - goto end; - } - else { - /* Correctly handled below */ - } + result = parse_uevent_info( + udev_device_get_sysattr_value(hid_dev, "uevent"), + &bus_type, + &dev_vid, + &dev_pid, + &serial_number_utf8, + &product_name_utf8); + + if (!result) { + /* parse_uevent_info() failed for at least one field. */ + goto end; + } + + /* Filter out unhandled devices right away */ + switch (bus_type) { + case BUS_BLUETOOTH: + case BUS_I2C: + case BUS_USB: + case BUS_SPI: + break; + + default: + goto end; + } + + /* Create the record. */ + root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (!root) + goto end; + + cur_dev = root; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = dev_path? strdup(dev_path): NULL; + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Serial Number */ + cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + + /* Release Number */ + cur_dev->release_number = 0x0; + + /* Interface Number */ + cur_dev->interface_number = -1; + + switch (bus_type) { + case BUS_USB: + /* The device pointed to by raw_dev contains information about + the hidraw device. In order to get information about the + USB device, get the parent device with the + subsystem/devtype pair of "usb"/"usb_device". This will + be several levels up the tree, but the function will find + it. */ + usb_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_device"); + + /* uhid USB devices + * Since this is a virtual hid interface, no USB information will + * be available. */ + if (!usb_dev) { + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + break; } - /* USB information not available (uhid) or another type of HID bus */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - switch (key) { - case DEVICE_STRING_MANUFACTURER: - wcsncpy(string, L"", maxlen); - ret = 0; - break; - case DEVICE_STRING_PRODUCT: - retm = mbstowcs(string, product_name_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_SERIAL: - retm = mbstowcs(string, serial_number_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_COUNT: - default: - ret = -1; - break; - } + cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer"); + cur_dev->product_string = copy_udev_string(usb_dev, "product"); + + cur_dev->bus_type = HID_API_BUS_USB; + + str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); + cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; + + /* Get a handle to the interface's udev node. */ + intf_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_interface"); + if (intf_dev) { + str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); + cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; } + + break; + + case BUS_BLUETOOTH: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + + break; + case BUS_I2C: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_I2C; + + break; + + case BUS_SPI: + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + cur_dev->bus_type = HID_API_BUS_SPI; + + break; + + default: + /* Unknown device type - this should never happen, as we + * check for USB and Bluetooth devices above */ + break; + } + + /* Usage Page and Usage */ + result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); + if (result >= 0) { + unsigned short page = 0, usage = 0; + unsigned int pos = 0; + /* + * Parse the first usage and usage page + * out of the report descriptor. + */ + if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + + /* + * Parse any additional usage and usage pages + * out of the report descriptor. + */ + while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { + /* Create new record for additional usage pairs */ + struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info *prev_dev = cur_dev; + + if (!tmp) + continue; + cur_dev->next = tmp; + cur_dev = tmp; + + /* Update fields */ + cur_dev->path = dev_path? strdup(dev_path): NULL; + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; + cur_dev->release_number = prev_dev->release_number; + cur_dev->interface_number = prev_dev->interface_number; + cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; + cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + cur_dev->usage_page = page; + cur_dev->usage = usage; + cur_dev->bus_type = prev_dev->bus_type; } } @@ -592,12 +743,47 @@ static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t free(serial_number_utf8); free(product_name_utf8); + return root; +} + +static struct hid_device_info * create_device_info_for_hid_device(hid_device *dev) { + struct udev *udev; + struct udev_device *udev_dev; + struct stat s; + int ret = -1; + struct hid_device_info *root = NULL; + + register_device_error(dev, NULL); + + /* Get the dev_t (major/minor numbers) from the file handle. */ + ret = fstat(dev->device_handle, &s); + if (-1 == ret) { + register_device_error(dev, "Failed to stat device handle"); + return NULL; + } + + /* Create the udev object */ + udev = udev_new(); + if (!udev) { + register_device_error(dev, "Couldn't create udev context"); + return NULL; + } + + /* Open a udev device from the dev_t. 'c' means character device. */ + udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); + if (udev_dev) { + root = create_device_info_for_device(udev_dev); + } + + if (!root) { + /* TODO: have a better error reporting via create_device_info_for_device */ + register_device_error(dev, "Couldn't create hid_device_info"); + } + udev_device_unref(udev_dev); - /* parent and hid_dev don't need to be (and can't be) unref'd. - I'm not sure why, but they'll throw double-free() errors. */ udev_unref(udev); - return ret; + return root; } HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() @@ -614,6 +800,9 @@ int HID_API_EXPORT hid_init(void) { const char *locale; + /* indicate no error */ + register_global_error(NULL); + /* Set the locale if it's not set. */ locale = setlocale(LC_CTYPE, NULL); if (!locale) @@ -630,7 +819,6 @@ int HID_API_EXPORT hid_exit(void) return 0; } - struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct udev *udev; @@ -639,9 +827,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; - struct hid_device_info *prev_dev = NULL; /* previous device */ hid_init(); + /* register_global_error: global error is reset by hid_init */ /* Create the udev object */ udev = udev_new(); @@ -659,202 +847,62 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, create a udev_device record for it */ udev_list_entry_foreach(dev_list_entry, devices) { const char *sysfs_path; - const char *dev_path; - const char *str; + unsigned short dev_vid = 0; + unsigned short dev_pid = 0; + unsigned bus_type = 0; struct udev_device *raw_dev; /* The device's hidraw udev node. */ - struct udev_device *hid_dev; /* The device's HID udev node. */ - struct udev_device *usb_dev; /* The device's USB udev node. */ - struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ - unsigned short dev_vid; - unsigned short dev_pid; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; - unsigned bus_type; - int result; - struct hidraw_report_descriptor report_desc; + struct hid_device_info * tmp; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */ sysfs_path = udev_list_entry_get_name(dev_list_entry); - raw_dev = udev_device_new_from_syspath(udev, sysfs_path); - dev_path = udev_device_get_devnode(raw_dev); - - hid_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "hid", - NULL); - - if (!hid_dev) { - /* Unable to find parent hid device. */ - goto next; - } - - result = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - if (!result) { - /* parse_uevent_info() failed for at least one field. */ - goto next; - } + if (!sysfs_path) + continue; - /* Filter out unhandled devices right away */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - break; + if (vendor_id != 0 || product_id != 0) { + if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) + continue; - default: - goto next; + if (vendor_id != 0 && vendor_id != dev_vid) + continue; + if (product_id != 0 && product_id != dev_pid) + continue; } - /* Check the VID/PID against the arguments */ - if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { - struct hid_device_info *tmp; + raw_dev = udev_device_new_from_syspath(udev, sysfs_path); + if (!raw_dev) + continue; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + tmp = create_device_info_for_device(raw_dev); + if (tmp) { if (cur_dev) { cur_dev->next = tmp; } else { root = tmp; } - prev_dev = cur_dev; cur_dev = tmp; - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = dev_path? strdup(dev_path): NULL; - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Serial Number */ - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); - - /* Release Number */ - cur_dev->release_number = 0x0; - - /* Interface Number */ - cur_dev->interface_number = -1; - - switch (bus_type) { - case BUS_USB: - /* The device pointed to by raw_dev contains information about - the hidraw device. In order to get information about the - USB device, get the parent device with the - subsystem/devtype pair of "usb"/"usb_device". This will - be several levels up the tree, but the function will find - it. */ - usb_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_device"); - - /* uhid USB devices - Since this is a virtual hid interface, no USB information will - be available. */ - if (!usb_dev) { - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - break; - } - - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); - cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); - - /* Release Number */ - str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); - cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; - - /* Get a handle to the interface's udev node. */ - intf_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_interface"); - if (intf_dev) { - str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); - cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; - } - - break; - - case BUS_BLUETOOTH: - case BUS_I2C: - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - - break; - - default: - /* Unknown device type - this should never happen, as we - * check for USB and Bluetooth devices above */ - break; - } - - /* Usage Page and Usage */ - result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); - if (result >= 0) { - unsigned short page = 0, usage = 0; - unsigned int pos = 0; - /* - * Parse the first usage and usage page - * out of the report descriptor. - */ - if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - - /* - * Parse any additional usage and usage pages - * out of the report descriptor. - */ - while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - /* Create new record for additional usage pairs */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - cur_dev->next = tmp; - prev_dev = cur_dev; - cur_dev = tmp; - - /* Update fields */ - cur_dev->path = strdup(dev_path); - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; - cur_dev->release_number = prev_dev->release_number; - cur_dev->interface_number = prev_dev->interface_number; - cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; - cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; - cur_dev->usage_page = page; - cur_dev->usage = usage; - } + /* move the pointer to the tail of returnd list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; } } - next: - free(serial_number_utf8); - free(product_name_utf8); udev_device_unref(raw_dev); - /* hid_dev, usb_dev and intf_dev don't need to be (and can't be) - unref()d. It will cause a double-free() error. I'm not - sure why. */ } /* Free the enumerator and udev objects. */ udev_enumerate_unref(enumerate); udev_unref(udev); + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error("No HID devices found in the system."); + } else { + register_global_error("No HID devices with requested VID/PID found in the system."); + } + } + return root; } @@ -874,14 +922,17 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { - /* Set global error to none */ - register_global_error(NULL); - struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device *handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && @@ -904,7 +955,7 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const /* Open the device */ handle = hid_open_path(path_to_open); } else { - register_global_error("No such device"); + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -914,52 +965,36 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const hid_device * HID_API_EXPORT hid_open_path(const char *path) { - /* Set global error to none */ - register_global_error(NULL); - hid_device *dev = NULL; hid_init(); + /* register_global_error: global error is reset by hid_init */ dev = new_hid_device(); + if (!dev) { + register_global_error("Couldn't allocate memory"); + return NULL; + } - /* OPEN HERE */ - dev->device_handle = open(path, O_RDWR); + dev->device_handle = open(path, O_RDWR | O_CLOEXEC); - /* If we have a good handle, return it. */ if (dev->device_handle >= 0) { - /* Set device error to none */ - register_device_error(dev, NULL); - - /* Get the report descriptor */ int res, desc_size = 0; - struct hidraw_report_descriptor rpt_desc; - - memset(&rpt_desc, 0x0, sizeof(rpt_desc)); - /* Get Report Descriptor Size */ + /* Make sure this is a HIDRAW device - responds to HIDIOCGRDESCSIZE */ res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) - register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); - - /* Get Report Descriptor */ - rpt_desc.size = desc_size; - res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); if (res < 0) { - register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); - } else { - /* Determine if this device uses numbered reports. */ - dev->uses_numbered_reports = - uses_numbered_reports(rpt_desc.value, - rpt_desc.size); + hid_close(dev); + register_device_error_format(dev, "ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); + return NULL; } return dev; } else { - /* Unable to open any devices. */ - register_global_error(strerror(errno)); + /* Unable to open a device. */ free(dev); + register_global_error_format("Failed to open a device with path '%s': %s", path, strerror(errno)); return NULL; } } @@ -1016,9 +1051,11 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t else { /* Check for errors on the file descriptor. This will indicate a device disconnection. */ - if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) + if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) { // We cannot use strerror() here as no -1 was returned from poll(). + register_device_error(dev, "hid_read_timeout: unexpected poll error (device disconnected)"); return -1; + } } } @@ -1053,6 +1090,8 @@ int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); if (res < 0) register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); @@ -1064,6 +1103,8 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); if (res < 0) register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); @@ -1075,6 +1116,8 @@ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned c { int res; + register_device_error(dev, NULL); + res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data); if (res < 0) register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); @@ -1087,38 +1130,108 @@ void HID_API_EXPORT hid_close(hid_device *dev) if (!dev) return; - int ret = close(dev->device_handle); - - register_global_error((ret == -1)? strerror(errno): NULL); + close(dev->device_handle); /* Free the device error message */ register_device_error(dev, NULL); + hid_free_enumeration(dev->device_info); + free(dev); } int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->manufacturer_string) { + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->product_string) { + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->serial_number) { + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; +} + + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + // Lazy initialize device_info + dev->device_info = create_device_info_for_hid_device(dev); + } + + // create_device_info_for_hid_device will set an error if needed + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { - (void)dev; (void)string_index; (void)string; (void)maxlen; + + register_device_error(dev, "hid_get_indexed_string: not supported by hidraw"); + return -1; } diff --git a/hidapi/mac/hid.c b/hidapi/mac/hid.c index d78ac9b..303ec66 100644 --- a/hidapi/mac/hid.c +++ b/hidapi/mac/hid.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -118,13 +119,13 @@ static struct hid_api_version api_version = { static IOHIDManagerRef hid_mgr = 0x0; static int is_macos_10_10_or_greater = 0; static IOOptionBits device_open_options = 0; +static wchar_t *last_global_error_str = NULL; /* --- */ struct hid_device_ { IOHIDDeviceRef device_handle; IOOptionBits open_options; int blocking; - int uses_numbered_reports; int disconnected; CFStringRef run_loop_mode; CFRunLoopRef run_loop; @@ -132,6 +133,7 @@ struct hid_device_ { uint8_t *input_report_buf; CFIndex max_input_report_len; struct input_report *input_reports; + struct hid_device_info* device_info; pthread_t thread; pthread_mutex_t mutex; /* Protects input_reports */ @@ -139,22 +141,28 @@ struct hid_device_ { pthread_barrier_t barrier; /* Ensures correct startup sequence */ pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ int shutdown_thread; + wchar_t *last_error_str; }; static hid_device *new_hid_device(void) { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + if (dev == NULL) { + return NULL; + } + dev->device_handle = NULL; dev->open_options = device_open_options; dev->blocking = 1; - dev->uses_numbered_reports = 0; dev->disconnected = 0; dev->run_loop_mode = NULL; dev->run_loop = NULL; dev->source = NULL; dev->input_report_buf = NULL; dev->input_reports = NULL; + dev->device_info = NULL; dev->shutdown_thread = 0; + dev->last_error_str = NULL; /* Thread objects */ pthread_mutex_init(&dev->mutex, NULL); @@ -187,6 +195,7 @@ static void free_hid_device(hid_device *dev) if (dev->source) CFRelease(dev->source); free(dev->input_report_buf); + hid_free_enumeration(dev->device_info); /* Clean up the thread objects */ pthread_barrier_destroy(&dev->shutdown_barrier); @@ -198,6 +207,88 @@ static void free_hid_device(hid_device *dev) free(dev); } + +/* The caller must free the returned string with free(). */ +static wchar_t *utf8_to_wchar_t(const char *utf8) +{ + wchar_t *ret = NULL; + + if (utf8) { + size_t wlen = mbstowcs(NULL, utf8, 0); + if ((size_t) -1 == wlen) { + return wcsdup(L""); + } + ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); + if (ret == NULL) { + /* as much as we can do at this point */ + return NULL; + } + mbstowcs(ret, utf8, wlen+1); + ret[wlen] = 0x0000; + } + + return ret; +} + + +/* Makes a copy of the given error message (and decoded according to the + * currently locale) into the wide string pointer pointed by error_str. + * The last stored error string is freed. + * Use register_error_str(NULL) to free the error message completely. */ +static void register_error_str(wchar_t **error_str, const char *msg) +{ + free(*error_str); + *error_str = utf8_to_wchar_t(msg); +} + +/* Similar to register_error_str, but allows passing a format string with va_list args into this function. */ +static void register_error_str_vformat(wchar_t **error_str, const char *format, va_list args) +{ + char msg[1024]; + vsnprintf(msg, sizeof(msg), format, args); + + register_error_str(error_str, msg); +} + +/* Set the last global error to be reported by hid_error(NULL). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored global error message is freed. + * Use register_global_error(NULL) to indicate "no error". */ +static void register_global_error(const char *msg) +{ + register_error_str(&last_global_error_str, msg); +} + +/* Similar to register_global_error, but allows passing a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&last_global_error_str, format, args); + va_end(args); +} + +/* Set the last error for a device to be reported by hid_error(dev). + * The given error message will be copied (and decoded according to the + * currently locale, so do not pass in string constants). + * The last stored device error message is freed. + * Use register_device_error(dev, NULL) to indicate "no error". */ +static void register_device_error(hid_device *dev, const char *msg) +{ + register_error_str(&dev->last_error_str, msg); +} + +/* Similar to register_device_error, but you can pass a format string into this function. */ +static void register_device_error_format(hid_device *dev, const char *format, ...) +{ + va_list args; + va_start(args, format); + register_error_str_vformat(&dev->last_error_str, format, args); + va_end(args); +} + + static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref = IOHIDDeviceGetProperty(device, key); @@ -322,6 +413,7 @@ static int init_hid_manager(void) return 0; } + register_global_error("Failed to create IOHIDManager"); return -1; } @@ -340,6 +432,8 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str() for failure. */ int HID_API_EXPORT hid_init(void) { + register_global_error(NULL); + if (!hid_mgr) { is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ hid_darwin_set_open_exclusive(1); /* Backward compatibility */ @@ -359,6 +453,9 @@ int HID_API_EXPORT hid_exit(void) hid_mgr = NULL; } + /* Free global error message */ + register_global_error(NULL); + return 0; } @@ -375,6 +472,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, unsigned short dev_pid; int BUF_LEN = 256; wchar_t buf[BUF_LEN]; + CFTypeRef transport_prop; struct hid_device_info *cur_dev; io_object_t iokit_dev; @@ -453,6 +551,22 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, cur_dev->interface_number = -1; } + /* Bus Type */ + transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); + + if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { + if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_USB; + /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ + } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { + cur_dev->bus_type = HID_API_BUS_BLUETOOTH; + } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportI2CValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_I2C; + } else if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportSPIValue), 0) == kCFCompareEqualTo) { + cur_dev->bus_type = HID_API_BUS_SPI; + } + } + return cur_dev; } @@ -511,8 +625,10 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, int i; /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) + if (hid_init() < 0) { return NULL; + } + /* register_global_error: global error is set/reset by hid_init */ /* give the IOHIDManager a chance to update itself */ process_pending_events(); @@ -540,14 +656,17 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); - if (device_set == NULL) { - return NULL; - } - /* Convert the list into a C array so we can iterate easily. */ - num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); + IOHIDDeviceRef *device_array = NULL; + + if (device_set != NULL) { + /* Convert the list into a C array so we can iterate easily. */ + num_devices = CFSetGetCount(device_set); + device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + } else { + num_devices = 0; + } /* Iterate over each device, making an entry for it. */ for (i = 0; i < num_devices; i++) { @@ -577,7 +696,16 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } free(device_array); - CFRelease(device_set); + if (device_set != NULL) + CFRelease(device_set); + + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error("No HID devices found in the system."); + } else { + register_global_error("No HID devices with requested VID/PID found in the system."); + } + } return root; } @@ -600,11 +728,18 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device * handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); + if (devs == NULL) { + /* register_global_error: global error is already set by hid_enumerate */ + return NULL; + } + cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && @@ -624,8 +759,9 @@ hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short pr } if (path_to_open) { - /* Open the device */ handle = hid_open_path(path_to_open); + } else { + register_global_error("Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -806,17 +942,25 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) hid_device *dev = NULL; io_registry_entry_t entry = MACH_PORT_NULL; IOReturn ret = kIOReturnInvalid; + char str[32]; /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) + if (hid_init() < 0) { goto return_error; + } + /* register_global_error: global error is set/reset by hid_init */ dev = new_hid_device(); + if (!dev) { + register_global_error("Couldn't allocate memory"); + return NULL; + } /* Get the IORegistry entry for the given path */ entry = hid_open_service_registry_from_path(path); if (entry == MACH_PORT_NULL) { /* Path wasn't valid (maybe device was removed?) */ + register_global_error("hid_open_path: device mach entry not found with the given path"); goto return_error; } @@ -824,42 +968,41 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); if (dev->device_handle == NULL) { /* Error creating the HID device */ + register_global_error("hid_open_path: failed to create IOHIDDevice from the mach entry"); goto return_error; } /* Open the IOHIDDevice */ ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); - if (ret == kIOReturnSuccess) { - char str[32]; + if (ret != kIOReturnSuccess) { + register_global_error_format("hid_open_path: failed to open IOHIDDevice from mach entry: (0x%08X) %s", ret, mach_error_string(ret)); + goto return_error; + } - /* Create the buffers for receiving data */ - dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); - dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); + /* Create the buffers for receiving data */ + dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); + dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); - /* Create the Run Loop Mode for this device. - printing the reference seems to work. */ - sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); - dev->run_loop_mode = - CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); + dev->run_loop_mode = + CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); - /* Attach the device to a Run Loop */ - IOHIDDeviceRegisterInputReportCallback( - dev->device_handle, dev->input_report_buf, dev->max_input_report_len, - &hid_report_callback, dev); - IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); + /* Attach the device to a Run Loop */ + IOHIDDeviceRegisterInputReportCallback( + dev->device_handle, dev->input_report_buf, dev->max_input_report_len, + &hid_report_callback, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); - /* Start the read thread */ - pthread_create(&dev->thread, NULL, read_thread, dev); + /* Start the read thread */ + pthread_create(&dev->thread, NULL, read_thread, dev); - /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); - IOObjectRelease(entry); - return dev; - } - else { - goto return_error; - } + IOObjectRelease(entry); + return dev; return_error: if (dev->device_handle != NULL) @@ -879,7 +1022,10 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char IOReturn res; unsigned char report_id; + register_device_error(dev, NULL); + if (!data || (length == 0)) { + register_device_error(dev, strerror(EINVAL)); return -1; } @@ -894,6 +1040,7 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char /* Avoid crash if the device has been unplugged. */ if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); return -1; } @@ -902,11 +1049,12 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char report_id, data_to_send, length_to_send); - if (res == kIOReturnSuccess) { - return (int) length; + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceSetReport failed: (0x%08X) %s", res, mach_error_string(res)); + return -1; } - return -1; + return (int) length; } static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length) @@ -916,6 +1064,8 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data IOReturn res = kIOReturnSuccess; const unsigned char report_id = data[0]; + register_device_error(dev, NULL); + if (report_id == 0x0) { /* Not using numbered Reports. Don't send the report number. */ @@ -925,6 +1075,7 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data /* Avoid crash if the device has been unplugged. */ if (dev->disconnected) { + register_device_error(dev, "Device is disconnected"); return -1; } @@ -933,14 +1084,16 @@ static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data report_id, report, &report_length); - if (res == kIOReturnSuccess) { - if (report_id == 0x0) { /* 0 report number still present at the beginning */ - report_length++; - } - return (int) report_length; + if (res != kIOReturnSuccess) { + register_device_error_format(dev, "IOHIDDeviceGetReport failed: (0x%08X) %s", res, mach_error_string(res)); + return -1; } - return -1; + if (report_id == 0x0) { /* 0 report number still present at the beginning */ + report_length++; + } + + return (int) report_length; } int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) @@ -962,7 +1115,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) return (int) len; } -static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) +static int cond_wait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) { while (!dev->input_reports) { int res = pthread_cond_wait(cond, mutex); @@ -975,14 +1128,15 @@ static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_ to sleep. See the pthread_cond_timedwait() man page for details. */ - if (dev->shutdown_thread || dev->disconnected) + if (dev->shutdown_thread || dev->disconnected) { return -1; + } } return 0; } -static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) +static int cond_timedwait(hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) { while (!dev->input_reports) { int res = pthread_cond_timedwait(cond, mutex, abstime); @@ -995,8 +1149,9 @@ static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_m to sleep. See the pthread_cond_timedwait() man page for details. */ - if (dev->shutdown_thread || dev->disconnected) + if (dev->shutdown_thread || dev->disconnected) { return -1; + } } return 0; @@ -1020,6 +1175,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t /* Return if the device has been disconnected. */ if (dev->disconnected) { bytes_read = -1; + register_device_error(dev, "hid_read_timeout: device disconnected"); goto ret; } @@ -1028,6 +1184,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t has been an error. An error code of -1 should be returned. */ bytes_read = -1; + register_device_error(dev, "hid_read_timeout: thread shutdown"); goto ret; } @@ -1041,6 +1198,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t bytes_read = return_data(dev, data, length); else { /* There was an error, or a device disconnection. */ + register_device_error(dev, "hid_read_timeout: error waiting for more data"); bytes_read = -1; } } @@ -1059,12 +1217,14 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t } res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); - if (res == 0) + if (res == 0) { bytes_read = return_data(dev, data, length); - else if (res == ETIMEDOUT) + } else if (res == ETIMEDOUT) { bytes_read = 0; - else + } else { + register_device_error(dev, "hid_read_timeout: error waiting for more data"); bytes_read = -1; + } } else { /* Purely non-blocking */ @@ -1101,7 +1261,7 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, } int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ +{ return get_report(dev, kIOHIDReportTypeInput, data, length); } @@ -1161,17 +1321,72 @@ void HID_API_EXPORT hid_close(hid_device *dev) int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_manufacturer_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) + { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) + { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_product_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_serial_number(dev->device_handle, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; +} + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + dev->device_info = create_device_info(dev->device_handle); + if (!dev->device_info) { + register_device_error(dev, "Failed to create hid_device_info"); + } + } + + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) @@ -1181,9 +1396,8 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index (void) string; (void) maxlen; - /* TODO: */ - - return 0; + register_device_error(dev, "hid_get_indexed_string: not available on this platform"); + return -1; } int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id) @@ -1193,6 +1407,7 @@ int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *lo *location_id = (uint32_t) res; return 0; } else { + register_device_error(dev, "Failed to get IOHIDLocationID property"); return -1; } } @@ -1217,8 +1432,13 @@ int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - (void) dev; - /* TODO: */ + if (dev) { + if (dev->last_error_str == NULL) + return L"Success"; + return dev->last_error_str; + } - return L"hid_error is not implemented yet"; + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; } diff --git a/hidapi/windows/hid.c b/hidapi/windows/hid.c index da47ff6..3769276 100644 --- a/hidapi/windows/hid.c +++ b/hidapi/windows/hid.c @@ -189,6 +189,11 @@ struct hid_device_ { static hid_device *new_hid_device() { hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + + if (dev == NULL) { + return NULL; + } + dev->device_handle = INVALID_HANDLE_VALUE; dev->blocking = TRUE; dev->output_report_length = 0; @@ -224,9 +229,6 @@ static void free_hid_device(hid_device *dev) static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR *op) { - if (!error_buffer) - return; - free(*error_buffer); *error_buffer = NULL; @@ -281,19 +283,8 @@ static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR } } -static void register_winapi_error(hid_device *dev, const WCHAR *op) -{ - if (!dev) - return; - - register_winapi_error_to_buffer(&dev->last_error_str, op); -} - static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error) { - if (!error_buffer) - return; - free(*error_buffer); *error_buffer = NULL; @@ -302,14 +293,28 @@ static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR } } -static void register_string_error(hid_device *dev, const WCHAR *string_error) +static void register_winapi_error(hid_device *dev, const WCHAR *op) { - if (!dev) - return; + register_winapi_error_to_buffer(&dev->last_error_str, op); +} +static void register_string_error(hid_device *dev, const WCHAR *string_error) +{ register_string_error_to_buffer(&dev->last_error_str, string_error); } +static wchar_t *last_global_error_str = NULL; + +static void register_global_winapi_error(const WCHAR *op) +{ + register_winapi_error_to_buffer(&last_global_error_str, op); +} + +static void register_global_error(const WCHAR *string_error) +{ + register_string_error_to_buffer(&last_global_error_str, string_error); +} + static HANDLE open_device(const wchar_t *path, BOOL open_rw) { HANDLE handle; @@ -339,9 +344,11 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str() int HID_API_EXPORT hid_init(void) { + register_global_error(NULL); #ifndef HIDAPI_USE_DDK if (!hidapi_initialized) { if (lookup_functions() < 0) { + register_global_winapi_error(L"resolve DLL functions"); return -1; } hidapi_initialized = TRUE; @@ -356,6 +363,7 @@ int HID_API_EXPORT hid_exit(void) free_library_handles(); hidapi_initialized = FALSE; #endif + register_global_error(NULL); return 0; } @@ -502,12 +510,43 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi /* Normalize to upper case */ for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p); + /* USB devices + https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support + https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */ + if (wcsstr(compatible_id, L"USB") != NULL) { + dev->bus_type = HID_API_BUS_USB; + break; + } + + /* Bluetooth devices + https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */ + if (wcsstr(compatible_id, L"BTHENUM") != NULL) { + dev->bus_type = HID_API_BUS_BLUETOOTH; + break; + } + /* Bluetooth LE devices */ if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { /* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices Request this info via dev node properties instead. https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */ hid_internal_get_ble_info(dev, dev_node); + + dev->bus_type = HID_API_BUS_BLUETOOTH; + break; + } + + /* I2C devices + https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */ + if (wcsstr(compatible_id, L"PNP0C50") != NULL) { + dev->bus_type = HID_API_BUS_I2C; + break; + } + + /* SPI devices + https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */ + if (wcsstr(compatible_id, L"PNP0C51") != NULL) { + dev->bus_type = HID_API_BUS_SPI; break; } } @@ -523,6 +562,9 @@ static char *hid_internal_UTF16toUTF8(const wchar_t *src) int len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); if (len) { dst = (char*)calloc(len, sizeof(char)); + if (dst == NULL) { + return NULL; + } WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dst, len, NULL, NULL); } @@ -535,6 +577,9 @@ static wchar_t *hid_internal_UTF8toUTF16(const char *src) int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); if (len) { dst = (wchar_t*)calloc(len, sizeof(wchar_t)); + if (dst == NULL) { + return NULL; + } MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dst, len); } @@ -608,8 +653,10 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor wchar_t* device_interface_list = NULL; DWORD len; - if (hid_init() < 0) + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ return NULL; + } /* Retrieve HID Interface Class GUID https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ @@ -621,6 +668,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor do { cr = CM_Get_Device_Interface_List_SizeW(&len, &interface_class_guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); if (cr != CR_SUCCESS) { + register_global_error(L"Failed to get size of HID device interface list"); break; } @@ -630,9 +678,13 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor device_interface_list = (wchar_t*)calloc(len, sizeof(wchar_t)); if (device_interface_list == NULL) { + register_global_error(L"Failed to allocate memory for HID device interface list"); return NULL; } cr = CM_Get_Device_Interface_ListW(&interface_class_guid, NULL, device_interface_list, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (cr != CR_SUCCESS && cr != CR_BUFFER_SMALL) { + register_global_error(L"Failed to get HID device interface list"); + } } while (cr == CR_BUFFER_SMALL); if (cr != CR_SUCCESS) { @@ -684,6 +736,14 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor CloseHandle(device_handle); } + if (root == NULL) { + if (vendor_id == 0 && product_id == 0) { + register_global_error(L"No HID devices found in the system."); + } else { + register_global_error(L"No HID devices with requested VID/PID found in the system."); + } + } + end_of_function: free(device_interface_list); @@ -712,8 +772,10 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi const char *path_to_open = NULL; hid_device *handle = NULL; + /* register_global_error: global error is reset by hid_enumerate/hid_init */ devs = hid_enumerate(vendor_id, product_id); if (!devs) { + /* register_global_error: global error is already set by hid_enumerate */ return NULL; } @@ -738,6 +800,8 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi if (path_to_open) { /* Open the device */ handle = hid_open_path(path_to_open); + } else { + register_global_error(L"Device with requested VID/PID/(SerialNumber) not found"); } hid_free_enumeration(devs); @@ -753,12 +817,16 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps; - if (hid_init() < 0) + if (hid_init() < 0) { + /* register_global_error: global error is reset by hid_init */ goto end_of_function; + } interface_path = hid_internal_UTF8toUTF16(path); - if (!interface_path) + if (!interface_path) { + register_string_error(dev, L"Path conversion failure"); goto end_of_function; + } /* Open a handle to the device */ device_handle = open_device(interface_path, TRUE); @@ -773,23 +841,36 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) device_handle = open_device(interface_path, FALSE); /* Check the validity of the limited device_handle. */ - if (device_handle == INVALID_HANDLE_VALUE) + if (device_handle == INVALID_HANDLE_VALUE) { + register_global_winapi_error(L"open_device"); goto end_of_function; + } } /* Set the Input Report buffer size to 64 reports. */ - if (!HidD_SetNumInputBuffers(device_handle, 64)) + if (!HidD_SetNumInputBuffers(device_handle, 64)) { + register_global_winapi_error(L"set input buffers"); goto end_of_function; + } /* Get the Input Report length for the device. */ - if (!HidD_GetPreparsedData(device_handle, &pp_data)) + if (!HidD_GetPreparsedData(device_handle, &pp_data)) { + register_global_winapi_error(L"get preparsed data"); goto end_of_function; + } - if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) + if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) { + register_global_error(L"HidP_GetCaps"); goto end_of_function; + } dev = new_hid_device(); + if (dev == NULL) { + register_global_error(L"hid_device allocation error"); + goto end_of_function; + } + dev->device_handle = device_handle; device_handle = INVALID_HANDLE_VALUE; @@ -819,11 +900,13 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * unsigned char *buf; - if (!data || (length==0)) { + if (!data || !length) { register_string_error(dev, L"Zero buffer/length"); return function_result; } + register_string_error(dev, NULL); + /* Make sure the right number of bytes are passed to WriteFile. Windows expects the number of bytes which are in the _longest_ report (plus one for the report number) bytes even if the data is a report @@ -887,6 +970,13 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char BOOL res = FALSE; BOOL overlapped = FALSE; + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + /* Copy the handle for convenience. */ HANDLE ev = dev->ol.hEvent; @@ -977,6 +1067,13 @@ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const u unsigned char *buf; size_t length_to_send; + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + /* Windows expects at least caps.FeatureReportByteLength bytes passed to HidD_SetFeature(), even if the report is shorter. Any less sent and the function fails with error ERROR_INVALID_PARAMETER set. Any more @@ -1012,6 +1109,13 @@ static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *dat OVERLAPPED ol; memset(&ol, 0, sizeof(ol)); + if (!data || !length) { + register_string_error(dev, L"Zero buffer/length"); + return -1; + } + + register_string_error(dev, NULL); + res = DeviceIoControl(dev->device_handle, report_type, data, (DWORD) length, @@ -1068,66 +1172,74 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) - { - register_string_error(dev, L"NULL device/info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) - { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } wcsncpy(string, dev->device_info->manufacturer_string, maxlen); string[maxlen - 1] = L'\0'; + register_string_error(dev, NULL); + return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) - { - register_string_error(dev, L"NULL device/info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) - { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } - wcsncpy(string, dev->device_info->product_string, maxlen); string[maxlen - 1] = L'\0'; + register_string_error(dev, NULL); + return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) - { - register_string_error(dev, L"NULL device/info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) - { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } - wcsncpy(string, dev->device_info->serial_number, maxlen); string[maxlen - 1] = L'\0'; + register_string_error(dev, NULL); + return 0; } +HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + register_string_error(dev, L"NULL device info"); + return NULL; + } + + return dev->device_info; +} + int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { BOOL res; @@ -1138,6 +1250,8 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int return -1; } + register_string_error(dev, NULL); + return 0; } @@ -1149,31 +1263,29 @@ int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *conta DEVPROPTYPE property_type; ULONG len; - if (!container_id) - { + if (!container_id) { register_string_error(dev, L"Invalid Container ID"); return -1; } + register_string_error(dev, NULL); + interface_path = hid_internal_UTF8toUTF16(dev->device_info->path); - if (!interface_path) - { + if (!interface_path) { register_string_error(dev, L"Path conversion failure"); goto end; } /* Get the device id from interface path */ device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); - if (!device_id) - { + if (!device_id) { register_string_error(dev, L"Failed to get device interface property InstanceId"); goto end; } /* Open devnode from device id */ cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); - if (cr != CR_SUCCESS) - { + if (cr != CR_SUCCESS) { register_string_error(dev, L"Failed to locate device node"); goto end; } @@ -1203,8 +1315,9 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) return (wchar_t*)dev->last_error_str; } - /* Global error messages are not (yet) implemented on Windows. */ - return L"hid_error for global errors is not implemented yet"; + if (last_global_error_str == NULL) + return L"Success"; + return last_global_error_str; } #ifdef __cplusplus From ab8718126992b9912061f0177cd2a3e8e5e3866d Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 13:24:11 +0200 Subject: [PATCH 07/17] go fmt --- hid_enabled.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hid_enabled.go b/hid_enabled.go index 9959c6b..9b45c58 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -62,8 +62,9 @@ import ( // for enumeration, causing crashes if called concurrently. // // For more details, see: -// https://developer.apple.com/documentation/iokit/1438371-iohidmanagersetdevicematching -// > "subsequent calls will cause the hid manager to release previously enumerated devices" +// +// https://developer.apple.com/documentation/iokit/1438371-iohidmanagersetdevicematching +// > "subsequent calls will cause the hid manager to release previously enumerated devices" var enumerateLock sync.Mutex // Supported returns whether this platform is supported by the HID library or not. @@ -75,9 +76,9 @@ func Supported() bool { // Enumerate returns a list of all the HID devices attached to the system which // match the vendor and product id: -// - If the vendor id is set to 0 then any vendor matches. -// - If the product id is set to 0 then any product matches. -// - If the vendor and product id are both 0, all HID devices are returned. +// - If the vendor id is set to 0 then any vendor matches. +// - If the product id is set to 0 then any product matches. +// - If the vendor and product id are both 0, all HID devices are returned. func Enumerate(vendorID uint16, productID uint16) []DeviceInfo { enumerateLock.Lock() defer enumerateLock.Unlock() From 38a301712e4738eadded63c83f25d54568e62b99 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 13:25:06 +0200 Subject: [PATCH 08/17] add underlying bus type to DeviceInfo --- hid.go | 33 +++++++++++++++++++++++++++++++-- hid_enabled.go | 1 + 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/hid.go b/hid.go index 60a40b8..20ab58e 100644 --- a/hid.go +++ b/hid.go @@ -17,6 +17,33 @@ var ErrDeviceClosed = errors.New("hid: device closed") // operating system is not supported by the library. var ErrUnsupportedPlatform = errors.New("hid: unsupported platform") +const ( + BusUnknown = 0x00 + BusUSB = 0x01 + BusBluetooth = 0x02 + BusI2C = 0x03 + BusSPI = 0x04 +) + +type BusType int + +func (t BusType) String() string { + switch t { + case BusUSB: + return "usb" + case BusBluetooth: + return "bluetooth" + case BusI2C: + return "i2c" + case BusSPI: + return "spi" + case BusUnknown: + fallthrough + default: + return "unknown" + } +} + // DeviceInfo is a hidapi info structure. type DeviceInfo struct { Path string // Platform-specific device path @@ -26,12 +53,14 @@ type DeviceInfo struct { Serial string // Serial Number Manufacturer string // Manufacturer String Product string // Product string - UsagePage uint16 // Usage Page for this Device/Interface (Windows/Mac only) - Usage uint16 // Usage for this Device/Interface (Windows/Mac only) + UsagePage uint16 // Usage Page for this Device/Interface (Windows/Mac/hidraw only) + Usage uint16 // Usage for this Device/Interface (Windows/Mac/hidraw only) // The USB interface which this logical device // represents. Valid on both Linux implementations // in all cases, and valid on the Windows implementation // only if the device contains more than one interface. Interface int + + BusType BusType // Underlying bus type } diff --git a/hid_enabled.go b/hid_enabled.go index 9b45c58..d7a14a8 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -101,6 +101,7 @@ func Enumerate(vendorID uint16, productID uint16) []DeviceInfo { UsagePage: uint16(head.usage_page), Usage: uint16(head.usage), Interface: int(head.interface_number), + BusType: BusType(head.bus_type), } if head.serial_number != nil { info.Serial, _ = wcharTToString(head.serial_number) From d87f996e1e5637ae80b33e65f5168dacba57e843 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 13:26:13 +0200 Subject: [PATCH 09/17] add GetDeviceInfo() function hid_get_device_info() was introduced in hidapi 0.13 --- hid_disabled.go | 5 +++++ hid_enabled.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/hid_disabled.go b/hid_disabled.go index 78eb56a..5a7a5dd 100644 --- a/hid_disabled.go +++ b/hid_disabled.go @@ -98,3 +98,8 @@ func (dev *Device) GetInputReport(b []byte) (int, error) { func (dev *Device) SetNonblocking(b bool) error { return ErrUnsupportedPlatform } + +// GetDeviceInfo gets the DeviceInfo from a HID device. +func (dev *Device) GetDeviceInfo() (*DeviceInfo, error) { + return nil, ErrUnsupportedPlatform +} diff --git a/hid_enabled.go b/hid_enabled.go index d7a14a8..a82d448 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -425,3 +425,57 @@ func (dev *Device) SetNonblocking(b bool) error { return nil } + +// GetDeviceInfo gets the DeviceInfo from a HID device. +func (dev *Device) GetDeviceInfo() (*DeviceInfo, error) { + // Abort if device closed in between + dev.lock.Lock() + device := dev.device + dev.lock.Unlock() + + if device == nil { + return nil, ErrDeviceClosed + } + + i := C.hid_get_device_info(device) + if i == nil { + // If the read failed, verify if closed or other error + dev.lock.Lock() + device = dev.device + dev.lock.Unlock() + + if device == nil { + return nil, ErrDeviceClosed + } + + // Device not closed, some other error occurred + message := C.hid_error(device) + if message == nil { + return nil, errors.New("hidapi: unknown failure") + } + failure, _ := wcharTToString(message) + return nil, errors.New("hidapi: " + failure) + } + + info := &DeviceInfo{ + Path: C.GoString(i.path), + VendorID: uint16(i.vendor_id), + ProductID: uint16(i.product_id), + Release: uint16(i.release_number), + UsagePage: uint16(i.usage_page), + Usage: uint16(i.usage), + Interface: int(i.interface_number), + BusType: BusType(i.bus_type), + } + if i.serial_number != nil { + info.Serial, _ = wcharTToString(i.serial_number) + } + if i.product_string != nil { + info.Product, _ = wcharTToString(i.product_string) + } + if i.manufacturer_string != nil { + info.Manufacturer, _ = wcharTToString(i.manufacturer_string) + } + + return info, nil +} From e48fe3a6e78637bbe89785af9ed6522bc9c62d74 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 13:26:49 +0200 Subject: [PATCH 10/17] add windows specific GetContainerId() function hid_winapi_get_container_id() was introduced in hidapi 0.12 --- go.mod | 2 ++ go.sum | 2 ++ hid_windows.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 go.sum create mode 100644 hid_windows.go diff --git a/go.mod b/go.mod index b80ef01..3c89d0e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/bearsh/hid go 1.17 + +require golang.org/x/sys v0.6.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..789d7a1 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/hid_windows.go b/hid_windows.go new file mode 100644 index 0000000..0179f4d --- /dev/null +++ b/hid_windows.go @@ -0,0 +1,43 @@ +package hid + +import ( + "errors" + "golang.org/x/sys/windows" + "unsafe" +) + +/* +#include "hidapi/windows/hidapi_winapi.h" +*/ +import "C" + +// GetContainerId gets the container ID for a HID device. +// This function is windows specific. +func (dev *Device) GetContainerId() (*windows.GUID, error) { + // Abort if device closed in between + dev.lock.Lock() + device := dev.device + dev.lock.Unlock() + + if device == nil { + return nil, ErrDeviceClosed + } + + var c_guid C.GUID + + res := int(C.hid_winapi_get_container_id(device, &c_guid)) + + if res < 0 { + return nil, errors.New("hidapi: get container id") + } + + guid := &windows.GUID{ + Data1: uint32(c_guid.Data1), + Data2: uint16(c_guid.Data2), + Data3: uint16(c_guid.Data3), + } + + C.memcpy(unsafe.Pointer(&guid.Data4[0]), unsafe.Pointer(&c_guid.Data4[0]), C.size_t(len(guid.Data4))) + + return guid, nil +} From c7689052ee76d6f05e2d5e7a3ee7eeb86ffa1bfa Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 13:27:07 +0200 Subject: [PATCH 11/17] ci: bump checkout and setup-go action --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d2eeecb..04c27b4 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -18,12 +18,12 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.17 @@ -40,7 +40,7 @@ jobs: - name: Build hidraw run: go build -tags hidraw -v ./... if: startsWith(matrix.os,'ubuntu') - + - name: Test hidraw run: go test -tags hidraw -v ./... if: startsWith(matrix.os,'ubuntu') From e48bf12f7880d642fc1538ba6cfc9ab37d90d49e Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 27 Mar 2023 15:47:56 +0200 Subject: [PATCH 12/17] in Read() do not wrap ReadTimeout() but use hid_read() from hidapi this way we do not need to have access to the private 'blocking' member of the hid_device struct. this adds some duplicated code but has the benefit to not rely on private symbols... --- hid_enabled.go | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/hid_enabled.go b/hid_enabled.go index a82d448..c958486 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -248,21 +248,52 @@ func (dev *Device) SendFeatureReport(b []byte) (int, error) { return written, nil } -// Read is a wrapper to ReadTimeout that will check if device blocking is enabled -// and set timeout accordingly. +// Read retrieves an input report from a HID device. // -// This reproduces C.hid_read() behavior in wrapping hid_read_timeout: -// return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +// Input reports are returned to the host through the INTERRUPT IN +// endpoint. The first byte will contain the Report number if the +// device uses numbered reports. func (dev *Device) Read(b []byte) (int, error) { - var timeout int - if int(dev.device.blocking) == 1 { - timeout = -1 + // Abort if nothing to read + if len(b) == 0 { + return 0, nil + } + // Abort if device closed in between + dev.lock.Lock() + device := dev.device + dev.lock.Unlock() + + if device == nil { + return 0, ErrDeviceClosed } - return dev.ReadTimeout(b, timeout) + // Execute the read operation + read := int(C.hid_read(device, (*C.uchar)(&b[0]), C.size_t(len(b)))) + if read == -1 { + // If the read failed, verify if closed or other error + dev.lock.Lock() + device = dev.device + dev.lock.Unlock() + + if device == nil { + return 0, ErrDeviceClosed + } + // Device not closed, some other error occurred + message := C.hid_error(device) + if message == nil { + return 0, errors.New("hidapi: unknown failure") + } + failure, _ := wcharTToString(message) + return 0, errors.New("hidapi: " + failure) + } + return read, nil } // ReadTimeout retrieves an input report from a HID device with a timeout. If timeout is -1 a // blocking read is performed, else a non-blocking that waits timeout milliseconds +// +// Input reports are returned to the host through the INTERRUPT IN +// endpoint. The first byte will contain the Report number if the +// device uses numbered reports. func (dev *Device) ReadTimeout(b []byte, timeout int) (int, error) { // Abort if nothing to read if len(b) == 0 { From df50e5121bb55204a4933455fd20804b7678e49d Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Tue, 23 May 2023 14:40:55 +0200 Subject: [PATCH 13/17] bump hidapi to v0.14 --- hidapi/README.md | 27 +- hidapi/VERSION | 2 +- hidapi/hidapi/hidapi.h | 63 +- hidapi/libusb/hid.c | 14 +- hidapi/linux/hid.c | 49 +- hidapi/mac/hid.c | 155 ++- hidapi/windows/hid.c | 268 +++-- hidapi/windows/hidapi_cfgmgr32.h | 20 +- .../windows/hidapi_descriptor_reconstruct.c | 987 ++++++++++++++++++ .../windows/hidapi_descriptor_reconstruct.h | 238 +++++ hidapi/windows/hidapi_hidpi.h | 7 + hidapi/windows/hidapi_winapi.h | 16 + 12 files changed, 1708 insertions(+), 138 deletions(-) create mode 100644 hidapi/windows/hidapi_descriptor_reconstruct.c create mode 100644 hidapi/windows/hidapi_descriptor_reconstruct.h diff --git a/hidapi/README.md b/hidapi/README.md index c2f378f..257b9f3 100644 --- a/hidapi/README.md +++ b/hidapi/README.md @@ -2,15 +2,17 @@ | CI instance | Status | |----------------------|--------| -| `Linux/macOS/Windows master` | [![GitHub Builds](https://github.com/libusb/hidapi/workflows/GitHub%20Builds/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) | -| `Windows master` | [![Build status](https://ci.appveyor.com/api/projects/status/xfmr5fo8w0re8ded/branch/master?svg=true)](https://ci.appveyor.com/project/libusb/hidapi/branch/master) | -| `Linux/BSD, last build (branch/PR)` | [![builds.sr.ht status](https://builds.sr.ht/~z3ntu/hidapi.svg)](https://builds.sr.ht/~z3ntu/hidapi) | +| `Linux/macOS/Windows (master)` | [![GitHub Builds](https://github.com/libusb/hidapi/workflows/GitHub%20Builds/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) | +| `Windows (master)` | [![Build status](https://ci.appveyor.com/api/projects/status/xfmr5fo8w0re8ded/branch/master?svg=true)](https://ci.appveyor.com/project/libusb/hidapi/branch/master) | +| `BSD, last build (branch/PR)` | [![builds.sr.ht status](https://builds.sr.ht/~z3ntu/hidapi.svg)](https://builds.sr.ht/~z3ntu/hidapi) | +| `Coverity Scan (last)` | ![Coverity Scan](https://scan.coverity.com/projects/583/badge.svg) | HIDAPI is a multi-platform library which allows an application to interface with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS. HIDAPI can be either built as a shared library (`.so`, `.dll` or `.dylib`) or -can be embedded directly into a target application by adding a single source -file (per platform) and a single header. +can be embedded directly into a target application by adding a _single source_ +file (per platform) and a single header.
+See [remarks](BUILD.md#embedding-hidapi-directly-into-your-source-tree) on embedding _directly_ into your build system. HIDAPI library was originally developed by Alan Ott ([signal11](https://github.com/signal11)). @@ -96,7 +98,7 @@ device spec. Writing data (`hid_write`) at random to your HID devices can break ```c #include // printf -#include // wprintf +#include // wchar_t #include @@ -116,22 +118,27 @@ int main(int argc, char* argv[]) // Open the device using the VID, PID, // and optionally the Serial number. handle = hid_open(0x4d8, 0x3f, NULL); + if (!handle) { + printf("Unable to open device\n"); + hid_exit(); + return 1; + } // Read the Manufacturer String res = hid_get_manufacturer_string(handle, wstr, MAX_STR); - wprintf(L"Manufacturer String: %s\n", wstr); + printf("Manufacturer String: %ls\n", wstr); // Read the Product String res = hid_get_product_string(handle, wstr, MAX_STR); - wprintf(L"Product String: %s\n", wstr); + printf("Product String: %ls\n", wstr); // Read the Serial Number String res = hid_get_serial_number_string(handle, wstr, MAX_STR); - wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr); + printf("Serial Number String: (%d) %ls\n", wstr[0], wstr); // Read Indexed String 1 res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); - wprintf(L"Indexed String 1: %s\n", wstr); + printf("Indexed String 1: %ls\n", wstr); // Toggle LED (cmd 0x80). The first byte is the report number (0x0). buf[0] = 0x0; diff --git a/hidapi/VERSION b/hidapi/VERSION index a8839f7..0548fb4 100644 --- a/hidapi/VERSION +++ b/hidapi/VERSION @@ -1 +1 @@ -0.11.2 \ No newline at end of file +0.14.0 \ No newline at end of file diff --git a/hidapi/hidapi/hidapi.h b/hidapi/hidapi/hidapi.h index 22b3cd6..744ceb0 100644 --- a/hidapi/hidapi/hidapi.h +++ b/hidapi/hidapi/hidapi.h @@ -7,7 +7,7 @@ libusb/hidapi Team - Copyright 2022, All Rights Reserved. + Copyright 2023, All Rights Reserved. At the discretion of the user of this library, this software may be licensed under the terms of the @@ -29,13 +29,17 @@ #include +/* #480: this is to be refactored properly for v1.0 */ #ifdef _WIN32 + #ifndef HID_API_NO_EXPORT_DEFINE #define HID_API_EXPORT __declspec(dllexport) - #define HID_API_CALL -#else - #define HID_API_EXPORT /**< API export macro */ - #define HID_API_CALL /**< API call macro */ + #endif #endif +#ifndef HID_API_EXPORT + #define HID_API_EXPORT /**< API export macro */ +#endif +/* To be removed in v1.0 */ +#define HID_API_CALL /**< API call macro */ #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ @@ -48,12 +52,12 @@ @ingroup API */ -#define HID_API_VERSION_MINOR 13 +#define HID_API_VERSION_MINOR 14 /** @brief Static/compile-time patch version of the library. @ingroup API */ -#define HID_API_VERSION_PATCH 1 +#define HID_API_VERSION_PATCH 0 /* Helper macros */ #define HID_API_AS_STR_IMPL(x) #x @@ -66,7 +70,9 @@ This macro was added in version 0.12.0. Convenient function to be used for compile-time checks, like: + @code{.c} #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + @endcode @ingroup API */ @@ -99,10 +105,11 @@ #ifdef __cplusplus extern "C" { #endif + /** A structure to hold the version numbers. */ struct hid_api_version { - int major; - int minor; - int patch; + int major; /**< major version number */ + int minor; /**< minor version number */ + int patch; /**< patch version number */ }; struct hid_device_; @@ -113,27 +120,27 @@ extern "C" { @ingroup API */ typedef enum { - /* Unknown bus type */ + /** Unknown bus type */ HID_API_BUS_UNKNOWN = 0x00, - /* USB bus + /** USB bus Specifications: https://usb.org/hid */ HID_API_BUS_USB = 0x01, - /* Bluetooth or Bluetooth LE bus + /** Bluetooth or Bluetooth LE bus Specifications: https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ https://www.bluetooth.com/specifications/specs/hid-service-1-0/ https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ HID_API_BUS_BLUETOOTH = 0x02, - /* I2C bus + /** I2C bus Specifications: https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ HID_API_BUS_I2C = 0x03, - /* SPI bus + /** SPI bus Specifications: https://www.microsoft.com/download/details.aspx?id=103325 */ HID_API_BUS_SPI = 0x04, @@ -165,11 +172,9 @@ extern "C" { /** The USB interface which this logical device represents. - * Valid on both Linux implementations in all cases. - * Valid on the Windows implementation only if the device - contains more than one interface. - * Valid on the Mac implementation if and only if the device - is a USB HID device. */ + Valid only if the device is a USB HID device. + Set to -1 in all other cases. + */ int interface_number; /** Pointer to the next device */ @@ -545,6 +550,23 @@ extern "C" { */ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); + /** @brief Get a report descriptor from a HID device. + + Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0) + + User has to provide a preallocated buffer where descriptor will be copied to. + The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param buf The buffer to copy descriptor into. + @param buf_size The size of the buffer in bytes. + + @returns + This function returns non-negative number of bytes actually copied, or -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size); + /** @brief Get a string describing the last error which occurred. This function is intended for logging/debugging purposes. @@ -600,4 +622,3 @@ extern "C" { #endif #endif - diff --git a/hidapi/libusb/hid.c b/hidapi/libusb/hid.c index b46b1c7..188e536 100644 --- a/hidapi/libusb/hid.c +++ b/hidapi/libusb/hid.c @@ -526,12 +526,12 @@ static char *make_path(libusb_device *dev, int config_number, int interface_numb return strdup(str); } -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) { return &api_version; } -HID_API_EXPORT const char* HID_API_CALL hid_version_str() +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) { return HID_API_VERSION_STR; } @@ -833,8 +833,10 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev = tmp; } - if (res >= 0) + if (res >= 0) { libusb_close(handle); + handle = NULL; + } } } /* altsettings */ } /* interfaces */ @@ -1631,6 +1633,12 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index } +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + return hid_get_report_descriptor_libusb(dev->device_handle, dev->interface, dev->report_descriptor_size, buf, buf_size); +} + + HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { (void)dev; diff --git a/hidapi/linux/hid.c b/hidapi/linux/hid.c index 1b1d2c0..a499f04 100644 --- a/hidapi/linux/hid.c +++ b/hidapi/linux/hid.c @@ -454,7 +454,7 @@ static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned } char buf[1024]; - res = read(handle, buf, sizeof(buf)); + res = read(handle, buf, sizeof(buf) - 1); /* -1 for '\0' at the end */ close(handle); if (res < 0) { @@ -481,6 +481,28 @@ static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_ty return res; } +static int get_hid_report_descriptor_from_hidraw(hid_device *dev, struct hidraw_report_descriptor *rpt_desc) +{ + int desc_size = 0; + + /* Get Report Descriptor Size */ + int res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + register_device_error_format(dev, "ioctl(GRDESCSIZE): %s", strerror(errno)); + return res; + } + + /* Get Report Descriptor */ + memset(rpt_desc, 0x0, sizeof(*rpt_desc)); + rpt_desc->size = desc_size; + res = ioctl(dev->device_handle, HIDIOCGRDESC, rpt_desc); + if (res < 0) { + register_device_error_format(dev, "ioctl(GRDESC): %s", strerror(errno)); + } + + return res; +} + /* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. @@ -786,12 +808,12 @@ static struct hid_device_info * create_device_info_for_hid_device(hid_device *de return root; } -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) { return &api_version; } -HID_API_EXPORT const char* HID_API_CALL hid_version_str() +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) { return HID_API_VERSION_STR; } @@ -985,7 +1007,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); if (res < 0) { hid_close(dev); - register_device_error_format(dev, "ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); + register_global_error_format("ioctl(GRDESCSIZE) error for '%s', not a HIDRAW device?: %s", path, strerror(errno)); return NULL; } @@ -1236,6 +1258,25 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index } +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + struct hidraw_report_descriptor rpt_desc; + int res = get_hid_report_descriptor_from_hidraw(dev, &rpt_desc); + if (res < 0) { + /* error already registered */ + return res; + } + + if (rpt_desc.size < buf_size) { + buf_size = (size_t) rpt_desc.size; + } + + memcpy(buf, rpt_desc.value, buf_size); + + return (int) buf_size; +} + + /* Passing in NULL means asking for the last global error message. */ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { diff --git a/hidapi/mac/hid.c b/hidapi/mac/hid.c index 303ec66..5c0914a 100644 --- a/hidapi/mac/hid.c +++ b/hidapi/mac/hid.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -302,7 +303,7 @@ static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) { CFTypeRef ref; - int32_t value; + int32_t value = 0; ref = IOHIDDeviceGetProperty(device, key); if (ref) { @@ -314,6 +315,36 @@ static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) return 0; } +static bool try_get_int_property(IOHIDDeviceRef device, CFStringRef key, int32_t *out_val) +{ + bool result = false; + CFTypeRef ref; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + result = CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, out_val); + } + } + return result; +} + +static bool try_get_ioregistry_int_property(io_service_t service, CFStringRef property, int32_t *out_val) +{ + bool result = false; + CFTypeRef ref = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault, 0); + + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + result = CFNumberGetValue(ref, kCFNumberSInt32Type, out_val); + } + + CFRelease(ref); + } + + return result; +} + static CFArrayRef get_usage_pairs(IOHIDDeviceRef device) { return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey)); @@ -417,12 +448,12 @@ static int init_hid_manager(void) return -1; } -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) { return &api_version; } -HID_API_EXPORT const char* HID_API_CALL hid_version_str() +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) { return HID_API_VERSION_STR; } @@ -466,6 +497,46 @@ static void process_pending_events(void) { } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); } +static int read_usb_interface_from_hid_service_parent(io_service_t hid_service) +{ + int32_t result = -1; + bool success = false; + io_registry_entry_t current = IO_OBJECT_NULL; + kern_return_t res; + int parent_number = 0; + + res = IORegistryEntryGetParentEntry(hid_service, kIOServicePlane, ¤t); + while (KERN_SUCCESS == res + /* Only search up to 3 parent entries. + * With the default driver - the parent-of-interest supposed to be the first one, + * but lets assume some custom drivers or so, with deeper tree. */ + && parent_number < 3) { + io_registry_entry_t parent = IO_OBJECT_NULL; + int32_t interface_number = -1; + parent_number++; + + success = try_get_ioregistry_int_property(current, CFSTR(kUSBInterfaceNumber), &interface_number); + if (success) { + result = interface_number; + break; + } + + res = IORegistryEntryGetParentEntry(current, kIOServicePlane, &parent); + if (parent) { + IOObjectRelease(current); + current = parent; + } + + } + + if (current) { + IOObjectRelease(current); + current = IO_OBJECT_NULL; + } + + return result; +} + static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage) { unsigned short dev_vid; @@ -475,7 +546,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, CFTypeRef transport_prop; struct hid_device_info *cur_dev; - io_object_t iokit_dev; + io_service_t hid_service; kern_return_t res; uint64_t entry_id = 0; @@ -499,9 +570,9 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, /* Fill in the path (as a unique ID of the service entry) */ cur_dev->path = NULL; - iokit_dev = IOHIDDeviceGetService(dev); - if (iokit_dev != MACH_PORT_NULL) { - res = IORegistryEntryGetRegistryEntryID(iokit_dev, &entry_id); + hid_service = IOHIDDeviceGetService(dev); + if (hid_service != MACH_PORT_NULL) { + res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id); } else { res = KERN_INVALID_ARGUMENT; @@ -510,10 +581,11 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, if (res == KERN_SUCCESS) { /* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long, so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need - 9+1+20+1=31 bytes byffer, but allocate 32 for simple alignment */ - cur_dev->path = calloc(1, 32); + 9+1+20+1=31 bytes buffer, but allocate 32 for simple alignment */ + const size_t path_len = 32; + cur_dev->path = calloc(1, path_len); if (cur_dev->path != NULL) { - sprintf(cur_dev->path, "DevSrvsID:%llu", entry_id); + snprintf(cur_dev->path, path_len, "DevSrvsID:%llu", entry_id); } } @@ -539,24 +611,32 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, /* Release Number */ cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); - /* Interface Number */ - /* We can only retrieve the interface number for USB HID devices. - * IOKit always seems to return 0 when querying a standard USB device - * for its interface. */ - int is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass; - if (is_usb_hid) { - /* Get the interface number */ - cur_dev->interface_number = get_int_property(dev, CFSTR(kUSBInterfaceNumber)); - } else { - cur_dev->interface_number = -1; - } + /* Interface Number. + * We can only retrieve the interface number for USB HID devices. + * See below */ + cur_dev->interface_number = -1; /* Bus Type */ transport_prop = IOHIDDeviceGetProperty(dev, CFSTR(kIOHIDTransportKey)); if (transport_prop != NULL && CFGetTypeID(transport_prop) == CFStringGetTypeID()) { if (CFStringCompare((CFStringRef)transport_prop, CFSTR(kIOHIDTransportUSBValue), 0) == kCFCompareEqualTo) { + int32_t interface_number = -1; cur_dev->bus_type = HID_API_BUS_USB; + + /* A IOHIDDeviceRef used to have this simple property, + * until macOS 13.3 - we will try to use it. */ + if (try_get_int_property(dev, CFSTR(kUSBInterfaceNumber), &interface_number)) { + cur_dev->interface_number = interface_number; + } else { + /* Otherwise fallback to io_service_t property. + * (of one of the parent services). */ + cur_dev->interface_number = read_usb_interface_from_hid_service_parent(hid_service); + + /* If the above doesn't work - + * no (known) fallback exists at this point. */ + } + /* Match "Bluetooth", "BluetoothLowEnergy" and "Bluetooth Low Energy" strings */ } else if (CFStringHasPrefix((CFStringRef)transport_prop, CFSTR(kIOHIDTransportBluetoothValue))) { cur_dev->bus_type = HID_API_BUS_BLUETOOTH; @@ -876,7 +956,7 @@ static void *read_thread(void *param) while (!dev->shutdown_thread && !dev->disconnected) { code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); /* Return if the device has been disconnected */ - if (code == kCFRunLoopRunFinished) { + if (code == kCFRunLoopRunFinished || code == kCFRunLoopRunStopped) { dev->disconnected = 1; break; } @@ -946,7 +1026,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* Set up the HID Manager if it hasn't been done */ if (hid_init() < 0) { - goto return_error; + return NULL; } /* register_global_error: global error is set/reset by hid_init */ @@ -985,7 +1065,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* Create the Run Loop Mode for this device. printing the reference seems to work. */ - sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); + snprintf(str, sizeof(str), "HIDAPI_%p", (void*) dev->device_handle); dev->run_loop_mode = CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); @@ -1430,6 +1510,33 @@ int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev) return (dev->open_options == kIOHIDOptionsTypeSeizeDevice) ? 1 : 0; } +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + CFTypeRef ref = IOHIDDeviceGetProperty(dev->device_handle, CFSTR(kIOHIDReportDescriptorKey)); + if (ref != NULL && CFGetTypeID(ref) == CFDataGetTypeID()) { + CFDataRef report_descriptor = (CFDataRef) ref; + const UInt8 *descriptor_buf = CFDataGetBytePtr(report_descriptor); + CFIndex descriptor_buf_len = CFDataGetLength(report_descriptor); + size_t copy_len = (size_t) descriptor_buf_len; + + if (descriptor_buf == NULL || descriptor_buf_len < 0) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + if (buf_size < copy_len) { + copy_len = buf_size; + } + + memcpy(buf, descriptor_buf, copy_len); + return copy_len; + } + else { + register_device_error(dev, "Failed to get kIOHIDReportDescriptorKey property"); + return -1; + } +} + HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { if (dev) { diff --git a/hidapi/windows/hid.c b/hidapi/windows/hid.c index 3769276..6999691 100644 --- a/hidapi/windows/hid.c +++ b/hidapi/windows/hid.c @@ -30,7 +30,7 @@ extern "C" { #endif -#include "hidapi.h" +#include "hidapi_winapi.h" #include @@ -52,7 +52,6 @@ typedef LONG NTSTATUS; /*#define HIDAPI_USE_DDK*/ -#include #include "hidapi_cfgmgr32.h" #include "hidapi_hidclass.h" #include "hidapi_hidsdi.h" @@ -265,7 +264,7 @@ static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR if (!msg) return; - int printf_written = swprintf(msg, msg_len + 1, L"%.*ls: (0x%08X) %.*ls", op_len, op, error_code, system_err_len, system_err_buf); + int printf_written = swprintf(msg, msg_len + 1, L"%.*ls: (0x%08X) %.*ls", (int)op_len, op, error_code, (int)system_err_len, system_err_buf); if (printf_written < 0) { @@ -283,6 +282,15 @@ static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR } } +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif +/* A bug in GCC/mingw gives: + * error: array subscript 0 is outside array bounds of 'wchar_t *[0]' {aka 'short unsigned int *[]'} [-Werror=array-bounds] + * | free(*error_buffer); + * Which doesn't make sense in this context. */ + static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR *string_error) { free(*error_buffer); @@ -293,6 +301,10 @@ static void register_string_error_to_buffer(wchar_t **error_buffer, const WCHAR } } +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + static void register_winapi_error(hid_device *dev, const WCHAR *op) { register_winapi_error_to_buffer(&dev->last_error_str, op); @@ -332,12 +344,12 @@ static HANDLE open_device(const wchar_t *path, BOOL open_rw) return handle; } -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() +HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void) { return &api_version; } -HID_API_EXPORT const char* HID_API_CALL hid_version_str() +HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) { return HID_API_VERSION_STR; } @@ -409,63 +421,178 @@ static void* hid_internal_get_device_interface_property(const wchar_t* interface return property_value; } -static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) +static void hid_internal_towupper(wchar_t* string) { - wchar_t *manufacturer_string, *serial_number, *product_string; - /* Manufacturer String */ - manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING); - if (manufacturer_string) { - free(dev->manufacturer_string); - dev->manufacturer_string = manufacturer_string; + for (wchar_t* p = string; *p; ++p) *p = towupper(*p); +} + +static int hid_internal_extract_int_token_value(wchar_t* string, const wchar_t* token) +{ + int token_value; + wchar_t* startptr, * endptr; + + startptr = wcsstr(string, token); + if (!startptr) + return -1; + + startptr += wcslen(token); + token_value = wcstol(startptr, &endptr, 16); + if (endptr == startptr) + return -1; + + return token_value; +} + +static void hid_internal_get_usb_info(struct hid_device_info* dev, DEVINST dev_node) +{ + wchar_t *device_id = NULL, *hardware_ids = NULL; + + device_id = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); + if (!device_id) + goto end; + + /* Normalize to upper case */ + hid_internal_towupper(device_id); + + /* Check for Xbox Common Controller class (XUSB) device. + https://docs.microsoft.com/windows/win32/xinput/directinput-and-xusb-devices + https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput + */ + if (hid_internal_extract_int_token_value(device_id, L"IG_") != -1) { + /* Get devnode parent to reach out USB device. */ + if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS) + goto end; + } + + /* Get the hardware ids from devnode */ + hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST); + if (!hardware_ids) + goto end; + + /* Get additional information from USB device's Hardware ID + https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers + https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-interfaces-not-grouped-in-collections + */ + for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) { + /* Normalize to upper case */ + hid_internal_towupper(hardware_id); + + if (dev->release_number == 0) { + /* USB_DEVICE_DESCRIPTOR.bcdDevice value. */ + int release_number = hid_internal_extract_int_token_value(hardware_id, L"REV_"); + if (release_number != -1) { + dev->release_number = (unsigned short)release_number; + } + } + + if (dev->interface_number == -1) { + /* USB_INTERFACE_DESCRIPTOR.bInterfaceNumber value. */ + int interface_number = hid_internal_extract_int_token_value(hardware_id, L"MI_"); + if (interface_number != -1) { + dev->interface_number = interface_number; + } + } } - /* Serial Number String (MAC Address) */ - serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING); - if (serial_number) { - free(dev->serial_number); - dev->serial_number = serial_number; + /* Try to get USB device manufacturer string if not provided by HidD_GetManufacturerString. */ + if (wcslen(dev->manufacturer_string) == 0) { + wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_Manufacturer, DEVPROP_TYPE_STRING); + if (manufacturer_string) { + free(dev->manufacturer_string); + dev->manufacturer_string = manufacturer_string; + } } - /* Get devnode grandparent to reach out Bluetooth LE device node */ - if (CM_Get_Parent(&dev_node, dev_node, 0) != CR_SUCCESS) - return; + /* Try to get USB device serial number if not provided by HidD_GetSerialNumberString. */ + if (wcslen(dev->serial_number) == 0) { + DEVINST usb_dev_node = dev_node; + if (dev->interface_number != -1) { + /* Get devnode parent to reach out composite parent USB device. + https://docs.microsoft.com/windows-hardware/drivers/usbcon/enumeration-of-the-composite-parent-device + */ + if (CM_Get_Parent(&usb_dev_node, dev_node, 0) != CR_SUCCESS) + goto end; + } - /* Product String */ - product_string = hid_internal_get_devnode_property(dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING); - if (product_string) { - free(dev->product_string); - dev->product_string = product_string; + /* Get the device id of the USB device. */ + free(device_id); + device_id = hid_internal_get_devnode_property(usb_dev_node, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); + if (!device_id) + goto end; + + /* Extract substring after last '\\' of Instance ID. + For USB devices it may contain device's serial number. + https://docs.microsoft.com/windows-hardware/drivers/install/instance-ids + */ + for (wchar_t *ptr = device_id + wcslen(device_id); ptr > device_id; --ptr) { + /* Instance ID is unique only within the scope of the bus. + For USB devices it means that serial number is not available. Skip. */ + if (*ptr == L'&') + break; + + if (*ptr == L'\\') { + free(dev->serial_number); + dev->serial_number = _wcsdup(ptr + 1); + break; + } + } } -} -/* USB Device Interface Number. - It can be parsed out of the Hardware ID if a USB device is has multiple interfaces (composite device). - See https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections - and https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers + /* If we can't get the interface number, it means that there is only one interface. */ + if (dev->interface_number == -1) + dev->interface_number = 0; - hardware_id is always expected to be uppercase. +end: + free(device_id); + free(hardware_ids); +} + +/* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices + Request this info via dev node properties instead. + https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */ -static int hid_internal_get_interface_number(const wchar_t* hardware_id) +static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) { - int interface_number; - wchar_t *startptr, *endptr; - const wchar_t *interface_token = L"&MI_"; + if (wcslen(dev->manufacturer_string) == 0) { + /* Manufacturer Name String (UUID: 0x2A29) */ + wchar_t* manufacturer_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_Manufacturer, DEVPROP_TYPE_STRING); + if (manufacturer_string) { + free(dev->manufacturer_string); + dev->manufacturer_string = manufacturer_string; + } + } - startptr = wcsstr(hardware_id, interface_token); - if (!startptr) - return -1; + if (wcslen(dev->serial_number) == 0) { + /* Serial Number String (UUID: 0x2A25) */ + wchar_t* serial_number = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_DeviceAddress, DEVPROP_TYPE_STRING); + if (serial_number) { + free(dev->serial_number); + dev->serial_number = serial_number; + } + } - startptr += wcslen(interface_token); - interface_number = wcstol(startptr, &endptr, 16); - if (endptr == startptr) - return -1; + if (wcslen(dev->product_string) == 0) { + /* Model Number String (UUID: 0x2A24) */ + wchar_t* product_string = hid_internal_get_devnode_property(dev_node, (const DEVPROPKEY*)&PKEY_DeviceInterface_Bluetooth_ModelNumber, DEVPROP_TYPE_STRING); + if (!product_string) { + DEVINST parent_dev_node = 0; + /* Fallback: Get devnode grandparent to reach out Bluetooth LE device node */ + if (CM_Get_Parent(&parent_dev_node, dev_node, 0) == CR_SUCCESS) { + /* Device Name (UUID: 0x2A00) */ + product_string = hid_internal_get_devnode_property(parent_dev_node, &DEVPKEY_NAME, DEVPROP_TYPE_STRING); + } + } - return interface_number; + if (product_string) { + free(dev->product_string); + dev->product_string = product_string; + } + } } static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev) { - wchar_t *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL; + wchar_t *device_id = NULL, *compatible_ids = NULL; CONFIGRET cr; DEVINST dev_node; @@ -479,22 +606,6 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi if (cr != CR_SUCCESS) goto end; - /* Get the hardware ids from devnode */ - hardware_ids = hid_internal_get_devnode_property(dev_node, &DEVPKEY_Device_HardwareIds, DEVPROP_TYPE_STRING_LIST); - if (!hardware_ids) - goto end; - - /* Search for interface number in hardware ids */ - for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) { - /* Normalize to upper case */ - for (wchar_t* p = hardware_id; *p; ++p) *p = towupper(*p); - - dev->interface_number = hid_internal_get_interface_number(hardware_id); - - if (dev->interface_number != -1) - break; - } - /* Get devnode parent */ cr = CM_Get_Parent(&dev_node, dev_node, 0); if (cr != CR_SUCCESS) @@ -508,13 +619,14 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi /* Now we can parse parent's compatible IDs to find out the device bus type */ for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) { /* Normalize to upper case */ - for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p); + hid_internal_towupper(compatible_id); /* USB devices https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */ if (wcsstr(compatible_id, L"USB") != NULL) { dev->bus_type = HID_API_BUS_USB; + hid_internal_get_usb_info(dev, dev_node); break; } @@ -527,12 +639,8 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi /* Bluetooth LE devices */ if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { - /* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices - Request this info via dev node properties instead. - https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */ - hid_internal_get_ble_info(dev, dev_node); - dev->bus_type = HID_API_BUS_BLUETOOTH; + hid_internal_get_ble_info(dev, dev_node); break; } @@ -552,7 +660,6 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi } end: free(device_id); - free(hardware_ids); free(compatible_ids); } @@ -597,9 +704,14 @@ static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, /* Create the record. */ dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); + if (dev == NULL) { + return NULL; + } + /* Fill out the record */ dev->next = NULL; dev->path = hid_internal_UTF16toUTF8(path); + dev->interface_number = -1; attrib.Size = sizeof(HIDD_ATTRIBUTES); if (HidD_GetAttributes(handle, &attrib)) { @@ -824,7 +936,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) interface_path = hid_internal_UTF8toUTF16(path); if (!interface_path) { - register_string_error(dev, L"Path conversion failure"); + register_global_error(L"Path conversion failure"); goto end_of_function; } @@ -1307,6 +1419,22 @@ int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *conta } +int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size) +{ + PHIDP_PREPARSED_DATA pp_data = NULL; + + if (!HidD_GetPreparsedData(dev->device_handle, &pp_data) || pp_data == NULL) { + register_string_error(dev, L"HidD_GetPreparsedData"); + return -1; + } + + int res = hid_winapi_descriptor_reconstruct_pp_data(pp_data, buf, buf_size); + + HidD_FreePreparsedData(pp_data); + + return res; +} + HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { if (dev) { @@ -1320,6 +1448,10 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) return last_global_error_str; } +#ifndef hidapi_winapi_EXPORTS +#include "hidapi_descriptor_reconstruct.c" +#endif + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/hidapi/windows/hidapi_cfgmgr32.h b/hidapi/windows/hidapi_cfgmgr32.h index 720906e..638512a 100644 --- a/hidapi/windows/hidapi_cfgmgr32.h +++ b/hidapi/windows/hidapi_cfgmgr32.h @@ -32,6 +32,10 @@ /* This part of the header mimics cfgmgr32.h, but only what is used by HIDAPI */ +#include +#include +#include + typedef DWORD RETURN_TYPE; typedef RETURN_TYPE CONFIGRET; typedef DWORD DEVNODE, DEVINST; @@ -54,15 +58,17 @@ typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags); // from devpkey.h -static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, {0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac} }, 10 }; // DEVPROP_TYPE_STRING -static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, {0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57} }, 256 }; // DEVPROP_TYPE_STRING -static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 3 }; // DEVPROP_TYPE_STRING_LIST -static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 4 }; // DEVPROP_TYPE_STRING_LIST -static DEVPROPKEY DEVPKEY_Device_ContainerId = { { 0x8c7ed206, 0x3f8a, 0x4827, {0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c} }, 2 }; // DEVPROP_TYPE_GUID +DEFINE_DEVPROPKEY(DEVPKEY_NAME, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); // DEVPROP_TYPE_STRING +DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING +DEFINE_DEVPROPKEY(DEVPKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); // DEVPROP_TYPE_STRING +DEFINE_DEVPROPKEY(DEVPKEY_Device_HardwareIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 3); // DEVPROP_TYPE_STRING_LIST +DEFINE_DEVPROPKEY(DEVPKEY_Device_CompatibleIds, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 4); // DEVPROP_TYPE_STRING_LIST +DEFINE_DEVPROPKEY(DEVPKEY_Device_ContainerId, 0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c, 2); // DEVPROP_TYPE_GUID // from propkey.h -static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 1 }; // DEVPROP_TYPE_STRING -static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 4 }; // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_DeviceAddress, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 1); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_Manufacturer, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 4); // DEVPROP_TYPE_STRING +DEFINE_PROPERTYKEY(PKEY_DeviceInterface_Bluetooth_ModelNumber, 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A, 5); // DEVPROP_TYPE_STRING #endif diff --git a/hidapi/windows/hidapi_descriptor_reconstruct.c b/hidapi/windows/hidapi_descriptor_reconstruct.c new file mode 100644 index 0000000..a4efb25 --- /dev/null +++ b/hidapi/windows/hidapi_descriptor_reconstruct.c @@ -0,0 +1,987 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ +#include "hidapi_descriptor_reconstruct.h" + +/** + * @brief References to report descriptor buffer. + * + */ +struct rd_buffer { + unsigned char* buf; /* Pointer to the array which stores the reconstructed descriptor */ + size_t buf_size; /* Size of the buffer in bytes */ + size_t byte_idx; /* Index of the next report byte to write to buf array */ +}; + +/** + * @brief Function that appends a byte to encoded report descriptor buffer. + * + * @param[in] byte Single byte to append. + * @param rpt_desc Pointer to report descriptor buffer struct. + */ +static void rd_append_byte(unsigned char byte, struct rd_buffer* rpt_desc) { + if (rpt_desc->byte_idx < rpt_desc->buf_size) { + rpt_desc->buf[rpt_desc->byte_idx] = byte; + rpt_desc->byte_idx++; + } +} + +/** + * @brief Writes a short report descriptor item according USB HID spec 1.11 chapter 6.2.2.2. + * + * @param[in] rd_item Enumeration identifying type (Main, Global, Local) and function (e.g Usage or Report Count) of the item. + * @param[in] data Data (Size depends on rd_item 0,1,2 or 4bytes). + * @param list Chained list of report descriptor bytes. + * + * @return Returns 0 if successful, -1 for error. + */ +static int rd_write_short_item(rd_items rd_item, LONG64 data, struct rd_buffer* rpt_desc) { + if (rd_item & 0x03) { + // Invalid input data, last to bits are reserved for data size + return -1; + } + + if (rd_item == rd_main_collection_end) { + // Item without data (1Byte prefix only) + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x00; + rd_append_byte(oneBytePrefix, rpt_desc); + } + else if ((rd_item == rd_global_logical_minimum) || + (rd_item == rd_global_logical_maximum) || + (rd_item == rd_global_physical_minimum) || + (rd_item == rd_global_physical_maximum)) { + // Item with signed integer data + if ((data >= -128) && (data <= 127)) { + // 1Byte prefix + 1Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01; + char localData = (char)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + } + else if ((data >= -32768) && (data <= 32767)) { + // 1Byte prefix + 2Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02; + INT16 localData = (INT16)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + } + else if ((data >= -2147483648LL) && (data <= 2147483647)) { + // 1Byte prefix + 4Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03; + INT32 localData = (INT32)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + rd_append_byte(localData >> 16 & 0xFF, rpt_desc); + rd_append_byte(localData >> 24 & 0xFF, rpt_desc); + } + else { + // Data out of 32 bit signed integer range + return -1; + } + } + else { + // Item with unsigned integer data + if ((data >= 0) && (data <= 0xFF)) { + // 1Byte prefix + 1Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01; + unsigned char localData = (unsigned char)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + } + else if ((data >= 0) && (data <= 0xFFFF)) { + // 1Byte prefix + 2Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02; + UINT16 localData = (UINT16)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + } + else if ((data >= 0) && (data <= 0xFFFFFFFF)) { + // 1Byte prefix + 4Byte data + unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03; + UINT32 localData = (UINT32)data; + rd_append_byte(oneBytePrefix, rpt_desc); + rd_append_byte(localData & 0xFF, rpt_desc); + rd_append_byte(localData >> 8 & 0xFF, rpt_desc); + rd_append_byte(localData >> 16 & 0xFF, rpt_desc); + rd_append_byte(localData >> 24 & 0xFF, rpt_desc); + } + else { + // Data out of 32 bit unsigned integer range + return -1; + } + } + return 0; +} + +static struct rd_main_item_node * rd_append_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) { + struct rd_main_item_node *new_list_node; + + // Determine last node in the list + while (*list != NULL) + { + list = &(*list)->next; + } + + new_list_node = malloc(sizeof(*new_list_node)); // Create new list entry + new_list_node->FirstBit = first_bit; + new_list_node->LastBit = last_bit; + new_list_node->TypeOfNode = type_of_node; + new_list_node->CapsIndex = caps_index; + new_list_node->CollectionIndex = collection_index; + new_list_node->MainItemType = main_item_type; + new_list_node->ReportID = report_id; + new_list_node->next = NULL; // NULL marks last node in the list + + *list = new_list_node; + return new_list_node; +} + +static struct rd_main_item_node * rd_insert_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) { + // Insert item after the main item node referenced by list + struct rd_main_item_node *next_item = (*list)->next; + (*list)->next = NULL; + rd_append_main_item_node(first_bit, last_bit, type_of_node, caps_index, collection_index, main_item_type, report_id, list); + (*list)->next->next = next_item; + return (*list)->next; +} + +static struct rd_main_item_node * rd_search_main_item_list_for_bit_position(int search_bit, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) { + // Determine first INPUT/OUTPUT/FEATURE main item, where the last bit position is equal or greater than the search bit position + + while (((*list)->next->MainItemType != rd_collection) && + ((*list)->next->MainItemType != rd_collection_end) && + !(((*list)->next->LastBit >= search_bit) && + ((*list)->next->ReportID == report_id) && + ((*list)->next->MainItemType == main_item_type)) + ) + { + list = &(*list)->next; + } + return *list; +} + +int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned char *buf, size_t buf_size) +{ + hidp_preparsed_data *pp_data = (hidp_preparsed_data *) preparsed_data; + + // Check if MagicKey is correct, to ensure that pp_data points to an valid preparse data structure + if (memcmp(pp_data->MagicKey, "HidP KDR", 8) != 0) { + return -1; + } + + struct rd_buffer rpt_desc = { + .buf = buf, + .buf_size = buf_size, + .byte_idx = 0 + }; + + // Set pointer to the first node of link_collection_nodes + phid_pp_link_collection_node link_collection_nodes = (phid_pp_link_collection_node)(((unsigned char*)&pp_data->caps[0]) + pp_data->FirstByteOfLinkCollectionArray); + + // **************************************************************************************************************************** + // Create lookup tables for the bit range of each report per collection (position of first bit and last bit in each collection) + // coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE] + // **************************************************************************************************************************** + + // Allocate memory and initialize lookup table + rd_bit_range ****coll_bit_range; + coll_bit_range = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_bit_range)); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_bit_range[collection_node_idx] = malloc(256 * sizeof(*coll_bit_range[0])); // 256 possible report IDs (incl. 0x00) + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + coll_bit_range[collection_node_idx][reportid_idx] = malloc(NUM_OF_HIDP_REPORT_TYPES * sizeof(*coll_bit_range[0][0])); + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + coll_bit_range[collection_node_idx][reportid_idx][rt_idx] = malloc(sizeof(rd_bit_range)); + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = -1; + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = -1; + } + } + } + + // Fill the lookup table where caps exist + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) { + int first_bit, last_bit; + first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8 + + pp_data->caps[caps_idx].BitPosition; + last_bit = first_bit + pp_data->caps[caps_idx].ReportSize + * pp_data->caps[caps_idx].ReportCount - 1; + if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit == -1 || + coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit > first_bit) { + coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit = first_bit; + } + if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit < last_bit) { + coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit = last_bit; + } + } + } + + // ************************************************************************* + // -Determine hierachy levels of each collections and store it in: + // coll_levels[COLLECTION_INDEX] + // -Determine number of direct childs of each collections and store it in: + // coll_number_of_direct_childs[COLLECTION_INDEX] + // ************************************************************************* + int max_coll_level = 0; + int *coll_levels = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_levels[0])); + int *coll_number_of_direct_childs = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_number_of_direct_childs[0])); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_levels[collection_node_idx] = -1; + coll_number_of_direct_childs[collection_node_idx] = 0; + } + + { + int actual_coll_level = 0; + USHORT collection_node_idx = 0; + while (actual_coll_level >= 0) { + coll_levels[collection_node_idx] = actual_coll_level; + if ((link_collection_nodes[collection_node_idx].NumberOfChildren > 0) && + (coll_levels[link_collection_nodes[collection_node_idx].FirstChild] == -1)) { + actual_coll_level++; + coll_levels[collection_node_idx] = actual_coll_level; + if (max_coll_level < actual_coll_level) { + max_coll_level = actual_coll_level; + } + coll_number_of_direct_childs[collection_node_idx]++; + collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild; + } + else if (link_collection_nodes[collection_node_idx].NextSibling != 0) { + coll_number_of_direct_childs[link_collection_nodes[collection_node_idx].Parent]++; + collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling; + } + else { + actual_coll_level--; + if (actual_coll_level >= 0) { + collection_node_idx = link_collection_nodes[collection_node_idx].Parent; + } + } + } + } + + // ********************************************************************************* + // Propagate the bit range of each report from the child collections to their parent + // and store the merged result for the parent + // ********************************************************************************* + for (int actual_coll_level = max_coll_level - 1; actual_coll_level >= 0; actual_coll_level--) { + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + if (coll_levels[collection_node_idx] == actual_coll_level) { + USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild; + while (child_idx) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + // Merge bit range from childs + if ((coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit != -1) && + (coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit)) { + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit; + } + if (coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit < coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit) { + coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit; + } + child_idx = link_collection_nodes[child_idx].NextSibling; + } + } + } + } + } + } + + // ************************************************************************************************* + // Determine child collection order of the whole hierachy, based on previously determined bit ranges + // and store it this index coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX] + // ************************************************************************************************* + USHORT **coll_child_order; + coll_child_order = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_child_order)); + { + BOOLEAN *coll_parsed_flag; + coll_parsed_flag = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_parsed_flag[0])); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_parsed_flag[collection_node_idx] = FALSE; + } + int actual_coll_level = 0; + USHORT collection_node_idx = 0; + while (actual_coll_level >= 0) { + if ((coll_number_of_direct_childs[collection_node_idx] != 0) && + (coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] == FALSE)) { + coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] = TRUE; + coll_child_order[collection_node_idx] = malloc((coll_number_of_direct_childs[collection_node_idx]) * sizeof(*coll_child_order[0])); + + { + // Create list of child collection indices + // sorted reverse to the order returned to HidP_GetLinkCollectionNodeschild + // which seems to match teh original order, as long as no bit position needs to be considered + USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild; + int child_count = coll_number_of_direct_childs[collection_node_idx] - 1; + coll_child_order[collection_node_idx][child_count] = child_idx; + while (link_collection_nodes[child_idx].NextSibling) { + child_count--; + child_idx = link_collection_nodes[child_idx].NextSibling; + coll_child_order[collection_node_idx][child_count] = child_idx; + } + } + + if (coll_number_of_direct_childs[collection_node_idx] > 1) { + // Sort child collections indices by bit positions + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + for (int child_idx = 1; child_idx < coll_number_of_direct_childs[collection_node_idx]; child_idx++) { + // since the coll_bit_range array is not sorted, we need to reference the collection index in + // our sorted coll_child_order array, and look up the corresponding bit ranges for comparing values to sort + int prev_coll_idx = coll_child_order[collection_node_idx][child_idx - 1]; + int cur_coll_idx = coll_child_order[collection_node_idx][child_idx]; + if ((coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) && + (coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) && + (coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit)) { + // Swap position indices of the two compared child collections + USHORT idx_latch = coll_child_order[collection_node_idx][child_idx - 1]; + coll_child_order[collection_node_idx][child_idx - 1] = coll_child_order[collection_node_idx][child_idx]; + coll_child_order[collection_node_idx][child_idx] = idx_latch; + } + } + } + } + } + actual_coll_level++; + collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild; + } + else if (link_collection_nodes[collection_node_idx].NextSibling != 0) { + collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling; + } + else { + actual_coll_level--; + if (actual_coll_level >= 0) { + collection_node_idx = link_collection_nodes[collection_node_idx].Parent; + } + } + } + free(coll_parsed_flag); + } + + + // *************************************************************************************** + // Create sorted main_item_list containing all the Collection and CollectionEnd main items + // *************************************************************************************** + struct rd_main_item_node *main_item_list = NULL; // List root + // Lookup table to find the Collection items in the list by index + struct rd_main_item_node **coll_begin_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_begin_lookup)); + struct rd_main_item_node **coll_end_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_end_lookup)); + { + int *coll_last_written_child = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_last_written_child[0])); + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + coll_last_written_child[collection_node_idx] = -1; + } + + int actual_coll_level = 0; + USHORT collection_node_idx = 0; + struct rd_main_item_node *firstDelimiterNode = NULL; + struct rd_main_item_node *delimiterCloseNode = NULL; + coll_begin_lookup[0] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list); + while (actual_coll_level >= 0) { + if ((coll_number_of_direct_childs[collection_node_idx] != 0) && + (coll_last_written_child[collection_node_idx] == -1)) { + // Collection has child collections, but none is written to the list yet + + coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][0]; + collection_node_idx = coll_child_order[collection_node_idx][0]; + + // In a HID Report Descriptor, the first usage declared is the most preferred usage for the control. + // While the order in the WIN32 capabiliy strutures is the opposite: + // Here the preferred usage is the last aliased usage in the sequence. + + if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) { + // Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output) + firstDelimiterNode = main_item_list; + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list); + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list); + delimiterCloseNode = main_item_list; + } + else { + // Normal not aliased collection + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list); + actual_coll_level++; + } + + + } + else if ((coll_number_of_direct_childs[collection_node_idx] > 1) && + (coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][coll_number_of_direct_childs[collection_node_idx] - 1])) { + // Collection has child collections, and this is not the first child + + int nextChild = 1; + while (coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][nextChild - 1]) { + nextChild++; + } + coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][nextChild]; + collection_node_idx = coll_child_order[collection_node_idx][nextChild]; + + if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) { + // Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output) + firstDelimiterNode = main_item_list; + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list); + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list); + delimiterCloseNode = main_item_list; + } + else if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) { + coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode); + } + else if (!link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) { + coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode); + coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_open, 0, &firstDelimiterNode); + firstDelimiterNode = NULL; + main_item_list = delimiterCloseNode; + delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE + } + if (!link_collection_nodes[collection_node_idx].IsAlias) { + coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list); + actual_coll_level++; + } + } + else { + actual_coll_level--; + coll_end_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection_end, 0, &main_item_list); + collection_node_idx = link_collection_nodes[collection_node_idx].Parent; + } + } + free(coll_last_written_child); + } + + + // **************************************************************** + // Inserted Input/Output/Feature main items into the main_item_list + // in order of reconstructed bit positions + // **************************************************************** + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + // Add all value caps to node list + struct rd_main_item_node *firstDelimiterNode = NULL; + struct rd_main_item_node *delimiterCloseNode = NULL; + for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) { + struct rd_main_item_node *coll_begin = coll_begin_lookup[pp_data->caps[caps_idx].LinkCollection]; + int first_bit, last_bit; + first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8 + + pp_data->caps[caps_idx].BitPosition; + last_bit = first_bit + pp_data->caps[caps_idx].ReportSize * + pp_data->caps[caps_idx].ReportCount - 1; + + for (int child_idx = 0; child_idx < coll_number_of_direct_childs[pp_data->caps[caps_idx].LinkCollection]; child_idx++) { + // Determine in which section before/between/after child collection the item should be inserted + if (first_bit < coll_bit_range[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit) + { + // Note, that the default value for undefined coll_bit_range is -1, which can't be greater than the bit position + break; + } + coll_begin = coll_end_lookup[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]]; + } + struct rd_main_item_node *list_node; + list_node = rd_search_main_item_list_for_bit_position(first_bit, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &coll_begin); + + // In a HID Report Descriptor, the first usage declared is the most preferred usage for the control. + // While the order in the WIN32 capabiliy strutures is the opposite: + // Here the preferred usage is the last aliased usage in the sequence. + + if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode == NULL)) { + // Alliased Usage (First node in pp_data->caps -> Last entry in report descriptor output) + firstDelimiterNode = list_node; + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node); + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_close, pp_data->caps[caps_idx].ReportID, &list_node); + delimiterCloseNode = list_node; + } else if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) { + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node); + } + else if (!pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) { + // Alliased Collection (Last node in pp_data->caps -> First entry in report descriptor output) + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node); + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_open, pp_data->caps[caps_idx].ReportID, &list_node); + firstDelimiterNode = NULL; + list_node = delimiterCloseNode; + delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE + } + if (!pp_data->caps[caps_idx].IsAlias) { + rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &list_node); + } + } + } + + + // *********************************************************** + // Add const main items for padding to main_item_list + // -To fill all bit gaps + // -At each report end for 8bit padding + // Note that information about the padding at the report end, + // is not stored in the preparsed data, but in practice all + // report descriptors seem to have it, as assumed here. + // *********************************************************** + { + int last_bit_position[NUM_OF_HIDP_REPORT_TYPES][256]; + struct rd_main_item_node *last_report_item_lookup[NUM_OF_HIDP_REPORT_TYPES][256]; + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + last_bit_position[rt_idx][reportid_idx] = -1; + last_report_item_lookup[rt_idx][reportid_idx] = NULL; + } + } + + struct rd_main_item_node *list = main_item_list; // List root; + + while (list->next != NULL) + { + if ((list->MainItemType >= rd_input) && + (list->MainItemType <= rd_feature)) { + // INPUT, OUTPUT or FEATURE + if (list->FirstBit != -1) { + if ((last_bit_position[list->MainItemType][list->ReportID] + 1 != list->FirstBit) && + (last_report_item_lookup[list->MainItemType][list->ReportID] != NULL) && + (last_report_item_lookup[list->MainItemType][list->ReportID]->FirstBit != list->FirstBit) // Happens in case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array + ) { + struct rd_main_item_node *list_node = rd_search_main_item_list_for_bit_position(last_bit_position[list->MainItemType][list->ReportID], list->MainItemType, list->ReportID, &last_report_item_lookup[list->MainItemType][list->ReportID]); + rd_insert_main_item_node(last_bit_position[list->MainItemType][list->ReportID] + 1, list->FirstBit - 1, rd_item_node_padding, -1, 0, list->MainItemType, list->ReportID, &list_node); + } + last_bit_position[list->MainItemType][list->ReportID] = list->LastBit; + last_report_item_lookup[list->MainItemType][list->ReportID] = list; + } + } + list = list->next; + } + // Add 8 bit padding at each report end + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + if (last_bit_position[rt_idx][reportid_idx] != -1) { + int padding = 8 - ((last_bit_position[rt_idx][reportid_idx] + 1) % 8); + if (padding < 8) { + // Insert padding item after item referenced in last_report_item_lookup + rd_insert_main_item_node(last_bit_position[rt_idx][reportid_idx] + 1, last_bit_position[rt_idx][reportid_idx] + padding, rd_item_node_padding, -1, 0, (rd_main_items) rt_idx, (unsigned char) reportid_idx, &last_report_item_lookup[rt_idx][reportid_idx]); + } + } + } + } + } + + + // *********************************** + // Encode the report descriptor output + // *********************************** + UCHAR last_report_id = 0; + USAGE last_usage_page = 0; + LONG last_physical_min = 0;// If both, Physical Minimum and Physical Maximum are 0, the logical limits should be taken as physical limits according USB HID spec 1.11 chapter 6.2.2.7 + LONG last_physical_max = 0; + ULONG last_unit_exponent = 0; // If Unit Exponent is Undefined it should be considered as 0 according USB HID spec 1.11 chapter 6.2.2.7 + ULONG last_unit = 0; // If the first nibble is 7, or second nibble of Unit is 0, the unit is None according USB HID spec 1.11 chapter 6.2.2.7 + BOOLEAN inhibit_write_of_usage = FALSE; // Needed in case of delimited usage print, before the normal collection or cap + int report_count = 0; + while (main_item_list != NULL) + { + int rt_idx = main_item_list->MainItemType; + int caps_idx = main_item_list->CapsIndex; + if (main_item_list->MainItemType == rd_collection) { + if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) { + // Write "Usage Page" at the begin of a collection - except it refers the same table as wrote last + rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc); + last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage; + } + if (inhibit_write_of_usage) { + // Inhibit only once after DELIMITER statement + inhibit_write_of_usage = FALSE; + } + else { + // Write "Usage" of collection + rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc); + } + // Write begin of "Collection" + rd_write_short_item(rd_main_collection, link_collection_nodes[main_item_list->CollectionIndex].CollectionType, &rpt_desc); + } + else if (main_item_list->MainItemType == rd_collection_end) { + // Write "End Collection" + rd_write_short_item(rd_main_collection_end, 0, &rpt_desc); + } + else if (main_item_list->MainItemType == rd_delimiter_open) { + if (main_item_list->CollectionIndex != -1) { + // Write "Usage Page" inside of a collection delmiter section + if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) { + rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc); + last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage; + } + } + else if (main_item_list->CapsIndex != 0) { + // Write "Usage Page" inside of a main item delmiter section + if (pp_data->caps[caps_idx].UsagePage != last_usage_page) { + rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc); + last_usage_page = pp_data->caps[caps_idx].UsagePage; + } + } + // Write "Delimiter Open" + rd_write_short_item(rd_local_delimiter, 1, &rpt_desc); // 1 = open set of aliased usages + } + else if (main_item_list->MainItemType == rd_delimiter_usage) { + if (main_item_list->CollectionIndex != -1) { + // Write aliased collection "Usage" + rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc); + } if (main_item_list->CapsIndex != 0) { + // Write aliased main item range from "Usage Minimum" to "Usage Maximum" + if (pp_data->caps[caps_idx].IsRange) { + rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc); + rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc); + } + else { + // Write single aliased main item "Usage" + rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc); + } + } + } + else if (main_item_list->MainItemType == rd_delimiter_close) { + // Write "Delimiter Close" + rd_write_short_item(rd_local_delimiter, 0, &rpt_desc); // 0 = close set of aliased usages + // Inhibit next usage write + inhibit_write_of_usage = TRUE; + } + else if (main_item_list->TypeOfNode == rd_item_node_padding) { + // Padding + // The preparsed data doesn't contain any information about padding. Therefore all undefined gaps + // in the reports are filled with the same style of constant padding. + + // Write "Report Size" with number of padding bits + rd_write_short_item(rd_global_report_size, (main_item_list->LastBit - main_item_list->FirstBit + 1), &rpt_desc); + + // Write "Report Count" for padding always as 1 + rd_write_short_item(rd_global_report_count, 1, &rpt_desc); + + if (rt_idx == HidP_Input) { + // Write "Input" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const + rd_write_short_item(rd_main_input, 0x03, &rpt_desc); // Const / Abs + } + else if (rt_idx == HidP_Output) { + // Write "Output" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const + rd_write_short_item(rd_main_output, 0x03, &rpt_desc); // Const / Abs + } + else if (rt_idx == HidP_Feature) { + // Write "Feature" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const + rd_write_short_item(rd_main_feature, 0x03, &rpt_desc); // Const / Abs + } + report_count = 0; + } + else if (pp_data->caps[caps_idx].IsButtonCap) { + // Button + // (The preparsed data contain different data for 1 bit Button caps, than for parametric Value caps) + + if (last_report_id != pp_data->caps[caps_idx].ReportID) { + // Write "Report ID" if changed + rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc); + last_report_id = pp_data->caps[caps_idx].ReportID; + } + + // Write "Usage Page" when changed + if (pp_data->caps[caps_idx].UsagePage != last_usage_page) { + rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc); + last_usage_page = pp_data->caps[caps_idx].UsagePage; + } + + // Write only local report items for each cap, if ReportCount > 1 + if (pp_data->caps[caps_idx].IsRange) { + report_count += (pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin); + } + + if (inhibit_write_of_usage) { + // Inhibit only once after Delimiter - Reset flag + inhibit_write_of_usage = FALSE; + } + else { + if (pp_data->caps[caps_idx].IsRange) { + // Write range from "Usage Minimum" to "Usage Maximum" + rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc); + rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc); + } + else { + // Write single "Usage" + rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc); + } + } + + if (pp_data->caps[caps_idx].IsDesignatorRange) { + // Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum" + rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc); + rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) { + // Designator set 0 is a special descriptor set (of the HID Physical Descriptor), + // that specifies the number of additional descriptor sets. + // Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "Designator Index" + rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc); + } + + if (pp_data->caps[caps_idx].IsStringRange) { + // Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum" + rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc); + rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) { + // String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages, + // therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "String Index" + rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc); + } + + if ((main_item_list->next != NULL) && + ((int)main_item_list->next->MainItemType == rt_idx) && + (main_item_list->next->TypeOfNode == rd_item_node_cap) && + (pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) && + (!pp_data->caps[caps_idx].IsRange) && // This node in list is no array + (!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array + (pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) && + (pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField) + ) { + if (main_item_list->next->FirstBit != main_item_list->FirstBit) { + // In case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array, the report count should be incremented + + // Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields + report_count++; + } + } + else { + + if ((pp_data->caps[caps_idx].Button.LogicalMin == 0) && + (pp_data->caps[caps_idx].Button.LogicalMax == 0)) { + // While a HID report descriptor must always contain LogicalMinimum and LogicalMaximum, + // the preparsed data contain both fields set to zero, for the case of simple buttons + // Write "Logical Minimum" set to 0 and "Logical Maximum" set to 1 + rd_write_short_item(rd_global_logical_minimum, 0, &rpt_desc); + rd_write_short_item(rd_global_logical_maximum, 1, &rpt_desc); + } + else { + // Write logical range from "Logical Minimum" to "Logical Maximum" + rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].Button.LogicalMin, &rpt_desc); + rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].Button.LogicalMax, &rpt_desc); + } + + // Write "Report Size" + rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc); + + // Write "Report Count" + if (!pp_data->caps[caps_idx].IsRange) { + // Variable bit field with one bit per button + // In case of multiple usages with the same items, only "Usage" is written per cap, and "Report Count" is incremented + rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc); + } + else { + // Button array of "Report Size" x "Report Count + rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount, &rpt_desc); + } + + + // Buttons have only 1 bit and therefore no physical limits/units -> Set to undefined state + if (last_physical_min != 0) { + // Write "Physical Minimum", but only if changed + last_physical_min = 0; + rd_write_short_item(rd_global_physical_minimum, last_physical_min, &rpt_desc); + } + if (last_physical_max != 0) { + // Write "Physical Maximum", but only if changed + last_physical_max = 0; + rd_write_short_item(rd_global_physical_maximum, last_physical_max, &rpt_desc); + } + if (last_unit_exponent != 0) { + // Write "Unit Exponent", but only if changed + last_unit_exponent = 0; + rd_write_short_item(rd_global_unit_exponent, last_unit_exponent, &rpt_desc); + } + if (last_unit != 0) { + // Write "Unit",but only if changed + last_unit = 0; + rd_write_short_item(rd_global_unit, last_unit, &rpt_desc); + } + + // Write "Input" main item + if (rt_idx == HidP_Input) { + rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + // Write "Output" main item + else if (rt_idx == HidP_Output) { + rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + // Write "Feature" main item + else if (rt_idx == HidP_Feature) { + rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + report_count = 0; + } + } + else { + + if (last_report_id != pp_data->caps[caps_idx].ReportID) { + // Write "Report ID" if changed + rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc); + last_report_id = pp_data->caps[caps_idx].ReportID; + } + + // Write "Usage Page" if changed + if (pp_data->caps[caps_idx].UsagePage != last_usage_page) { + rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc); + last_usage_page = pp_data->caps[caps_idx].UsagePage; + } + + if (inhibit_write_of_usage) { + // Inhibit only once after Delimiter - Reset flag + inhibit_write_of_usage = FALSE; + } + else { + if (pp_data->caps[caps_idx].IsRange) { + // Write usage range from "Usage Minimum" to "Usage Maximum" + rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc); + rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc); + } + else { + // Write single "Usage" + rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc); + } + } + + if (pp_data->caps[caps_idx].IsDesignatorRange) { + // Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum" + rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc); + rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) { + // Designator set 0 is a special descriptor set (of the HID Physical Descriptor), + // that specifies the number of additional descriptor sets. + // Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "Designator Index" + rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc); + } + + if (pp_data->caps[caps_idx].IsStringRange) { + // Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum" + rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc); + rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc); + } + else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) { + // String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages, + // therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it. + // Write single "String Index" + rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc); + } + + if ((pp_data->caps[caps_idx].BitField & 0x02) != 0x02) { + // In case of an value array overwrite "Report Count" + pp_data->caps[caps_idx].ReportCount = pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin + 1; + } + + + // Print only local report items for each cap, if ReportCount > 1 + if ((main_item_list->next != NULL) && + ((int) main_item_list->next->MainItemType == rt_idx) && + (main_item_list->next->TypeOfNode == rd_item_node_cap) && + (!pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) && + (!pp_data->caps[caps_idx].IsRange) && // This node in list is no array + (!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array + (pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMin == pp_data->caps[caps_idx].NotButton.LogicalMin) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMax == pp_data->caps[caps_idx].NotButton.LogicalMax) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMin == pp_data->caps[caps_idx].NotButton.PhysicalMin) && + (pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMax == pp_data->caps[caps_idx].NotButton.PhysicalMax) && + (pp_data->caps[main_item_list->next->CapsIndex].UnitsExp == pp_data->caps[caps_idx].UnitsExp) && + (pp_data->caps[main_item_list->next->CapsIndex].Units == pp_data->caps[caps_idx].Units) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportSize == pp_data->caps[caps_idx].ReportSize) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) && + (pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField) && + (pp_data->caps[main_item_list->next->CapsIndex].ReportCount == 1) && + (pp_data->caps[caps_idx].ReportCount == 1) + ) { + // Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields + report_count++; + } + else { + // Value + + // Write logical range from "Logical Minimum" to "Logical Maximum" + rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].NotButton.LogicalMin, &rpt_desc); + rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].NotButton.LogicalMax, &rpt_desc); + + if ((last_physical_min != pp_data->caps[caps_idx].NotButton.PhysicalMin) || + (last_physical_max != pp_data->caps[caps_idx].NotButton.PhysicalMax)) { + // Write range from "Physical Minimum" to " Physical Maximum", but only if one of them changed + rd_write_short_item(rd_global_physical_minimum, pp_data->caps[caps_idx].NotButton.PhysicalMin, &rpt_desc); + last_physical_min = pp_data->caps[caps_idx].NotButton.PhysicalMin; + rd_write_short_item(rd_global_physical_maximum, pp_data->caps[caps_idx].NotButton.PhysicalMax, &rpt_desc); + last_physical_max = pp_data->caps[caps_idx].NotButton.PhysicalMax; + } + + + if (last_unit_exponent != pp_data->caps[caps_idx].UnitsExp) { + // Write "Unit Exponent", but only if changed + rd_write_short_item(rd_global_unit_exponent, pp_data->caps[caps_idx].UnitsExp, &rpt_desc); + last_unit_exponent = pp_data->caps[caps_idx].UnitsExp; + } + + if (last_unit != pp_data->caps[caps_idx].Units) { + // Write physical "Unit", but only if changed + rd_write_short_item(rd_global_unit, pp_data->caps[caps_idx].Units, &rpt_desc); + last_unit = pp_data->caps[caps_idx].Units; + } + + // Write "Report Size" + rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc); + + // Write "Report Count" + rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc); + + if (rt_idx == HidP_Input) { + // Write "Input" main item + rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + else if (rt_idx == HidP_Output) { + // Write "Output" main item + rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + else if (rt_idx == HidP_Feature) { + // Write "Feature" main item + rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc); + } + report_count = 0; + } + } + + // Go to next item in main_item_list and free the memory of the actual item + struct rd_main_item_node *main_item_list_prev = main_item_list; + main_item_list = main_item_list->next; + free(main_item_list_prev); + } + + // Free multidimensionable array: coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE] + // Free multidimensionable array: coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX] + for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) { + for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) { + for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) { + free(coll_bit_range[collection_node_idx][reportid_idx][rt_idx]); + } + free(coll_bit_range[collection_node_idx][reportid_idx]); + } + free(coll_bit_range[collection_node_idx]); + if (coll_number_of_direct_childs[collection_node_idx] != 0) free(coll_child_order[collection_node_idx]); + } + free(coll_bit_range); + free(coll_child_order); + + // Free one dimensional arrays + free(coll_begin_lookup); + free(coll_end_lookup); + free(coll_levels); + free(coll_number_of_direct_childs); + + return (int) rpt_desc.byte_idx; +} diff --git a/hidapi/windows/hidapi_descriptor_reconstruct.h b/hidapi/windows/hidapi_descriptor_reconstruct.h new file mode 100644 index 0000000..2ec8b20 --- /dev/null +++ b/hidapi/windows/hidapi_descriptor_reconstruct.h @@ -0,0 +1,238 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ +#ifndef HIDAPI_DESCRIPTOR_RECONSTRUCT_H__ +#define HIDAPI_DESCRIPTOR_RECONSTRUCT_H__ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +/* Do not warn about wcsncpy usage. + https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "hidapi_winapi.h" + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4200) +#pragma warning(disable: 4201) +#endif + +#include + +#include "hidapi_hidsdi.h" + +#define NUM_OF_HIDP_REPORT_TYPES 3 + +typedef enum rd_items_ { + rd_main_input = 0x80, /* 1000 00 nn */ + rd_main_output = 0x90, /* 1001 00 nn */ + rd_main_feature = 0xB0, /* 1011 00 nn */ + rd_main_collection = 0xA0, /* 1010 00 nn */ + rd_main_collection_end = 0xC0, /* 1100 00 nn */ + rd_global_usage_page = 0x04, /* 0000 01 nn */ + rd_global_logical_minimum = 0x14, /* 0001 01 nn */ + rd_global_logical_maximum = 0x24, /* 0010 01 nn */ + rd_global_physical_minimum = 0x34, /* 0011 01 nn */ + rd_global_physical_maximum = 0x44, /* 0100 01 nn */ + rd_global_unit_exponent = 0x54, /* 0101 01 nn */ + rd_global_unit = 0x64, /* 0110 01 nn */ + rd_global_report_size = 0x74, /* 0111 01 nn */ + rd_global_report_id = 0x84, /* 1000 01 nn */ + rd_global_report_count = 0x94, /* 1001 01 nn */ + rd_global_push = 0xA4, /* 1010 01 nn */ + rd_global_pop = 0xB4, /* 1011 01 nn */ + rd_local_usage = 0x08, /* 0000 10 nn */ + rd_local_usage_minimum = 0x18, /* 0001 10 nn */ + rd_local_usage_maximum = 0x28, /* 0010 10 nn */ + rd_local_designator_index = 0x38, /* 0011 10 nn */ + rd_local_designator_minimum = 0x48, /* 0100 10 nn */ + rd_local_designator_maximum = 0x58, /* 0101 10 nn */ + rd_local_string = 0x78, /* 0111 10 nn */ + rd_local_string_minimum = 0x88, /* 1000 10 nn */ + rd_local_string_maximum = 0x98, /* 1001 10 nn */ + rd_local_delimiter = 0xA8 /* 1010 10 nn */ +} rd_items; + +typedef enum rd_main_items_ { + rd_input = HidP_Input, + rd_output = HidP_Output, + rd_feature = HidP_Feature, + rd_collection, + rd_collection_end, + rd_delimiter_open, + rd_delimiter_usage, + rd_delimiter_close, +} rd_main_items; + +typedef struct rd_bit_range_ { + int FirstBit; + int LastBit; +} rd_bit_range; + +typedef enum rd_item_node_type_ { + rd_item_node_cap, + rd_item_node_padding, + rd_item_node_collection, +} rd_node_type; + +struct rd_main_item_node { + int FirstBit; /* Position of first bit in report (counting from 0) */ + int LastBit; /* Position of last bit in report (counting from 0) */ + rd_node_type TypeOfNode; /* Information if caps index refers to the array of button caps, value caps, + or if the node is just a padding element to fill unused bit positions. + The node can also be a collection node without any bits in the report. */ + int CapsIndex; /* Index in the array of caps */ + int CollectionIndex; /* Index in the array of link collections */ + rd_main_items MainItemType; /* Input, Output, Feature, Collection or Collection End */ + unsigned char ReportID; + struct rd_main_item_node* next; +}; + +typedef struct hid_pp_caps_info_ { + USHORT FirstCap; + USHORT NumberOfCaps; // Includes empty caps after LastCap + USHORT LastCap; + USHORT ReportByteLength; +} hid_pp_caps_info, *phid_pp_caps_info; + +typedef struct hid_pp_link_collection_node_ { + USAGE LinkUsage; + USAGE LinkUsagePage; + USHORT Parent; + USHORT NumberOfChildren; + USHORT NextSibling; + USHORT FirstChild; + ULONG CollectionType : 8; + ULONG IsAlias : 1; + ULONG Reserved : 23; + // Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end +} hid_pp_link_collection_node, *phid_pp_link_collection_node; + +typedef struct hidp_unknown_token_ { + UCHAR Token; /* Specifies the one-byte prefix of a global item. */ + UCHAR Reserved[3]; + ULONG BitField; /* Specifies the data part of the global item. */ +} hidp_unknown_token, * phidp_unknown_token; + +typedef struct hid_pp_cap_ { + USAGE UsagePage; + UCHAR ReportID; + UCHAR BitPosition; + USHORT ReportSize; // WIN32 term for this is BitSize + USHORT ReportCount; + USHORT BytePosition; + USHORT BitCount; + ULONG BitField; + USHORT NextBytePosition; + USHORT LinkCollection; + USAGE LinkUsagePage; + USAGE LinkUsage; + + // Start of 8 Flags in one byte + BOOLEAN IsMultipleItemsForArray:1; + + BOOLEAN IsPadding:1; + BOOLEAN IsButtonCap:1; + BOOLEAN IsAbsolute:1; + BOOLEAN IsRange:1; + BOOLEAN IsAlias:1; // IsAlias is set to TRUE in the first n-1 capability structures added to the capability array. IsAlias set to FALSE in the nth capability structure. + BOOLEAN IsStringRange:1; + BOOLEAN IsDesignatorRange:1; + // End of 8 Flags in one byte + BOOLEAN Reserved1[3]; + + hidp_unknown_token UnknownTokens[4]; // 4 x 8 Byte + + union { + struct { + USAGE UsageMin; + USAGE UsageMax; + USHORT StringMin; + USHORT StringMax; + USHORT DesignatorMin; + USHORT DesignatorMax; + USHORT DataIndexMin; + USHORT DataIndexMax; + } Range; + struct { + USAGE Usage; + USAGE Reserved1; + USHORT StringIndex; + USHORT Reserved2; + USHORT DesignatorIndex; + USHORT Reserved3; + USHORT DataIndex; + USHORT Reserved4; + } NotRange; + }; + union { + struct { + LONG LogicalMin; + LONG LogicalMax; + } Button; + struct { + BOOLEAN HasNull; + UCHAR Reserved4[3]; + LONG LogicalMin; + LONG LogicalMax; + LONG PhysicalMin; + LONG PhysicalMax; + } NotButton; + }; + ULONG Units; + ULONG UnitsExp; + +} hid_pp_cap, *phid_pp_cap; + +typedef struct hidp_preparsed_data_ { + UCHAR MagicKey[8]; + USAGE Usage; + USAGE UsagePage; + USHORT Reserved[2]; + + // CAPS structure for Input, Output and Feature + hid_pp_caps_info caps_info[3]; + + USHORT FirstByteOfLinkCollectionArray; + USHORT NumberLinkCollectionNodes; + +#if defined(__MINGW32__) || defined(__CYGWIN__) + // MINGW fails with: Flexible array member in union not supported + // Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html + union { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + hid_pp_cap caps[0]; + hid_pp_link_collection_node LinkCollectionArray[0]; +#pragma GCC diagnostic pop + }; +#else + union { + hid_pp_cap caps[]; + hid_pp_link_collection_node LinkCollectionArray[]; + }; +#endif + +} hidp_preparsed_data; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/hidapi/windows/hidapi_hidpi.h b/hidapi/windows/hidapi_hidpi.h index 343ddeb..75a5812 100644 --- a/hidapi/windows/hidapi_hidpi.h +++ b/hidapi/windows/hidapi_hidpi.h @@ -29,6 +29,13 @@ /* This part of the header mimics hidpi.h, but only what is used by HIDAPI */ +typedef enum _HIDP_REPORT_TYPE +{ + HidP_Input, + HidP_Output, + HidP_Feature +} HIDP_REPORT_TYPE; + typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; typedef struct _HIDP_CAPS diff --git a/hidapi/windows/hidapi_winapi.h b/hidapi/windows/hidapi_winapi.h index 884712a..43071f5 100644 --- a/hidapi/windows/hidapi_winapi.h +++ b/hidapi/windows/hidapi_winapi.h @@ -26,6 +26,8 @@ #ifndef HIDAPI_WINAPI_H__ #define HIDAPI_WINAPI_H__ +#include + #include #include "hidapi.h" @@ -51,6 +53,20 @@ extern "C" { */ int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id); + /** + * @brief Reconstructs a HID Report Descriptor from a Win32 HIDP_PREPARSED_DATA structure. + * This reconstructed report descriptor is logical identical to the real report descriptor, + * but not byte wise identical. + * + * @param[in] hidp_preparsed_data Pointer to the HIDP_PREPARSED_DATA to read, i.e.: the value of PHIDP_PREPARSED_DATA, + * as returned by HidD_GetPreparsedData WinAPI function. + * @param buf Pointer to the buffer where the report descriptor should be stored. + * @param[in] buf_size Size of the buffer. The recommended size for the buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + * + * @return Returns size of reconstructed report descriptor if successful, -1 for error. + */ + int HID_API_EXPORT_CALL hid_winapi_descriptor_reconstruct_pp_data(void *hidp_preparsed_data, unsigned char *buf, size_t buf_size); + #ifdef __cplusplus } #endif From a016a888dbf6c22ea604e985ee4f6209c8dee4fb Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Tue, 23 May 2023 14:42:36 +0200 Subject: [PATCH 14/17] implement GetReportDescriptor() (hid_get_report_descriptor()) --- hid_disabled.go | 5 +++++ hid_enabled.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/hid_disabled.go b/hid_disabled.go index 5a7a5dd..025cfc3 100644 --- a/hid_disabled.go +++ b/hid_disabled.go @@ -103,3 +103,8 @@ func (dev *Device) SetNonblocking(b bool) error { func (dev *Device) GetDeviceInfo() (*DeviceInfo, error) { return nil, ErrUnsupportedPlatform } + +// GetDeviceInfo gets a report descriptor from a HID device. +func (dev *Device) GetReportDescriptor() ([]byte, error) { + return nil, ErrUnsupportedPlatform +} diff --git a/hid_enabled.go b/hid_enabled.go index c958486..12791e6 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -510,3 +510,39 @@ func (dev *Device) GetDeviceInfo() (*DeviceInfo, error) { return info, nil } + +// GetDeviceInfo gets a report descriptor from a HID device. +func (dev *Device) GetReportDescriptor() ([]byte, error) { + // Abort if device closed in between + dev.lock.Lock() + device := dev.device + dev.lock.Unlock() + + if device == nil { + return nil, ErrDeviceClosed + } + + b := make([]byte, C.HID_API_MAX_REPORT_DESCRIPTOR_SIZE) + + read := int(C.hid_get_report_descriptor(device, (*C.uchar)(&b[0]), C.size_t(len(b)))) + if read == -1 { + // If the read failed, verify if closed or other error + dev.lock.Lock() + device = dev.device + dev.lock.Unlock() + + if device == nil { + return nil, ErrDeviceClosed + } + + // Device not closed, some other error occurred + message := C.hid_error(device) + if message == nil { + return nil, errors.New("hidapi: unknown failure") + } + failure, _ := wcharTToString(message) + return nil, errors.New("hidapi: " + failure) + } + + return b[:read], nil +} From 53e13007d42d0408a055337342fd0e858b246d6e Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Tue, 23 May 2023 15:09:25 +0200 Subject: [PATCH 15/17] call hid_init() on init This function should be called at the beginning of execution however, if there is a chance of HIDAPI handles being opened by different threads simultaneously. --- hid_enabled.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hid_enabled.go b/hid_enabled.go index 12791e6..8ffbe52 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -57,6 +57,14 @@ import ( "unsafe" ) +func init() { + // just to be sure: from the docs: + // This function should be called at the beginning of + // execution however, if there is a chance of HIDAPI handles + // being opened by different threads simultaneously. + C.hid_init() +} + // enumerateLock is a mutex serializing access to USB device enumeration needed // by the macOS USB HID system calls, which require 2 consecutive method calls // for enumeration, causing crashes if called concurrently. From bcfcd0719e6628d060fa61564c527db5e3b4b5d4 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Mon, 4 Sep 2023 15:09:20 +0200 Subject: [PATCH 16/17] add OpenByPath() function --- hid_disabled.go | 6 ++++++ hid_enabled.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/hid_disabled.go b/hid_disabled.go index 025cfc3..cd78e2f 100644 --- a/hid_disabled.go +++ b/hid_disabled.go @@ -28,6 +28,12 @@ type Device struct { DeviceInfo // Embed the infos for easier access } +// OpenByPath connects to an HID device by the given path name. On platforms that this file +// implements the method just returns an error. +func OpenByPath(p string) (*Device, error) { + return nil, ErrUnsupportedPlatform +} + // Open connects to an HID device by its path name. On platforms that this file // implements the method just returns an error. func (info DeviceInfo) Open() (*Device, error) { diff --git a/hid_enabled.go b/hid_enabled.go index 8ffbe52..05192a6 100644 --- a/hid_enabled.go +++ b/hid_enabled.go @@ -151,6 +151,48 @@ type Device struct { lock sync.Mutex } +// OpenByPath open the HID USB device by path and return a device handle. +func OpenByPath(p string) (*Device, error) { + path := C.CString(p) + defer C.free(unsafe.Pointer(path)) + + device := C.hid_open_path(path) + if device == nil { + return nil, errors.New("hidapi: failed to open device") + } + + info := C.hid_get_device_info(device) + if info == nil { + return nil, errors.New("hidapi: failed to query device info") + } + + dev := &Device{ + device: device, + DeviceInfo: DeviceInfo{ + Path: C.GoString(info.path), + VendorID: uint16(info.vendor_id), + ProductID: uint16(info.product_id), + Release: uint16(info.release_number), + UsagePage: uint16(info.usage_page), + Usage: uint16(info.usage), + Interface: int(info.interface_number), + BusType: BusType(info.bus_type), + }, + } + + if info.serial_number != nil { + dev.Serial, _ = wcharTToString(info.serial_number) + } + if info.product_string != nil { + dev.Product, _ = wcharTToString(info.product_string) + } + if info.manufacturer_string != nil { + dev.Manufacturer, _ = wcharTToString(info.manufacturer_string) + } + + return dev, nil +} + // Close releases the HID USB device handle. func (dev *Device) Close() error { dev.lock.Lock() From 0ab1e455cb24b90c71c330baf9d12d5249c535f5 Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Wed, 13 Sep 2023 12:41:12 +0200 Subject: [PATCH 17/17] ci: cross compile to windows to build-check the 'disabled' path --- .github/workflows/go.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 04c27b4..0b23a9a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -44,3 +44,7 @@ jobs: - name: Test hidraw run: go test -tags hidraw -v ./... if: startsWith(matrix.os,'ubuntu') + + - name: Cross compile to win (hid disabled) + run: GOOS=windows go build -v ./... + if: startsWith(matrix.os,'ubuntu')