summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPete Batard <pbatard@gmail.com>2010-10-14 15:39:27 +0100
committerPete Batard <pbatard@gmail.com>2010-10-14 15:39:27 +0100
commit724a15527decc77b25db8ac2642ac7893f330efe (patch)
treee32a298328cb58577383e095780c6ecc3acfdcfc
parentb368dbbc2917452836b5ab2c96e2b9659de782c6 (diff)
downloadlibusb-pbh312.tar.gz
merged -> pbr314pbh312
-rw-r--r--bump.sh11
-rw-r--r--configure.ac2
-rw-r--r--libusb/libusb_version.h2
-rw-r--r--libusb/os/windows_usb.c322
-rw-r--r--libusb/os/windows_usb.h1
5 files changed, 232 insertions, 106 deletions
diff --git a/bump.sh b/bump.sh
index 71415bf..913512c 100644
--- a/bump.sh
+++ b/bump.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-
-# This script bumps the version and updates the git tree accordingly, with tag
+# bump the version and update the git tree accordingly
+# !!!THIS SCRIPT IS FOR INTERNAL DEVELOPER USE ONLY!!!
type -P sed &>/dev/null || { echo "sed command not found. Aborting." >&2; exit 1; }
type -P git &>/dev/null || { echo "git command not found. Aborting." >&2; exit 1; }
@@ -19,8 +19,11 @@ if [ ! ${TAG:0:3} = 'pbh' ]; then
exit 1
fi
TAGVER=${TAG:3}
+case $TAGVER in *[!0-9]*)
+ echo "$TAGVER is not a number"
+ exit 1
+esac
OFFSET=9000
-# increment - ideally, we'd check that tagver is really numeric here
TAGVER=`expr $TAGVER + 1`
TAGVER_OFF=`expr $TAGVER + $OFFSET`
echo "Bumping version to pbh$TAGVER (nano: $TAGVER_OFF)"
@@ -29,5 +32,5 @@ mv configure.ac~ configure.ac
# we're duplicating libusb_version.h generation here, but that avoids having to run configure
sed -e "s/\(^#define LIBUSB_VERSION_NANO.*\)/#define LIBUSB_VERSION_NANO $TAGVER_OFF/" libusb/libusb_version.h > libusb/libusb_version.h~
mv libusb/libusb_version.h~ libusb/libusb_version.h
-git commit -a -m "bumped internal version"
+git commit -a -m "bumped internal version" -e
git tag "pbh$TAGVER" \ No newline at end of file
diff --git a/configure.ac b/configure.ac
index 25853ab..6943942 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
m4_define(LIBUSB_MAJOR, [1])
m4_define(LIBUSB_MINOR, [0])
m4_define(LIBUSB_MICRO, [8])
-m4_define(LIBUSB_NANO, [9311])
+m4_define(LIBUSB_NANO, [9312])
AC_INIT([libusb], LIBUSB_MAJOR.LIBUSB_MINOR.LIBUSB_MICRO, [libusb-devel@lists.sourceforge.net], [libusb], [http://www.libusb.org/])
diff --git a/libusb/libusb_version.h b/libusb/libusb_version.h
index 75f1bd1..86b3ec9 100644
--- a/libusb/libusb_version.h
+++ b/libusb/libusb_version.h
@@ -24,6 +24,6 @@
#define LIBUSB_VERSION_MAJOR 1
#define LIBUSB_VERSION_MINOR 0
#define LIBUSB_VERSION_MICRO 8
-#define LIBUSB_VERSION_NANO 9311
+#define LIBUSB_VERSION_NANO 9312
#endif
diff --git a/libusb/os/windows_usb.c b/libusb/os/windows_usb.c
index b809801..547d9c1 100644
--- a/libusb/os/windows_usb.c
+++ b/libusb/os/windows_usb.c
@@ -4,6 +4,7 @@
* With contributions from Michael Plante, Orin Eman et al.
* Parts of this code adapted from libusb-win32-v1 by Stephan Meyer
* HID Reports IOCTLs inspired from HIDAPI by Alan Ott, Signal 11 Software
+ * Hash table functions adapted from glibc, by Ulrich Drepper et al.
* Major code testing contribution by Xiaofan Chen
*
* This library is free software; you can redistribute it and/or
@@ -390,6 +391,173 @@ err_exit:
return NULL;
}
+/* Hash table functions - modified From glibc 2.3.2:
+ [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986
+ [Knuth] The Art of Computer Programming, part 3 (6.4) */
+typedef struct htab_entry {
+ unsigned long used;
+ char* str;
+} htab_entry;
+htab_entry* htab_table = NULL;
+usbi_mutex_t htab_write_mutex = NULL;
+unsigned long htab_size, htab_filled;
+
+/* For the used double hash method the table size has to be a prime. To
+ correct the user given table size we need a prime test. This trivial
+ algorithm is adequate because the code is called only during init and
+ the number is likely to be small */
+static int isprime(unsigned long number)
+{
+ // no even number will be passed
+ unsigned int divider = 3;
+
+ while((divider * divider < number) && (number % divider != 0))
+ divider += 2;
+
+ return (number % divider != 0);
+}
+
+/* Before using the hash table we must allocate memory for it.
+ We allocate one element more as the found prime number says.
+ This is done for more effective indexing as explained in the
+ comment for the hash function. */
+int htab_create(struct libusb_context *ctx, unsigned long nel)
+{
+ if (htab_table != NULL) {
+ usbi_err(ctx, "hash table already allocated");
+ }
+
+ // Create a mutex
+ usbi_mutex_init(&htab_write_mutex, NULL);
+
+ // Change nel to the first prime number not smaller as nel.
+ nel |= 1;
+ while(!isprime(nel))
+ nel += 2;
+
+ htab_size = nel;
+ usbi_dbg("using %d entries hash table", nel);
+ htab_filled = 0;
+
+ // allocate memory and zero out.
+ htab_table = (htab_entry*)calloc(htab_size + 1, sizeof(htab_entry));
+ if (htab_table == NULL) {
+ usbi_err(ctx, "could not allocate space for hash table");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* After using the hash table it has to be destroyed. */
+void htab_destroy(void)
+{
+ size_t i;
+ if (htab_table == NULL) {
+ return;
+ }
+
+ for (i=0; i<htab_size; i++) {
+ if (htab_table[i].used) {
+ safe_free(htab_table[i].str);
+ }
+ }
+ usbi_mutex_destroy(&htab_write_mutex);
+ safe_free(htab_table);
+}
+
+/* This is the search function. It uses double hashing with open addressing.
+ We use an trick to speed up the lookup. The table is created with one
+ more element available. This enables us to use the index zero special.
+ This index will never be used because we store the first hash index in
+ the field used where zero means not used. Every other value means used.
+ The used field can be used as a first fast comparison for equality of
+ the stored and the parameter value. This helps to prevent unnecessary
+ expensive calls of strcmp. */
+unsigned long htab_hash(char* str)
+{
+ unsigned long hval, hval2;
+ unsigned long idx;
+ unsigned long r = 5381;
+ int c;
+ char* sz = str;
+
+ // Compute main hash value (algorithm suggested by Nokia)
+ while ((c = *sz++))
+ r = ((r << 5) + r) + c;
+ if (r == 0)
+ ++r;
+
+ // compute table hash: simply take the modulus
+ hval = r % htab_size;
+ if (hval == 0)
+ ++hval;
+
+ // Try the first index
+ idx = hval;
+
+ if (htab_table[idx].used) {
+ if ( (htab_table[idx].used == hval)
+ && (safe_strcmp(str, htab_table[idx].str) == 0) ) {
+ // existing hash
+ return idx;
+ }
+ usbi_dbg("hash collision ('%s' vs '%s')", str, htab_table[idx].str);
+
+ // Second hash function, as suggested in [Knuth]
+ hval2 = 1 + hval % (htab_size - 2);
+
+ do {
+ // Because size is prime this guarantees to step through all available indexes
+ if (idx <= hval2) {
+ idx = htab_size + idx - hval2;
+ } else {
+ idx -= hval2;
+ }
+
+ // If we visited all entries leave the loop unsuccessfully
+ if (idx == hval) {
+ break;
+ }
+
+ // If entry is found use it.
+ if ( (htab_table[idx].used == hval)
+ && (safe_strcmp(str, htab_table[idx].str) == 0) ) {
+ return idx;
+ }
+ }
+ while (htab_table[idx].used);
+ }
+
+ // Not found => New entry
+
+ // If the table is full return an error
+ if (htab_filled >= htab_size) {
+ usbi_err(NULL, "hash table is full (%d entries)", htab_size);
+ return 0;
+ }
+
+ // Concurrent threads might be storing the same entry at the same time
+ // (eg. "simultaneous" enums from different threads) => use a mutex
+ usbi_mutex_lock(&htab_write_mutex);
+ // Just free any previously allocated string (which should be the same as
+ // new one). The possibility of concurrent threads storing a collision
+ // string (same hash, different string) at the same time is extremely low
+ safe_free(htab_table[idx].str);
+ htab_table[idx].used = hval;
+ htab_table[idx].str = malloc(safe_strlen(str)+1);
+ if (htab_table[idx].str == NULL) {
+ usbi_err(NULL, "could not duplicate string for hash table");
+ usbi_mutex_unlock(&htab_write_mutex);
+ return 0;
+ }
+ memcpy(htab_table[idx].str, str, safe_strlen(str)+1);
+ ++htab_filled;
+ usbi_mutex_unlock(&htab_write_mutex);
+
+ return idx;
+}
+
/*
* Returns the Device ID path of a device's parent
*/
@@ -410,7 +578,7 @@ static unsigned long get_parent_session_id(DWORD devinst)
if (sanitized_path == NULL) {
return 0;
}
- session_id = usbi_hash(sanitized_path);
+ session_id = htab_hash(sanitized_path);
safe_free(sanitized_path);
return session_id;
}
@@ -441,12 +609,12 @@ static unsigned long get_grandparent_session_id(DWORD devinst, bool* non_usb_gp)
*non_usb_gp = true;
return 0;
}
- // TODO: try without sanitizing
+ // TODO (post hotplug): try without sanitizing
sanitized_path = sanitize_path(path);
if (sanitized_path == NULL) {
return 0;
}
- session_id = usbi_hash(sanitized_path);
+ session_id = htab_hash(sanitized_path);
safe_free(sanitized_path);
return session_id;
}
@@ -835,6 +1003,9 @@ static int windows_init(struct libusb_context *ctx)
goto init_exit;
}
+ // Create a hash table to store session ids Second parameter is better if prime
+ htab_create(ctx, HTAB_SIZE);
+
r = LIBUSB_SUCCESS;
}
@@ -864,6 +1035,7 @@ init_exit: // Holds semaphore here.
CloseHandle(timer_mutex);
timer_mutex = NULL;
}
+ htab_destroy();
}
if (r != LIBUSB_SUCCESS)
@@ -877,17 +1049,14 @@ init_exit: // Holds semaphore here.
/*
* HCD (root) hubs need to have their device descriptor manually populated
*
- * Note that we follow the Linux convention and use the "Linux Foundation root hub"
- * vendor ID as well as the product ID to indicate the hub speed
+ * Note that, like Microsoft does in the device manager, we populate the
+ * Vendor and Device ID for HCD hubs with the ones from the PCI HCD device.
*/
static int force_hcd_device_descriptor(struct libusb_device *dev)
{
- DWORD size;
- HANDLE handle;
- USB_HUB_CAPABILITIES hub_caps;
- USB_HUB_CAPABILITIES_EX hub_caps_ex;
- struct windows_device_priv *priv = __device_priv(dev);
+ struct windows_device_priv *parent_priv, *priv = __device_priv(dev);
struct libusb_context *ctx = DEVICE_CTX(dev);
+ int vid, pid;
dev->num_configurations = 1;
priv->dev_descriptor.bLength = sizeof(USB_DEVICE_DESCRIPTOR);
@@ -895,39 +1064,19 @@ static int force_hcd_device_descriptor(struct libusb_device *dev)
priv->dev_descriptor.bNumConfigurations = 1;
priv->active_config = 1;
- handle = CreateFileA(priv->path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
- FILE_FLAG_OVERLAPPED, NULL);
- if (handle == INVALID_HANDLE_VALUE) {
- usbi_warn(ctx, "could not open hub %s: %s", priv->path, windows_error_str(0));
- return LIBUSB_ERROR_IO;
+ if (priv->parent_dev == NULL) {
+ usbi_err(ctx, "program assertion failed - HCD hub has no parent");
+ return LIBUSB_ERROR_NO_DEVICE;
}
-
- // The following is used to set the VID:PID of root HUBs similarly to what
- // Linux does: 1d6b:0001 is for 1x root hubs, 1d6b:0002 for 2x
- // TODO: pick up PCI VID/PID from parent
- priv->dev_descriptor.idVendor = 0x1d6b; // Linux Foundation root hub
- if (windows_version >= WINDOWS_VISTA_AND_LATER) {
- size = sizeof(USB_HUB_CAPABILITIES_EX);
- if (DeviceIoControl(handle, IOCTL_USB_GET_HUB_CAPABILITIES_EX, &hub_caps_ex,
- size, &hub_caps_ex, size, &size, NULL)) {
- // Sanity check. HCD hub should always be root
- if (!hub_caps_ex.CapabilityFlags.HubIsRoot) {
- usbi_warn(ctx, "program assertion failed - HCD hub is not reported as root hub.");
- }
- priv->dev_descriptor.idProduct = hub_caps_ex.CapabilityFlags.HubIsHighSpeedCapable?2:1;
- }
+ parent_priv = __device_priv(priv->parent_dev);
+ if (sscanf(parent_priv->path, "\\\\.\\PCI#VEN_%04x&DEV_%04x%*s", &vid, &pid) == 2) {
+ priv->dev_descriptor.idVendor = (uint16_t)vid;
+ priv->dev_descriptor.idProduct = (uint16_t)pid;
} else {
- size = sizeof(USB_HUB_CAPABILITIES);
- if (!DeviceIoControl(handle, IOCTL_USB_GET_HUB_CAPABILITIES, &hub_caps,
- size, &hub_caps, size, &size, NULL)) {
- usbi_warn(ctx, "could not read hub capabilities (std) for hub %s: %s",
- priv->path, windows_error_str(0));
- priv->dev_descriptor.idProduct = 1; // Indicate 1x speed
- } else {
- priv->dev_descriptor.idProduct = hub_caps.HubIs2xCapable?2:1;
- }
+ usbi_warn(ctx, "could not infer VID/PID of HCD hub from '%s'", parent_priv->path);
+ priv->dev_descriptor.idVendor = 0x1d6b; // Linux Foundation root hub
+ priv->dev_descriptor.idProduct = 1;
}
-
return LIBUSB_SUCCESS;
}
@@ -1231,7 +1380,6 @@ static int set_hid_interface(struct libusb_context* ctx, struct libusb_device* d
/*
* get_device_list: libusb backend device enumeration function
*/
-// TODO: alert users on HID KBD/Mouse (kbhid, mouhid)
static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs)
{
struct discovered_devs *discdevs = *_discdevs;
@@ -1262,15 +1410,10 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
LONG s;
uint8_t api;
bool non_usb_hid_parent;
- // Keep a chained list of newly allocated devs to unref
- // TODO: use a perf friendly realloc instead of a chained list
- struct unref_dev {
- libusb_device* dev;
- struct unref_dev* next;
- };
- struct unref_dev* unref_root = NULL;
- struct unref_dev* unref_tmp;
- struct unref_dev** unref_cur = &unref_root;
+ // Keep a list of newly allocated devs to unref
+ libusb_device** unref_list;
+ unsigned int unref_size = 64;
+ unsigned int unref_cur = 0;
// PASS 1 : (re)enumerate HCDs (allows for HCD hotplug)
// PASS 2 : (re)enumerate HUBS
@@ -1289,29 +1432,12 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
guid[HID_PASS] = &hid_guid;
nb_guids = HID_PASS+1;
+ unref_list = malloc(unref_size*sizeof(libusb_device*));
+ if (unref_list == NULL) {
+ return LIBUSB_ERROR_NO_MEM;
+ }
+
for (pass = 0; ((pass < nb_guids) && (r == LIBUSB_SUCCESS)); pass++) {
-/*
- switch(pass) {
- case HCD_PASS:
- usbi_dbg("PROCESSING HCDs %s", guid_to_string(guid[pass]));
- break;
- case HUB_PASS:
- usbi_dbg("PROCESSING HUBs %s", guid_to_string(guid[pass]));
- break;
- case DEV_PASS:
- usbi_dbg("PROCESSING DEVs %s", guid_to_string(guid[pass]));
- break;
- case GEN_PASS:
- usbi_dbg("PROCESSING GENs");
- break;
- case HID_PASS:
- usbi_dbg("PROCESSING HIDs %s", guid_to_string(guid[pass]));
- break;
- default:
- usbi_dbg("PROCESSING EXTs %s", guid_to_string(guid[pass]));
- break;
- }
-*/
for (i = 0; ; i++) {
// safe loop: free up any (unprotected) dynamic resource
// NB: this is always executed before breaking the loop
@@ -1355,12 +1481,10 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
dev_interface_details->DevicePath);
continue;
}
- // TODO: speed things further by not sanitizing it?
dev_id_path = sanitize_path(path);
if (dev_id_path == NULL) {
usbi_warn(ctx, "could not sanitize device id path for '%s'", dev_interface_details->DevicePath);
}
-// usbi_dbg("PRO: %s", dev_id_path);
// The SPDRP_ADDRESS for USB devices is the device port number on the hub
port_nr = 0;
@@ -1385,7 +1509,8 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
size = sizeof(strbuf);
if (!SetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_DRIVER,
&reg_type, (BYTE*)strbuf, size, &size)) {
- usbi_dbg("DRIVERLESS: '%s'", dev_id_path);
+ usbi_info(ctx, "The following device has no driver: '%s'", dev_id_path);
+ usbi_info(ctx, "libusb will not be able to access it.");
}
// ...and to add the additional device interface GUIDs
key = SetupDiOpenDevRegKey(dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
@@ -1481,7 +1606,7 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
continue;
}
usbi_dbg("found existing device for session [%lX]", session_id);
- // TODO: reuse priv data that can be reused - for now, just recreate
+ // TODO (post hotplug): reuse priv data that can be reused - for now, just recreate
if (pass == GEN_PASS) {
windows_device_priv_release(dev);
}
@@ -1498,13 +1623,16 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
usbi_dbg("allocating new device for session [%lX]", session_id);
// Keep track of devices that need unref
- if ((*unref_cur = malloc(sizeof(struct unref_dev))) == NULL) {
- usbi_err(ctx, "could not allocate struct for unref. aborting.");
- LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
+ unref_list[unref_cur++] = dev;
+ if (unref_cur > unref_size) {
+ unref_size += 64;
+ unref_list = realloc(unref_list, unref_size*sizeof(libusb_device*));
+ if (unref_list == NULL) {
+ usbi_err(ctx, "could not realloc list for unref - aborting.");
+ LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
+ }
}
- (*unref_cur)->dev = dev;
- (*unref_cur)->next = NULL;
- unref_cur = &((*unref_cur)->next);
+
// Append newly created devices to the list of discovered devices
if (pass != HCD_PASS) {
discdevs = discovered_devs_append(*_discdevs, dev);
@@ -1514,7 +1642,6 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
*_discdevs = discdevs;
}
}
-
priv = __device_priv(dev);
}
@@ -1587,12 +1714,10 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
}
// Unref newly allocated devs
- while (unref_root != NULL) {
- unref_tmp = unref_root;
- unref_root = unref_root->next;
- safe_unref_device(unref_tmp->dev);
- safe_free(unref_tmp);
+ for (i=0; i<unref_cur; i++) {
+ safe_unref_device(unref_list[i]);
}
+ safe_free(unref_list);
return r;
}
@@ -1658,6 +1783,8 @@ static void windows_exit(struct libusb_context *ctx)
TerminateThread(hotplug_thread, 1);
}
}
+
+ htab_destroy();
}
ReleaseSemaphore(semaphore, 1, NULL); // increase count back to 1
@@ -2896,7 +3023,7 @@ static int winusb_abort_transfers(struct usbi_transfer *itransfer)
* IOCTL_USB_HUB_CYCLE_PORT ioctl was removed from Vista => the best we can do is
* cycle the pipes (and even then, the control pipe can not be reset using WinUSB)
*/
-// TODO (2nd official release): see if we can force eject the device and redetect it (reuse hotplug?)
+// TODO (post hotplug): see if we can force eject the device and redetect it (reuse hotplug?)
static int winusb_reset_device(struct libusb_device_handle *dev_handle)
{
struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev);
@@ -3266,11 +3393,6 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i
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++;
- }
-
// Add a trailing byte to detect overflows
buf = (uint8_t*)calloc(expected_size+1, 1);
if (buf == NULL) {
@@ -3280,7 +3402,9 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i
usbi_dbg("report ID: 0x%02X", buf[0]);
tp->hid_expected_size = expected_size;
+ read_size = expected_size;
+ // NB: The size returned by DeviceIoControl doesn't include report IDs when not in use (0)
if (!DeviceIoControl(hid_handle, ioctl_code, buf, expected_size+1,
buf, expected_size+1, &read_size, overlapped)) {
if (GetLastError() != ERROR_IO_PENDING) {
@@ -3296,7 +3420,7 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i
// 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");
+ usbi_warn(NULL, "program assertion failed - read completed synchronously, but no data was read");
*size = 0;
} else {
if (buf[0] != id) {
@@ -3309,12 +3433,11 @@ static int _hid_get_report(struct hid_device_priv* dev, HANDLE hid_handle, int i
r = LIBUSB_COMPLETED;
}
+ *size = MIN((size_t)read_size, *size);
if (id == 0) {
// Discard report ID
- *size = MIN((size_t)read_size-1, *size);
memcpy(data, buf+1, *size);
} else {
- *size = MIN((size_t)read_size, *size);
memcpy(data, buf, *size);
}
}
@@ -3372,6 +3495,7 @@ static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int i
}
}
+ // NB: The size returned by DeviceIoControl doesn't include report IDs when not in use (0)
if (!DeviceIoControl(hid_handle, ioctl_code, buf, write_size,
buf, write_size, &write_size, overlapped)) {
if (GetLastError() != ERROR_IO_PENDING) {
@@ -3385,11 +3509,9 @@ static int _hid_set_report(struct hid_device_priv* dev, HANDLE hid_handle, int i
}
// Transfer completed synchronously
+ *size = write_size;
if (write_size == 0) {
usbi_dbg("program assertion failed - write completed synchronously, but no data was written");
- *size = 0;
- } else {
- *size = write_size - ((id == 0)?1:0);
}
safe_free(buf);
return LIBUSB_COMPLETED;
diff --git a/libusb/os/windows_usb.h b/libusb/os/windows_usb.h
index a1319ca..2138361 100644
--- a/libusb/os/windows_usb.h
+++ b/libusb/os/windows_usb.h
@@ -89,6 +89,7 @@ inline void upperize(char* str) {
#define TIMER_REQUEST_RETRY_MS 100
#define ERR_BUFFER_SIZE 256
#define LIST_SEPARATOR ';'
+#define HTAB_SIZE 1021
// Handle code for HID interface that have been claimed ("dibs")
#define INTERFACE_CLAIMED ((HANDLE)(intptr_t)0xD1B5)