windows: fix USB 3.0 speed detection on Windows 8 or later
authorPete Batard <pete@akeo.ie>
Sun, 18 May 2014 19:18:29 +0000 (20:18 +0100)
committerPete Batard <pete@akeo.ie>
Sun, 18 May 2014 19:19:03 +0000 (20:19 +0100)
* ...since Microsoft broke IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX between Windows 7 and Windows 8
* Also improve Windows version detection and reporting
* Closes #10

libusb/os/poll_windows.h
libusb/os/wince_usb.c
libusb/os/windows_usb.c
libusb/os/windows_usb.h
libusb/version_nano.h

index deed206..aa4c985 100644 (file)
 
 #define DUMMY_HANDLE ((HANDLE)(LONG_PTR)-2)
 
+/* Windows versions */
 enum windows_version {
-       WINDOWS_UNSUPPORTED,
-       WINDOWS_CE,
-       WINDOWS_XP,
-       WINDOWS_2003,   // also includes XP 64
-       WINDOWS_VISTA_AND_LATER,
+       WINDOWS_CE = -2,
+       WINDOWS_UNDEFINED = -1,
+       WINDOWS_UNSUPPORTED = 0,
+       WINDOWS_XP = 0x51,
+       WINDOWS_2003 = 0x52,    // Also XP x64
+       WINDOWS_VISTA = 0x60,
+       WINDOWS_7 = 0x61,
+       WINDOWS_8 = 0x62,
+       WINDOWS_8_1_OR_LATER = 0x63,
+       WINDOWS_MAX
 };
-extern enum windows_version windows_version;
+extern int windows_version;
 
 #define MAX_FDS     256
 
index 98304e7..4d9b3cc 100644 (file)
@@ -38,7 +38,7 @@ unsigned __stdcall wince_clock_gettime_threaded(void* param);
 uint64_t hires_frequency, hires_ticks_to_ps;
 int errno;
 const uint64_t epoch_time = UINT64_C(116444736000000000);       // 1970.01.01 00:00:000 in MS Filetime
-enum windows_version windows_version = WINDOWS_CE;
+int windows_version = WINDOWS_CE;
 static int concurrent_usage = -1;
 // Timer thread
 // NB: index 0 is for monotonic and 1 is for the thread exit event
index 488ea7b..19b2542 100644 (file)
@@ -100,7 +100,8 @@ static int composite_copy_transfer_data(int sub_api, struct usbi_transfer *itran
 // Global variables
 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;
+int windows_version = WINDOWS_UNDEFINED;
+static char windows_version_str[128] = "Windows Undefined";
 // Concurrency
 static int concurrent_usage = -1;
 usbi_mutex_t autoclaim_lock;
@@ -804,6 +805,118 @@ static void auto_release(struct usbi_transfer *itransfer)
        usbi_mutex_unlock(&autoclaim_lock);
 }
 
