From 1feadb5f78e1d2cdfd2aaf0011e041c2848bae37 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Tue, 5 Oct 2010 12:29:32 +0100 Subject: [PATCH] Windows: use IOCTLs for HID input, output and feature reports * fixes feature reports not providing actual read size (reported by Axel Rohde http://marc.info/?m=127033070021994) * removes the USE_HIDD_FOR_REPORTS macro * IOCTL usage inspired from HIDAPI by Alan Ott --- libusb/os/windows_usb.c | 198 +++++++++++------------------------------------- libusb/os/windows_usb.h | 19 ++++- 2 files changed, 62 insertions(+), 155 deletions(-) diff --git a/libusb/os/windows_usb.c b/libusb/os/windows_usb.c index 210a1fe..019de55 100644 --- a/libusb/os/windows_usb.c +++ b/libusb/os/windows_usb.c @@ -3,6 +3,7 @@ * Copyright (c) 2009-2010 Pete Batard * With contributions from Michael Plante, Orin Eman et al. * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * HID Reports IOCTLs inspired from HIDAPI by Alan Ott, Signal 11 Software * Major code testing contribution by Xiaofan Chen * * This library is free software; you can redistribute it and/or @@ -21,13 +22,6 @@ */ // COMPILATION OPTIONS: -// - Use HidD_(G/S)et(In/Out)putReport instead of (Read/Write)File for HID -// Note that http://msdn.microsoft.com/en-us/library/ms789883.aspx: -// "In addition, some devices might not support HidD_GetInputReport, -// and will become unresponsive if this routine is used." -// => Don't blame libusb if you can't read or write HID reports when the -// option below is enabled. -#define USE_HIDD_FOR_REPORTS // - Should libusb automatically claim (and release) the interfaces it requires? #define AUTO_CLAIM // - Forces instant overlapped completion on timeouts: can prevents extensive @@ -45,6 +39,7 @@ #include #include #include // for string to GUID conv. requires libole32.a +#include #include #include "poll_windows.h" @@ -3197,10 +3192,11 @@ static int _hid_get_descriptor(struct hid_device_priv* dev, HANDLE hid_handle, i } static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int id, void *data, - struct windows_transfer_priv *tp, size_t *size, OVERLAPPED* overlapped) + struct windows_transfer_priv *tp, size_t *size, OVERLAPPED* overlapped, + int report_type) { uint8_t *buf; - DWORD read_size, expected_size = (DWORD)*size; + DWORD ioctl_code, read_size, expected_size = (DWORD)*size; int r = LIBUSB_SUCCESS; if (tp->hid_buffer != NULL) { @@ -3212,6 +3208,18 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i return LIBUSB_ERROR_INVALID_PARAM; } + switch (report_type) { + case HID_REPORT_TYPE_INPUT: + ioctl_code = IOCTL_HID_GET_INPUT_REPORT; + break; + case HID_REPORT_TYPE_FEATURE: + ioctl_code = IOCTL_HID_GET_FEATURE; + break; + default: + usbi_dbg("unknown HID report type %d", report_type); + return LIBUSB_ERROR_INVALID_PARAM; + } + // When report IDs are not in use, add an extra byte for the report ID if (id==0) { expected_size++; @@ -3225,16 +3233,12 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i buf[0] = (uint8_t)id; // Must be set always usbi_dbg("report ID: 0x%02X", buf[0]); - // NB: HidD_GetInputReport sends an request to the device for the Input Report - // (and blocks until response) whereas ReadFile waits for input to be generated - // asynchronously -#if !defined(USE_HIDD_FOR_REPORTS) - // Use ReadFile instead of HidD_GetInputReport for async I/O - // TODO: send a request paquet? tp->hid_expected_size = expected_size; - if (!ReadFile(hid_handle, buf, expected_size+1, &read_size, overlapped)) { + + if (!DeviceIoControl(hid_handle, ioctl_code, buf, expected_size+1, + buf, expected_size+1, &read_size, overlapped)) { if (GetLastError() != ERROR_IO_PENDING) { - usbi_dbg("Failed to Read HID Input Report: %s", windows_error_str(0)); + usbi_dbg("Failed to Read HID Report: %s", windows_error_str(0)); safe_free(buf); return LIBUSB_ERROR_IO; } @@ -3243,15 +3247,7 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i tp->hid_dest = data; // copy dest, as not necessarily the start of the transfer buffer return LIBUSB_SUCCESS; } -#else - // Synchronous request for the Input Report - if (!HidD_GetInputReport(hid_handle, buf, expected_size)) { - usbi_dbg("Failed to Read HID Input Report: %s", windows_error_str(0)); - safe_free(buf); - return LIBUSB_ERROR_IO; - } - read_size = expected_size; // Can't detect overflows with this API -#endif + // Transfer completed synchronously => copy and discard extra buffer if (read_size == 0) { usbi_dbg("program assertion failed - read completed synchronously, but no data was read"); @@ -3281,10 +3277,11 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i } static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int id, void *data, - struct windows_transfer_priv *tp, size_t *size, OVERLAPPED* overlapped) + struct windows_transfer_priv *tp, size_t *size, OVERLAPPED* overlapped, + int report_type) { uint8_t *buf = NULL; - DWORD write_size= (DWORD)*size; + DWORD ioctl_code, write_size= (DWORD)*size; if (tp->hid_buffer != NULL) { usbi_dbg("program assertion failed: hid_buffer is not NULL"); @@ -3295,6 +3292,18 @@ static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int i return LIBUSB_ERROR_INVALID_PARAM; } + switch (report_type) { + case HID_REPORT_TYPE_OUTPUT: + ioctl_code = IOCTL_HID_SET_OUTPUT_REPORT; + break; + case HID_REPORT_TYPE_FEATURE: + ioctl_code = IOCTL_HID_SET_FEATURE; + break; + default: + usbi_dbg("unknown HID report type %d", report_type); + return LIBUSB_ERROR_INVALID_PARAM; + } + usbi_dbg("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 @@ -3317,9 +3326,8 @@ static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int i } } -#if !defined(USE_HIDD_FOR_REPORTS) - // Une WriteFile instead of HidD_SetOutputReport for async I/O - if (!WriteFile(hid_handle, buf, write_size, &write_size, overlapped)) { + if (!DeviceIoControl(hid_handle, ioctl_code, buf, write_size, + buf, write_size, &write_size, overlapped)) { if (GetLastError() != ERROR_IO_PENDING) { usbi_dbg("Failed to Write HID Output Report: %s", windows_error_str(0)); safe_free(buf); @@ -3329,15 +3337,7 @@ static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int i tp->hid_dest = NULL; return LIBUSB_SUCCESS; } -#else - if (!HidD_SetOutputReport(hid_handle, buf, write_size)) { - usbi_dbg("Failed to Write HID Output Report: %s", windows_error_str(0)); - if (id == 0) { - safe_free(buf); - } - return LIBUSB_ERROR_IO; - } -#endif + // Transfer completed synchronously if (write_size == 0) { usbi_dbg("program assertion failed - write completed synchronously, but no data was written"); @@ -3349,100 +3349,6 @@ static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int i return LIBUSB_COMPLETED; } -static int _hid_get_feature(struct hid_device_priv* dev, HANDLE hid_handle, int id, void *data, size_t *size) -{ - uint8_t *buf = (uint8_t*)data; // default with report ID is to use data - ULONG read_size = (ULONG)*size; - int r = LIBUSB_ERROR_OTHER; - uint32_t err; - - if ((*size == 0) || (*size > MAX_HID_REPORT_SIZE)) { - usbi_dbg("invalid size (%d)", *size); - return LIBUSB_ERROR_INVALID_PARAM; - } - - // When report IDs are not in use, we must prefix an extra zero ID - if (id == 0) { - read_size++; - buf = (uint8_t*)calloc(1, read_size); - if (buf == NULL) { - return LIBUSB_ERROR_NO_MEM; - } - } - buf[0] = (uint8_t)id; - usbi_dbg("report ID: 0x%02X", buf[0]); - - if (HidD_GetFeature(hid_handle, buf, read_size)) { - if (buf[0] != id) { - usbi_warn(NULL, "mismatched report ID (data is %02X, parameter is %02X)", buf[0], id); - } - if (id == 0) { - memcpy(data, buf+1, *size); - } - r = LIBUSB_COMPLETED; - } else { - err = GetLastError(); - switch (err) { - case ERROR_INVALID_FUNCTION: - r = LIBUSB_ERROR_NOT_FOUND; - break; - default: - usbi_dbg("error %s", windows_error_str(err)); - r = LIBUSB_ERROR_OTHER; - break; - } - } - if (id == 0) { - safe_free(buf); - } - return r; -} - -static int _hid_set_feature(struct hid_device_priv* dev, HANDLE hid_handle, int id, void *data, size_t *size) -{ - uint8_t *buf = (uint8_t*)data; - uint32_t err; - int r = LIBUSB_ERROR_OTHER; - ULONG write_size = (ULONG)*size; - - if ((*size == 0) || (*size > MAX_HID_REPORT_SIZE)) { - usbi_dbg("invalid size (%d)", *size); - return LIBUSB_ERROR_INVALID_PARAM; - } - - if (id == 0) { - write_size++; - buf = (uint8_t*)calloc(write_size, 1); - if (buf == NULL) { - return LIBUSB_ERROR_NO_MEM; - } - memcpy(buf+1, data, *size); - buf[0] = (uint8_t)id; - } else if (buf[0] != id) { - usbi_warn(NULL, "mismatched report ID (data is %02X, parameter is %02X)", buf[0], id); - return LIBUSB_ERROR_INVALID_PARAM; - } - - usbi_dbg("report ID: 0x%02X", buf[0]); - - if (HidD_SetFeature(hid_handle, buf, write_size)) { - r = LIBUSB_COMPLETED; - } else { - err = GetLastError(); - switch (err) { - case ERROR_INVALID_FUNCTION: - r = LIBUSB_ERROR_NOT_FOUND; - default: - usbi_dbg("error %s", windows_error_str(err)); - r = LIBUSB_ERROR_OTHER; - } - } - if (id == 0) { - safe_free(buf); - } - return r; -} - static int _hid_class_request(struct hid_device_priv* dev, HANDLE hid_handle, int request_type, int request, int value, int index, void *data, struct windows_transfer_priv *tp, size_t *size, OVERLAPPED* overlapped) @@ -3454,25 +3360,11 @@ static int _hid_class_request(struct hid_device_priv* dev, HANDLE hid_handle, in && (LIBUSB_REQ_RECIPIENT(request_type) != LIBUSB_RECIPIENT_DEVICE) ) return LIBUSB_ERROR_INVALID_PARAM; - if (LIBUSB_REQ_OUT(request_type) - && request == HID_REQ_SET_REPORT - && report_type == HID_REPORT_TYPE_OUTPUT) - return _hid_set_report(dev, hid_handle, report_id, data, tp, size, overlapped); - - if (LIBUSB_REQ_IN(request_type) - && request == HID_REQ_GET_REPORT - && report_type == HID_REPORT_TYPE_INPUT) - return _hid_get_report(dev, hid_handle, report_id, data, tp, size, overlapped); - - if (LIBUSB_REQ_OUT(request_type) - && request == HID_REQ_SET_REPORT - && report_type == HID_REPORT_TYPE_FEATURE) - return _hid_set_feature(dev, hid_handle, report_id, data, size); - - if (LIBUSB_REQ_IN(request_type) - && request == HID_REQ_GET_REPORT - && report_type == HID_REPORT_TYPE_FEATURE) - return _hid_get_feature(dev, hid_handle, report_id, data, size); + if (LIBUSB_REQ_OUT(request_type) && request == HID_REQ_SET_REPORT) + return _hid_set_report(dev, hid_handle, report_id, data, tp, size, overlapped, report_type); + + if (LIBUSB_REQ_IN(request_type) && request == HID_REQ_GET_REPORT) + return _hid_get_report(dev, hid_handle, report_id, data, tp, size, overlapped, report_type); return LIBUSB_ERROR_INVALID_PARAM; } diff --git a/libusb/os/windows_usb.h b/libusb/os/windows_usb.h index 1a40ad7..3b07855 100644 --- a/libusb/os/windows_usb.h +++ b/libusb/os/windows_usb.h @@ -197,6 +197,21 @@ 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)) +// The following are used for HID reports IOCTLs +#define HID_CTL_CODE(id) \ + CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_NEITHER, FILE_ANY_ACCESS) +#define HID_BUFFER_CTL_CODE(id) \ + CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_BUFFERED, FILE_ANY_ACCESS) +#define HID_IN_CTL_CODE(id) \ + CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS) +#define HID_OUT_CTL_CODE(id) \ + CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) + +#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) +#define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104) +#define IOCTL_HID_SET_FEATURE HID_IN_CTL_CODE(100) +#define IOCTL_HID_SET_OUTPUT_REPORT HID_IN_CTL_CODE(101) + enum libusb_hid_request_type { HID_REQ_GET_REPORT = 0x01, HID_REQ_GET_IDLE = 0x02, @@ -345,7 +360,7 @@ struct windows_transfer_priv { typedef DWORD DEVNODE, DEVINST; typedef DEVNODE *PDEVNODE, *PDEVINST; typedef DWORD RETURN_TYPE; -typedef RETURN_TYPE CONFIGRET; +typedef RETURN_TYPE CONFIGRET; #define CR_SUCCESS 0x00000000 #define CR_NO_SUCH_DEVNODE 0x0000000D @@ -468,7 +483,7 @@ typedef struct _USB_INTERFACE_DESCRIPTOR { typedef struct _USB_CONFIGURATION_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; - USHORT wTotalLength; + USHORT wTotalLength; UCHAR bNumInterfaces; UCHAR bConfigurationValue; UCHAR iConfiguration; -- 2.7.4