From f7e714f7b4f1df3338ef5554bcc9807c99f684e3 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 15 Oct 2026 14:55:48 +0100 Subject: [PATCH 1/2] [core] actually report USB 3.2/USB 4.0 devices * Thanks to the moronic people of the USB Forum (yes, moronic, as evidenced by their refusal to update their USB versioning like an intelligent and considerate life form and match a maximum USB speed to a x.y version), we have to use more ill- devised extensions and APIs, *THAT STILL ARE UNABLE TO PROPERLY REPORT THE MAXIMUM ACHIEVABLE SPEED OF A USB DEVICE*! * But at least, we can detect the actual USB 3.1 (10 Gbps), USB 3.2 (20 Gbps), USB 4.0 (40 Gbps) and USB 4.1 (80 Gbps) speeds used by the device (yes, I *AM* equating USB speed with USB version, LIKE A SANE PERSON WHO CARES ABOUT END USERS SHOULD!) and report that, through a mix of BOS analysis and use of the new (Windows 11 24H2) IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION API. --- src/dev.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++---- src/dev.h | 63 +++++++++++++++++--- src/rufus.rc | 10 ++-- 3 files changed, 209 insertions(+), 25 deletions(-) diff --git a/src/dev.c b/src/dev.c index b1f1cdf7..7ee2116e 100644 --- a/src/dev.c +++ b/src/dev.c @@ -49,18 +49,83 @@ extern RUFUS_DRIVE rufus_drive[MAX_DRIVES]; extern BOOL enable_HDDs, enable_VHDs, use_fake_units, enable_vmdk, usb_debug; extern BOOL list_non_usb_removable_drives, its_a_me_mario; +static inline uint64_t ssp_to_bps(uint32_t v) +{ + static const uint64_t scale[4] = { 1ULL, 1000ULL, 1000000ULL, 1000000000ULL }; + return (uint64_t)(v >> 16) * scale[(v >> 4) & 0x3]; +} + +static uint64_t get_max_bps(const uint8_t* bos, uint32_t len) +{ + uint8_t l, min_rx_lanes, min_tx_lanes; + uint32_t i, n, ssp, offset, attr, avail; + uint64_t bps, max_bps = 0; + + if (len < 5 || bos[0] < 5 || bos[1] != 0x0F) + return 0; + offset = bos[0]; // skip the BOS header + + while (offset + 3 <= len) { + l = bos[offset]; + if (l < 3 || offset + l > len) + break; + // Look for the SUPERSPEED_PLUS (0x0A) Device Capability descriptor (0x10) + if (bos[offset + 1] == 0x10 && bos[offset + 2] == 0x0A && l >= 12) { + // Get the bmAttributes field + memcpy(&attr, &bos[offset + 4], sizeof(attr)); + // Get the min lanes from the wFunctionalitySupport field + min_rx_lanes = bos[offset + 9] & 0x0F; + min_tx_lanes = bos[offset + 9] >> 4; + n = (attr & 0x1F) + 1; // Number of Sublink Speed Attributes + avail = (l - 12) / 4; + if (n > avail) + n = avail; + for (i = 0; i < n; i++) { + memcpy(&ssp, &bos[offset + 12 + 4 * i], sizeof(ssp)); + bps = ssp_to_bps(ssp) * ((ssp & 0x80) ? min_tx_lanes : min_rx_lanes); + if (bps > max_bps) + max_bps = bps; + } + } + // Next descriptor + offset += l; + } + return max_bps; +} + +static uint32_t version_to_speed(uint16_t usb_version) { + switch (usb_version) { + case 0x0100: return USB_SPEED_LOW_1_5M; + case 0x0110: return USB_SPEED_FULL_12M; + case 0x0200: + case 0x0201: + case 0x0210: return USB_SPEED_HIGH_480M; + case 0x0300: return USB_SPEED_SUPER_5G; + case 0x0310: return USB_SPEED_SUPER_10G; + case 0x0320: return USB_SPEED_SUPER_20G; + case 0x0400: return USB_SPEED_SUPER_40G; + case 0x0410: return USB_SPEED_SUPER_80G; + default: return USB_SPEED_UNKNOWN; + } +} + /* * Get the VID, PID and current device speed */ static BOOL GetUSBProperties(char* parent_path, char* device_id, usb_device_props* props) { + int pass; BOOL r = FALSE; CONFIGRET cr; HANDLE handle = INVALID_HANDLE_VALUE; - DWORD size; DEVINST device_inst; USB_NODE_CONNECTION_INFORMATION_EX conn_info; USB_NODE_CONNECTION_INFORMATION_EX_V2 conn_info_v2; + MY_USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION ssp; + uint8_t buf[sizeof(USB_DESCRIPTOR_REQUEST) + 512] = { 0 }; + uint8_t* bos = &buf[sizeof(USB_DESCRIPTOR_REQUEST)]; + USB_DESCRIPTOR_REQUEST* req = (USB_DESCRIPTOR_REQUEST*)buf; + DWORD size, header_size = offsetof(USB_DESCRIPTOR_REQUEST, Data); if ((parent_path == NULL) || (device_id == NULL) || (props == NULL)) goto out; @@ -99,26 +164,92 @@ static BOOL GetUSBProperties(char* parent_path, char* device_id, usb_device_prop if ((conn_info.DeviceDescriptor.idVendor != 0) || (conn_info.DeviceDescriptor.idProduct != 0)) { props->vid = conn_info.DeviceDescriptor.idVendor; props->pid = conn_info.DeviceDescriptor.idProduct; - props->speed = conn_info.Speed + 1; + // Not exactly accurate since a USB 3.x device connected to a USB 2.0 port will use the + // 2.0 lanes and report a 2.0 device descriptor there, which means that, despite being + // able to operate at 3.0 speeds, we only see a max speed of 2.0. Our additional checks + // below should however be able to take care of detecting the max speed. + props->max_speed = version_to_speed(conn_info.DeviceDescriptor.bcdUSB); + props->current_speed = conn_info.Speed + 1; r = TRUE; } // The USB speed report of modern Windows is a complete mess + size = sizeof(ssp); + memset(&ssp, 0, size); + ssp.ConnectionIndex = (ULONG)props->port; + ssp.Length = size; size = sizeof(conn_info_v2); memset(&conn_info_v2, 0, size); conn_info_v2.ConnectionIndex = (ULONG)props->port; conn_info_v2.Length = size; conn_info_v2.SupportedUsbProtocols.Usb300 = 1; + // Requires Windows 11 24H2 or later + if (DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION, &ssp, sizeof(ssp), &ssp, sizeof(ssp), &size, NULL)) { + uint64_t cur_bps = max(ssp_to_bps(ssp.RxSuperSpeedPlus) * (ssp.RxLaneCount + 1), ssp_to_bps(ssp.TxSuperSpeedPlus) * (ssp.TxLaneCount + 1)); + if (cur_bps >= 80000000000ULL) + props->current_speed = USB_SPEED_SUPER_80G; + else if (cur_bps >= 40000000000ULL) + props->current_speed = USB_SPEED_SUPER_40G; + else if (cur_bps >= 20000000000ULL) + props->current_speed = USB_SPEED_SUPER_20G; + else if (cur_bps >= 10000000000ULL) + props->current_speed = USB_SPEED_SUPER_10G; + else if (cur_bps >= 5000000000ULL) + props->current_speed = USB_SPEED_SUPER_5G; + else + goto next; + props->max_speed = props->current_speed; + + // Read the BOS to compare with the advertised speed + size = 5; + for (pass = 0; pass < 2; pass++) { + req->ConnectionIndex = (ULONG)props->port; + req->SetupPacket.bmRequest = 0x80; + req->SetupPacket.bRequest = 0x06; + req->SetupPacket.wValue = (0x0F << 8); + req->SetupPacket.wIndex = 0; + req->SetupPacket.wLength = (USHORT)size; + if (!DeviceIoControl(handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, req, header_size + size, req, header_size + size, &size, NULL) || + size < header_size + 5) + break; + if (pass == 0) { + size = req->Data[2] | (req->Data[3] << 8); + if (size < 5 || size > 512) + break; + } + } + if (pass == 2) { + // Now the problem is that the max bps from the BOS is for *single* lane only, and the + // USB Forum *STUPIDLY* defined a minimum amount of lanes in the BOS but NOT a maximum + // or a preferred, which means we can not actually get the max speed of a USB 3.2 (2 x + // 10G lanes) if it's operating at USB 3.1 speed (1 x 10G lane). Somebody *REALLY* + // doesn't want users to find out if they are being screwed on USB speeds... + uint64_t max_bps = get_max_bps(bos, size); + if (max_bps >= 80000000000ULL) + props->max_speed = USB_SPEED_SUPER_80G; + else if (max_bps >= 40000000000ULL) + props->max_speed = USB_SPEED_SUPER_40G; + else if (max_bps >= 20000000000ULL) + props->max_speed = USB_SPEED_SUPER_20G; + else if (max_bps >= 10000000000ULL) + props->max_speed = USB_SPEED_SUPER_10G; + else if (max_bps >= 5000000000ULL) + props->max_speed = USB_SPEED_SUPER_5G; + } + goto out; + } + +next: if (!DeviceIoControl(handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, &conn_info_v2, size, &conn_info_v2, size, &size, NULL)) { uprintf("Could not get node connection information (V2) for device '%s': %s", device_id, WindowsErrorString()); } else if (conn_info_v2.Flags.DeviceIsOperatingAtSuperSpeedPlusOrHigher) { - props->speed = USB_SPEED_SUPER_PLUS; + props->current_speed = USB_SPEED_SUPER_10G; } else if (conn_info_v2.Flags.DeviceIsOperatingAtSuperSpeedOrHigher) { - props->speed = USB_SPEED_SUPER; + props->current_speed = USB_SPEED_SUPER_5G; } else if (conn_info_v2.Flags.DeviceIsSuperSpeedPlusCapableOrHigher) { - props->lower_speed = 2; + props->max_speed = USB_SPEED_SUPER_10G; } else if (conn_info_v2.Flags.DeviceIsSuperSpeedCapableOrHigher) { - props->lower_speed = 1; + props->max_speed = USB_SPEED_SUPER_5G; } out: @@ -471,7 +602,12 @@ BOOL GetDevices(DWORD devnum) const char* scsi_card_name[] = { "_SD_", "_SDHC_", "_SDXC_", "_MMC_", "_MS_", "_MSPro_", "_xDPicture_", "_O2Media_" }; - const char* usb_speed_name[USB_SPEED_MAX] = { "USB", "USB 1.0", "USB 1.1", "USB 2.0", "USB 3.0", "USB 3.1" }; + const char* usb_version[USB_SPEED_MAX] = { + "USB", "USB 1.0", "USB 1.1", "USB 2.1", "USB 3.0", "USB 3.1", "USB 3.2", "USB 4.0", "USB 4.1" + }; // See, USB Forum, it *REALLY* isn't that hard!!! + const char* usb_speed[USB_SPEED_MAX] = { + "???", "1.5 Mbps", "12 Mbps", "480 Mbps", "5 Gbps", "10 Gbps", "20 Gbps", "40 Gbps", "80 Gbps" + }; const char* windows_sandbox_vhd_label = "PortableBaseLayer"; // Hash table and String Array used to match a Device ID with the parent hub's Device Interface Path htab_table htab_devid = HTAB_EMPTY; @@ -847,12 +983,13 @@ BOOL GetDevices(DWORD devnum) if (s < ARRAYSIZE(ignore_vid_pid)) continue; } - if (props.speed >= USB_SPEED_MAX) - props.speed = 0; + if (props.current_speed >= USB_SPEED_MAX) + props.current_speed = 0; uprintf("Found %s%s%s device '%s' (%s) %s", props.is_UASP ? "UAS (" : "", - usb_speed_name[props.speed], props.is_UASP ? ")" : "", buffer, str, method_str); - if (props.lower_speed) - uprintf("NOTE: This device is a USB 3.%c device operating at lower speed...", '0' + props.lower_speed - 1); + usb_version[props.current_speed], props.is_UASP ? ")" : "", buffer, str, method_str); + if (props.max_speed > props.current_speed) + uprintf("NOTE: This a %s (%s) device operating at %s speed (%s)...", usb_version[props.max_speed], + usb_speed[props.max_speed], usb_version[props.current_speed], usb_speed[props.current_speed]); } devint_data.cbSize = sizeof(devint_data); devint_detail_data = NULL; diff --git a/src/dev.h b/src/dev.h index d0543d13..98710860 100644 --- a/src/dev.h +++ b/src/dev.h @@ -21,19 +21,22 @@ #include #define USB_SPEED_UNKNOWN 0 -#define USB_SPEED_LOW 1 -#define USB_SPEED_FULL 2 -#define USB_SPEED_HIGH 3 -#define USB_SPEED_SUPER 4 -#define USB_SPEED_SUPER_PLUS 5 -#define USB_SPEED_MAX 6 +#define USB_SPEED_LOW_1_5M 1 // USB 1.0 +#define USB_SPEED_FULL_12M 2 // USB 1.1 +#define USB_SPEED_HIGH_480M 3 // USB 2.0 +#define USB_SPEED_SUPER_5G 4 // USB 3.0 +#define USB_SPEED_SUPER_10G 5 // USB 3.1 +#define USB_SPEED_SUPER_20G 6 // USB 3.2 +#define USB_SPEED_SUPER_40G 7 // USB 4.0 +#define USB_SPEED_SUPER_80G 8 // USB 4.1 +#define USB_SPEED_MAX 9 /* List of the properties we are interested in */ typedef struct usb_device_props { int32_t vid; int32_t pid; - uint32_t speed; - uint32_t lower_speed; + uint32_t current_speed; + uint32_t max_speed; uint32_t port; BOOLEAN is_USB; BOOLEAN is_SCSI; @@ -145,3 +148,47 @@ const GUID GUID_DEVINTERFACE_USB_HUB = { 0xf18a0e88L, 0xc30c, 0x11d0, {0x88, 0x15, 0x00, 0xa0, 0xc9, 0x06, 0xbe, 0xd8} }; #define DEVID_HTAB_SIZE 257 + +#ifndef FILE_DEVICE_USB +#define FILE_DEVICE_USB FILE_DEVICE_UNKNOWN +#endif + +typedef struct _USB_DESCRIPTOR_REQUEST { + ULONG ConnectionIndex; + struct { + UCHAR bmRequest; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + } SetupPacket; + UCHAR Data[0]; +} USB_DESCRIPTOR_REQUEST, * PUSB_DESCRIPTOR_REQUEST; + +#ifndef USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION +#define USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION 260 +#endif + +#ifndef IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION +#define IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION CTL_CODE \ + (FILE_DEVICE_USB, USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, METHOD_BUFFERED, FILE_ANY_ACCESS) +#endif + +#ifndef USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION +#define USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION 289 +#endif + +#ifndef IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION +#define IOCTL_USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION CTL_CODE\ + (FILE_DEVICE_USB, USB_GET_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION, METHOD_BUFFERED, FILE_ANY_ACCESS) +#endif + +typedef struct { + ULONG ConnectionIndex; + ULONG Length; + ULONG RxSuperSpeedPlus; + ULONG RxLaneCount; + ULONG TxSuperSpeedPlus; + ULONG TxLaneCount; +} MY_USB_NODE_CONNECTION_SUPERSPEEDPLUS_INFORMATION; + diff --git a/src/rufus.rc b/src/rufus.rc index 27ac0fb0..d28b1d06 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.15.2409" +CAPTION "Rufus 4.15.2410" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -409,8 +409,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,15,2409,0 - PRODUCTVERSION 4,15,2409,0 + FILEVERSION 4,15,2410,0 + PRODUCTVERSION 4,15,2410,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -428,13 +428,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.15.2409" + VALUE "FileVersion", "4.15.2410" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2026 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.15.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.15.2409" + VALUE "ProductVersion", "4.15.2410" END END BLOCK "VarFileInfo" From 714e3ad90126fa0d751d071222f3b29e11d8f435 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 15 Oct 2026 14:57:57 +0100 Subject: [PATCH 2/2] [misc] add MinGW UCRT64 builds * And fix a warning in UCRT64 builds. --- .github/workflows/mingw.yml | 3 ++- src/ext2fs/nt_io.c | 2 +- src/rufus.rc | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index d59adfd5..ee7ae8d6 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -35,6 +35,7 @@ jobs: include: - { sys: mingw64, env: x86_64, exe: rufus.exe } - { sys: mingw32, env: i686, exe: rufus_x86.exe } + - { sys: ucrt64, env: ucrt-x86_64, exe: rufus_ucrt.exe } defaults: run: @@ -80,7 +81,7 @@ jobs: - name: Build run: | ./configure --disable-debug ${{ steps.set_alpha.outputs.option }} ${{ steps.set_beta.outputs.option }} - make -j4 + make -j4 ACLOCAL=: AUTOMAKE=: AUTOCONF=: AUTOHEADER=: mv ./src/rufus.exe ./${{ matrix.exe }} strip ./${{ matrix.exe }} upx --lzma --best ./${{ matrix.exe }} diff --git a/src/ext2fs/nt_io.c b/src/ext2fs/nt_io.c index f36dc3b2..81165e6f 100644 --- a/src/ext2fs/nt_io.c +++ b/src/ext2fs/nt_io.c @@ -277,7 +277,7 @@ static PCSTR _NormalizeDeviceName(IN PCSTR Device, IN PSTR NormalizedDeviceNameB return Device; // Handle custom paths of the form " " used by Rufus to // enable multi-partition access on removable devices, for pre 1703 platforms. - if (sscanf(Device, "%s %I64u %I64u", NormalizedDeviceNameBuffer, Offset, Size) < 1) + if (sscanf(Device, "%s %llu %llu", NormalizedDeviceNameBuffer, Offset, Size) < 1) return NULL; if ((NormalizedDeviceNameBuffer[1] == '\\') || (NormalizedDeviceNameBuffer[1] == '.')) NormalizedDeviceNameBuffer[1] = '?'; diff --git a/src/rufus.rc b/src/rufus.rc index d28b1d06..e82dbf12 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.15.2410" +CAPTION "Rufus 4.15.2411" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -409,8 +409,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,15,2410,0 - PRODUCTVERSION 4,15,2410,0 + FILEVERSION 4,15,2411,0 + PRODUCTVERSION 4,15,2411,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -428,13 +428,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.15.2410" + VALUE "FileVersion", "4.15.2411" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2026 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.15.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.15.2410" + VALUE "ProductVersion", "4.15.2411" END END BLOCK "VarFileInfo"