Electronic – Is USB UHCI polling-based or interrupt driven

interruptsusb

I've had a discussion with some colleagues whether USB is polling-based or interrupt-driven. Some claim USB keyboards involve larger overhead than i.e. PS/2 keyboards due to the requirement of former being polled by the main CPU. Now, with today's typical CPU speeds it would probably be a small amount of CPU-time spent in either case, but it is still interesting to know.

I've tried to find out myself. I suspect the answer could be dependent on the USB host controller interface. I looked specifically at UHCI which is one of the original standards and is rather simple to understand (ftp://ftp.netbsd.org/pub/NetBSD/misc/blymn/uhci11d.pdf). Based on my reading of this, it seems it could be argued both ways whether USB devices are polling based or not.

So here some facts I've gathered from reading the UHCI spec:

1) It's true a USB device (at least prior to USB 3.0 which is what applies to most/all keyboards) can't actively send an interrupt. They can only answer a query whether there is a pending interrupt. Hence, this query has to be sent periodically.

So far, point 1 suggests it is indeed polling-based. But then we have:

2) It is not the CPU itself which sends the queries, but rather the host controller.

3) The main CPU does, however, have to program the host controller, by laying out a detailed schedule of what happens in each ms.

4) The UHCI host controller can send interrupts to the main CPU to alert it of completed transfers. Hence, the main CPU doesn't need to poll for completion of transfers.

So now it would seem (and this is what some colleagues argued) that from the point-of-view of the main CPU, USB isn't polling based anyway. It is the host controller that periodically polls, and then delivers the interrupts to the main CPU. This wouldn't be so bad, because the host controller doesn't have other jobs, so it's not a problem it is doing this polling.

But unfortunately, I don't think this is really the case after all, because:

5) According to my reading of UHCI, there is no way the main CPU can configure a UHCI controller and USB keyboard such that once it is configured, the main CPU will not have to do further work until something happens, e.g. the user presses a key and the keyboard sets a pending interrupt which is delivered to the host controller and ultimately host as specified above. According to my reading, the main CPU has to continuously "work" to keep it up and running. There are as I see it two reasons for this:

  • Transfer descriptors have an Interrupt-on-completion bit that can be set so that the main CPU is interrupted upon completion of the transfer. So far so good, but unfortunately there's no way to differentiate between positive and negative answers to interrupt queries, so this means the main CPU will be interrupted every time a device is queried about interrupt status, no matter whether it has a pending interrupt or not.
  • The schedule cannot be reused. Each transfer descriptor has an ACTIVE bit that must be set to 1 for the host controller to execute it. The host controller sets it 0 after it has been executed. As a result, the main CPU will have to periodically go through the transfer descriptors and set them back to active, so that they will be executed again.

So even though it seems UHCI has the potential to deliver interrupts "for free" to the main CPU, it seems the above two points ruin it and means that even if the main CPU doesn't have to be engaged in out-right polling of the keyboard, it still has to periodically reschedule the polling, even in the case of no events. Which means it is sort of engaging in polling.

I'm really interested if someone could validate these observations, especially the two bullet points above. Bonus points if someone can point out how OHCI, EHCI, and XHCI differ in these aspects!

Best Answer

The key point is that normally, the interrupt transfers are not considered complete until some data is received. beyondLogic's great USB protocol docs say:

If an interrupt has been queued by the device, the function will send a data packet containing data relevant to the interrupt when it receives the IN Token. [...] If on the other hand an interrupt condition was not present when the host polled the interrupt endpoint with an IN token, then the function signals this state by sending a NAK.

For your specific controller, the table on page 22 in your doc talks about retry counter in 28:27. If you look at the table, you'd notice that "NAK received" does not decrement it! So as long as you set at least one retry, the controller will retry indefinitely on NAKs, without bothering the host.


But there is a much simpler way to check this -- find any Linux system with USB keyboard, and check the interrupt counter:

watch -d -n 0.5 cat /proc/interrupts

On my system, there is a line which says "IR-PCI-MSI 327680-edge xhci_hcd", and it has non-changing number if I am not touching mouse or keyboard. Moving the mouse causes this number to increase. So this is clearly interrupt based.