libusb icon indicating copy to clipboard operation
libusb copied to clipboard

WinUsb_ControlTransfer() sets wIndex to zero

Open Elmue opened this issue 4 months ago • 2 comments

This page: https://github.com/libusb/libusb/wiki/Windows#Known_Restrictions says under "Knows Issues":

With WinUSB, when LIBUSB_RECIPIENT_INTERFACE is used for the transfer, the WinUSB DLL forces the low byte of wIndex to the interface number, regardless of what you set it to.

This sounds like if this was a bug in WinUSB.dll. I saw in internet that many people misunderstood that, even saying that it is impossible that you send an interface request with wIndex = 1.

But I can send a SETUP request to the second interface setting wIndex = 1 and this will be transferred correctly over the USB cable which I prove with my hardware USB Spy Beagle and I also see it in my USB sniff software USBlyzer:

Image

I use the CANable Candlelight.

Image

I'am currently implementing a firmware uploader into my software HUD ECU Hacker I have to send this specific SETUP request to the second interface which will switch the processor from ST Microelectronics into DFU mode (Device Firmware Update mode).

The Candlelight has 2 interfaces: Interface 0 has two endpoints (pipes) and is used to send data to and receive data from the CAN bus:

Image

On the other hand interface 1 has no endpoint. It receives only SETUP requests and does nothing else than to switch the processor into bootloader mode:

Image

To upload a new firmware I have to send a SETUP packet with command DFU_DETACH (see first screenshot) to the second interface.

Now comes the important point: If I send this SETUP command to the same WinUSB instance that I use to transfer the CAN bus data I will see that Windows resets wIndex to zero which I send with value 1. So Windows manipulates my SETUP packet and I see on the USB cable a corrupt packet that has no effect. It will NOT switch the device into bootloader mode.

But the conclusion is wrong, that sending such a packet with WinUSB is impossible or that this is a bug. I have read this in other places linking to this wiki and people saying that WinUSB cannot do this. People are even saying that this is a limitation of the WinUSB driver. I have seen other websites where this wrong information is spread citing this Wiki . I have also seen people saying that Microsoft is giving us a limited WinUSB library and that Linux is supposedly much better. All this is WRONG. And these misunderstanding could be avoided by making this much clearer by using the following information.

The main problem is the lack of details in the MSDN explaining how to handle multi-interface devices correctly. The point is that WinUSB sees a USB device with 2 interface as if they were TWO DIFFERENT devices.

When you call WinUsb_Initialize() you get only ONE of the interfaces. And calling WinUsb_GetAssociatedInterface() does not give you the second interface! The second interface is not an "associated" nor an "alternate" interface. The second interface is handled by WinUSB as if it was a completely separate USB device.

So to send a SETUP packet to the second interface we have to OPEN ANOTHER device. To communicate with both interfaces we have to call CreateFile() and WinUsb_Initialize() twice with a different device path. A look into the registry of the CANable device:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_1D50&PID_606F

Image

The Device Parameters of Interface 0 (Multi Interface 0 = "MI_00") are in: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_1D50&PID_606F&MI_00\7&380dcbb4&0&0000\Device Parameters

Image

The resulting Device ID is easy to build by appending this GUID: "\?\USB#VID_1D50&PID_606F&MI_00#7&380dcbb4&0&0000#{c15b4308-04d3-11e6-b3ea-6057189e6443}"

Passing this to CreateFile() and the file handle to WinUsb_Initialize() gives full access to interface 0. But with this device path Microsoft does not allow to access interface 1. If you send anything to interface 1 with this handle of interface 0 Microsoft will reset wIndex from 1 to 0.

To access the second interface we have again a look into the registry:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_1D50&PID_606F&MI_01\7&380dcbb4&0&0001\Device Parameters

Image

This GUID is a random number that was created by Zadig. This allows us to build the device path for interface 1: "\?\USB#VID_1D50&PID_606F&MI_01#7&380dcbb4&0&0001#{43BFF950-A1BD-4BBD-9629-082ADFB0B8EC}"

Passing this to CreateFile() allows us to communicate with the second interface.

Why did Microsoft implement it like this? The obvious reason is that a Multi-Interface USB device may have the driver installed for one interface, but the driver for the other interface is not installed. That means that one interface is operational and the other one is not. This design allows to use the operational interface while ignoring the other one. A CANable can send data to a CAN bus while the driver for the firmware update interface is missing. It is even possible that one application uses one interface while a completely different process uses the other interface at the same time. For example with a USB headset one application may send sound to the speakers while another application listens to the microphone.

Microsoft did even go one step further: I found that Microsoft did a lot of work to make working with different interfaces easy for the developer. When I request the configuration descriptor I get different results depending on the interface for which I open WinUSB. When I use the DeviceID for interface 0 Microsoft removes the descriptor for the interface 1 and the DFU descriptor from the returned data. When I use the DeviceID for interface 1 Microsoft removes the descriptor for the interface 0 and the 2 endpoint descriptos from the returned data. So WinUSB behaves really as if I was working with 2 completely independent devices.

Summary: There is no bug in WinUSB. You have only to use it correctly. Microsoft should have documented this. And your Wiki should explain how to avoid that the SETUP packet is manipulated to avoid all the misunderstandings.!

I have to admit that in internet there is very few detailed information about USB. Therefore I compiled and uploaded a very useful USB tutorial that I recommend to everybody. It is part of my project Oszi Waveform Analyzer which transfers an oscilloscope capture over USB: https://github.com/Elmue/Oszi-Waveform-Analyzer/blob/main/Documentation/USB%20Tutorial.chm

Elmü

Elmue avatar Aug 09 '25 22:08 Elmue

With WinUSB, when LIBUSB_RECIPIENT_INTERFACE is used for the transfer, the WinUSB DLL forces the low byte of wIndex to the interface number, regardless of what you set it to.

This statement is exactly correct, even with regard to all your detailed comments. I agree it could be extended to describe in more details like you just did. But once you open a device/claim the interface, you cannot simply use wIndex to choose which usb interface is going to be a recepient of the transfer (unlike on any other platform, except Windows/WinUSB).

And it doesn't mean you cannot access other interfaces of a particular usb device, libusb documentation doesn't state that.


If you want to suggest a better wording that you find currently confusing, please do suggest. But it have to remain strictly correct.

Youw avatar Aug 10 '25 21:08 Youw

How about writing this into the Wiki:

Handling USB devices with multiple interfaces is a special case. WinUSB handles each interface completely separate from the others. Each interface must be opened with it's own individual DevicePath ("\?\USB#VID_...) and Microsoft allows only accessing one interface with one handle that was returned from WinUsb_Initialize(). If you want to send a SETUP request to interface 1, but use the handle for interface 0 and set Recipient = Interface and wIndex = 1, this will not work. Microsoft will reset wIndex to the interface number for which the handle was opened and the command will not reach the desired destination. For more details and an example please read Issue 1675.

Elmue avatar Aug 11 '25 06:08 Elmue