USB Polling Rate Adventure

I like dance games, that's no secret. But recently I had some problems with my dance pad: getting those "Fantastics" was just too damn hard.

At first I, of course, assumed that it was just me being bad at the game, but then I started investigating.

Investigation

Let's have a look at the USB device with lsusb:

$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 006: ID 17ef:6099 Lenovo 
Bus 001 Device 005: ID 046d:085c Logitech, Inc. 
Bus 001 Device 004: ID 17ef:608d Lenovo 
Bus 001 Device 003: ID 6667:c006            <----- THAT'S THE DANCE PAD
Bus 001 Device 002: ID 8087:0a2b Intel Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

The device with vendorID:productID pair 6667:c006 is the dance pad. Let's have a closer look:

$ lsusb -vd 6667:c006

Bus 001 Device 003: ID 6667:c006  
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x6667 
  idProduct          0xc006 
  bcdDevice            1.02
  iManufacturer           1 www.laser-tek.pl
  iProduct                2 Mata taneczna L-TEK
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           34
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.01
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      32
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              10          <----- REQUESTED POLLING RATE
Device Status:     0x0000
  (Bus Powered)

It turns out that the USB device built into the pad requests a polling interval of 10ms. Given that the standard ITG timing window for "Fantastic" is only 21ms, this makes it very hard to reliably get good scores. Not good.

The Linux kernel takes the polling interval that's requested by the device and rounds it down to the nearest power of two. That's the rate it will effectively use. And indeed using wireshark and the usbmon kernel module I verified that this is actually the case. Here is the capture file. You can see that the reports are roughly 8ms apart.

Attempted fix #1

At that point I found a nice page on the Arch Linux wiki that explains everything I never wanted to know about USB polling rates in Linux. It also mentions a parameter for the usbhid kernel module that allows to force a faster polling rate.

Great, this will fix the issue!

# rmmod usbhid && modprobe usbhid jspoll=1

Dance pad replugged, wireshark running, and ... nope! Didn't work.

The dance mat doesn't register as a joystick, but as a gamepad and the jspoll parameter only affects joysticks. Interestingly, there is no equivalent parameter for gamepads. Yay for consistent interfaces!

Attempted fix #2

Haha, what did I learn to write code for. Let's modify the kernel to force a polling rate of 1ms for all devices. (Yes, I was desperate.)

About two hours later I had my own modified kernel and it actually booted without issues. Still, no dice. The polling rate didn't change.

I read up a bit more on USB and found out that for USB 3 the polling of devices is apparently not done by the kernel, but by the host controller. That's why my patch didn't work.

To verify that theory I fired up my only machine without USB 3 hardware, an old Thinkpad. The patch worked! Unfortunately that machine is too slow and has overheating issues, so I can't run StepMania on it.

Communication with the manufacturer

At that point I didn't want to invest even more time in the issue and decided to write to the manufacturer to ask for help. I sent them the lsusb output, the wireshark capture file and some more Linux debug information accompanied by a verbose description of what is wrong.

Unfortunately, the only "help" I got out of them was a comment that my problems must be caused by a software issue, probably Linux related.

We cannot help you.

To be fair I never tried the pad with Windows, so I don't know if that would have changed anything.

Replacing the controller

Not wiser than before I decided to replace the original USB controller with an Arduino micro board. The micro is a good fit for a project like that because it has USB built in. It was about an afternoon of work to get everything wired up (not counting the shopping trip to get all necessary material) and writing the software running on the board to register as an HID gamepad.

original controller arduino, WIP arduino, untamed arduino, neat

Getting the Arduino into the casing was a bit of a challenge, because it was higher than the original controller, so I had to carve out more space with an old screwdriver.

First time I turned it on, it actually worked. Nice!

You can find the software running on the Arduino on my GitLab page.

Conclusion

The polling issues have been resolved. My scores are not significantly better yet (it has been one day with the new controller so far), but now I can at least be sure that all the mistakes are my own doing.


Posted on Dec 14, 2019 by Martin Natano