Many low-cost USB keyboards have a limit of either 3 or 6 simultaneous
keypresses before they wedge and stop supplying any more keypress events
(at least until you release one of the pressed keys).
Some newer (usually called "gaming") keyboards use a different way of
reporting keypress events in order to support an arbitrary number of
simultaneous keypresses (likely under the assumption that in certain
games, you might need more than 3 or 6 keys pressed at the same time).
Our USB HID keyboard code had some bad assumptions in it - namely, that
every key reported in this fashion was a 'modifier' key like shift,
alt, or control, and that there would only be 8 such keys maximum.
The diff below reworks the HID keyboard code a bit to remove these
assumptions and support these newer style USB keyboards. It also
changes the term 'modifier' to 'variable' which is a more proper
description of these keys (as per the HID spec), in various
comments and printfs.
If you have a USB keyboard (whether or not it is a "gaming" variety),
please test this diff to ensure that it does not break you. I've tested
on i386 and amd64 on a variety of keyboards and haven't seen any
problems, so it's time to turn it loose to a wider audience.
-ml
Index: hidkbd.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/hidkbd.c,v
retrieving revision 1.5
diff -a -u -r1.5 hidkbd.c
--- hidkbd.c 9 Nov 2011 14:22:38 -0000 1.5
+++ hidkbd.c 29 Jun 2012 06:45:59 -0000
@@ -41,6 +41,7 @@
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/ioctl.h>
+#include <sys/malloc.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
@@ -164,6 +165,8 @@
{
const char *parserr;
+ kbd->sc_var = NULL;
+
parserr = hidkbd_parse_desc(kbd, id, desc, dlen);
if (parserr != NULL) {
printf(": %s\n", parserr);
@@ -171,8 +174,8 @@
}
#ifdef DIAGNOSTIC
- printf(": %d modifier keys, %d key codes",
- kbd->sc_nmod, kbd->sc_nkeycode);
+ printf(": %d variable keys, %d key codes",
+ kbd->sc_nvar, kbd->sc_nkeycode);
#endif
kbd->sc_device = self;
@@ -245,6 +248,9 @@
if (kbd->sc_wskbddev != NULL)
rv = config_detach(kbd->sc_wskbddev, flags);
+ if (kbd->sc_var != NULL)
+ free(kbd->sc_var, M_DEVBUF);
+
return (rv);
}
@@ -263,11 +269,9 @@
}
#endif
- /* extract key modifiers */
- ud->modifiers = 0;
- for (i = 0; i < kbd->sc_nmod; i++)
- if (hid_get_data(data, &kbd->sc_modloc[i]))
- ud->modifiers |= kbd->sc_mods[i].mask;
+ /* extract variable keys */
+ for (i = 0; i < kbd->sc_nvar; i++)
+ ud->var[i] = (u_int8_t)hid_get_data(data, &kbd->sc_var[i].loc);
/* extract keycodes */
memcpy(ud->keycode, data + kbd->sc_keycodeloc.pos / 8,
@@ -311,7 +315,6 @@
void
hidkbd_decode(struct hidkbd *kbd, struct hidkbd_data *ud)
{
- uint32_t mod, omod;
u_int16_t ibuf[MAXKEYS]; /* chars events */
int s;
int nkeys, i, j;
@@ -347,15 +350,15 @@
return; /* ignore */
}
nkeys = 0;
- mod = ud->modifiers;
- omod = kbd->sc_odata.modifiers;
- if (mod != omod)
- for (i = 0; i < kbd->sc_nmod; i++)
- if (( mod & kbd->sc_mods[i].mask) !=
- (omod & kbd->sc_mods[i].mask))
- ADDKEY(kbd->sc_mods[i].key |
- (mod & kbd->sc_mods[i].mask
- ? PRESS : RELEASE));
+
+ for (i = 0; i < kbd->sc_nvar; i++)
+ if ((kbd->sc_odata.var[i] & kbd->sc_var[i].mask) !=
+ (ud->var[i] & kbd->sc_var[i].mask)) {
+ ADDKEY(kbd->sc_var[i].key |
+ ((ud->var[i] & kbd->sc_var[i].mask) ?
+ PRESS : RELEASE));
+ }
+
if (memcmp(ud->keycode, kbd->sc_odata.keycode, kbd->sc_nkeycode) != 0) {
/* Check for released keys. */
for (i = 0; i < kbd->sc_nkeycode; i++) {
@@ -547,10 +550,36 @@
{
struct hid_data *d;
struct hid_item h;
- int imod;
+ int i, ivar = 0;
- imod = 0;
kbd->sc_nkeycode = 0;
+
+ d = hid_start_parse(desc, dlen, hid_input);
+ while (hid_get_item(d, &h)) {
+ if (h.kind != hid_input || (h.flags & HIO_CONST) ||
+ HID_GET_USAGE_PAGE(h.usage) != HUP_KEYBOARD ||
+ h.report_ID != id)
+ continue;
+ if (h.flags & HIO_VARIABLE)
+ ivar++;
+ }
+ hid_end_parse(d);
+
+ if (ivar > MAXVARS) {
+ DPRINTF((": too many variable keys\n"));
+ ivar = MAXVARS;
+ }
+
+ kbd->sc_nvar = ivar;
+ kbd->sc_var = (struct hidkbd_variable *)malloc(
+ sizeof(struct hidkbd_variable) * ivar, M_DEVBUF,
+ M_NOWAIT);
+
+ if (!kbd->sc_var)
+ return NULL;
+
+ i = 0;
+
d = hid_start_parse(desc, dlen, hid_input);
while (hid_get_item(d, &h)) {
if (h.kind != hid_input || (h.flags & HIO_CONST) ||
@@ -562,20 +591,18 @@
"cnt=%d", imod,
h.usage, h.flags, h.loc.pos, h.loc.size, h.loc.count));
if (h.flags & HIO_VARIABLE) {
- /* modifier reports should be one bit each */
+ /* variable reports should be one bit each */
if (h.loc.size != 1) {
- DPRINTF((": bad modifier size\n"));
+ DPRINTF((": bad variable size\n"));
continue;
}
- /* single item */
- if (imod < MAXMOD) {
- kbd->sc_modloc[imod] = h.loc;
- kbd->sc_mods[imod].mask = 1 << imod;
- kbd->sc_mods[imod].key = HID_GET_USAGE(h.usage);
- imod++;
- } else {
- /* ignore extra modifiers */
- DPRINTF((": too many modifier keys\n"));
+
+ /* variable report */
+ if (ivar < MAXVARS) {
+ kbd->sc_var[i].loc = h.loc;
+ kbd->sc_var[i].mask = 1 << (i % 8);
+ kbd->sc_var[i].key = HID_GET_USAGE(h.usage);
+ i++;
}
} else {
/* keys array should be in bytes, on a byte boundary */
@@ -600,11 +627,10 @@
}
DPRINTF(("\n"));
}
- kbd->sc_nmod = imod;
hid_end_parse(d);
/* don't attach if no keys... */
- if (kbd->sc_nkeycode == 0)
+ if (kbd->sc_nkeycode == 0 && ivar == 0)
return "no usable key codes array";
hid_locate(desc, dlen, HID_USAGE2(HUP_LEDS, HUD_LED_NUM_LOCK),
Index: hidkbdsc.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/hidkbdsc.h,v
retrieving revision 1.2
diff -a -u -r1.2 hidkbdsc.h
--- hidkbdsc.h 9 Nov 2011 14:22:38 -0000 1.2
+++ hidkbdsc.h 29 Jun 2012 06:39:37 -0000
@@ -32,13 +32,19 @@
*/
#define MAXKEYCODE 6
-#define MAXMOD 8 /* max 32 */
+#define MAXVARS 128
-#define MAXKEYS (MAXMOD+2*MAXKEYCODE)
+#define MAXKEYS (MAXVARS+2*MAXKEYCODE)
+
+struct hidkbd_variable {
+ struct hid_location loc;
+ u_int8_t mask;
+ u_int8_t key;
+};
struct hidkbd_data {
- u_int32_t modifiers;
u_int8_t keycode[MAXKEYCODE];
+ u_int8_t var[MAXVARS];
};
struct hidkbd {
@@ -47,12 +53,8 @@
struct hidkbd_data sc_odata;
/* input reports */
- struct hid_location sc_modloc[MAXMOD];
- u_int sc_nmod;
- struct {
- u_int32_t mask;
- u_int8_t key;
- } sc_mods[MAXMOD];
+ u_int sc_nvar;
+ struct hidkbd_variable *sc_var;
struct hid_location sc_keycodeloc;
u_int sc_nkeycode;