Myths about USB NKRO and how USB HID works

A common myth about USB keyboards is that they can only support 6-key rollover, and not report more than 6 keys being pressed at a time. In fact, this is wholly untrue; the USB HID device class can support full N-key rollover (NKRO). While keyboards which purport to support USB NKRO are now common, some of them have the ability to “disable” NKRO for compatibility reasons. This is peculiar and unnecessary and USB HID in no way requires it.

A properly implemented USB keyboard with NKRO can provide full NKRO over USB, and not need any option to “turn NKRO on and off”.

How USB HID works

The USB Human Interface Device (HID) device class is designed to provide highly generic support for arbitrary input devices: keyboards, mice, joysticks, gamepads, foot pedals, etc. The HID specification's analysis of this problem domain and its designed solution is actually quite elegant, certainly by USB standards.

To put it simply, a USB HID device provides a stream of Input Reports to the host, describing the current state of the device's inputs (in the case of a keyboard, keys). These reports are binary data, the structure of which may be device-specific and can be completely arbitrary. Two different keyboards may use very different structures to report their status to the host; a joystick's reports, for example, will naturally be very different in turn.

Since the structure of this binary data can be arbitrary, how is a host supposed to know how to interpret it? At enumeration time, the device sends to the host a Report Descriptor, which is a structure in a standard format defined by the HID specification. This structure describes the format of the Input Reports. Crudely, you might view it as analogous to a binary serialization of a structure definition in a programming language or IDL.

The Report Descriptor provided by the device to the host specifies what fields are in the Input Report, what size they are, at what offset, and what they mean. Since the Report Descriptor language is limited, the kinds of structure which can be described by them are limited, and thus so in practice are the kinds of structure which can be used by HID devices. However, the Report Descriptor language is expressive enough to cover the vast majority of use cases for “human interface devices”:

It can represent inputs which are expressed by integers in a defined range (e.g. [-128, 127] or [0, 65535] might be used for an analog input). In the degenerate case, these values might be a single bit, as is the case for keyboards. 1

Arbitrarily many inputs can be represented. For example, you could create an HID device with hundreds of on-off binary inputs, represented as a bitfield.

Since in some cases bitfields might be very large, another option given is to use an array of integers to represent inputs which are currently on, with unused elements in the array containing 0.

Every input defined in a Report Descriptor must be associated with a “usage code”, which references a massive table of usage codes incorporated in the USB HID specification. This identifies the meaning of each input according to some standard rubric. For example, there are usage codes for each key on a keyboard; for every button on a mouse, etc. In this regard, the USB HID specification's usage tables are rather like the Unicode of input devices, seeking to allocate codepoints for every conceivable type of input meaning.

This isn't an exhaustive list of what Report Descriptors can express. Full details can be found in the USB HID specification.

There are also Output Reports, sent by the host to the device, which work in an entirely analogous fashion, and which are described by the same Report Descriptors. Usage codes exist for Num Lock, Caps Lock and Scroll Lock LEDs, for example.

How USB keyboards work

Given the above, there's nothing to stop a 106-key USB keyboard from simply declaring a 106-bit bitfield, with one bit representing every key. Whenever a key state changes, the 106-bit bitfield is retransmitted to the host. This being the case, where does the “USB is limited to 6-key rollover” myth come from?

Since the HID Report Descriptor format is complex, it was felt that BIOS writers might be neither able or inclined to incorporate a full implementation of HID which can parse and understand arbitrary Report Descriptors. Thus, the USB HID specification includes an additional notion of a “boot keyboard”, which can be used by the BIOS when e.g. configuring your PC's BIOS settings.

A keyboard complying with the “boot protocol” is required to use a specific format for its Input Reports set out in the HID specification. That way, BIOSes can be hardcoded to use and expect that format of Input Report and never even have to look at the corresponding Report Descriptor.

The “boot protocol” format so prescribed is as follows:

1 ui Bitfield of Modifier Keys (LCtrl, RCtrl, LShift, RShift, LAlt, RAlt, LWindows, RWindows) 1 ui Reserved (0) 1 ui Scancode 1 (or 0) 1 ui Scancode 2 (or 0) 1 ui Scancode 3 (or 0) 1 ui Scancode 4 (or 0) 1 ui Scancode 5 (or 0) 1 ui Scancode 6 (or 0)

In other words, aside from the eight standard modifier keys, which are exempted from the key rollover limit, the currently depressed keys are packed into a 6-element fixed-length array of scancodes.

In practice, all keyboards implement the boot protocol. This is where the 6-key limit originates. However, a keyboard can still implement the boot protocol and also provide NKRO. When a keyboard is powered, as per the HID specification, it defaults to using its own preferred reporting formats and can use NKRO-enabled input reports if it chooses. The HID specification states that a host which can only understand the boot protocol must explicitly request the keyboard switch to it by requesting it using a special command. Thus, your NKRO-enabled keyboard might be limited to 6-key rollover in your BIOS's setup screen, but a properly implemented OS with full support for USB HID needn't suffer from this limitation.

Workarounds for buggy implementations. In practice, the vast majority of cheap, non-NKRO USB keyboards use the “boot protocol” as their default and only reporting format. For this reason, it's not wholly implausible that there might be BIOSes out there which expect the boot protocol, but fail to send the command requesting it. I don't have any data on the existence or prevalence of such BIOSes, but I have heard of at least one keyboard using a clever workaround: the boot protocol format can be prepended to the NKRO-enabled input reports, but marked as padding in the Report Descriptors. Something like this:

1 ui Bitfield of Depressed Modifier Keys (LCtrl, RCtrl, LShift, RShift, LAlt, RAlt, LWindows, RWindows) 1 ui Padding 1 ui "Padding" (actually: Scancode 1) 1 ui "Padding" (actually: Scancode 2) 1 ui "Padding" (actually: Scancode 3) 1 ui "Padding" (actually: Scancode 4) 1 ui "Padding" (actually: Scancode 5) 1 ui "Padding" (actually: Scancode 6) 13 ui Bitfield of Depressed Non-Modifier Keys

Since boot protocol hosts completely ignore report descriptors, they examine the “padding” and get the data they are expecting. Hosts which properly examine report descriptors will correctly ignore the “padding” and use the NKRO-enabled key state bitfields in the latter area of the input report.

Conclusions

Nothing in USB HID limits a keyboard to only supporting 6-key rollover. The specification contains accommodations to allow a host to use a keyboard, limited to 6-key rollover, if it doesn't contain a full HID stack and can't understand report descriptors, but this relates only to BIOS setup screens, etc. All modern OSes have a full HID stack and don't suffer this limitation.

Moreover, although there may be some buggy hosts which expect the boot protocol format without requesting it, these can be accommodated in a compatible fashion without sacrificing NKRO for compliant hosts. Thus, the ability to “turn off NKRO” is no more necessary for USB keyboards than it is for PS/2 keyboards.