Some consumer control descriptor reports uses an array of usages instead
authoranton <anton@openbsd.org>
Tue, 24 Aug 2021 10:53:43 +0000 (10:53 +0000)
committeranton <anton@openbsd.org>
Tue, 24 Aug 2021 10:53:43 +0000 (10:53 +0000)
of enumerating all supported ones. This representation is more compact
as all supported usages are given by the array min and max boundaries.

In enumerating mode, the interrupt report has one level indirection as
it includes the bit offset of the usage requiring the driver to map bits
to usages in order to resolve the key press.
In array mode, the interrupt report includes the usage without any
indirection, removing the need map bits to usages.

ucc(4) can now operate either in enumerating or array mode depending on
the descriptor report. This should make more keyboards usable.

Thanks to Alessandro De Laurenzis <just22 at atlantide dot mooo dot com>
for testing.

sys/dev/usb/ucc.c

index 4970026..332a31e 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ucc.c,v 1.5 2021/08/23 17:50:26 anton Exp $   */
+/*     $OpenBSD: ucc.c,v 1.6 2021/08/24 10:53:43 anton Exp $   */
 
 /*
  * Copyright (c) 2021 Anton Lindqvist <anton@openbsd.org>
@@ -49,13 +49,13 @@ struct ucc_softc {
        keysym_t                 *sc_map;
        u_int                     sc_maplen;
        u_int                     sc_mapsiz;
-       u_int                     sc_nkeys;
 
        /* Key mappings used in raw mode. */
        const struct ucc_keysym **sc_raw;
-       u_int                     sc_rawlen;
        u_int                     sc_rawsiz;
 
+       u_int                     sc_nusages;
+       int                       sc_isarray;
        int                       sc_mode;
 
        /* Last pressed key. */
@@ -85,10 +85,12 @@ int ucc_enable(void *, int);
 void   ucc_set_leds(void *, int);
 int    ucc_ioctl(void *, u_long, caddr_t, int, struct proc *);
 
-int    ucc_parse_hid(struct ucc_softc *, void *, int);
+int    ucc_hid_parse(struct ucc_softc *, void *, int);
+int    ucc_hid_parse_array(struct ucc_softc *, const struct hid_item *);
+int    ucc_add_key(struct ucc_softc *, int32_t, u_int);
 int    ucc_bit_to_raw(struct ucc_softc *, u_int, u_char *);
 int    ucc_usage_to_sym(int32_t, const struct ucc_keysym **);
-void   ucc_raw_to_scancode(u_char *, int *, u_char, int);
+int    ucc_intr_to_usage(u_char *, u_int, int32_t *);
 void   ucc_input(struct ucc_softc *, u_int, int);
 void   ucc_rawinput(struct ucc_softc *, u_char, int);
 int    ucc_setbits(u_char *, int, u_int *);
@@ -164,15 +166,16 @@ ucc_attach(struct device *parent, struct device *self, void *aux)
        sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
        sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
 
-       error = ucc_parse_hid(sc, desc, size);
+       error = ucc_hid_parse(sc, desc, size);
        if (error) {
                printf(" hid error %d\n", error);
                return;
        }
 
-       printf(" %d key%s, %d mapping%s\n",
-           sc->sc_nkeys, sc->sc_nkeys == 1 ? "" : "s",
-           sc->sc_rawlen, sc->sc_rawlen == 1 ? "" : "s");
+       printf(": %d usage%s, %d key%s, %s\n",
+           sc->sc_nusages, sc->sc_nusages == 1 ? "" : "s",
+           sc->sc_maplen / 2, sc->sc_maplen / 2 == 1 ? "" : "s",
+           sc->sc_isarray ? "array" : "enum");
 
        /* Cannot load an empty map. */
        if (sc->sc_maplen > 0)
@@ -188,8 +191,8 @@ ucc_detach(struct device *self, int flags)
        if (sc->sc_wskbddev != NULL)
                error = config_detach(sc->sc_wskbddev, flags);
        uhidev_close(&sc->sc_hdev);
