ADB-USB Wombat firmware fix for unrecognized USB keyboards and mice

Wombat device in the plastic case

Mea culpa time here. Since the beginnings of the Wombat there have been reports that certain USB keyboards and mice weren’t recognized by the device. Nothing happened when typing or moving the mouse, and the Wombat’s activity LED didn’t blink. The affected devices were usually fancier keyboards and mice with lots of buttons and features, as opposed to plain vanilla $8 two-button mice and generic 101-key keyboards. I had always chalked this up to some unknown issue in the Microchip USB stack code for handling of peripherals with multiple USB interfaces or a single interface with multiple types of data. It was something in the bowels of that code, which I didn’t write and didn’t understand too well, so I treated it as a regrettable known incompatibility.

Recently a few Wombat customers reported more problems like these related to the Logitech Bolt USB receiver, and I decided to take another look. One helpful customer sent me a dump of the Bolt’s USB HID report descriptor as reported by Linux. I dug through the code with the help of AI, attempting to analyze how the USB stack would handle this report descriptor. It looked hopeless, until…

After wading through thousands of lines of mind-numbing USB goo, I found the smoking gun. Upon completing a USB transfer, the code was storing the number of bytes transferred in an 8-bit local variable and then comparing it to the 16-bit expected transfer size. The result was that any transfer larger than 255 bytes would always fail! Please queue the laugh track and sad trombone sound effects.

What does this have to do with composite USB devices? Nothing, except that composite USB devices have more interfaces with more data to report, resulting in larger report descriptors that are more likely to exceed 255 bytes.

Firmware 0.3.11 was released yesterday to fix this bug. It’s not just something relevant to the Logitech Bolt. If you’ve been using the Wombat in USB-to-ADB mode and encountered certain keyboards or mice that just mysteriously didn’t work with the Wombat, please try the new firmware. There’s a good chance that this will fix it.

4 Comments

  1. Some USB devices have obscenely long HID report descriptors. One example I found is the Sony DualShock 4 gamepad (as used on the PlayStation 4), which has one 507 bytes long! Mostly because it has a bazillion Feature items each with their own Report ID and vendor-specific Usage. No idea what they’re used for.

    Was the error in the Wombat code or the Microchip USB stack?

    Oh, and a pedantic correction: a report descriptor won’t be large because a device has multiple interfaces. A report descriptor is only related to a single interface, so a descriptor will only be large because it’s large. To paraphrase a meme: “It’s like that because of the way it is.” 🙂

  2. Thanks for the clarification on the non-importance for descriptor size of having multiple interfaces. That’s funny about the DualShock descriptor, I can see why it would need to be so large! The Bolt receiver I was looking at had one report descriptor that was something like 470 bytes for a digitizer. I didn’t look in detail, but I think the receiver has report descriptors for all the devices that could possibly be paired with the Bolt, regardless of whether they’re actually currently connected. That’s the same approach the Wombat itself takes when operating as a USB device.

    The byte/word bug was in the Microchip USB code. It’s not a maintained vendor library so much as sample code that Microchip provides for reading keyboards and mice through hubs. For a small-brained person like me, it’s incredibly complicated and difficult to follow. I feel like I understand very well how ADB communications with peripherals work, but the equivalent on the USB side is 100x more complex, and I’ve mostly been treating that low-level USB code as a black box.

  3. It’s a little bit unfortunate that the common C compilers don’t warn if integer sizes don’t match, not even with a lot of warnings enabled.

    BUT: There is help available. Run lint on the code. It does a static code analysis and finds a lot of bad code.

    At work, we use PC-lint for some projects. Its manual starts with the friendly message “DON’T SHOOT THE MESSENGER”, and this message is desperately needed. Out of the box, PC-lint is an extremely annoying and extremely frustrating bean counter, complaining at least twice about every single line of source code you feed it. You need a configuration file that disables a lot of warnings and errors, especially if you cross-compile for a microcontroller. And especially if you use third-party headers and libraries. It does not like macros very much, and it also does not like GCC extensions. You can work around both.

    Despite all of that, PC-lint is a great tool once you have configured it properly. It finds so many potential errors that it is worth all of the frustration to set it up for your project. Of course, PC-lint complains about assinging a 16 bit integer to an 8 bit integer. I’m 100 % sure it would have found this error out of the box.

    From experience, I would run PC-lint on the Wombat firmware. If Microchip managed to overflow an integer once, they managed to do it more than once. And if not that, they surely managed to mess up something else.

    You don’t have to use PC-lint, there are other tools that do a static code analysis on C code. See https://en.wikipedia.org/wiki/Lint_(software) and the links in that article.

    PC-lint is not my first weapon of choice to find errors. Just reading the code, and perhaps some debug printfs are usually sufficient. After that, the interactive debugger. And only then, if there is still no clue why things go wrong, I run PC-lint. Just because PC-lint needs some time. Each project has its own little things that PC-lint does not like, so you need to work around that. And even after that, PC-lint will drown you in error and warning messages, most of them completely unrelated to your current problem.

    Yes, in theory, we should start every project with a PC-lint setup and run PC-lint in every build. We tried that. It does not work that way. PC-lint is too slow to run in every build. In our experience, a release build is a good point to run PC-lint, to find newly introduced errors. Clean up, test again, build a release again.

Leave a comment

For customer support issues, please use the Customer Support link instead of writing blog comments. Your email address is only for admin use and won't be published.