What is required to implement a USB HS virtual hub ? - Page 1
Forum section: electronics / general

author: Arte
author_rank: Contributor
timestamp: January 09, 2024, 05:12:01 pm
content: Hello. I'm a software engineer turned USB peripheral maker - I have some experience with writing USB-related firmware and the RP2040 chip (I interact with registers directly in my current rp2040-based firmwares) but none with writing USB drivers, more mature MCU lines like the STM32, USB high speed and complex PCB design.I would like to make a (HS) compound device that presents simultaneously a vendor class device, to communicate with existing applications expecting a specific device, and a HID class device (namely a keyboard). Multi-function devices are traditionally achieved using composite devices, however, in my experience, a composite device doesn't quite work as well as a compound device with Windows driver. For starters, WinUSB, which is the only driver that can be queried for via WCID without requiring prior driver installation (libusbK and libusb0 are not provided by default in Windows distros), hardly even supports composite devices: applications have to claim the entire device, they can't claim a particular interface so that 2 applications can talk to 2 part of the device at once. LibusbK supports that, but I believe all interfaces have to be paired with libusbK, hence no simultaneous Vendor/HID, on top of that requiring user installation.Things would be a lot simpler on the driver end if the chip just presented a hub with several devices attached (unclear to me whether another way even exists). Driver/device pairings are independant per device within a hub (or nothing would work, ever).Thing is, 'normal' USB modules, as found in common MCUs, aren't meant to be hubs, as hub tasks like managing the different speed of downstream devices is no simple task. But it's one thing to be a hub, and another to be a virtual hub where we know very well what devices are connected and what they do, because we control that.From the USB Complete chapter on hubs, when communicating with downstream devices of the same speed, the hub acts as a broadcaster and transmits the packets to all downstream devices. Then based on the address in the packet, the devices know whether to respond.... and that's about all I could gather. I'm not sure what happens next - I figure one of the device answers, and the hub expects that (but if it did, why broadcast ? but if it didn't, how does it react fast enough ?) and forwards the response upstream.In my experience, USB PHYs must be pre-armed with filled buffers to respond to incoming requests, because of very short response time requirements.So if I understand correctly, aside from the 'control' considerations of implementing a virtual hub (implementing the hub class, correctly answering requests etc. - sounds manageable), the one true challenge, speed-wise, for USB modules when implementing a virtual hub, is that the contents to reply to requests to any of the virtual downstream devices must be ready, and which to reply with must depend on the address contained within the transfer, as transfers will arrive on the same endpoint (the non-control ep of the hub). And that's it ?So I wonder, do I understand this right and, is it possible for fast MCUs to comply with the USB protocol while picking the buffer conditionally on the provided address ? On one hand, if what's exposed by the PHYsrequirethat the buffer for an endpoint be pre-filled, I can see that not being possible, but merely checking the address doesn't sound expensive either... I was hoping someone here might have experience with such concerns, as I find parsing the relevant documents quite difficult (& even stuff that aims to make the USB specs more accessible like USB complete doesn't have the answers). For the record the setup I'm considering so far is a STM32H7 with a USB3300 via ULPI.Regards, Julien

author: magic
author_rank: Super Contributor
timestamp: January 09, 2024, 05:29:07 pm
content: I imagine this may or may not be possible, depending on internal operation of the USB peripheral.For example, if the USB must be programmed with your device's bus address to work and then it automatically ignores all packets not destined to it, it will ignore packets addressed to the emulated devices.If the USB selects transfer buffer based on the endpoint number only (i.e. can't be programmed otherwise), you will have another problem if two emulated devices happen to make use of the same EP number.

author: ataradov
author_rank: Super Contributor
timestamp: January 09, 2024, 05:51:08 pm
content: There is no way to do this with regular USB peripherals. None of them would recognize two different addresses. And there is no way you will be able to somehow swap them fast enough.It would be far easier to just use a hub IC.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 09, 2024, 10:36:52 pm
content: And I don't understand why you wouldn't implement a composite device. This is how it should be done, and have never seen particular issues on Windows. There are millions of composite USB devices on the market.

