Windows: enumeration overhaul
authorPeter Stuge <peter@stuge.se>
Tue, 10 May 2011 08:05:29 +0000 (10:05 +0200)
committerPeter Stuge <peter@stuge.se>
Mon, 13 Jun 2011 20:06:31 +0000 (22:06 +0200)
* uses multiple passes in a single call
* uses a hash table, in anticipation for hotplug
* adds a (dummy) HUB interface for harmonization
* adds calloc on device struct creation in core
  (to detect and avoid unnecessary double initialization)

libusb/core.c
libusb/os/windows_usb.c
libusb/os/windows_usb.h

index 6605c96..c40d9d5 100644 (file)
@@ -508,7 +508,7 @@ struct libusb_device *usbi_alloc_device(struct libusb_context *ctx,
        unsigned long session_id)
 {
        size_t priv_size = usbi_backend->device_priv_size;
-       struct libusb_device *dev = malloc(sizeof(*dev) + priv_size);
+       struct libusb_device *dev = calloc(1, sizeof(*dev) + priv_size);
        int r;
 
        if (!dev)
index 6750c55..6983287 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (c) 2009-2010 Pete Batard <pbatard@gmail.com>
  * With contributions from Michael Plante, Orin Eman et al.
  * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer
+ * 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
@@ -93,7 +94,6 @@ static int composite_copy_transfer_data(struct usbi_transfer *itransfer, uint32_
 
 
 // Global variables
-struct windows_hcd_priv* hcd_root = NULL;
 uint64_t hires_frequency, hires_ticks_to_ps;
 const uint64_t epoch_time = UINT64_C(116444736000000000);      // 1970.01.01 00:00:000 in MS Filetime
 enum windows_version windows_version = WINDOWS_UNSUPPORTED;
@@ -114,32 +114,6 @@ HANDLE timer_response = NULL;
 bool api_winusb_available = false;
 #define CHECK_WINUSB_AVAILABLE do { if (!api_winusb_available) return LIBUSB_ERROR_ACCESS; } while (0)
 
-
-/*
- * Converts a WCHAR string to UTF8 (allocate returned string)
- * Returns NULL on error
- */
-static char* wchar_to_utf8(LPCWSTR wstr)
-{
-       int size;
-       char* str;
-
-       // Find out the size we need to allocate for our converted string
-       size = wchar_to_utf8_ms(wstr, NULL, 0);
-       if (size <= 1)  // An empty string would be size 1
-               return NULL;
-
-       if ((str = malloc(size)) == NULL)
-               return NULL;
-
-       if (wchar_to_utf8_ms(wstr, str, size) != size) {
-               safe_free(str);
-               return NULL;
-       }
-
-       return str;
-}
-
 static inline BOOLEAN guid_eq(const GUID *guid1, const GUID *guid2) {
        if ((guid1 != NULL) && (guid2 != NULL)) {
                return (memcmp(guid1, guid2, sizeof(GUID)) == 0);
@@ -147,15 +121,16 @@ static inline BOOLEAN guid_eq(const GUID *guid1, const GUID *guid2) {
        return false;
 }
 
-#if 0
-static char* guid_to_string(const GUID guid)
+#if defined(ENABLE_DEBUG_LOGGING)
+static char* guid_to_string(const GUID* guid)
 {
        static char guid_string[MAX_GUID_STRING_LENGTH];
 
+       if (guid == NULL) return NULL;
        sprintf(guid_string, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
-               (unsigned int)guid.Data1, guid.Data2, guid.Data3,
-               guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
-               guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
+               (unsigned int)guid->Data1, guid->Data2, guid->Data3,
+               guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
+               guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
        return guid_string;
 }
 #endif
@@ -247,7 +222,6 @@ static int init_dlls(void)
        DLL_LOAD(Cfgmgr32.dll, CM_Get_Child, TRUE);
        DLL_LOAD(Cfgmgr32.dll, CM_Get_Sibling, TRUE);
        DLL_LOAD(Cfgmgr32.dll, CM_Get_Device_IDA, TRUE);
-
        // Prefixed to avoid conflict with header files
        DLL_LOAD_PREFIXED(OLE32.dll, p, CLSIDFromString, TRUE);
        DLL_LOAD_PREFIXED(SetupAPI.dll, p, SetupDiGetClassDevsA, TRUE);
@@ -257,11 +231,48 @@ static int init_dlls(void)
        DLL_LOAD_PREFIXED(SetupAPI.dll, p, SetupDiDestroyDeviceInfoList, TRUE);
        DLL_LOAD_PREFIXED(SetupAPI.dll, p, SetupDiOpenDevRegKey, TRUE);
        DLL_LOAD_PREFIXED(SetupAPI.dll, p, SetupDiGetDeviceRegistryPropertyA, TRUE);
-       DLL_LOAD_PREFIXED(SetupAPI.dll, p, SetupDiGetDeviceRegistryPropertyW, TRUE);
+       DLL_LOAD_PREFIXED(AdvAPI32.dll, p, RegQueryValueExW, TRUE);
+       DLL_LOAD_PREFIXED(AdvAPI32.dll, p, RegCloseKey, TRUE);
        return LIBUSB_SUCCESS;
 }
 
 /*
+ * enumerate interfaces for the whole USB class
+ *
+ * Parameters:
+ * dev_info: a pointer to a dev_info list
+ * dev_info_data: a pointer to an SP_DEVINFO_DATA to be filled (or NULL if not needed)
+ * guid: the GUID for which to retrieve interface details
+ * index: zero based index of the interface in the device info list
+ *
+ * Note: it is the responsibility of the caller to free the DEVICE_INTERFACE_DETAIL_DATA
+ * structure returned and call this function repeatedly using the same guid (with an
+ * incremented index starting at zero) until all interfaces have been returned.
+ */
+static bool get_devinfo_data(struct libusb_context *ctx,
+       HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, unsigned _index)
+{
+       if (_index <= 0) {
+               *dev_info = pSetupDiGetClassDevsA(NULL, "USB", NULL, DIGCF_PRESENT|DIGCF_ALLCLASSES);
+               if (*dev_info == INVALID_HANDLE_VALUE) {
+                       return false;
+               }
+       }
+
+       dev_info_data->cbSize = sizeof(SP_DEVINFO_DATA);
+       if (!pSetupDiEnumDeviceInfo(*dev_info, _index, dev_info_data)) {
+               if (GetLastError() != ERROR_NO_MORE_ITEMS) {
+                       usbi_err(ctx, "Could not obtain device info data for index %u: %s",
+                               _index, windows_error_str(0));
+               }
+               pSetupDiDestroyDeviceInfoList(*dev_info);
+               *dev_info = INVALID_HANDLE_VALUE;
+               return false;
+       }
+       return true;
+}
+
+/*
  * enumerate interfaces for a specific GUID
  *
  * Parameters:
@@ -275,17 +286,14 @@ static int init_dlls(void)
  * incremented index starting at zero) until all interfaces have been returned.
  */
 SP_DEVICE_INTERFACE_DETAIL_DATA_A *get_interface_details(struct libusb_context *ctx,
-       HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, GUID guid, unsigned _index)
+       HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, const GUID* guid, unsigned _index)
 {
        SP_DEVICE_INTERFACE_DATA dev_interface_data;
        SP_DEVICE_INTERFACE_DETAIL_DATA_A *dev_interface_details = NULL;
        DWORD size;
 
        if (_index <= 0) {
-               *dev_info = pSetupDiGetClassDevsA(&guid, NULL, NULL, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
-       }
-       if (*dev_info == INVALID_HANDLE_VALUE) {
-               return NULL;
+               *dev_info = pSetupDiGetClassDevsA(guid, NULL, NULL, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
        }
 
        if (dev_info_data != NULL) {
@@ -302,7 +310,7 @@ SP_DEVICE_INTERFACE_DETAIL_DATA_A *get_interface_details(struct libusb_context *
        }
 
        dev_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
-       if (!pSetupDiEnumDeviceInterfaces(*dev_info, NULL, &guid, _index, &dev_interface_data)) {
+       if (!pSetupDiEnumDeviceInterfaces(*dev_info, NULL, guid, _index, &dev_interface_data)) {
                if (GetLastError() != ERROR_NO_MORE_ITEMS) {
                        usbi_err(ctx, "Could not obtain interface data for index %u: %s",
                                _index, windows_error_str(0));
@@ -320,8 +328,7 @@ SP_DEVICE_INTERFACE_DETAIL_DATA_A *get_interface_details(struct libusb_context *
                                _index, windows_error_str(0));
                        goto err_exit;
                }
-       }
-       else {
+       } else {
                usbi_err(ctx, "program assertion failed - http://msdn.microsoft.com/en-us/library/ms792901.aspx is wrong.");
                goto err_exit;
        }
@@ -346,6 +353,205 @@ 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;
+static htab_entry* htab_table = NULL;
+static usbi_mutex_t htab_write_mutex = NULL;
+static 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.  */
+static 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.  */
+static 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.  */
+static 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 session ID of a device's nth level ancestor
+ * If there's no device at the nth level, return 0
+ */
+static unsigned long get_ancestor_session_id(DWORD devinst, unsigned level)
+{
+       DWORD parent_devinst;
+       unsigned long session_id = 0;
+       char* sanitized_path = NULL;
+       char path[MAX_PATH_LENGTH];
+       unsigned i;
+
+       if (level < 1) return 0;
+       for (i = 0; i<level; i++) {
+               if (CM_Get_Parent(&parent_devinst, devinst, 0) != CR_SUCCESS) {
+                       return 0;
+               }
+               devinst = parent_devinst;
+       }
+       if (CM_Get_Device_IDA(devinst, path, MAX_PATH_LENGTH, 0) != CR_SUCCESS) {
+               return 0;
+       }
+       // TODO (post hotplug): try without sanitizing
+       sanitized_path = sanitize_path(path);
+       if (sanitized_path == NULL) {
+               return 0;
+       }
+       session_id = htab_hash(sanitized_path);
+       safe_free(sanitized_path);
+       return session_id;
+}
+
 /*
  * Populate the endpoints addresses of the device_priv interface helper structs
  */
@@ -429,7 +635,7 @@ static int auto_claim(struct libusb_transfer *transfer, int *interface_number, i
        {
                for (current_interface=0; current_interface<USB_MAXINTERFACES; current_interface++) {
                        // Must claim an interface of the same API type
-                       if ( (priv->usb_interface[current_interface].apib == &usb_api_backend[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);
                                if (handle_priv->autoclaim_count[current_interface] != 0) {
@@ -492,14 +698,9 @@ static void auto_release(struct usbi_transfer *itransfer)
  */
 static int windows_init(struct libusb_context *ctx)
 {
-       HDEVINFO dev_info;
-       SP_DEVICE_INTERFACE_DETAIL_DATA_A *dev_interface_details = NULL;
-       GUID guid;
-       libusb_bus_t bus;
        int i, r = LIBUSB_ERROR_OTHER;
        OSVERSIONINFO os_version;
        HANDLE semaphore;
-       struct windows_hcd_priv** _hcd_cur;
        char sem_name[11+1+8]; // strlen(libusb_init)+'\0'+(32-bit hex PID)
 
        sprintf(sem_name, "libusb_init%08X", (unsigned int)GetCurrentProcessId()&0xFFFFFFFF);
@@ -515,13 +716,11 @@ static int windows_init(struct libusb_context *ctx)
                usbi_err(ctx, "failure to access semaphore: %s", windows_error_str(0));
                CloseHandle(semaphore);
                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 ( ++concurrent_usage == 0 ) {        // First init?
-               _hcd_cur = &hcd_root;
-
                // Detect OS version
                memset(&os_version, 0, sizeof(OSVERSIONINFO));
                os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
@@ -588,47 +787,14 @@ static int windows_init(struct libusb_context *ctx)
                }
                SetThreadAffinityMask(timer_thread, 0);
 
-               guid = GUID_DEVINTERFACE_USB_HOST_CONTROLLER;
-
-               r = LIBUSB_SUCCESS;
-               for (bus = 0; ; bus++)
-               {
-                       // safe loop: free up any (unprotected) dynamic resource
-                       // NB: this is always executed before breaking the loop
-                       safe_free(dev_interface_details);
-                       safe_free(*_hcd_cur);
-
-                       dev_interface_details = get_interface_details(ctx, &dev_info, NULL, guid, bus);
-                       // safe loop: end of loop condition
-                       if ((dev_interface_details == NULL) || (r != LIBUSB_SUCCESS))
-                               break;
-
-                       // Will need to change storage and size of libusb_bus_t if this ever occurs
-                       if (bus == LIBUSB_BUS_MAX) {
-                               usbi_warn(ctx, "program assertion failed - found more than %d buses, skipping the rest.", LIBUSB_BUS_MAX);
-                               continue;
-                       }
-
-                       // Allocate and init a new priv structure to hold our data
-                       if ((*_hcd_cur = malloc(sizeof(struct windows_hcd_priv))) == NULL) {
-                               usbi_err(ctx, "could not allocate private structure for bus %u. aborting.", bus);
-                               LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
-                       }
-                       windows_hcd_priv_init(*_hcd_cur);
-                       (*_hcd_cur)->path = sanitize_path(dev_interface_details->DevicePath);
-
-                       _hcd_cur = &((*_hcd_cur)->next);
-               }
-               // TODO (2nd official release): thread for hotplug (see darwin source)
+               // Create a hash table to store session ids. Second parameter is better if prime
+               htab_create(ctx, HTAB_SIZE);
        }
-
-       if (hcd_root == NULL)
-               r = LIBUSB_ERROR_NO_DEVICE;
-       else
-               r = LIBUSB_SUCCESS;
+       // At this stage, either we went through full init successfully, or didn't need to
+       r = LIBUSB_SUCCESS;
 
 init_exit: // Holds semaphore here.
-       if(!concurrent_usage && r != LIBUSB_SUCCESS) { // First init failed?
+       if (!concurrent_usage && r != LIBUSB_SUCCESS) { // First init failed?
                if (timer_thread) {
                        SetEvent(timer_request[1]); // actually the signal to quit the thread.
                        if (WAIT_OBJECT_0 != WaitForSingleObject(timer_thread, INFINITE)) {
@@ -653,6 +819,7 @@ init_exit: // Holds semaphore here.
                        CloseHandle(timer_mutex);
                        timer_mutex = NULL;
                }
+               htab_destroy();
        }
 
        if (r != LIBUSB_SUCCESS)
@@ -664,88 +831,43 @@ init_exit: // Holds semaphore here.
 }
 
 /*
- * Initialize device structure, including active config
- */
-static int initialize_device(struct libusb_device *dev, libusb_bus_t busnum,
-       libusb_devaddr_t devaddr, char *path, int connection_index, uint8_t active_config,
-       struct libusb_device *parent_dev)
-{
-       struct windows_device_priv *priv = __device_priv(dev);
-
-       windows_device_priv_init(priv);
-
-       dev->bus_number = busnum;
-       dev->device_address = devaddr;
-       priv->path = path;
-       priv->connection_index = connection_index;
-       priv->parent_dev = parent_dev;
-
-       priv->active_config = active_config;
-
-       if (priv->active_config != 0) {
-               usbi_dbg("active config: %d", priv->active_config);
-       } else {
-               // USB devices that don't have a config value are usually missing a driver
-               // TODO (after first official release): use this for automated driver installation
-               // NB: SetupDiGetDeviceRegistryProperty w/ SPDRP_INSTALL_STATE would tell us
-               // if the driver is properly installed, but driverless devices don't seem to
-               // be enumerable by SetupDi...
-               usbi_dbg("* This device has no driver => libusb will not be able to access it *");
-       }
-
-       return LIBUSB_SUCCESS;
-}
-
-/*
  * 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, HANDLE handle)
+static int force_hcd_device_descriptor(struct libusb_device *dev)
 {
-       DWORD size;
-       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);
        priv->dev_descriptor.bDescriptorType = USB_DEVICE_DESCRIPTOR_TYPE;
-       dev->num_configurations = priv->dev_descriptor.bNumConfigurations = 1;
-
-       // The following is used to set the VIS:PID of root HUBs similarly to what
-       // Linux does: 1d6b:0001 is for 1x root hubs, 1d6b:0002 for 2x
-       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;
-               }
+       priv->dev_descriptor.bNumConfigurations = 1;
+       priv->active_config = 1;
+
+       if (priv->parent_dev == NULL) {
+               usbi_err(ctx, "program assertion failed - HCD hub has no parent");
+               return LIBUSB_ERROR_NO_DEVICE;
+       }
+       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;
 }
 
 /*
  * fetch and cache all the config descriptors through I/O
  */
-static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle)
+static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle, char* device_id)
 {
        DWORD size, ret_size;
        struct libusb_context *ctx = DEVICE_CTX(dev);
@@ -778,7 +900,7 @@ static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle
                size = sizeof(USB_CONFIGURATION_DESCRIPTOR_SHORT);
                memset(&cd_buf_short, 0, size);
 
-               cd_buf_short.req.ConnectionIndex = priv->connection_index;
+               cd_buf_short.req.ConnectionIndex = (ULONG)priv->port;
                cd_buf_short.req.SetupPacket.bmRequest = LIBUSB_ENDPOINT_IN;
                cd_buf_short.req.SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR;
                cd_buf_short.req.SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | i;
@@ -788,24 +910,24 @@ static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle
                // Dummy call to get the required data size
                if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, &cd_buf_short, size,
                        &cd_buf_short, size, &ret_size, NULL)) {
-                       usbi_err(ctx, "could not access configuration descriptor (dummy): %s", windows_error_str(0));
+                       usbi_err(ctx, "could not access configuration descriptor (dummy) for '%s': %s", device_id, windows_error_str(0));
                        LOOP_BREAK(LIBUSB_ERROR_IO);
                }
 
                if ((ret_size != size) || (cd_buf_short.data.wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR))) {
-                       usbi_err(ctx, "unexpected configuration descriptor size (dummy).");
+                       usbi_err(ctx, "unexpected configuration descriptor size (dummy) for '%s'.", device_id);
                        LOOP_BREAK(LIBUSB_ERROR_IO);
                }
 
                size = sizeof(USB_DESCRIPTOR_REQUEST) + cd_buf_short.data.wTotalLength;
                if ((cd_buf_actual = (PUSB_DESCRIPTOR_REQUEST)malloc(size)) == NULL) {
-                       usbi_err(ctx, "could not allocate configuration descriptor buffer. aborting.");
+                       usbi_err(ctx, "could not allocate configuration descriptor buffer for '%s'.", device_id);
                        LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
                }
                memset(cd_buf_actual, 0, size);
 
                // Actual call
-               cd_buf_actual->ConnectionIndex = priv->connection_index;
+               cd_buf_actual->ConnectionIndex = (ULONG)priv->port;
                cd_buf_actual->SetupPacket.bmRequest = LIBUSB_ENDPOINT_IN;
                cd_buf_actual->SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR;
                cd_buf_actual->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | i;
@@ -814,19 +936,19 @@ static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle
 
                if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, cd_buf_actual, size,
                        cd_buf_actual, size, &ret_size, NULL)) {
-                       usbi_err(ctx, "could not access configuration descriptor (actual): %s", windows_error_str(0));
+                       usbi_err(ctx, "could not access configuration descriptor (actual) for '%s': %s", device_id, windows_error_str(0));
                        LOOP_BREAK(LIBUSB_ERROR_IO);
                }
 
                cd_data = (PUSB_CONFIGURATION_DESCRIPTOR)((UCHAR*)cd_buf_actual+sizeof(USB_DESCRIPTOR_REQUEST));
 
                if ((size != ret_size) || (cd_data->wTotalLength != cd_buf_short.data.wTotalLength)) {
-                       usbi_err(ctx, "unexpected configuration descriptor size (actual).");
+                       usbi_err(ctx, "unexpected configuration descriptor size (actual) for '%s'.", device_id);
                        LOOP_BREAK(LIBUSB_ERROR_IO);
                }
 
                if (cd_data->bDescriptorType != USB_CONFIGURATION_DESCRIPTOR_TYPE) {
-                       usbi_err(ctx, "not a configuration descriptor");
+                       usbi_err(ctx, "not a configuration descriptor for '%s'", device_id);
                        LOOP_BREAK(LIBUSB_ERROR_IO);
                }
 
@@ -844,628 +966,535 @@ static int cache_config_descriptors(struct libusb_device *dev, HANDLE hub_handle
 }
 
 /*
- * Recursively enumerates and finds all hubs & devices
+ * Populate a libusb device structure
  */
-static int usb_enumerate_hub(struct libusb_context *ctx, struct discovered_devs **_discdevs,
-       HANDLE hub_handle, libusb_bus_t busnum, struct libusb_device *parent_dev, uint8_t nb_ports)
+static int init_device(struct libusb_device* dev, struct libusb_device* parent_dev,
+                                          uint8_t port_number, char* device_id, DWORD devinst)
 {
-       struct discovered_devs *discdevs = *_discdevs;
-       struct libusb_device *dev = NULL;
-       DWORD size, size_initial, size_fixed, getname_ioctl;
-       HANDLE handle = INVALID_HANDLE_VALUE;
-       USB_HUB_NAME_FIXED s_hubname;
+       HANDLE handle;
+       DWORD size;
        USB_NODE_CONNECTION_INFORMATION conn_info;
-       USB_NODE_INFORMATION hub_node;
-       bool is_hcd, need_unref = false;
-       int i, r;
-       LPCWSTR wstr;
-       char *tmp_str = NULL, *path_str = NULL;
-       unsigned long session_id;
-       libusb_devaddr_t devaddr = 0;
        struct windows_device_priv *priv, *parent_priv;
+       struct libusb_context *ctx = DEVICE_CTX(dev);
+       struct libusb_device* tmp_dev;
+       unsigned i;
 
-       // obviously, root (HCD) hubs have no parent
-       is_hcd = (parent_dev == NULL);
-       if (is_hcd)
-       {
-               if (nb_ports != 1) {
-                       usbi_warn(ctx, "program assertion failed - invalid number of ports for HCD.");
-                       return LIBUSB_ERROR_INVALID_PARAM;
+       if ((dev == NULL) || (parent_dev == NULL)) {
+               return LIBUSB_ERROR_NOT_FOUND;
+       }
+       priv = __device_priv(dev);
+       parent_priv = __device_priv(parent_dev);
+       if (parent_priv->apib->id != USB_API_HUB) {
+               usbi_warn(ctx, "parent for device '%s' is not a hub", device_id);
+               return LIBUSB_ERROR_NOT_FOUND;
+       }
+
+       // It is possible for the parent hub not to have been initialized yet
+       // If that's the case, lookup the ancestors to set the bus number
+       if (parent_dev->bus_number == 0) {
+               for (i=2; ; i++) {
+                       tmp_dev = usbi_get_device_by_session_id(ctx, get_ancestor_session_id(devinst, i));
+                       if (tmp_dev == NULL) break;
+                       if (tmp_dev->bus_number != 0) {
+                               usbi_dbg("got bus number from ancestor #%d", i);
+                               parent_dev->bus_number = tmp_dev->bus_number;
+                               break;
+                       }
                }
-               parent_priv = NULL;
-               size_initial = sizeof(USB_ROOT_HUB_NAME);
-               size_fixed = sizeof(USB_ROOT_HUB_NAME_FIXED);
-               getname_ioctl = IOCTL_USB_GET_ROOT_HUB_NAME;
        }
-       else
-       {
-               parent_priv = __device_priv(parent_dev);
-               size_initial = sizeof(USB_NODE_CONNECTION_NAME);
-               size_fixed = sizeof(USB_NODE_CONNECTION_NAME_FIXED);
-               getname_ioctl = IOCTL_USB_GET_NODE_CONNECTION_NAME;
+       if (parent_dev->bus_number == 0) {
+               usbi_err(ctx, "program assertion failed: unable to find ancestor bus number for '%s'", device_id);
+               return LIBUSB_ERROR_NOT_FOUND;
        }
+       dev->bus_number = parent_dev->bus_number;
+       priv->port = port_number;
+       priv->depth = parent_priv->depth + 1;
+       priv->parent_dev = parent_dev;
 
-       // Loop through all the ports on this hub
-       for (i = 1, r = LIBUSB_SUCCESS; ; i++)
-       {
-               // safe loop: release all dynamic resources
-               if (need_unref) {
-                       safe_unref_device(dev);
-                       need_unref = false;
+       // If the device address is already set, we can stop here
+       if (dev->device_address != 0) {
+               return LIBUSB_SUCCESS;
+       }
+       memset(&conn_info, 0, sizeof(conn_info));
+       if (priv->depth != 0) { // Not a HCD hub
+               handle = CreateFileA(parent_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", parent_priv->path, windows_error_str(0));
+                       return LIBUSB_ERROR_ACCESS;
+               }
+               size = sizeof(USB_NODE_CONNECTION_INFORMATION);
+               conn_info.ConnectionIndex = (ULONG)port_number;
+               if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, &conn_info, size,
+                       &conn_info, size, &size, NULL)) {
+                       usbi_warn(ctx, "could not get node connection information for device '%s': %s",
+                               device_id, windows_error_str(0));
+                       safe_closehandle(handle);
+                       return LIBUSB_ERROR_NO_DEVICE;
+               }
+               if (conn_info.ConnectionStatus == NoDeviceConnected) {
+                       usbi_err(ctx, "device '%s' is no longer connected!", device_id);
+                       safe_closehandle(handle);
+                       return LIBUSB_ERROR_NO_DEVICE;
+               }
+               dev->device_address = (uint8_t)conn_info.DeviceAddress;
+               memcpy(&priv->dev_descriptor, &(conn_info.DeviceDescriptor), sizeof(USB_DEVICE_DESCRIPTOR));
+               dev->num_configurations = priv->dev_descriptor.bNumConfigurations;
+               priv->active_config = conn_info.CurrentConfigurationValue;
+               usbi_dbg("found %d configurations (active conf: %d)", dev->num_configurations, priv->active_config);
+               // If we can't read the config descriptors, just set the number of confs to zero
+               if (cache_config_descriptors(dev, handle, device_id) != LIBUSB_SUCCESS) {
+                       dev->num_configurations = 0;
+                       priv->dev_descriptor.bNumConfigurations = 0;
                }
-               safe_free(tmp_str);
-               safe_free(path_str);
                safe_closehandle(handle);
 
-               // safe loop: end of loop condition
-               if ((i > nb_ports) || (r != LIBUSB_SUCCESS))
-                       break;
-
-               memset(&conn_info, 0, sizeof(conn_info));
-               // For non HCDs, check if the node on this port is a hub or a regular device
-               if (!is_hcd) {
-                       size = sizeof(USB_NODE_CONNECTION_INFORMATION);
-                       conn_info.ConnectionIndex = i;
-                       if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, &conn_info, size,
-                               &conn_info, size, &size, NULL)) {
-                               usbi_warn(ctx, "could not get node connection information: %s", windows_error_str(0));
-                               continue;
-                       }
-
-                       if (conn_info.ConnectionStatus == NoDeviceConnected) {
-                               continue;
-                       }
-
-                       if (conn_info.DeviceAddress == LIBUSB_DEVADDR_MAX) {
-                               usbi_warn(ctx, "program assertion failed - device address is %d "
-                                       "(conflicts with root hub), ignoring device", LIBUSB_DEVADDR_MAX);
-                               continue;
-                       }
-
-                       s_hubname.u.node.ConnectionIndex = i;   // Only used for non HCDs (s_hubname is an union)
+               if (conn_info.DeviceAddress > UINT8_MAX) {
+                       usbi_err(ctx, "program assertion failed: device address overflow");
                }
-               else
-               {
-                       // HCDs have only 1 node, and it's always a hub
-                       conn_info.DeviceAddress = LIBUSB_DEVADDR_MAX;   // using 0 can conflict with driverless devices
-                       conn_info.DeviceIsHub = true;
-                       conn_info.CurrentConfigurationValue = 1;
-               }
-
-               // If this node is a hub (HCD or not), open it
-               if (conn_info.DeviceIsHub) {
-                       size = size_initial;
-                       if (!DeviceIoControl(hub_handle, getname_ioctl, &s_hubname, size,
-                               &s_hubname, size, &size, NULL)) {
-                               usbi_warn(ctx, "could not get hub path (dummy): %s", windows_error_str(0));
-                               continue;
-                       }
+               dev->device_address = (uint8_t)conn_info.DeviceAddress;
+       } else {
+               dev->device_address = UINT8_MAX;        // Hubs from HCD have a devaddr of 255
+               force_hcd_device_descriptor(dev);
+       }
 
-                       size = is_hcd?s_hubname.u.root.ActualLength:s_hubname.u.node.ActualLength;
-                       if (size > size_fixed) {
-                               usbi_warn(ctx, "program assertion failed - hub path is too long");
-                               continue;
-                       }
+       usbi_dbg("(bus: %d, addr: %d, depth: %d, port: %d): '%s'",
+               dev->bus_number, dev->device_address, priv->depth, priv->port, device_id);
 
-                       if (!is_hcd) {
-                               // previous call trashes some of the data
-                               s_hubname.u.node.ConnectionIndex = i;
-                       }
-                       if (!DeviceIoControl(hub_handle, getname_ioctl, &s_hubname, size,
-                               &s_hubname, size, &size, NULL)) {
-                               usbi_warn(ctx, "could not get hub path (actual): %s", windows_error_str(0));
-                               continue;
-                       }
+       return LIBUSB_SUCCESS;
+}
 
-                       // Add prefix
-                       wstr = is_hcd?s_hubname.u.root.RootHubName:s_hubname.u.node.NodeName;
-                       tmp_str = wchar_to_utf8(wstr);
-                       if (tmp_str == NULL) {
-                               usbi_err(ctx, "could not convert hub path string.");
-                               LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
-                       }
+// Returns the api type, or 0 if not found/unsupported
+static uint8_t get_api_type(struct libusb_context *ctx,
+                                               HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data)
+{
+       // Precedence for filter drivers vs driver is in the order of this array
+       struct driver_lookup lookup[3] = {
+               {"\0\0", SPDRP_SERVICE, "driver"},
+               {"\0\0", SPDRP_UPPERFILTERS, "upper filter driver"},
+               {"\0\0", SPDRP_LOWERFILTERS, "lower filter driver"}
+       };
+       DWORD size, reg_type;
+       unsigned k, l;
+       uint8_t api;
 
-                       path_str = sanitize_path(tmp_str);
-                       if (path_str == NULL) {
-                               usbi_err(ctx, "could not sanitize hub path string.");
-                               LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
+       // Check the service & filter names to know the API we should use
+       for (k=0; k<3; k++) {
+               if (pSetupDiGetDeviceRegistryPropertyA(*dev_info, dev_info_data, lookup[k].reg_prop,
+                       &reg_type, (BYTE*)lookup[k].list, MAX_KEY_LENGTH, &size)) {
+                       // Turn the REG_SZ SPDRP_SERVICE into REG_MULTI_SZ
+                       if (lookup[k].reg_prop == SPDRP_SERVICE) {
+                               // our buffers are MAX_KEY_LENGTH+1 so we can overflow if needed
+                               lookup[k].list[safe_strlen(lookup[k].list)+1] = 0;
                        }
-
-                       // Open Hub
-                       handle = CreateFileA(path_str, 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", path_str, windows_error_str(0));
-                               continue;
+                       // MULTI_SZ is a pain to work with. Turn it into something much more manageable
+                       // NB: none of the driver names we check against contain LIST_SEPARATOR,
+                       // (currently ';'), so even if an unsuported one does, it's not an issue
+                       for (l=0; (lookup[k].list[l] != 0) || (lookup[k].list[l+1] != 0); l++) {
+                               if (lookup[k].list[l] == 0) {
+                                       lookup[k].list[l] = LIST_SEPARATOR;
+                               }
                        }
-               }
-
-               // Generate a session ID
-               // Will need to change the session_id computation if this assertion fails
-               if (conn_info.DeviceAddress > LIBUSB_DEVADDR_MAX) {
-                       usbi_warn(ctx, "program assertion failed - device address is greater than %d, ignoring device",
-                               LIBUSB_DEVADDR_MAX);
-                       continue;
+                       upperize(lookup[k].list);
+                       usbi_dbg("%s(s): %s", lookup[k].designation, lookup[k].list);
                } else {
-                       devaddr = (uint8_t)conn_info.DeviceAddress;
-               }
-               // Same trick as linux for session_id, with same caveat
-               session_id = busnum << (sizeof(libusb_devaddr_t)*8) | devaddr;
-               usbi_dbg("busnum %d devaddr %d session_id %ld", busnum, devaddr, session_id);
-
-               // Allocate device if needed
-               dev = usbi_get_device_by_session_id(ctx, session_id);
-               if (dev) {
-                       usbi_dbg("using existing device for session %ld", session_id);
-                       priv = __device_priv(dev);
-                       // Because we are rebuilding the list, there's no guarantee
-                       // the parent device pointer is still the same.
-                       // Other device data should still be reusable
-                       priv->parent_dev = parent_dev;
-               } else {
-                       usbi_dbg("allocating new device for session %ld", session_id);
-                       if ((dev = usbi_alloc_device(ctx, session_id)) == NULL) {
-                               LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
-                       }
-                       need_unref = true;
-
-                       LOOP_CHECK(initialize_device(dev, busnum, devaddr, path_str, i,
-                               conn_info.CurrentConfigurationValue, parent_dev));
-                       priv = __device_priv(dev);
-
-                       path_str = NULL;        // protect our path from being freed
-
-                       // Setup the cached descriptors. Note that only non HCDs can fetch descriptors
-                       if (!is_hcd) {
-                               // The device descriptor has been read with conn_info
-                               memcpy(&priv->dev_descriptor, &(conn_info.DeviceDescriptor), sizeof(USB_DEVICE_DESCRIPTOR));
-                               dev->num_configurations = priv->dev_descriptor.bNumConfigurations;
-                               // If we can't read the config descriptors, just set the number of confs to zero
-                               if (cache_config_descriptors(dev, hub_handle) != LIBUSB_SUCCESS) {
-                                       dev->num_configurations = 0;
-                                       priv->dev_descriptor.bNumConfigurations = 0;
-                               }
-                       } else {
-                               LOOP_CHECK(force_hcd_device_descriptor(dev, handle));
+                       if (GetLastError() != ERROR_INVALID_DATA) {
+                               usbi_dbg("could not access %s: %s", lookup[k].designation, windows_error_str(0));
                        }
-                       LOOP_CHECK(usbi_sanitize_device(dev));
+                       lookup[k].list[0] = 0;
                }
+       }
 
-               discdevs = discovered_devs_append(*_discdevs, dev);
-               if (!discdevs) {
-                       LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
+       for (api=1; api<USB_API_MAX; api++) {
+               for (k=0; k<3; k++) {
+                       if (is_api_driver(lookup[k].list, api)) {
+                               usbi_dbg("matched %s name against %s", lookup[k].designation, usb_api_backend[api].designation);
+                               break;
+                       }
                }
+               if (k >= 3) continue;
+               return api;
+       }
+       return 0;
+}
 
-               *_discdevs = discdevs;
+static int set_composite_interface(struct libusb_context* ctx, struct libusb_device* dev,
+                                                       char* dev_interface_path, char* device_id, uint8_t api)
+{
+       unsigned i;
+       struct windows_device_priv *priv = __device_priv(dev);
+       int interface_number;
 
-               // Finally, if device is a hub, recurse
-               if (conn_info.DeviceIsHub) {
-                       // Find number of ports for this hub
-                       size =  sizeof(USB_NODE_INFORMATION);
-                       if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_INFORMATION, &hub_node, size,
-                               &hub_node, size, &size, NULL)) {
-                               usbi_warn(ctx, "could not retreive information for hub %s: %s",
-                                       priv->path, windows_error_str(0));
-                               continue;
-                       }
+       if (priv->apib->id != USB_API_COMPOSITE) {
+               usbi_err(ctx, "program assertion failed: '%s' is not composite", device_id);
+               return LIBUSB_ERROR_NO_DEVICE;
+       }
 
-                       if (hub_node.NodeType != UsbHub) {
-                               usbi_warn(ctx, "unexpected hub type (%d) for hub %s", hub_node.NodeType, priv->path);
-                               continue;
-                       }
+       // Because MI_## are not necessarily in sequential order (some composite
+       // devices will have only MI_00 & MI_03 for instance), we retrieve the actual
+       // interface number from the path's MI value
+       interface_number = 0;
+       for (i=0; device_id[i] != 0; ) {
+               if ( (device_id[i++] == 'M') && (device_id[i++] == 'I')
+                 && (device_id[i++] == '_') ) {
+                       interface_number = (device_id[i++] - '0')*10;
+                       interface_number += device_id[i] - '0';
+                       break;
+               }
+       }
 
-                       usbi_dbg("%d ports Hub: %s", hub_node.u.HubInformation.HubDescriptor.bNumberOfPorts, priv->path);
+       if (device_id[i] == 0) {
+               usbi_warn(ctx, "failure to read interface number for %s. Using default value %d",
+                       device_id, interface_number);
+       }
 
-                       usb_enumerate_hub(ctx, _discdevs, handle, busnum, dev,
-                               hub_node.u.HubInformation.HubDescriptor.bNumberOfPorts);
-               }
+       if (priv->usb_interface[interface_number].path != NULL) {
+               usbi_warn(ctx, "interface[%d] already set - ignoring: %s", interface_number, device_id);
+               return LIBUSB_ERROR_ACCESS;
        }
 
-       return r;
+       usbi_dbg("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->composite_api_flags |= 1<<api;
+
+       return LIBUSB_SUCCESS;
 }
 
 /*
- * Composite device interfaces are not enumerated using GUID_DEVINTERFACE_USB_DEVICE,
- * but instead require a different lookup mechanism
+ * get_device_list: libusb backend device enumeration function
  */
-static int set_composite_device(struct libusb_context *ctx, DEVINST devinst, struct windows_device_priv *priv)
+static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs)
 {
-       DEVINST child_devinst;
-       unsigned i, j, max_guids, nb_paths, interface_number;
-       uint8_t api;
-       bool found;
-       DWORD type, size;
-       CONFIGRET r;
+       struct discovered_devs *discdevs = *_discdevs;
        HDEVINFO dev_info;
        SP_DEVINFO_DATA dev_info_data;
        SP_DEVICE_INTERFACE_DETAIL_DATA_A *dev_interface_details = NULL;
+#define MAX_ENUM_GUIDS 64
+       const GUID* guid[MAX_ENUM_GUIDS];
+#define HCD_PASS 0
+#define HUB_PASS 1
+#define GEN_PASS 2
+#define DEV_PASS 3
+       int r = LIBUSB_SUCCESS;
+       unsigned int nb_guids, pass, i, j, ancestor;
+       char path[MAX_PATH_LENGTH];
+       char strbuf[MAX_PATH_LENGTH];
+       struct libusb_device *dev, *parent_dev;
+       struct windows_device_priv *priv, *parent_priv;
+       char* dev_interface_path = NULL;
+       char* dev_id_path = NULL;
+       unsigned long session_id;
+       DWORD size, reg_type, port_nr, install_state;
        HKEY key;
        WCHAR guid_string_w[MAX_GUID_STRING_LENGTH];
-       GUID guid, class_guid;
-       GUID guid_table[MAX_USB_DEVICES];
-       char* sanitized_path[MAX_USB_DEVICES];
-       uint8_t api_type[MAX_USB_DEVICES];
-       char* sanitized_short = NULL;
-       char path[MAX_PATH_LENGTH];
-       char driver[MAX_KEY_LENGTH];
-
-       dev_info = pSetupDiGetClassDevsA(NULL, "USB", NULL, DIGCF_PRESENT|DIGCF_ALLCLASSES);
-       if (dev_info == INVALID_HANDLE_VALUE) {
-               return LIBUSB_ERROR_NOT_FOUND;
+       GUID* if_guid;
+       LONG s;
+       uint8_t api;
+       // 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
+       // PASS 3 : (re)enumerate generic USB devices (including driverless)
+       //           and list additional USB device interface GUIDs to explore
+       // PASS 4 : (re)enumerate master USB devices that have a device interface
+       // PASS 5+: (re)enumerate device interfaced GUIDs and set the device interfaces.
+
+       // Init the GUID table
+       guid[HCD_PASS] = &GUID_DEVINTERFACE_USB_HOST_CONTROLLER;
+       guid[HUB_PASS] = &GUID_DEVINTERFACE_USB_HUB;
+       guid[GEN_PASS] = NULL;
+       guid[DEV_PASS] = &GUID_DEVINTERFACE_USB_DEVICE;
+       nb_guids = DEV_PASS+1;
+
+       unref_list = malloc(unref_size*sizeof(libusb_device*));
+       if (unref_list == NULL) {
+               return LIBUSB_ERROR_NO_MEM;
        }
 
-       max_guids = 0;
-
-       // First, retrieve all the device interface GUIDs
-       for (i = 0; ; i++)
-       {
-               dev_info_data.cbSize = sizeof(dev_info_data);
-               if (!pSetupDiEnumDeviceInfo(dev_info, i, &dev_info_data)) {
+       for (pass = 0; ((pass < nb_guids) && (r == LIBUSB_SUCCESS)); pass++) {
+//#define ENUM_DEBUG
+#ifdef ENUM_DEBUG
+               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;
+               default:
+                       usbi_dbg("PROCESSING EXTs %s", guid_to_string(guid[pass]));
                        break;
                }
+#endif
+               for (i = 0; ; i++) {
+                       // safe loop: free up any (unprotected) dynamic resource
+                       // NB: this is always executed before breaking the loop
+                       safe_free(dev_interface_details);
+                       safe_free(dev_interface_path);
+                       safe_free(dev_id_path);
+                       session_id = 0;
+                       priv = parent_priv = NULL;
+                       dev = parent_dev = NULL;
 
-               key = pSetupDiOpenDevRegKey(dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
-               if (key == INVALID_HANDLE_VALUE) {
-                       usbi_dbg("could not open registry key");
-                       continue;
-               }
-
-               size = sizeof(guid_string_w);
-               r = RegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, &type,
-                       (BYTE*)guid_string_w, &size);
-               RegCloseKey(key);
-               if (r != ERROR_SUCCESS) {
-                       continue;
-               }
-               pCLSIDFromString(guid_string_w, &guid);
-
-               // identical device interface GUIDs are not supposed to happen, but are a real possibility
-               // => check and ignore duplicates
-               found = false;
-               for (j=0; j<max_guids; j++) {
-                       if (memcmp(&guid_table[j], &guid, sizeof(GUID)) == 0) {
-                               found = true;
+                       // Safe loop: end of loop conditions
+                       if (r != LIBUSB_SUCCESS) {
                                break;
                        }
-               }
-               if (!found) {
-                       guid_table[max_guids++] = guid;
-                       if (max_guids > MAX_USB_DEVICES) {
-                               usbi_warn(ctx, "more than %d devices - ignoring the rest", MAX_USB_DEVICES);
+                       if ((pass == HCD_PASS) && (i == UINT8_MAX)) {
+                               usbi_warn(ctx, "program assertion failed - found more than %d buses, skipping the rest.", UINT8_MAX);
                                break;
                        }
-               }
-       }
-       pSetupDiDestroyDeviceInfoList(dev_info);
-
-       // Now let's find the device interface paths for all these devices
-       nb_paths = 0;
-       for (j=0; j<max_guids; j++)
-       {
-               guid = guid_table[j];
-
-               for (i = 0; ; i++)
-               {
-                       safe_free(dev_interface_details);
-                       dev_interface_details = get_interface_details(ctx, &dev_info, &dev_info_data, guid, i);
-                       if (dev_interface_details == NULL)
-                               break;
+                       if (pass != GEN_PASS) {
+                               // Except for GEN, all passes deal with device interfaces
+                               dev_interface_details = get_interface_details(ctx, &dev_info, &dev_info_data, guid[pass], i);
+                               if (dev_interface_details == NULL) {
+                                       break;
+                               } else {
+                                       dev_interface_path = sanitize_path(dev_interface_details->DevicePath);
+                                       if (dev_interface_path == NULL) {
+                                               usbi_warn(ctx, "could not sanitize device interface path for '%s'", dev_interface_details->DevicePath);
+                                               continue;
+                                       }
+                               }
+                       } else {
+                               if (!get_devinfo_data(ctx, &dev_info, &dev_info_data, i)) {
+                                       break;
+                               }
+                       }
 
-                       // In case we can't read the driver string through SPDRP_SERVICE,
-                       // we need the ClassGUID for comparison.
-                       if(!pSetupDiGetDeviceRegistryPropertyW(dev_info, &dev_info_data, SPDRP_CLASSGUID,
-                               NULL, (BYTE*)guid_string_w, sizeof(guid_string_w), &size)) {
-                               usbi_warn(ctx, "could not read class GUID for device %s, skipping: %s",
-                                       dev_interface_details->DevicePath, windows_error_str(0));
+                       // Read the Device ID path. This is what we'll use as UID
+                       // Note that if the device is plugged in a different port or hub, the Device ID changes
+                       if (CM_Get_Device_IDA(dev_info_data.DevInst, path, sizeof(path), 0) != CR_SUCCESS) {
+                               usbi_warn(ctx, "could not read the device id path for devinst %X, skipping",
+                                       dev_info_data.DevInst);
                                continue;
                        }
-                       pCLSIDFromString(guid_string_w, &class_guid);
-
-                       // Attempt to read the driver string
-                       if(!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_SERVICE,
-                               NULL, (BYTE*)driver, MAX_KEY_LENGTH, &size)) {
-                               driver[0] = 0;
+                       dev_id_path = sanitize_path(path);
+                       if (dev_id_path == NULL) {
+                               usbi_warn(ctx, "could not sanitize device id path for devinst %X, skipping",
+                                       dev_info_data.DevInst);
+                               continue;
                        }
+#ifdef ENUM_DEBUG
+                       usbi_dbg("PRO: %s", dev_id_path);
+#endif
 
-                       for (api=USB_API_WINUSB; api<USB_API_MAX; api++) {
-                               if ( (is_api_driver(driver, api))
-                                 || (guid_eq(&class_guid, usb_api_backend[api].class_guid)) ) {
-                                       api_type[nb_paths] = api;
-                                       sanitized_path[nb_paths++] = sanitize_path(dev_interface_details->DevicePath);
-                                       if (nb_paths > MAX_USB_DEVICES) {
-                                               usbi_warn(ctx, "more than %d devices - ignoring the rest", MAX_USB_DEVICES);
-                                               break;
-                                       }
+                       // The SPDRP_ADDRESS for USB devices is the device port number on the hub
+                       port_nr = 0;
+                       if ((pass >= HUB_PASS) && (pass <= GEN_PASS)) {
+                               if ( (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_ADDRESS,
+                                       &reg_type, (BYTE*)&port_nr, 4, &size))
+                                 || (size != 4) ) {
+                                       usbi_warn(ctx, "could not retrieve port number for device '%s', skipping: %s",
+                                               dev_id_path, windows_error_str(0));
+                                       continue;
                                }
                        }
-               }
-       }
-
-       // Finally, match the interface paths with the interfaces. We do that
-       // by looking at the children of the composite device
-       // NB: if the interfaces are not found in their expected position,
-       // claim_interface will issue a warning
-       found = false;
-       memset(&child_devinst, 0, sizeof(DEVINST));     // prevents /W4 warning
-       for (i = 0; i<USB_MAXINTERFACES; i++)
-       {
-               if (i == 0) {
-                       r = CM_Get_Child(&child_devinst, devinst, 0);
-               } else {
-                       r = CM_Get_Sibling(&child_devinst, child_devinst, 0);
-               }
-               if (r == CR_NO_SUCH_DEVNODE) {  // end of the siblings
-                       break;
-               } else if (r != CR_SUCCESS) {
-                       usbi_dbg("unable to find interface sibling #%d, error = %X", i, r);
-                       break;
-               }
-
-               r = CM_Get_Device_IDA(child_devinst, path, MAX_PATH_LENGTH, 0);
-               if (r != CR_SUCCESS) {
-                       usbi_err(ctx, "could not retrieve simple path for interface %d: CR error %d",
-                               i, r);
-                       continue;
-               }
-               sanitized_short = sanitize_path(path);
-               if (sanitized_short == NULL) {
-                       usbi_err(ctx, "could not sanitize path for interface %d", i);
-                       continue;
-               }
 
-               // Because MI_## are not necessarily in sequential order, we
-               // retrieve the actual interface number from the path's MI value
-               interface_number = i;
-               for (j=0; sanitized_short[j] != 0; ) {
-                       if ( (sanitized_short[j++] == 'M') && (sanitized_short[j++] == 'I')
-                         && (sanitized_short[j++] == '_') ) {
-                               interface_number = (sanitized_short[j++] - '0')*10;
-                               interface_number += sanitized_short[j] - '0';
+                       // Set API to use or get additional data from generic pass
+                       api = USB_API_UNSUPPORTED;
+                       switch (pass) {
+                       case HCD_PASS:
+                               break;
+                       case GEN_PASS:
+                               // We use the GEN pass to detect driverless devices...
+                               size = sizeof(strbuf);
+                               if (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_DRIVER,
+                                       &reg_type, (BYTE*)strbuf, size, &size)) {
+                                               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 = pSetupDiOpenDevRegKey(dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
+                               if (key != INVALID_HANDLE_VALUE) {
+                                       size = sizeof(guid_string_w);
+                                       s = pRegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, &reg_type,
+                                               (BYTE*)guid_string_w, &size);
+                                       pRegCloseKey(key);
+                                       if (s == ERROR_SUCCESS) {
+                                               if (nb_guids >= MAX_ENUM_GUIDS) {
+                                                       // If this assert is ever reported, grow a GUID table dynamically
+                                                       usbi_err(ctx, "program assertion failed: too many GUIDs");
+                                                       LOOP_BREAK(LIBUSB_ERROR_OVERFLOW);
+                                               }
+                                               if_guid = calloc(1, sizeof(GUID));
+                                               pCLSIDFromString(guid_string_w, if_guid);
+                                               guid[nb_guids++] = if_guid;
+                                               usbi_dbg("extra GUID: %s", guid_to_string(if_guid));
+                                       }
+                               }
+                               break;
+                       default:
+                               // Get the API type (after checking that the driver installation is OK)
+                               if ( (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_INSTALL_STATE,
+                                       &reg_type, (BYTE*)&install_state, 4, &size))
+                                 || (size != 4) ){
+                                       usbi_warn(ctx, "could not detect installation state of driver for '%s': %s",
+                                               dev_id_path, windows_error_str(0));
+                               } else if (install_state != 0) {
+                                       usbi_warn(ctx, "driver for device '%s' is reporting an issue (code: %d) - skipping",
+                                               dev_id_path, install_state);
+                                       continue;
+                               }
+                               api = get_api_type(ctx, &dev_info, &dev_info_data);
                                break;
                        }
-               }
-               if (sanitized_short[j] == 0) {
-                       usbi_warn(ctx, "failure to read interface number for %s. Using default value %d",
-                               sanitized_short, interface_number);
-               }
-
-               for (j=0; j<nb_paths; j++) {
-                       if ( (safe_strncmp(sanitized_path[j], sanitized_short, safe_strlen(sanitized_short)) == 0) ) {
-                               priv->usb_interface[interface_number].path = sanitized_path[j];
-                               priv->usb_interface[interface_number].apib = &usb_api_backend[api_type[j]];
-                               priv->composite_api_flags |= 1<<api_type[j];
-                               sanitized_path[j] = NULL;
-                       }
-               }
-               safe_free(sanitized_short);
-
-               if (priv->usb_interface[interface_number].path == NULL) {
-                       usbi_warn(ctx, "interface_path[%d]: unhandled API - interface will be disabled",
-                               interface_number);
-                       continue;
-               }
-               usbi_dbg("interface_path[%d]: %s", interface_number, priv->usb_interface[interface_number].path);
-               found = true;
-       }
-
-       for (j=0; j<nb_paths; j++) {
-               safe_free(sanitized_path[j]);
-       }
-
-       if (found == 0) {
-               usbi_warn(ctx, "composite device: no interfaces were found");
-               return LIBUSB_ERROR_NOT_FOUND;
-       }
-
-       return LIBUSB_SUCCESS;
-}
-
-/*
- * This function retrieves and sets the paths of all non-hub devices
- * NB: No I/O with device is required during this call
- */
-static int set_device_paths(struct libusb_context *ctx, struct discovered_devs *discdevs)
-{
-       // Precedence for filter drivers vs driver is in the order of this array
-       struct driver_lookup lookup[3] = {
-               {"\0\0", SPDRP_SERVICE, "driver"},
-               {"\0\0", SPDRP_UPPERFILTERS, "upper filter driver"},
-               {"\0\0", SPDRP_LOWERFILTERS, "lower filter driver"}
-       };
-       struct windows_device_priv *priv;
-       struct windows_device_priv *parent_priv;
-       char path[MAX_PATH_LENGTH];
-       char *sanitized_path = NULL;
-       HDEVINFO dev_info;
-       SP_DEVICE_INTERFACE_DETAIL_DATA_A *dev_interface_details = NULL;
-       SP_DEVINFO_DATA dev_info_data;
-       DEVINST parent_devinst;
-       GUID guid;
-       DWORD size, reg_type, install_state, port_nr;
-       int r = LIBUSB_SUCCESS;
-       unsigned i, j, k, l;
-       uint8_t api;
-       bool found;
-
-       // TODO (after first official release): MI_## automated driver installation
-       guid = GUID_DEVINTERFACE_USB_DEVICE;
-       for (i = 0; ; i++)
-       {
-               // safe loop: free up any (unprotected) dynamic resource
-               safe_free(dev_interface_details);
-               safe_free(sanitized_path);
-
-               dev_interface_details = get_interface_details(ctx, &dev_info, &dev_info_data, guid, i);
-               // safe loop: end of loop condition
-               if ( (dev_interface_details == NULL)
-                 || (r != LIBUSB_SUCCESS) )
-                       break;
-
-               // Check that the driver installation is OK
-               if ( (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_INSTALL_STATE,
-                       &reg_type, (BYTE*)&install_state, 4, &size))
-                 || (size != 4) ){
-                       usbi_warn(ctx, "could not detect installation state of driver for %s: %s",
-                               dev_interface_details->DevicePath, windows_error_str(0));
-               } else if (install_state != 0) {
-                       usbi_warn(ctx, "driver for device %s is reporting an issue (code: %d) - skipping",
-                               dev_interface_details->DevicePath, install_state);
-                       continue;
-               }
-
-               // The SPDRP_ADDRESS for USB devices should be the device port number on the hub
-               if ( (!pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, SPDRP_ADDRESS,
-                       &reg_type, (BYTE*)&port_nr, 4, &size))
-                 || (size != 4) ){
-                       usbi_warn(ctx, "could not retrieve port number for device %s, skipping: %s",
-                               dev_interface_details->DevicePath, windows_error_str(0));
-                       continue;
-               }
-
-               // Retrieve parent's path using PnP Configuration Manager (CM)
-               if (CM_Get_Parent(&parent_devinst, dev_info_data.DevInst, 0) != CR_SUCCESS) {
-                       usbi_warn(ctx, "could not retrieve parent info data for device %s, skipping: %s",
-                               dev_interface_details->DevicePath, windows_error_str(0));
-                       continue;
-               }
 
-               if (CM_Get_Device_IDA(parent_devinst, path, MAX_PATH_LENGTH, 0) != CR_SUCCESS) {
-                       usbi_warn(ctx, "could not retrieve parent's path for device %s, skipping: %s",
-                               dev_interface_details->DevicePath, windows_error_str(0));
-                       continue;
-               }
-
-               // Fix parent's path inconsistencies before attempting to compare
-               sanitized_path = sanitize_path(path);
-               if (sanitized_path == NULL) {
-                       usbi_warn(ctx, "could not sanitize parent's path for device %s, skipping.",
-                               dev_interface_details->DevicePath);
-                       continue;
-               }
-
-               // With the parent path and port number, we should be able to locate our device
-               // by comparing these values to the ones we got when enumerating hubs
-               found = false;
-               for (j=0; j<discdevs->len; j++) {
-                       priv = __device_priv(discdevs->devices[j]);
-
-                       if (priv->parent_dev == NULL) {
-                               continue;       // ignore HCDs
+                       // Find parent device (for the passes that need it)
+                       switch (pass) {
+                       case HCD_PASS:
+                       case DEV_PASS:
+                       case HUB_PASS:
+                               break;
+                       default:
+                               // Go through the ancestors until we see a face we recognize
+                               parent_dev = NULL;
+                               for (ancestor = 1; parent_dev == NULL; ancestor++) {
+                                       session_id = get_ancestor_session_id(dev_info_data.DevInst, ancestor);
+                                       if (session_id == 0) {
+                                               break;
+                                       }
+                                       parent_dev = usbi_get_device_by_session_id(ctx, session_id);
+                               }
+                               if (parent_dev == NULL) {
+                                       usbi_dbg("unlisted ancestor for '%s' (newly connected, etc.) - ignoring", dev_id_path);
+                                       continue;
+                               }
+                               parent_priv = __device_priv(parent_dev);
+                               // virtual USB devices are also listed during GEN - don't process these yet
+                               if ( (pass == GEN_PASS) && (parent_priv->apib->id != USB_API_HUB) ) {
+                                       continue;
+                               }
+                               break;
                        }
 
-                       parent_priv = __device_priv(priv->parent_dev);
-
-                       // NB: we compare strings of different lengths below => strncmp
-                       if ( (safe_strncmp(parent_priv->path, sanitized_path, safe_strlen(sanitized_path)) == 0)
-                         && (port_nr == priv->connection_index) ) {
-
-                               priv->path = sanitize_path(dev_interface_details->DevicePath);
-
-                               usbi_dbg("path (%d:%d): %s", discdevs->devices[j]->bus_number,
-                                       discdevs->devices[j]->device_address, priv->path);
-
-                               // Check the service & filter names to know the API we should use
-                               for (k=0; k<3; k++) {
-                                       if (pSetupDiGetDeviceRegistryPropertyA(dev_info, &dev_info_data, lookup[k].reg_prop,
-                                               &reg_type, (BYTE*)lookup[k].list, MAX_KEY_LENGTH, &size)) {
-                                               // Turn the REG_SZ SPDRP_SERVICE into REG_MULTI_SZ
-                                               if (lookup[k].reg_prop == SPDRP_SERVICE) {
-                                                       // our buffers are MAX_KEY_LENGTH+1 so we can overflow if needed
-                                                       lookup[k].list[safe_strlen(lookup[k].list)+1] = 0;
-                                               }
-                                               // MULTI_SZ is a pain to work with. Turn it into something much more manageable
-                                               // NB: none of the driver names we check against contain LIST_SEPARATOR,
-                                               // (currently ';'), so even if an unsuported one does, it's not an issue
-                                               for (l=0; (lookup[k].list[l] != 0) || (lookup[k].list[l+1] != 0); l++) {
-                                                       if (lookup[k].list[l] == 0) {
-                                                               lookup[k].list[l] = LIST_SEPARATOR;
-                                                       }
-                                               }
-                                               upperize(lookup[k].list);
-                                               usbi_dbg("%s(s): %s", lookup[k].designation, lookup[k].list);
-                                               found = true;
-                                       } else {
-                                               if (GetLastError() != ERROR_INVALID_DATA) {
-                                                       usbi_dbg("could not access %s: %s", lookup[k].designation, windows_error_str(0));
+                       // Create new or match existing device, using the (hashed) device_id as session id
+                       if (pass <= DEV_PASS) { // For subsequent passes, we'll lookup the parent
+                               // These are the passes that create "new" devices
+                               session_id = htab_hash(dev_id_path);
+                               dev = usbi_get_device_by_session_id(ctx, session_id);
+                               if (dev == NULL) {
+                                       if (pass == DEV_PASS) {
+                                               // This can occur if the OS only reports a newly plugged device after we started enum
+                                               usbi_warn(ctx, "'%s' was only detected in late pass (newly connected device?)"
+                                                       " - ignoring", dev_id_path);
+                                               continue;
+                                       }
+                                       usbi_dbg("allocating new device for session [%X]", session_id);
+                                       if ((dev = usbi_alloc_device(ctx, session_id)) == NULL) {
+                                               LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
+                                       }
+                                       windows_device_priv_init(dev);
+                                       // Keep track of devices that need unref
+                                       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);
                                                }
-                                               lookup[k].list[0] = 0;
                                        }
+                               } else {
+                                       usbi_dbg("found existing device for session [%X] (%d.%d)",
+                                               session_id, dev->bus_number, dev->device_address);
                                }
+                               priv = __device_priv(dev);
+                       }
 
-                               for (api=0; api<USB_API_MAX; api++) {
-                                       for (k=0; k<3; k++) {
-                                               if (is_api_driver(lookup[k].list, api)) {
-                                                       usbi_dbg("matched %s name against %s", lookup[k].designation, usb_api_backend[api].designation);
-                                                       break;
-                                               }
+                       // Setup device
+                       switch (pass) {
+                       case HCD_PASS:
+                               dev->bus_number = (uint8_t)(i + 1);     // bus 0 is reserved for disconnected
+                               dev->device_address = 0;
+                               dev->num_configurations = 0;
+                               priv->apib = &usb_api_backend[USB_API_HUB];
+                               priv->depth = UINT8_MAX;        // Overflow to 0 for HCD Hubs
+                               priv->path = dev_interface_path; dev_interface_path = NULL;
+                               break;
+                       case DEV_PASS:
+                               // If the device has already been setup, don't do it again
+                               if (priv->path != NULL)
+                                       break;
+                               // Take care of API initialization
+                               priv->path = dev_interface_path; dev_interface_path = NULL;
+                               priv->apib = &usb_api_backend[api];
+                               switch(api) {
+                               case USB_API_COMPOSITE:
+                                       break;
+                               default:
+                                       // For other devices, the first interface is the same as the device
+                                       priv->usb_interface[0].path = malloc(safe_strlen(priv->path)+1);
+                                       if (priv->usb_interface[0].path != NULL) {
+                                               safe_strcpy(priv->usb_interface[0].path, safe_strlen(priv->path)+1, priv->path);
+                                       }
+                                       // The following is needed if we want API calls to work for both simple
+                                       // and composite devices.
+                                       for(j=0; j<USB_MAXINTERFACES; j++) {
+                                               priv->usb_interface[j].apib = &usb_api_backend[api];
                                        }
-                                       if (k >= 3) continue;
-                                       priv->apib = &usb_api_backend[api];
-                                       switch(api) {
-                                       case USB_API_COMPOSITE:
-                                               set_composite_device(ctx, dev_info_data.DevInst, priv);
+                                       break;
+                               }
+                               break;
+                       case HUB_PASS:
+                               priv->apib = &usb_api_backend[api];
+                               priv->path = dev_interface_path; dev_interface_path = NULL;
+                               break;
+                       case GEN_PASS:
+                               r = init_device(dev, parent_dev, (uint8_t)port_nr, dev_id_path, dev_info_data.DevInst);
+                               if (r == LIBUSB_SUCCESS) {
+                                       // Append device to the list of discovered devices
+                                       discdevs = discovered_devs_append(*_discdevs, dev);
+                                       if (!discdevs) {
+                                               LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
+                                       }
+                                       *_discdevs = discdevs;
+                               } else if (r == LIBUSB_ERROR_NO_DEVICE) {
+                                       // This can occur if the device was disconnected but Windows hasn't
+                                       // refreshed its enumeration yet - in that case, we ignore the device
+                                       r = LIBUSB_SUCCESS;
+                               }
+                               break;
+                       default:        // later passes
+                               if (parent_priv->apib->id == USB_API_COMPOSITE) {
+                                       usbi_dbg("setting composite interface for [%lX]:", parent_dev->session_data);
+                                       switch (set_composite_interface(ctx, parent_dev, dev_interface_path, dev_id_path, api)) {
+                                       case LIBUSB_SUCCESS:
+                                               dev_interface_path = NULL;
+                                               break;
+                                       case LIBUSB_ERROR_ACCESS:
+                                               // interface has already been set => make sure dev_interface_path is freed then
                                                break;
                                        default:
-                                               // For other devices, the first interface is the same as the device
-                                               priv->usb_interface[0].path = malloc(safe_strlen(priv->path)+1);
-                                               if (priv->usb_interface[0].path != NULL) {
-                                                       safe_strcpy(priv->usb_interface[0].path, safe_strlen(priv->path)+1, priv->path);
-                                               }
-                                               // The following is needed if we want to API calls to work for both simple
-                                               // and composite devices, as
-                                               for(k=0; k<USB_MAXINTERFACES; k++) {
-                                                       priv->usb_interface[k].apib = &usb_api_backend[api];
-                                               }
+                                               LOOP_BREAK(r);
                                                break;
                                        }
                                }
                                break;
                        }
                }
-               if (!found) {
-                       usbi_warn(ctx, "could not match %s with a libusb device.", dev_interface_details->DevicePath);
-                       continue;
-               }
        }
 
-       return LIBUSB_SUCCESS;
-}
-
-/*
- * get_device_list: libusb backend device enumeration function
- */
-static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs)
-{
-       struct windows_hcd_priv* hcd;
-       HANDLE handle = INVALID_HANDLE_VALUE;
-       int r = LIBUSB_SUCCESS;
-       libusb_bus_t bus;
-
-       // Use the index of the HCD in the chained list as bus #
-       for (hcd = hcd_root, bus = 0; ; hcd = hcd->next, bus++)
-       {
-               safe_closehandle(handle);
-
-               if ( (hcd == NULL) || (r != LIBUSB_SUCCESS) )
-                       break;
-
-               if (bus == LIBUSB_BUS_MAX) {
-                       usbi_warn(ctx, "program assertion failed - got more than %d buses, skipping the rest.", LIBUSB_BUS_MAX);
-                       continue;
-               }
-
-               handle = CreateFileA(hcd->path, GENERIC_WRITE, FILE_SHARE_WRITE,
-                       NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
-               if (handle == INVALID_HANDLE_VALUE) {
-                       usbi_warn(ctx, "could not open bus %u, skipping: %s", bus, windows_error_str(0));
-                       continue;
-               }
-
-               LOOP_CHECK(usb_enumerate_hub(ctx, _discdevs, handle, bus, NULL, 1));
+       // Free any additional GUIDs
+       for (pass = DEV_PASS+1; pass < nb_guids; pass++) {
+               safe_free(guid[pass]);
        }
 
-       // Set the interface path for non-hubs
-       r = set_device_paths(ctx, *_discdevs);
+       // Unref newly allocated devs
+       for (i=0; i<unref_cur; i++) {
+               safe_unref_device(unref_list[i]);
+       }
+       safe_free(unref_list);
 
        return r;
 }
@@ -1475,7 +1504,6 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
  */
 static void windows_exit(void)
 {
-       struct windows_hcd_priv* hcd_tmp;
        int i;
        HANDLE semaphore;
        char sem_name[11+1+8]; // strlen(libusb_init)+'\0'+(32-bit hex PID)
@@ -1495,14 +1523,6 @@ static void windows_exit(void)
 
        // Only works if exits and inits are balanced exactly
        if (--concurrent_usage < 0) {   // Last exit
-               while (hcd_root != NULL)
-               {
-                       hcd_tmp = hcd_root;     // Keep a copy for free
-                       hcd_root = hcd_root->next;
-                       windows_hcd_priv_release(hcd_tmp);
-                       safe_free(hcd_tmp);
-               }
-
                for (i=0; i<USB_API_MAX; i++) {
                        usb_api_backend[i].exit();
                }
@@ -1531,6 +1551,7 @@ static void windows_exit(void)
                        CloseHandle(timer_mutex);
                        timer_mutex = NULL;
                }
+               htab_destroy();
        }
 
        ReleaseSemaphore(semaphore, 1, NULL);   // increase count back to 1
@@ -1714,8 +1735,7 @@ static int windows_detach_kernel_driver(struct libusb_device_handle *dev_handle,
 
 static void windows_destroy_device(struct libusb_device *dev)
 {
-       struct windows_device_priv *priv = __device_priv(dev);
-       windows_device_priv_release(priv, dev->num_configurations);
+       windows_device_priv_release(dev);
 }
 
 static void windows_clear_transfer_priv(struct usbi_transfer *itransfer)
@@ -2175,6 +2195,7 @@ static int unsupported_copy_transfer_data(struct usbi_transfer *itransfer, uint3
 }
 
 // These names must be uppercase
+const char* hub_driver_names[] = {"USBHUB"};
 const char* composite_driver_names[] = {"USBCCGP"};
 const char* winusb_driver_names[] = {"WINUSB"};
 const struct windows_usb_api_backend usb_api_backend[USB_API_MAX] = {
@@ -2200,6 +2221,27 @@ const struct windows_usb_api_backend usb_api_backend[USB_API_MAX] = {
                unsupported_abort_transfers,
                unsupported_copy_transfer_data,
        }, {
+               USB_API_HUB,
+               "HUB API",
+               &CLASS_GUID_UNSUPPORTED,
+               hub_driver_names,
+               sizeof(hub_driver_names)/sizeof(hub_driver_names[0]),
+               unsupported_init,
+               unsupported_exit,
+               unsupported_open,
+               unsupported_close,
+               unsupported_claim_interface,
+               unsupported_set_interface_altsetting,
+               unsupported_release_interface,
+               unsupported_clear_halt,
+               unsupported_reset_device,
+               unsupported_submit_bulk_transfer,
+               unsupported_submit_iso_transfer,
+               unsupported_submit_control_transfer,
+               unsupported_abort_control,
+               unsupported_abort_transfers,
+               unsupported_copy_transfer_data,
+       }, {
                USB_API_COMPOSITE,
                "Composite API",
                &CLASS_GUID_COMPOSITE,
index 5b0eef8..5ad0304 100644 (file)
 #define false FALSE
 #endif
 
-#if !defined(libusb_bus_t)
-#define libusb_bus_t uint8_t
-#define LIBUSB_BUS_MAX UINT8_MAX
-#endif
-#if !defined(libusb_devaddr_t)
-#define libusb_devaddr_t uint8_t
-#define LIBUSB_DEVADDR_MAX UINT8_MAX
-#endif
-
 // Missing from MSVC6 setupapi.h
 #if !defined(SPDRP_ADDRESS)
 #define SPDRP_ADDRESS  28
@@ -96,26 +87,33 @@ inline void upperize(char* str) {
 #define TIMER_REQUEST_RETRY_MS      100
 #define ERR_BUFFER_SIZE             256
 #define LIST_SEPARATOR              ';'
+#define HTAB_SIZE                   1021
 
-// http://msdn.microsoft.com/en-us/library/bb663109.aspx
-// http://msdn.microsoft.com/en-us/library/bb663093.aspx
+// 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
 #if !defined(GUID_DEVINTERFACE_USB_HOST_CONTROLLER)
 const GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER = { 0x3ABF6F2D, 0x71C4, 0x462A, {0x8A, 0x92, 0x1E, 0x68, 0x61, 0xE6, 0xAF, 0x27} };
 #endif
 #if !defined(GUID_DEVINTERFACE_USB_DEVICE)
 const GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED} };
 #endif
+#if !defined(GUID_DEVINTERFACE_USB_HUB)
+const GUID GUID_DEVINTERFACE_USB_HUB = { 0xF18A0E88, 0xC30C, 0x11D0, {0x88, 0x15, 0x00, 0xA0, 0xC9, 0x06, 0xBE, 0xD8} };
+#endif
+const GUID GUID_NULL = { 0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} };
 
 
 /*
  * Multiple USB API backend support
  */
 #define USB_API_UNSUPPORTED 0
-#define USB_API_COMPOSITE   1
-#define USB_API_WINUSB      2
-#define USB_API_MAX         3
+#define USB_API_HUB         1
+#define USB_API_COMPOSITE   2
+#define USB_API_WINUSB      3
+#define USB_API_MAX         4
 
-const GUID CLASS_GUID_UNSUPPORTED   = { 0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x57, 0xDA} };
+#define CLASS_GUID_UNSUPPORTED      GUID_NULL
 const GUID CLASS_GUID_LIBUSB_WINUSB = { 0x78A1C341, 0x4539, 0x11D3, {0xB8, 0x8D, 0x00, 0xC0, 0x4F, 0xAD, 0x51, 0x71} };
 const GUID CLASS_GUID_COMPOSITE     = { 0x36FC9E60, 0xC465, 0x11cF, {0x80, 0x56, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00} };
 
@@ -153,44 +151,35 @@ extern const struct windows_usb_api_backend usb_api_backend[USB_API_MAX];
  * private structures definition
  * with inline pseudo constructors/destructors
  */
-
-// HCDs
-struct windows_hcd_priv {
-       char *path;
-       struct windows_hcd_priv *next;
-};
-
-static inline void windows_hcd_priv_init(struct windows_hcd_priv* p) {
-       p->path = NULL;
-       p->next = NULL;
-}
-
-static inline void windows_hcd_priv_release(struct windows_hcd_priv* p) {
-       safe_free(p->path);
-}
-
 typedef struct libusb_device_descriptor USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;
 struct windows_device_priv {
-       struct libusb_device *parent_dev;   // access to parent is required for usermode ops
-       ULONG connection_index;             // also required for some usermode ops
-       char *path;                         // path used by Windows to reference the USB node
+       uint8_t depth;                                          // distance to HCD
+       uint8_t port;                                           // port number on the hub
+       struct libusb_device *parent_dev;       // access to parent is required for usermode ops
+       char *path;                                                     // device interface path
        struct windows_usb_api_backend const *apib;
        struct {
-               char *path;                     // each interface needs a Windows device interface path,
+               char *path;                                             // each interface needs a device interface path,
                struct windows_usb_api_backend const *apib; // an API backend (multiple drivers support),
-               int8_t nb_endpoints;            // and a set of endpoint addresses (USB_MAXENDPOINTS)
+               int8_t nb_endpoints;                    // and a set of endpoint addresses (USB_MAXENDPOINTS)
                uint8_t *endpoint;
        } usb_interface[USB_MAXINTERFACES];
-       uint8_t composite_api_flags;        // composite devices require additional data
+       uint8_t composite_api_flags;            // composite devices require additional data
        uint8_t active_config;
        USB_DEVICE_DESCRIPTOR dev_descriptor;
-       unsigned char **config_descriptor;  // list of pointers to the cached config descriptors
+       unsigned char **config_descriptor;      // list of pointers to the cached config descriptors
 };
 
-static inline void windows_device_priv_init(struct windows_device_priv* p) {
+static inline struct windows_device_priv *__device_priv(struct libusb_device *dev) {
+       return (struct windows_device_priv *)dev->os_priv;
+}
+
+static inline void windows_device_priv_init(libusb_device* dev) {
+       struct windows_device_priv* p = __device_priv(dev);
        int i;
+       p->depth = 0;
+       p->port = 0;
        p->parent_dev = NULL;
-       p->connection_index = 0;
        p->path = NULL;
        p->apib = &usb_api_backend[USB_API_UNSUPPORTED];
        p->composite_api_flags = 0;
@@ -205,11 +194,12 @@ static inline void windows_device_priv_init(struct windows_device_priv* p) {
        }
 }
 
-static inline void windows_device_priv_release(struct windows_device_priv* p, int num_configurations) {
+static inline void windows_device_priv_release(libusb_device* dev) {
+       struct windows_device_priv* p = __device_priv(dev);
        int i;
        safe_free(p->path);
-       if ((num_configurations > 0) && (p->config_descriptor != NULL)) {
-               for (i=0; i < num_configurations; i++)
+       if ((dev->num_configurations > 0) && (p->config_descriptor != NULL)) {
+               for (i=0; i < dev->num_configurations; i++)
                        safe_free(p->config_descriptor[i]);
        }
        safe_free(p->config_descriptor);
@@ -219,10 +209,6 @@ static inline void windows_device_priv_release(struct windows_device_priv* p, in
        }
 }
 
-static inline struct windows_device_priv *__device_priv(struct libusb_device *dev) {
-       return (struct windows_device_priv *)dev->os_priv;
-}
-
 struct interface_handle_t {
        HANDLE dev_handle; // WinUSB needs an extra handle for the file
        HANDLE api_handle; // used by the API to communicate with the device
@@ -297,9 +283,8 @@ DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiDestroyDeviceInfoList, (HDEVINFO));
 DLL_DECLARE_PREFIXED(WINAPI, HKEY, p, SetupDiOpenDevRegKey, (HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM));
 DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceRegistryPropertyA, (HDEVINFO,
                        PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD));
-DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiGetDeviceRegistryPropertyW, (HDEVINFO,
-                       PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD));
-
+DLL_DECLARE_PREFIXED(WINAPI, LONG, p, RegQueryValueExW, (HKEY, LPCWSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD));
+DLL_DECLARE_PREFIXED(WINAPI, LONG, p, RegCloseKey, (HKEY));
 
 /*
  * Windows DDK API definitions. Most of it copied from MinGW's includes
@@ -330,7 +315,6 @@ typedef RETURN_TYPE CONFIGRET;
 #define USB_REQUEST_SET_INTERFACE               LIBUSB_REQUEST_SET_INTERFACE
 #define USB_REQUEST_SYNC_FRAME                  LIBUSB_REQUEST_SYNCH_FRAME
 
-#define HCD_GET_ROOT_HUB_NAME                   258
 #define USB_GET_NODE_INFORMATION                258
 #define USB_GET_NODE_CONNECTION_INFORMATION     259
 #define USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION 260
@@ -505,7 +489,6 @@ typedef struct _USB_HUB_NAME_FIXED {
        } u;
 } USB_HUB_NAME_FIXED;
 
-
 typedef struct _USB_HUB_INFORMATION {
        USB_HUB_DESCRIPTOR  HubDescriptor;
        BOOLEAN  HubIsBusPowered;