summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikolai Kondrashov <Nikolai.Kondrashov@redhat.com>2014-07-12 21:02:33 +0300
committerNikolai Kondrashov <Nikolai.Kondrashov@redhat.com>2014-07-12 21:02:33 +0300
commitcc74aae8d020e0ddd069dbb107900cec54e56cd5 (patch)
tree398a64e5f3b76f1c045c083a9129beb13a82f154
parent10f8a6ba71e416605f8e0521198554ffbf72ad9a (diff)
downloadusbhid-dump-cc74aae8d020e0ddd069dbb107900cec54e56cd5.tar.gz
Retrieve declared descriptor size
Request transfer of report descriptor with the destination buffer size equal to report descriptor size declared in HID descriptor, instead of the maximum possible report descriptor size. Only fall back to the maximum possible size, if report descriptor size cannot be retrieved. This fixes ERROR_PIPE when retrieving report descriptors from some devices (e.g. Waltop Vega tablet).
-rw-r--r--include/uhd/iface.h5
-rw-r--r--include/uhd/misc.h26
-rw-r--r--lib/iface.c4
-rw-r--r--lib/iface_list.c57
-rw-r--r--src/usbhid-dump.c10
5 files changed, 92 insertions, 10 deletions
diff --git a/include/uhd/iface.h b/include/uhd/iface.h
index eabaa7d..9317015 100644
--- a/include/uhd/iface.h
+++ b/include/uhd/iface.h
@@ -45,6 +45,7 @@ struct uhd_iface {
uint8_t int_in_ep_addr; /**< Interrupt IN EP address */
uint16_t int_in_ep_maxp; /**< Interrupt IN EP maximum
packet size */
+ uint16_t rd_len; /**< Report descriptor length */
bool detached; /**< True if the interface was
detached from the kernel
driver, false otherwise */
@@ -77,13 +78,15 @@ extern bool uhd_iface_valid(const uhd_iface *iface);
* @param number Interface number.
* @param int_in_ep_addr Interrupt in endpoint address.
* @param int_in_ep_maxp Interrupt in endpoint maximum packet size.
+ * @param rd_len Report descriptor length.
*
* @return New interface or NULL, if failed to allocate.
*/
extern uhd_iface *uhd_iface_new(const uhd_dev *dev,
uint8_t number,
uint8_t int_in_ep_addr,
- uint16_t int_in_ep_maxp);
+ uint16_t int_in_ep_maxp,
+ uint16_t rd_len);
/**
* Free an interface.
diff --git a/include/uhd/misc.h b/include/uhd/misc.h
index cae0df2..bcb79de 100644
--- a/include/uhd/misc.h
+++ b/include/uhd/misc.h
@@ -27,10 +27,36 @@
#ifndef __UHD_MISC_H__
#define __UHD_MISC_H__
+#include <stdint.h>
+
#ifdef __cplusplus
extern "C" {
#endif
+#pragma pack(1)
+
+/** HID extra descriptor record */
+typedef struct uhd_hid_descriptor_extra uhd_hid_descriptor_extra;
+
+struct uhd_hid_descriptor_extra {
+ uint8_t bDescriptorType;
+ uint16_t wDescriptorLength;
+};
+
+/** HID class-specific descriptor */
+typedef struct uhd_hid_descriptor uhd_hid_descriptor;
+
+struct uhd_hid_descriptor {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t bcdHID;
+ uint8_t bCountryCode;
+ uint8_t bNumDescriptors;
+ uhd_hid_descriptor_extra extra[1];
+};
+
+#pragma pack()
+
/**
* Maximum descriptor size.
*
diff --git a/lib/iface.c b/lib/iface.c
index 7209188..2cd9ea0 100644
--- a/lib/iface.c
+++ b/lib/iface.c
@@ -45,7 +45,8 @@ uhd_iface *
uhd_iface_new(const uhd_dev *dev,
uint8_t number,
uint8_t int_in_ep_addr,
- uint16_t int_in_ep_maxp)
+ uint16_t int_in_ep_maxp,
+ uint16_t rd_len)
{
uhd_iface *iface;
libusb_device *lusb_dev;
@@ -60,6 +61,7 @@ uhd_iface_new(const uhd_dev *dev,
iface->number = number;
iface->int_in_ep_addr = int_in_ep_addr;
iface->int_in_ep_maxp = int_in_ep_maxp;
+ iface->rd_len = rd_len;
iface->detached = false;
iface->claimed = false;
iface->submitted = false;
diff --git a/lib/iface_list.c b/lib/iface_list.c
index 1b028b1..bd208aa 100644
--- a/lib/iface_list.c
+++ b/lib/iface_list.c
@@ -27,6 +27,7 @@
#include "config.h"
#include "uhd/iface_list.h"
+#include "uhd/misc.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
@@ -76,6 +77,10 @@ uhd_iface_list_new(uhd_dev *dev_list,
uhd_dev *dev;
struct libusb_config_descriptor *config = NULL;
const struct libusb_interface *lusb_iface;
+ const struct libusb_interface_descriptor *iface_desc;
+ const uhd_hid_descriptor *hid_desc;
+ const uhd_hid_descriptor_extra *hid_desc_extra;
+ uint16_t rd_len;
const struct libusb_endpoint_descriptor *ep_list;
uint8_t ep_num;
const struct libusb_endpoint_descriptor *ep;
@@ -101,14 +106,51 @@ uhd_iface_list_new(uhd_dev *dev_list,
lusb_iface - config->interface < config->bNumInterfaces;
lusb_iface++)
{
- /* Skip interfaces with altsettings and non-HID interfaces */
- if (lusb_iface->num_altsetting != 1 ||
- lusb_iface->altsetting->bInterfaceClass != LIBUSB_CLASS_HID)
+ /* Skip interfaces with altsettings */
+ if (lusb_iface->num_altsetting != 1)
continue;
+ iface_desc = lusb_iface->altsetting;
+
+ /* Skip non-HID interfaces */
+ if (iface_desc->bInterfaceClass != LIBUSB_CLASS_HID)
+ continue;
+
+ /*
+ * Try to retrieve report descriptor length
+ */
+ rd_len = UHD_MAX_DESCRIPTOR_SIZE;
+ /* If interface descriptor has space for a HID descriptor */
+ if (iface_desc->extra_length >= (int)sizeof(uhd_hid_descriptor))
+ {
+ hid_desc = (const uhd_hid_descriptor *)iface_desc->extra;
+ /* If this is truly a HID class descriptor */
+ if (hid_desc->bDescriptorType == LIBUSB_DT_HID)
+ {
+ /* For each extra HID descriptor entry */
+ for (hid_desc_extra = hid_desc->extra;
+ hid_desc_extra <
+ hid_desc->extra + hid_desc->bNumDescriptors &&
+ (uint8_t *)hid_desc_extra <
+ (uint8_t *)hid_desc + hid_desc->bLength &&
+ (unsigned char *)hid_desc_extra <
+ iface_desc->extra +
+ iface_desc->extra_length;
+ hid_desc_extra++) {
+ /* If this is a report descriptor entry */
+ if (hid_desc_extra->bDescriptorType ==
+ LIBUSB_DT_REPORT)
+ {
+ rd_len = hid_desc_extra->wDescriptorLength;
+ break;
+ }
+ }
+ }
+ }
+
/* Retrieve endpoint list */
- ep_list = lusb_iface->altsetting->endpoint;
- ep_num = lusb_iface->altsetting->bNumEndpoints;
+ ep_list = iface_desc->endpoint;
+ ep_num = iface_desc->bNumEndpoints;
/* For each endpoint */
for (ep = ep_list; (ep - ep_list) < ep_num; ep++)
@@ -123,8 +165,9 @@ uhd_iface_list_new(uhd_dev *dev_list,
/* Create the interface */
iface = uhd_iface_new(
dev,
- lusb_iface->altsetting->bInterfaceNumber,
- ep->bEndpointAddress, ep->wMaxPacketSize);
+ iface_desc->bInterfaceNumber,
+ ep->bEndpointAddress, ep->wMaxPacketSize,
+ rd_len);
if (iface == NULL)
{
err = LIBUSB_ERROR_NO_MEM;
diff --git a/src/usbhid-dump.c b/src/usbhid-dump.c
index 892ea6e..e0d03bf 100644
--- a/src/usbhid-dump.c
+++ b/src/usbhid-dump.c
@@ -179,12 +179,20 @@ dump_iface_list_descriptor(const uhd_iface *list)
UHD_IFACE_LIST_FOR_EACH(iface, list)
{
+ if (iface->rd_len > sizeof(buf))
+ {
+ err = LIBUSB_ERROR_NO_MEM;
+ LIBUSB_IFACE_FAILURE(iface, "report descriptor too long: %hu",
+ iface->rd_len);
+ return false;
+ }
+
rc = libusb_control_transfer(iface->dev->handle,
/* See HID spec, 7.1.1 */
0x81,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_REPORT << 8), iface->number,
- buf, sizeof(buf), UHD_IO_TIMEOUT);
+ buf, iface->rd_len, UHD_IO_TIMEOUT);
if (rc < 0)
{
err = rc;