author: Arte
author_rank: Contributor
timestamp: January 10, 2024, 12:57:13 am
content: In my experience, it is required that all the interfaces of a composite device be paired with the same driver. You can't have interface 1 paired with HidUSB and interface 2 paired with libusbK. libusb would fail to talk to that device, even if you're just targetting interface 2 in your libusb app.Additionnally, some apps don't even work with libusbK, only WinUSB, and WinUSB claims the entire device. This matters in the context of posing as existing devices.And I guess a hub IC would work yeah... but then you also have to add other ICs just for the sake of having several USB PHYs to connect to the hub, so that ends up expensive in money and power... and if you wanted several interfaces on HS (e.g vendor + audio), the BOM explodes. As for the address, My assumption was that addresses included the path to the hub and so I was hoping ICs wouldn't ignore addresses that are theirs, suffixed by something i.e the hub presented would have address 1.7.3 and the emulated device would have 1.7.3.4, 1.7.3.5... and the device would see 1.7.3.4 and, being 1.7.3, would conclude it has to process that.

author: ataradov
author_rank: Super Contributor
timestamp: January 10, 2024, 01:04:27 am
content: Quote from: Arte on January 10, 2024, 12:57:13 amI don't know about libusbK, but in general it is not the case. I have device with 3 interfaces - CDC, HID and raw bulk (using WinUSB driver and a regular libusb for access). This works perfectly fine.Quote from: Arte on January 10, 2024, 12:57:13 amThis is not true. Or not always true. I have not tried HID+WinUSB, But HID or WinUSB and CDC work fine.

author: Arte
author_rank: Contributor
timestamp: January 10, 2024, 01:18:52 am
content: Oh ? That would be fantastic news. Maybe the not claiming different interfaces thing is local to the interfaces managed by WinUSB ? I personally confirmed that getting process A to claim interface 1 and process B to claim interface 2 would only work when both were paired with libusbK, and not when they're both paired with WinUSB. I must have hastily generalized, but I didn't hallucinate that part. By the way, this is listed on libusbK's website, under "libusbK.sys advantages when compared to WinUSB.sys:" "Allows proccesses concurrent access to the same device." (https://libusbk.sourceforge.net/UsbK3/usbk_comparisons.html)

author: ataradov
author_rank: Super Contributor
timestamp: January 10, 2024, 01:23:53 am
content: Well, I don't know what libusb from MinGW is doing. It may not be actually using WinUSB driver and WinUSB is just needed to make Windows recognize the device. It is also possible that if there are multiple WinUSB interfaces, then two applications can't use them separately.And I have no idea about the "K" version at all. Libusb on Windows is extremely confusing. Windows programming sucks in general. And USB on Windows sucks even more. So, who knows.But the situation where you have CDC + something custom is very common and if CDC stopped working with you access the device, it would be a huge issue.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 10, 2024, 01:29:02 am
content: Quote from: Arte on January 10, 2024, 12:57:13 amYes you can. You can absolutely assign each interface to a different driver. I haven't done it with a HID interface, but I have with a CDC interface using a VCP driver, and a custom vendor interface using libusb. Haven't used "libusbK" in ages though, I have done it with WinUSB, which has been available since Win 7. It's just a matter of writing the appropriate .inf file. libusbK may have some limitations, I strongly suggest going for WinUSB - it works fine with libusb.

author: Arte
author_rank: Contributor
timestamp: January 10, 2024, 01:39:46 am
content: My mistake then, I guess what's really going on here is that, within the interfaces that are paired with WinUSB, different processes can't concurrently access these. Now that you point CDC interfaces out, the Picoprobe for instance does present a CDC interface and a vendor interface...Also - of writing the appropriate .inf file ? I never wrote one so far, I just had the device ask to be paired with WinUSB or libusbK via WCID (once libusbK is installed on the host computer) and that was good enough.What is the .inf file used for if you're just using standard classes / vendor class with WinUSB/libusbK ?Also I'm a bit confused about mention of the 'K' version. My understanding so far is that the drivers to communicate with vendor classes directly are WinUSB (default installed), libusb0 and libusbK (not default installed), all of which can be asked for via WCID (among other things) and installed via Zadig / libwdi. And libusb is the library used to talk to them. And libusbK, from my newbie perspective, is pretty much WinUSB but better but not preinstalled, and old USB library versions don't support it.