+/* Windows version dtection */
+static BOOL is_x64(void)
+{
+       BOOL ret = FALSE;
+       // Detect if we're running a 32 or 64 bit system
+       if (sizeof(uintptr_t) < 8) {
+               DLL_LOAD_PREFIXED(Kernel32.dll, p, IsWow64Process, FALSE);
+               if (pIsWow64Process != NULL) {
+                       (*pIsWow64Process)(GetCurrentProcess(), &ret);
+               }
+       } else {
+               ret = TRUE;
+       }
+       return ret;
+}
+
+static void get_windows_version(void)
+{
+       OSVERSIONINFOEXA vi, vi2;
+       const char* w = 0;
+       const char* w64 = "32 bit";
+       char* vptr;
+       size_t vlen;
+       unsigned major, minor;
+       ULONGLONG major_equal, minor_equal;
+       BOOL ws;
+
+       memset(&vi, 0, sizeof(vi));
+       vi.dwOSVersionInfoSize = sizeof(vi);
+       if (!GetVersionExA((OSVERSIONINFOA *)&vi)) {
+               memset(&vi, 0, sizeof(vi));
+               vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
+               if (!GetVersionExA((OSVERSIONINFOA *)&vi))
+                       return;
+       }
+
+       if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
+
+               if (vi.dwMajorVersion > 6 || (vi.dwMajorVersion == 6 && vi.dwMinorVersion >= 2)) {
+                       // Starting with Windows 8.1 Preview, GetVersionEx() does no longer report the actual OS version
+                       // See: http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx
+
+                       major_equal = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
+                       for (major = vi.dwMajorVersion; major <= 9; major++) {
+                               memset(&vi2, 0, sizeof(vi2));
+                               vi2.dwOSVersionInfoSize = sizeof(vi2); vi2.dwMajorVersion = major;
+                               if (!VerifyVersionInfoA(&vi2, VER_MAJORVERSION, major_equal))
+                                       continue;
+                               if (vi.dwMajorVersion < major) {
+                                       vi.dwMajorVersion = major; vi.dwMinorVersion = 0;
+                               }
+
+                               minor_equal = VerSetConditionMask(0, VER_MINORVERSION, VER_EQUAL);
+                               for (minor = vi.dwMinorVersion; minor <= 9; minor++) {
+                                       memset(&vi2, 0, sizeof(vi2)); vi2.dwOSVersionInfoSize = sizeof(vi2);
+                                       vi2.dwMinorVersion = minor;
+                                       if (!VerifyVersionInfoA(&vi2, VER_MINORVERSION, minor_equal))
+                                               continue;
+                                       vi.dwMinorVersion = minor;
+                                       break;
+                               }
+
+                               break;
+                       }
+               }
+
+               if (vi.dwMajorVersion <= 0xf && vi.dwMinorVersion <= 0xf) {
+                       ws = (vi.wProductType <= VER_NT_WORKSTATION);
+                       windows_version = vi.dwMajorVersion << 4 | vi.dwMinorVersion;
+                       switch (windows_version) {
+                       case 0x50: w = "2000";
+                               break;
+                       case 0x51: w = "XP";
+                               break;
+                       case 0x52: w = (!GetSystemMetrics(89)?"2003":"2003_R2");
+                               break;
+                       case 0x60: w = (ws?"Vista":"2008");
+                               break;
+                       case 0x61: w = (ws?"7":"2008_R2");
+                               break;
+                       case 0x62: w = (ws?"8":"2012");
+                               break;
+                       case 0x63: w = (ws?"8.1":"2012_R2");
+                               break;
+                       case 0x64: w = (ws?"8.2":"2012_R3");
+                               break;
+                       default:
+                               if (windows_version < 0x50)
+                                       windows_version = WINDOWS_UNSUPPORTED;
+                               else
+                                       w = "9 or later";
+                               break;
+                       }
+               }
+       }
+
+       if (is_x64())
+               w64 = "64-bit";
+
+       vptr = &windows_version_str[sizeof("Windows ") - 1];
+       vlen = sizeof(windows_version_str) - sizeof("Windows ") - 1;
+       if (!w)
+               safe_sprintf(vptr, vlen, "%s %u.%u %s", (vi.dwPlatformId==VER_PLATFORM_WIN32_NT?"NT":"??"),
+                       (unsigned)vi.dwMajorVersion, (unsigned)vi.dwMinorVersion, w64);
+       else if (vi.wServicePackMinor)
+               safe_sprintf(vptr, vlen, "%s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, w64);
+       else if (vi.wServicePackMajor)
+               safe_sprintf(vptr, vlen, "%s SP%u %s", w, vi.wServicePackMajor, w64);
+       else
+               safe_sprintf(vptr, vlen, "%s %s", w, w64);
+}
+
 /*
  * init: libusb backend init function
  *
@@ -814,7 +927,6 @@ static void auto_release(struct usbi_transfer *itransfer)
 static int windows_init(struct libusb_context *ctx)
 {
        int i, r = LIBUSB_ERROR_OTHER;
-       OSVERSIONINFO os_version;
        HANDLE semaphore;
        char sem_name[11+1+8]; // strlen(libusb_init)+'\0'+(32-bit hex PID)
 
@@ -836,19 +948,8 @@ static int windows_init(struct libusb_context *ctx)
        // 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?
-               // Detect OS version
-               memset(&os_version, 0, sizeof(OSVERSIONINFO));
-               os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
-               windows_version = WINDOWS_UNSUPPORTED;
-               if ((GetVersionEx(&os_version) != 0) && (os_version.dwPlatformId == VER_PLATFORM_WIN32_NT)) {
-                       if ((os_version.dwMajorVersion == 5) && (os_version.dwMinorVersion == 1)) {
-                               windows_version = WINDOWS_XP;
-                       } else if ((os_version.dwMajorVersion == 5) && (os_version.dwMinorVersion == 2)) {
-                               windows_version = WINDOWS_2003; // also includes XP 64
-                       } else if (os_version.dwMajorVersion >= 6) {
-                               windows_version = WINDOWS_VISTA_AND_LATER;
-                       }
-               }
+               get_windows_version();
+               usbi_dbg(windows_version_str);
                if (windows_version == WINDOWS_UNSUPPORTED) {
                        usbi_err(ctx, "This version of Windows is NOT supported");
                        r = LIBUSB_ERROR_NOT_SUPPORTED;
@@ -1094,6 +1195,7 @@ static int init_device(struct libusb_device* dev, struct libusb_device* parent_d
        HANDLE handle;
        DWORD size;
        USB_NODE_CONNECTION_INFORMATION_EX conn_info;
+       USB_NODE_CONNECTION_INFORMATION_EX_V2 conn_info_v2;
        struct windows_device_priv *priv, *parent_priv;
        struct libusb_context *ctx;
        struct libusb_device* tmp_dev;
@@ -1172,6 +1274,23 @@ static int init_device(struct libusb_device* dev, struct libusb_device* parent_d
                        dev->num_configurations = 0;
                        priv->dev_descriptor.bNumConfigurations = 0;
                }
+
+               // In their great wisdom, Microsoft decided to BREAK the USB speed report between Windows 7 and Windows 8
+               if (windows_version >= WINDOWS_8) {
+                       memset(&conn_info_v2, 0, sizeof(conn_info_v2));
+                       size = sizeof(conn_info_v2);
+                       conn_info_v2.ConnectionIndex = (ULONG)port_number;
+                       conn_info_v2.Length = size;
+                       conn_info_v2.SupportedUsbProtocols.Usb300 = 1;
+                       if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2,
+                               &conn_info_v2, size, &conn_info_v2, size, &size, NULL)) {
+                               usbi_warn(ctx, "could not get node connection information (V2) for device '%s': %s",
+                                       device_id,  windows_error_str(0));
+                       } else if (conn_info_v2.Flags.DeviceIsOperatingAtSuperSpeedOrHigher) {
+                               conn_info.Speed = 3;
+                       }
+               }
+
                safe_closehandle(handle);
 
                if (conn_info.DeviceAddress > UINT8_MAX) {
index 069f3fc..413adfc 100644 (file)
@@ -310,6 +310,9 @@ struct driver_lookup {
 /* OLE32 dependency */
 DLL_DECLARE_PREFIXED(WINAPI, HRESULT, p, CLSIDFromString, (LPCOLESTR, LPCLSID));
 
