Freebsd – New TrueOS install; keyboard doesn’t work right

freebsdtrueosusb

I installed a new instance of TrueOS (a FreeBSD variant) yesterday, and had a USB keyboard plugged in (Logitech G510). It worked fine in the install env, during the first-boot setup, and up until the new instance rebooted. At this point, it seems like it's stopped sending input entirely, starting from when it's recognized during BSD's boot-time module loading phase, because that's when Scroll Lock stops working. However, the LCD screen and backlights are on, and if I plug another keyboard in it tracks status changes (Caps/NumLock, etc.) and it works fine in Windows/Linux and on my laptop.

I've tried various combinations of turning on/off Legacy USB and Hand-off in the BIOS, and using different ports, all to no avail. A different USB keyboard ("vendor 0x1241 USB Keyboard" in the logs below) works fine either way. The USB mouse has always worked. There is no PS/2 port.

Any idea what's going on here, and how to fix it? Thanks!

% dmesg | grep kb
kbd1 at kbdmux0
atkbdc0: <Keyboard controller (i8042)> at port 0x60,0x64 on isa0
atkbd0: <AT Keyboard> irq 1 on atkbdc0
kbd0 at atkbd0
atkbd0: [GIANT-LOCKED]
ukbd0 on uhub5
ukbd0: <vendor 0x1241 USB Keyboard, class 0/0, rev 1.10/2.80, addr 2> on usbus6
kbd2 at ukbd0
ukbd1 on uhub0
ukbd1: <Logitech G510 Gaming Keyboard, class 0/0, rev 2.00/1.65, addr 2> on usbus0
kbd3 at ukbd1

% ll /dev/*kb*
crw-------  1 root  wheel  0x47 Oct 31 10:46 /dev/atkbd0
lrwxr-xr-x  1 root  wheel     6 Oct 31 10:46 /dev/kbd0 -> atkbd0
lrwxr-xr-x  1 root  wheel     7 Oct 31 10:46 /dev/kbd1 -> kbdmux0
lrwxr-xr-x  1 root  wheel     5 Oct 31 10:46 /dev/kbd2 -> ukbd0
lrwxr-xr-x  1 root  wheel     5 Oct 31 10:46 /dev/kbd3 -> ukbd1
crw-------  1 root  wheel  0x25 Oct 31 10:46 /dev/kbdmux0
crw-------  1 root  wheel  0x62 Oct 31 10:46 /dev/ukbd0
crw-------  1 root  wheel  0x6a Oct 31 10:46 /dev/ukbd1

Best Answer

Précis

What is going on is actually quite simple: In order to provide true N-key rollover and all of those extra gaming and other keys, Logitech has adjusted the way that it reports keyboard input events over USB. The USB input report parsing library that is built into the FreeBSD kernel does not support this. If you had not filtered the kernel message output you would have seen messages like

hid_get_item: Number of items(991) truncated to 255 
hid_get_item: Number of items(257) truncated to 255 
or

hid_get_item:364: Number of items truncated to 255

Detail

The USB input report protocol allows USB HIDs to have two sets of up to 65531 keys each. The current USB standards define the codes for keys 4 (A a) to 231 (⌘ Right GUI) in the first set. A "gaming keyboard" has a lot more keys than that, most of which do not correspond with standardized key codes, and may in fact go in the second set in any case.

USB HIDs conceptually report keyboard activity as a huge bitmap, one bit for every key, describing the current up/down state of all keys on the keyboard. Whilst this is indeed the case for the 8 modifier keys, with codes 224 to 231, usually other keys are reported in the form of an inverted array. The keyboard input report from the device over the wire to the USB host comprises the indices of the bits in the bitmap that are set to 1.

This inverted array form saves space. My 126-key multimedia keyboard that I am typing this answer on right now would need 16 bytes per input report to report the entire keyboard in bitmap form. With the inverted array, however, it uses a mere 8 bytes, 6 of which are the array indices of non-modifier keys.

The problems with this inverted array form are that one does not get something for nothing:

  • It limits rollover. With the full bitmap, one would have true N-key rollover (subject to any hardware limitations of the keyboard matrix itself). With the inverted array form, the number of array indices limits the amount of rollover. Conventionally, because the USB HID standards actually describe this "boot" report format in an appendix, USB HID keyboards provide 6 array indices in their reports, allowing up to 6 (non-modifier) keys to be down simultaneously.
  • It limits key codes. The array indices are usually 8 bit integers. Make them wider so that they can accommodate more than 256 key codes, and either it takes more than 8 bytes to report 6 simultaneously pressed (non-modifier) keys, or one reduces the number of simultaneously pressed keys that can be reported to less than 6.

    Unfortunately, a gaming keyboard needs to be able to report far more than 256 key codes. As you can see from the aforementioned log excerpts, the Logitech gaming keyboard has key codes up to 257 in one case and up to 991 in another. This does not mean that it has 257 and 991 keys. This means that the range of key code values is that wide.

The limitations of the FreeBSD kernel, more specifically of the cut-down version of libusb that is linked into the FreeBSD kernel, are somewhat subtle. They are not, as is often reported, a limit on the bitmap size. They are a limit on what is known in USB parlance as the report count of an input item. FreeBSD caps the report count at 255, printing the aforementioned messages if the actual report count is greater than 255.

This has consequences for both the straight bitmap form and the inverted array form of keyboard input.

  • For the straight bitmap form, it means that bitmaps larger than 255 will be truncated at the 255th key code. i.e. only the first 64 bytes of the bitmap will be considered. This caps the range of key codes that will be recognized.
  • For the inverted array form, it means that only the first 255 array indices will be used. The first 255 array indices can contain any key code, of course. This caps the effective amount of rollover.

This is simply more of a problem for the former than the latter. No-one sensible would use the inverted array form rather than the bitmap form in the case where this would matter. An inverted array with 255 array indices is a lot less space efficient than the equivalent bitmap form, as a bitmap of the same size could represent 8 times as many key codes with true N-key rollover.

So Logitech wants to report any key code in ranges of either 257 or 991 key codes, to make all of the extra keys work. (Why it is split this way relates to the different "pages" that the codes for different types of keys are grouped into, and the way that input items cannot span multiple "pages". This is USB arcana that is beyond the scope of this answer.) And it wants to give people more than 6-key rollover.

To do so, it switches to a USB input report descriptor format that uses bitmaps and large report counts. (USB allows HIDs to have multiple input report formats, which they can switch amongst.) The FreeBSD kernel does not like this, and problems ensue.

The local fix is to force the Logitech keyboard to keep using the "boot" input report descriptor format, which provides 6-key rollover and cannot send the codes for any of the extra keys on the keyboard, instead of its "native" report descriptor format. This can be done at every system bootstrap with the usbconfig command, giving it the add_quirk UQ_KBD_BOOTPROTO subcommand.

The bodge is to put this quirk into the already existing list of pre-defined quirks that is built in to the FreeBSD kernel, and magically applied by the kernel to matching USB devices.

The service fix is to fix the cut-down libusb in the kernel so that it does not cap report counts at 255. No-one has done this, although it has been briefly and superficially discussed on one of the FreeBSD mailing lists.

Related Question