author: ataradov
author_rank: Super Contributor
timestamp: January 10, 2024, 01:40:56 am
content: You no longer even need INF files. WinUSB driver can be requested though MSOS descriptors automatically.

author: Arte
author_rank: Contributor
timestamp: January 10, 2024, 01:42:54 am
content: Right, by WCID I mean using MSOS descriptors.https://github.com/pbatard/libwdi/wiki/WCID-Devices

author: ataradov
author_rank: Super Contributor
timestamp: January 10, 2024, 01:43:32 am
content: No need for Zadig and all that nonsense anymore. With MSOS descriptors, you just plugin a device into a brand new PC and a WinUSB driver gets installed.Now it behaves like Linux did from the beginning, except with extra steps and still not as good. Way to go, Microsoft.I've also been looking at using WinUSB directly recently. And their APIs for that are nuts. I'm not sure who in their right mind would design it that way.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 10, 2024, 03:17:29 am
content: WinUSB does have some limitations also, compared to what you get on Linux.But at least it exists. Back when there was no such thing as WinUSB, you had to write your own driver, even for simple bulk transfer. There were some commercial ones (expensive of course). And then came libusbK (and libusbX IIRC? not sure, but in the same spirit.)But these days, I do enjoy Linux for custom USB devices. Very straightforward, no silly limitation when using libusb, and the latency I get is way lower than on Windows too.Zadig is a "dangerous" tool as well. I once (by mistake) assigned WinUSB to the USB hub my device was attached to, rather than to the device itself, and it "killed" access to all the ports attached to this hub, which included my keyboard.Reboots wouldn't be enough to solve it, I had to fix it manually playing with the registry, that wasn't fun.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: January 11, 2024, 05:43:56 pm
content: This Microsoft proprietary USB descriptor stuff is a bit silly, they need to really stop that. It doesn't even make sense because normally the added descriptors are duplicate data of what already exists.

author: ataradov
author_rank: Super Contributor
timestamp: January 11, 2024, 05:57:49 pm
content: The first version with the reserved string value is a hack, but has advantage of working with older versions of the USB spec. The second version with a BOS descriptor is better (although takes more effort to implement). Neither contains duplicate data.The descriptors are necessary given how Windows is designed. So, for them to stop needing those descriptors, they would need to redesign the way Windows USB stack is designed.The descriptors eliminate the need for INF files that simply install a standard Windows driver for the device. They supply the same information an INF file would supply.

