On Fri, Aug 27, 2021 at 07:05:06AM +0200, Anton Lindqvist wrote:
> Hi,
> An interrupt report contains the state of all items (Input, Output and
> Feature) from the corresponding descriptor report for a given report ID.
> The ordering of the items is identical in both the descriptor and
> interrupt report. As the interrupt report can cover more than Consumer
> Control related key presses, ucc must be more careful while examining
> the interrupt report in order to not confuse other items as key presses.
>
> While parsing the descriptor report, take note of the bits that
> represents Consumer Control key presses and use it to slice the
> interrupt report.
>
> Testing would be much appreciated. Note that UCC_DEBUG is intentionally
> enabled in the diff below. Please send me your dmesg after pressing at
> least one recognized key.
Updated diff fixing handling constant padding input.
Index: dev/usb/ucc.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/ucc.c,v
retrieving revision 1.13
diff -u -p -r1.13 ucc.c
--- dev/usb/ucc.c 26 Aug 2021 10:32:35 -0000 1.13
+++ dev/usb/ucc.c 27 Aug 2021 05:34:34 -0000
@@ -31,7 +31,7 @@
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>
-/* #define UCC_DEBUG */
+#define UCC_DEBUG
#ifdef UCC_DEBUG
#define DPRINTF(x...) do { if (ucc_debug) printf(x); } while (0)
void ucc_dump(const char *, u_char *, u_int);
@@ -58,6 +58,17 @@ struct ucc_softc {
int sc_isarray;
int sc_mode;
+ /*
+ * Slice of the interrupt buffer which represents a pressed key.
+ * See section 8 (Report Protocol) of the HID specification v1.11.
+ */
+ struct {
+ uint8_t *i_buf;
+ uint32_t i_bufsiz;
+ uint32_t i_off; /* offset in bits */
+ uint32_t i_len; /* length in bits */
+ } sc_input;
+
/* Last pressed key. */
union {
int sc_last_translate;
@@ -92,6 +103,7 @@ int ucc_add_key(struct ucc_softc *, int3
int ucc_bit_to_sym(struct ucc_softc *, u_int, const struct ucc_keysym **);
int ucc_usage_to_sym(int32_t, const struct ucc_keysym **);
int ucc_bits_to_usage(u_char *, u_int, int32_t *);
+int ucc_intr_slice(struct ucc_softc *, u_char *, u_char *, int *);
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 *);
@@ -194,6 +206,7 @@ ucc_detach(struct device *self, int flag
if (sc->sc_wskbddev != NULL)
error = config_detach(sc->sc_wskbddev, flags);
uhidev_close(&sc->sc_hdev);
+ free(sc->sc_input.i_buf, M_USBDEV, sc->sc_input.i_bufsiz);
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;
@@ -204,13 +217,30 @@ ucc_intr(struct uhidev *addr, void *data
{
struct ucc_softc *sc = (struct ucc_softc *)addr;
const struct ucc_keysym *us;
+ u_char *buf = sc->sc_input.i_buf;
int raw = sc->sc_mode == WSKBD_RAW;
+ int error;
u_int bit = 0;
u_char c = 0;
ucc_dump(__func__, data, len);
- if (ucc_setbits(data, len, &bit)) {
+ if (len > sc->sc_input.i_bufsiz) {
+ DPRINTF("%s: too much data: len %d, bufsiz %d\n", __func__,
+ len, sc->sc_input.i_bufsiz);
+ return;
+ }
+
+ error = ucc_intr_slice(sc, data, buf, &len);
+ if (error) {
+ DPRINTF("%s: slice failure: error %d\n", __func__, error);
+ return;
+ }
+
+ /* Dump the buffer again after slicing. */
+ ucc_dump(__func__, buf, len);
+
+ if (ucc_setbits(buf, len, &bit)) {
/* All zeroes, assume key up event. */
if (raw) {
if (sc->sc_last_raw != 0) {
@@ -227,7 +257,7 @@ ucc_intr(struct uhidev *addr, void *data
} else if (sc->sc_isarray) {
int32_t usage;
- if (ucc_bits_to_usage(data, len, &usage) ||
+ if (ucc_bits_to_usage(buf, len, &usage) ||
ucc_usage_to_sym(usage, &us))
goto unknown;
bit = us->us_usage;
@@ -338,10 +368,12 @@ ucc_ioctl(void *v, u_long cmd, caddr_t d
int
ucc_hid_parse(struct ucc_softc *sc, void *desc, int descsiz)
{
+ enum { OFFSET, LENGTH } istate = OFFSET;
struct hid_item hi;
struct hid_data *hd;
- int nsyms = nitems(ucc_keysyms);
int error = 0;
+ int nsyms = nitems(ucc_keysyms);
+ int repid = sc->sc_hdev.sc_report_id;
int isize;
/*
@@ -353,6 +385,10 @@ ucc_hid_parse(struct ucc_softc *sc, void
if (isize == 0)
return ENXIO;
+ /* Allocate buffer used to slice interrupt data. */
+ sc->sc_input.i_bufsiz = sc->sc_hdev.sc_isize;
+ sc->sc_input.i_buf = malloc(sc->sc_input.i_bufsiz, M_USBDEV, M_WAITOK);
+
/*
* Create mapping between each input bit and the corresponding usage,
* used in translating mode. Two entries are needed per bit in order
@@ -375,9 +411,28 @@ ucc_hid_parse(struct ucc_softc *sc, void
while (hid_get_item(hd, &hi)) {
u_int bit;
- if (hi.kind != hid_input ||
- HID_GET_USAGE_PAGE(hi.usage) != HUP_CONSUMER)
+ if (hi.report_ID != repid || hi.kind != hid_input)
+ continue;
+
+ if (HID_GET_USAGE_PAGE(hi.usage) != HUP_CONSUMER) {
+ uint32_t len = hi.loc.size * hi.loc.count;
+
+ switch (istate) {
+ case OFFSET:
+ sc->sc_input.i_off = hi.loc.pos + len;
+ break;
+ case LENGTH:
+ /* Constant padding. */
+ if (hi.flags & HIO_CONST)
+ sc->sc_input.i_len += len;
+ break;
+ }
continue;
+ }
+
+ /* Signal that the input offset is reached. */
+ istate = LENGTH;
+ sc->sc_input.i_len += hi.loc.size * hi.loc.count;
/*
* The usages could be expressed as an array instead of
@@ -395,6 +450,9 @@ ucc_hid_parse(struct ucc_softc *sc, void
}
hid_end_parse(hd);
+ DPRINTF("%s: input: off %d, len %d\n", __func__,
+ sc->sc_input.i_off, sc->sc_input.i_len);
+
return error;
}
@@ -482,25 +540,49 @@ int
ucc_bits_to_usage(u_char *buf, u_int buflen, int32_t *usage)
{
int32_t x = 0;
- int maxlen = sizeof(*usage);
int i;
- if (buflen == 0)
+ if (buflen == 0 || buflen > sizeof(*usage))
return 1;
- /*
- * XXX should only look at the bits given by the report size and report
- * count associated with the Consumer usage page.
- */
- if (buflen > maxlen)
- buflen = maxlen;
-
for (i = buflen - 1; i >= 0; i--) {
x |= buf[i];
if (i > 0)
x <<= 8;
}
*usage = x;
+ return 0;
+}
+
+int
+ucc_intr_slice(struct ucc_softc *sc, u_char *src, u_char *dst, int *len)
+{
+ int ilen = sc->sc_input.i_len;
+ int ioff = sc->sc_input.i_off;
+ int maxlen = *len;
+ int di, si;
+
+ if (maxlen == 0)
+ return 1;
+
+ memset(dst, 0, maxlen);
+ si = ioff;
+ di = 0;
+ for (; ilen > 0; ilen--) {
+ int db, sb;
+
+ sb = si / 8;
+ db = di / 8;
+ if (sb >= maxlen || db >= maxlen)
+ return 1;
+
+ if (src[sb] & (1 << (si % 8)))
+ dst[db] |= 1 << (di % 8);
+ si++;
+ di++;
+ }
+
+ *len = (sc->sc_input.i_len + 7) / 8;
return 0;
}