-       free(sc->sc_map, M_USBDEV, sc->sc_mapsiz);
-       free(sc->sc_raw, M_USBDEV, sc->sc_rawsiz);
+       free(sc->sc_map, M_USBDEV, sc->sc_mapsiz * sizeof(*sc->sc_map));
+       free(sc->sc_raw, M_USBDEV, sc->sc_rawsiz * sizeof(*sc->sc_raw));
        return error;
 }
 
@@ -199,6 +202,7 @@ ucc_intr(struct uhidev *addr, void *data, u_int len)
        struct ucc_softc *sc = (struct ucc_softc *)addr;
        int raw = sc->sc_mode == WSKBD_RAW;
        u_int bit = 0;
+       u_char c = 0;
 
        ucc_dump(__func__, data, len);
 
@@ -216,15 +220,21 @@ ucc_intr(struct uhidev *addr, void *data, u_int len)
                        }
                }
                return;
-       }
-       if (bit >= sc->sc_nkeys)
-               goto unknown;
-
-       if (raw) {
-               u_char c;
+       } else if (sc->sc_isarray) {
+               const struct ucc_keysym *us;
+               int32_t usage;
 
+               if (ucc_intr_to_usage(data, len, &usage) ||
+                   ucc_usage_to_sym(usage, &us))
+                       goto unknown;
+               bit = us->us_usage;
+               c = us->us_raw;
+       } else if (raw) {
                if (ucc_bit_to_raw(sc, bit, &c))
                        goto unknown;
+       }
+
+       if (raw) {
                if (c != 0) {
                        ucc_rawinput(sc, c, 0);
                        sc->sc_last_raw = c;
@@ -322,11 +332,11 @@ ucc_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
  * report and the corresponding pressed key.
  */
 int
-ucc_parse_hid(struct ucc_softc *sc, void *desc, int descsiz)
+ucc_hid_parse(struct ucc_softc *sc, void *desc, int descsiz)
 {
        struct hid_item hi;
        struct hid_data *hd;
-       u_int mapsiz, rawsiz;
+       int nsyms = nitems(ucc_keysyms);
        int isize;
 
        /*
@@ -339,59 +349,104 @@ ucc_parse_hid(struct ucc_softc *sc, void *desc, int descsiz)
                return ENXIO;
 
        /*
-        * Create mapping between each input bit and the corresponding key used
-        * in translating mode. Two entries are needed per bit in order
-        * construct a mapping.
+        * Create mapping between each input bit and the corresponding usage,
+        * used in translating mode. Two entries are needed per bit in order
+        * construct a mapping. Note that at most all known usages as given by
+        * ucc_keysyms can be inserted into this map.
         */
-       mapsiz = isize * 2;
-       sc->sc_mapsiz = mapsiz * sizeof(*sc->sc_map);
-       sc->sc_map = mallocarray(isize, 2 * sizeof(*sc->sc_map), M_USBDEV,
+       sc->sc_mapsiz = nsyms * 2;
+       sc->sc_map = mallocarray(nsyms, 2 * sizeof(*sc->sc_map), M_USBDEV,
            M_WAITOK | M_ZERO);
 
        /*
-        * Create mapping between each input bit and the corresponding scan
-        * code used in raw mode.
+        * Create mapping between each input bit and the corresponding usage,
+        * used in raw mode.
         */
-       rawsiz = isize;
-       sc->sc_rawsiz = rawsiz * sizeof(*sc->sc_raw);
+       sc->sc_rawsiz = isize;
        sc->sc_raw = mallocarray(isize, sizeof(*sc->sc_raw), M_USBDEV,
            M_WAITOK | M_ZERO);
 
        hd = hid_start_parse(desc, descsiz, hid_input);
        while (hid_get_item(hd, &hi)) {
-               const struct ucc_keysym *us;
-               int bit;
+               u_int bit;
+               int error;
 
-               if (HID_GET_USAGE_PAGE(hi.usage) != HUP_CONSUMER ||
-                   HID_GET_USAGE(hi.usage) == HUC_CONTROL)
+               if (HID_GET_USAGE_PAGE(hi.usage) != HUP_CONSUMER)
                        continue;
 
-               bit = sc->sc_nkeys++;
-               if (ucc_usage_to_sym(HID_GET_USAGE(hi.usage), &us))
+               /*
+                * The usages could be expressed as an array instead of
+                * enumerating all supported ones.
+                */
+               if (HID_GET_USAGE(hi.usage) == HUC_CONTROL) {
+                       error = ucc_hid_parse_array(sc, &hi);
+                       if (error)
+                               return error;
                        continue;
+               }
 
-               if (sc->sc_maplen + 2 >= mapsiz)
-                       return ENOMEM;
-               sc->sc_map[sc->sc_maplen++] = KS_KEYCODE(bit);
-               sc->sc_map[sc->sc_maplen++] = us->us_key;
+               bit = sc->sc_nusages++;
+               error = ucc_add_key(sc, HID_GET_USAGE(hi.usage), bit);
+               if (error)
+                       return error;
+       }
+       hid_end_parse(hd);
+
+       return 0;
+}
+
+int
+ucc_hid_parse_array(struct ucc_softc *sc, const struct hid_item *hi)
+{
+       int32_t max, min, usage;
+
+       min = HID_GET_USAGE(hi->usage_minimum);
+       max = HID_GET_USAGE(hi->usage_maximum);
+       if (min < 0 || max < 0 || min >= max)
+               return 0;
+
+       sc->sc_isarray = 1;
 
-               if (bit >= rawsiz)
+       for (usage = min; usage <= max; usage++) {
+               int error;
+
+               sc->sc_nusages++;
+               error = ucc_add_key(sc, usage, 0);
+               if (error)
+                       return error;
+       }
+
+       return 0;
+}
+
+int
+ucc_add_key(struct ucc_softc *sc, int32_t usage, u_int bit)
+{
+       const struct ucc_keysym *us;
+
+       if (ucc_usage_to_sym(usage, &us))
+               return 0;
+
+       if (sc->sc_maplen + 2 > sc->sc_mapsiz)
+               return ENOMEM;
+       sc->sc_map[sc->sc_maplen++] = KS_KEYCODE(sc->sc_isarray ? usage : bit);
+       sc->sc_map[sc->sc_maplen++] = us->us_key;
+
+       if (!sc->sc_isarray) {
+               if (bit >= sc->sc_rawsiz)
                        return ENOMEM;
                sc->sc_raw[bit] = us;
-               sc->sc_rawlen++;
-
-               DPRINTF("%s: bit %d, usage %s\n", __func__,
-                   bit, us->us_name);
        }
-       hid_end_parse(hd);
 
+       DPRINTF("%s: bit %d, usage %s\n", __func__,
+           bit, us->us_name);
        return 0;
 }
 
 int
 ucc_bit_to_raw(struct ucc_softc *sc, u_int bit, u_char *raw)
 {
-       if (bit >= sc->sc_nkeys)
+       if (bit >= sc->sc_rawsiz)
                return 1;
        *raw = sc->sc_raw[bit]->us_raw;
        return 0;
@@ -412,6 +467,24 @@ ucc_usage_to_sym(int32_t usage, const struct ucc_keysym **us)
        return 1;
 }
 
+int
+ucc_intr_to_usage(u_char *buf, u_int buflen, int32_t *usage)
+{
+       int32_t x = 0;
+       int i;
+
+       if (buflen == 0 || buflen > sizeof(*usage))
+               return 1;
+
+       for (i = buflen - 1; i >= 0; i--) {
+               x |= buf[i];
+               if (i > 0)
+                       x <<= 8;
+       }
+       *usage = x;
+       return 0;
+}
+
 void
 ucc_input(struct ucc_softc *sc, u_int bit, int release)
 {