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;

Reply via email to