+/* This call is only available from XP SP2 */
+DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, IsWow64Process, (HANDLE, PBOOL));
+
 /* SetupAPI dependencies */
 DLL_DECLARE_PREFIXED(WINAPI, HDEVINFO, p, SetupDiGetClassDevsA, (const GUID*, PCSTR, HWND, DWORD));
 DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, SetupDiEnumDeviceInfo, (HDEVINFO, DWORD, PSP_DEVINFO_DATA));
@@ -364,6 +367,9 @@ typedef RETURN_TYPE CONFIGRET;
 #if !defined(USB_GET_HUB_CAPABILITIES_EX)
 #define USB_GET_HUB_CAPABILITIES_EX             276
 #endif
+#if !defined(USB_GET_NODE_CONNECTION_INFORMATION_EX_V2)
+#define USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 279
+#endif
 
 #ifndef METHOD_BUFFERED
 #define METHOD_BUFFERED                         0
@@ -424,6 +430,9 @@ DLL_DECLARE(WINAPI, CONFIGRET, CM_Get_Device_IDA, (DEVINST, PCHAR, ULONG, ULONG)
 #define IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX \
   CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_INFORMATION_EX, METHOD_BUFFERED, FILE_ANY_ACCESS)
 
+#define IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 \
+  CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
 #define IOCTL_USB_GET_NODE_CONNECTION_ATTRIBUTES \
   CTL_CODE(FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_ATTRIBUTES, METHOD_BUFFERED, FILE_ANY_ACCESS)
 
@@ -564,6 +573,32 @@ typedef struct USB_NODE_CONNECTION_INFORMATION_EX {
 //     USB_PIPE_INFO  PipeList[0];
 } USB_NODE_CONNECTION_INFORMATION_EX, *PUSB_NODE_CONNECTION_INFORMATION_EX;
 
+typedef union _USB_PROTOCOLS {
+       ULONG  ul;
+       struct {
+               ULONG Usb110:1;
+               ULONG Usb200:1;
+               ULONG Usb300:1;
+               ULONG ReservedMBZ:29;
+       };
+} USB_PROTOCOLS, *PUSB_PROTOCOLS;
+
+typedef union _USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS {
+       ULONG ul;
+       struct {
+               ULONG DeviceIsOperatingAtSuperSpeedOrHigher:1;
+               ULONG DeviceIsSuperSpeedCapableOrHigher:1;
+               ULONG ReservedMBZ:30;
+       };
+} USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS, *PUSB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS;
+
+typedef struct _USB_NODE_CONNECTION_INFORMATION_EX_V2 {
+       ULONG ConnectionIndex;
+       ULONG Length;
+       USB_PROTOCOLS SupportedUsbProtocols;
+       USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS Flags;
+} USB_NODE_CONNECTION_INFORMATION_EX_V2, *PUSB_NODE_CONNECTION_INFORMATION_EX_V2;
+
 typedef struct USB_HUB_CAP_FLAGS {
        ULONG HubIsHighSpeedCapable:1;
        ULONG HubIsHighSpeed:1;
index 94ac72a..070de37 100644 (file)
@@ -1 +1 @@
-#define LIBUSB_NANO 10889
+#define LIBUSB_NANO 10890