author: Arte
author_rank: Contributor
timestamp: January 11, 2024, 07:07:28 pm
content: Would you mind clarifying what you mean by 2nd version or pointing me to where I could learn more ? I'm not aware of anything other than WCID, where Windows asks for string descriptor 0xFF then for a separate MOS descriptor if you replied a certain way to that 0xFF request. Quickly checking on the web I'm not sure what you're referring to.Also, what differs so greatly between Linux and Windows there ? For Linux don't you need to install udev rules to use vendor class devices, which is just as annoying ? I have little experience with Linux and the only device I manipulated whose documentation said to install udev rules... also worked without on my end, I believe ?... So I'm not sure what's happening over there.Regarding Windows I never understood why pairing with WinUSB isn't the default. Isn't just WinUSB a "hand user-land the controls" driver i.e, applications can send raw transfers to the device ? If a device declares vendor class and doesn't provide anything special, shouldn't "hand user-land the controls" be the default ?By the way, drifting from the original topic here, but, I've always wondered if it's possible to install a driver that's just like WinUSB, but pre-emptively polls devices. In the Dolphin Gamecube emulator that I developed stuff for, we communicate with a Nintendo device that reports a HID class, but no descriptors. This makes it unusable on Windows without extra work, whereas it works on the Nintendo consoles it's designed for, that already know the HID descriptor. So the current way is to ask users to use Zadig to pair it with WinUSB manually (or nowadays, use a separate application embedding libwdi that does it so users don't read half the instructions and pair their keyboard with WinUSB...). But WinUSB isn't great for the task, because it will only send transfers when requested. So we have a thread fully dedicated to telling WinUSB, 1000 times per second, that yes, we want to talk to the device on the next USB frame, thank you. Much fewer resources would be wasted if we could pair the device with a WinUSB-wrapping driver that could be told once that from now on, we want to poll this on every USB frame, and expose the latest version, or the recent history. I'm not sure how feasible that is.

author: ataradov
author_rank: Super Contributor
timestamp: January 11, 2024, 07:48:41 pm
content: This is the second version:https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specificationIt relies on the BOS descriptor, so bcdUSB must be at least 0x0210.Windows internally tries to abstract virtual devices (interfaces) within the same physical device, as separate devices. So, all the APIs are focused around enumerating the logical type of device you want without thinking about what physical device (or part of the device) implements it. So, all APIs look like "give me a list of all HID things".It is very non-trivial in WinUSB API to enumerate and take control over the whole physical device. So, if you have a device that implements HID for control and vendor for data transfer, you will have to do extra work and find both parts separately and then bind them together logically in your application.And then Windows does not make devices available to the system unless some driver is installed. Again, I think this is just an outdated assumption that all devices must have kernel-level drivers.Linux does not care and just lets you enumerate the whole tree. If you find the device you want, you can claim all the interfaces from it. It is very logical and straightforward.In my view Linux approach is far superior, but I see what they were going for in Windows. They are clearly wrong, as it creates a huge mess.No idea about the second part.

author: ataradov
author_rank: Super Contributor
timestamp: January 12, 2024, 07:38:56 pm
content: I'm looking deeper into the mess that is Windows USB stack. And it is amazingly crappy. Because of this attempt to separate interfaces into different devices, all requests for device or configuration descriptors are being faked. If you have WinUSB interface as part of a composite device, Windows will fake the descriptors as if the device was not composite and only had one interface.This has weird side effect that you can't get a product string descriptor of the device itself, since it substitutes string descriptor of the interface in that fake virtual device.And basically there is no way to make a universal user-land application that works with all USB devices. You are limited to working with class drivers.And I guess MS has practical reasons for some of the decisions they made. Installing drivers that way allows them to have fine policy control. System administrator can prohibit installation of subset of drivers. And allowed parts of the composite devices would continue to work. You can do some of that on Linux, but not in a generic way.

author: ataradov
author_rank: Super Contributor
timestamp: January 16, 2024, 04:25:45 pm
content: I've added CMSIS-DAP v2 support to my edbg project (https://github.com/ataradov/edbg/) for both Linux and Windows. Both use direct OS APIs with no external dependencies.Linux was super easy and fun. Talk about people knowing how to design good APIs.Windows was fighting me all the way, and the end result is still not as good.In the end the same basic functionality took 326 lines on Linux and 392 lines on Windows.Windows took less checking, since devices are enumerated by GUIDs, so right after the enumeration I know that it is a CMSIS-DAP device. But Windows side is essentially two separate parts. One for HID and one for WinUSB.Linux works with descriptors directly, so it requires more verification, but the rest of the code is pretty much universal. And again, Linux was super fun with clean and transparent APIs.

author: NorthGuy
author_rank: Super Contributor
timestamp: January 16, 2024, 05:01:22 pm
content: You can also write your own driver. Then you can do practically anything. Given your software origin, it's not difficult. This requires some bureaucracy though - you need to open Microsoft account and submit your driver for signing. You can go even further and distribute your driver through Windows Update so that it'll get installed without user needing to download anything manually, but this requires HLK testing, for which you'll need at least two PCs and lots of time.

author: ataradov
author_rank: Super Contributor
timestamp: January 16, 2024, 05:09:12 pm
content: Yeah, no. I'm not going to write drivers, especially not drivers for Windows. I'd rather drop Windows support entirely if driver development is necessary for anything I do.And I most certainly would not be crating MS accounts and signing anything.