libusb / libusb

A cross-platform library to access USB devices
https://libusb.info
GNU Lesser General Public License v2.1
5.28k stars 1.91k forks source link

Multiple WinUSB devices in single composite device #1177

Open Novakov opened 2 years ago

Novakov commented 2 years ago

Let's assume USB device reporting itself as composite device with two interfaces (0 and 1) of class vendor. Using Microsoft OS 1.0 descriptors they are assigned WinUSB driver (using WINUSB CompatibilityId descriptor with two sections) and unique DeviceInterfaceGUID (using two separate Extended Compatibilty descriptor, one for each interface)

Some screenshots: Device manager: image

Registry, interface 0 with GUID: image

Registry, interface 1 with GUID: image

Now, there is well known limitation of WinUSB: "WinUSB is not an option if: Your device is accessed by multiple applications". However in the context of composite device interface 0 and interface 1 are separate devices. Given that by using SetupAPI and WinUSB native API I'm able to lookup specific interface (single child device from composite device) that gives me paths:

Observation: From WinUSB point of view it is possible to access different child devices of composite device from multiple application. It is not possible to access the same child device of composite device from multiple application.

Sorry for long introduction and probably stating facts that might be obvious but I wanted to describe context and what is possible before going to libusb specifics.

Libusb allows to use WinUSB devices however it is not aware of DeviceInterfaceGUID and composite device is treated as a whole. In WinUSB backend implementation there is function winusbx_open (https://github.com/libusb/libusb/blob/master/libusb/os/windows_winusb.c#L2428) which opens all child devices:

image

As you can see priv->usb_interface array contains device paths for both child devices.

As both child devices are opened (even if only single interface is required) no other application can open any child device of composite device.

Function libusb_wrap_sys_device that (in theory) could be used to mix SetupAPI (lookup and open device) with libusb (issue usb transfers) is not implemented on Windows.

It would be great if libusb only opened child device required by specific application, as that would allow other applications to access remaining child devices. However, at this moment I have no idea how that can be implemented nicely in libusb. I'm reporting this issue to keep a record of my experiments and findings as it is not 100% clear from documentation how WinUSB and composite devices play together.

Novakov commented 2 years ago

Naive thought: WinUSB_Initialize call is delayed until winusbx_claim_interface, so between calls to libusb_open and libusb_claim_interface child composite device is opened (so not accessible to other applications) but not usb-initialized (so no USB transfers yet). If it would be possible to delay CreateFile until interface is claimed it would solve the issue. That would make libusb_open call effectively no-op meaning that attempt to open non-existing or already opened device not fail.

mcuee commented 2 years ago

@Novakov Just wondering if you can give Pull Request https://github.com/libusb/libusb/pull/965 a try. It is meant to sort out IAD device but it might help a bit as well.

Novakov commented 2 years ago

I already tried, it does not help alone but I think it might be required to fix this issue.

In essence:

I'm planning to prototype solution as described by my previous comment (lazy CreateFile) and give it a try in daily work.

mcuee commented 2 years ago

Thanks for the updates and I think your solution is worth a try.

Novakov commented 2 years ago

I implemented something very quick and ridden with printf and TODO but I would like to share progress with you as I'm only scratching the surface of libusb source code: https://github.com/Novakov/libusb/commit/a339b426fd9078f70c8d73140c97f537be1290e8#diff-bd7e9ed3e7d8a4a69d0664da0f6b8dbbff0b2c608d8dd63de02c379c98cf88b4

Basically: I'm doing nothing in opening file and when claiming interface I'm opening dev_handle if not already open.

Use case which previously failed: Orbtrace USB device and running orbuculum concurrently with openocd. Both applications can communicate with different interfaces of the same USB device which is great.

I'm planing to do much more testing with more sophisticated USB devices (MCU with different sets of descriptors) and see how it goes.

I woud very much appreciate any feedback and suggestions for edge cases, implementation itself or things to watch out for.

mcuee commented 2 years ago

@Novakov I will suggest that you create a pull request and then mention it as WIP (Work in Progress) so that more people can review and test it. Thanks,

Novakov commented 2 years ago

@mcuee submitted. Seems to be working fine and implementation is not hacky. Still some more testing to do but for now I'm optimistic about it

mcuee commented 1 year ago

The use case is really valid (different applications can communicate with different interfaces of the same USB device) and hopefully more people can come and test it.

tormodvolden commented 1 year ago

Just trying to summarize (probably helps me more writing this than it does anyone reading it!): PR 965 (merged) allows opening a non-first winusbx interface without wrongly trying to open the first (possibly non-winusbx) interface on the real device - when it should really only open the first associated interface (of the same child device). Prior to this, opening could be blocked unnecessarily if the first child device was taken by another driver (or context). It will still grab all the interfaces in the same group (child device). PR 1181 takes this further for the case of composite (child) devices, and will only open the interface needed, and not all interfaces of the same group, allowing the others to be opened by other drivers or libusb contexts.

That would make libusb_open call effectively no-op meaning that attempt to open non-existing or already opened device not fail.

This is possibly a significant change but one I think we should accept. Currently, opening the (child) device assures you have control of all its interfaces, and also that you have access. With 1181 you only know when claiming whether you have access. So access issues will pop up at a later point - maybe something to note in the release notes.

PR 1181 makes WinUSB look more like e.g. Linux where several processes can open the same device (unless exclusive locks are used), only claiming interfaces matters. So I think this is good for cross-platform applications. However on Linux you will pass the user permission test already at libusb_open.

mcuee commented 10 months ago

To me #1181 is a good improvement. Unfortunately we need more reviews and tests so we can not get it inside 1.0.27 release.