From b3eedbb43cf389bcbf1399bd986672333cb880ec Mon Sep 17 00:00:00 2001 From: mpi Date: Mon, 12 May 2014 17:03:28 +0000 Subject: [PATCH] Keep libusbhid's HID parser in sync with the kernel one. This is mostly a backport from FreeBSD's r205728, r224511 and 225839 without the new functions to set/get a report ID nor the usbhidctl(1) & usbhidaction(1) bits. Committing now to open the "Monday's Bump Parade". --- lib/libusbhid/data.c | 62 +++-- lib/libusbhid/parse.c | 496 ++++++++++++++++++++++-------------- lib/libusbhid/shlib_version | 2 +- lib/libusbhid/usbhid.3 | 9 +- lib/libusbhid/usbhid.h | 64 +++-- lib/libusbhid/usbvar.h | 7 +- 6 files changed, 387 insertions(+), 253 deletions(-) diff --git a/lib/libusbhid/data.c b/lib/libusbhid/data.c index 4f35e68227a..38f5ea6fb50 100644 --- a/lib/libusbhid/data.c +++ b/lib/libusbhid/data.c @@ -1,4 +1,4 @@ -/* $OpenBSD: data.c,v 1.6 2012/07/16 19:57:17 jasper Exp $ */ +/* $OpenBSD: data.c,v 1.7 2014/05/12 17:03:28 mpi Exp $ */ /* $NetBSD: data.c,v 1.1 2001/12/28 17:45:26 augustss Exp $ */ /* @@ -30,36 +30,68 @@ #include #include "usbhid.h" -int +int32_t hid_get_data(const void *p, const hid_item_t *h) { - const unsigned char *buf = p; - unsigned int hpos = h->pos, hsize = h->report_size; - int data, i, end, offs; + const uint8_t *buf; + uint32_t hpos; + uint32_t hsize; + uint32_t data; + int i, end, offs; + buf = p; + + /* Skip report ID byte. */ + if (h->report_ID > 0) + buf++; + + hpos = h->pos; /* bit position of data */ + hsize = h->report_size; /* bit length of data */ + + /* Range check and limit */ if (hsize == 0) return (0); + if (hsize > 32) + hsize = 32; + offs = hpos / 8; end = (hpos + hsize) / 8 - offs; data = 0; for (i = 0; i <= end; i++) data |= buf[offs + i] << (i*8); + + /* Correctly shift down data */ data >>= hpos % 8; - data &= (1 << hsize) - 1; - if (h->logical_minimum < 0) { - /* Need to sign extend */ - hsize = sizeof data * 8 - hsize; - data = (data << hsize) >> hsize; - } + hsize = 32 - hsize; + + /* Mask and sign extend in one */ + if ((h->logical_minimum < 0) || (h->logical_maximum < 0)) + data = (int32_t)((int32_t)data << hsize) >> hsize; + else + data = (uint32_t)((uint32_t)data << hsize) >> hsize; + return (data); } void -hid_set_data(void *p, const hid_item_t *h, int data) +hid_set_data(void *p, const hid_item_t *h, int32_t data) { - unsigned char *buf = p; - unsigned int hpos = h->pos, hsize = h->report_size; - int i, end, offs, mask; + uint8_t *buf; + uint32_t hpos; + uint32_t hsize; + uint32_t mask; + int i; + int end; + int offs; + + buf = p; + + /* Set report ID byte. */ + if (h->report_ID > 0) + *buf++ = h->report_ID & 0xff; + + hpos = h->pos; /* bit position of data */ + hsize = h->report_size; /* bit length of data */ if (hsize != 32) { mask = (1 << hsize) - 1; diff --git a/lib/libusbhid/parse.c b/lib/libusbhid/parse.c index 0a0bfe7a84d..a0d3baa0814 100644 --- a/lib/libusbhid/parse.c +++ b/lib/libusbhid/parse.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.c,v 1.8 2014/05/07 01:14:21 tedu Exp $ */ +/* $OpenBSD: parse.c,v 1.9 2014/05/12 17:03:28 mpi Exp $ */ /* $NetBSD: parse.c,v 1.2 2001/12/29 20:44:22 augustss Exp $ */ /* @@ -29,7 +29,6 @@ #include #include -#include #include #include @@ -37,38 +36,38 @@ #include "usbhid.h" #include "usbvar.h" -#define MAXUSAGE 100 -struct hid_data { - u_char *start; - u_char *end; - u_char *p; - hid_item_t cur; - unsigned int usages[MAXUSAGE]; - int nusage; - int minset; - int logminsize; - int multi; - int multimax; - int kindset; - int reportid; - - /* - * The start of collection item has no report ID set, so save - * it until we know the ID. - */ - hid_item_t savedcoll; - u_char hassavedcoll; - /* - * Absolute data position (bits) for input/output/feature. - * Assumes that hid_input, hid_output and hid_feature have - * values 0, 1 and 2. - */ - unsigned int kindpos[3]; -}; +#define MAXUSAGE 100 +#define MAXPUSH 4 +#define MAXID 64 +#define ITEMTYPES 3 -static int min(int x, int y) { return x < y ? x : y; } +struct hid_pos_data { + int32_t rid; + uint32_t pos[ITEMTYPES]; +}; -static int hid_get_item_raw(hid_data_t s, hid_item_t *h); +struct hid_data { + const uint8_t *start; + const uint8_t *end; + const uint8_t *p; + struct hid_item cur[MAXPUSH]; + struct hid_pos_data last_pos[MAXID]; + uint32_t pos[ITEMTYPES]; + int32_t usages_min[MAXUSAGE]; + int32_t usages_max[MAXUSAGE]; + int32_t usage_last; /* last seen usage */ + uint32_t loc_size; /* last seen size */ + uint32_t loc_count; /* last seen count */ + uint8_t kindset; /* we have 5 kinds so 8 bits are enough */ + uint8_t pushlevel; /* current pushlevel */ + uint8_t ncount; /* end usage item count */ + uint8_t icount; /* current usage item count */ + uint8_t nusage; /* end "usages_min/max" index */ + uint8_t iusage; /* current "usages_min/max" index */ + uint8_t ousage; /* current "usages_min/max" offset */ + uint8_t susage; /* usage set flags */ + int32_t reportid; /* requested report ID */ +}; static void hid_clear_local(hid_item_t *c) @@ -86,6 +85,60 @@ hid_clear_local(hid_item_t *c) c->set_delimiter = 0; } +static void +hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID) +{ + uint8_t i, j; + + /* check for same report ID - optimise */ + + if (c->report_ID == next_rID) + return; + + /* save current position for current rID */ + + if (c->report_ID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == c->report_ID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = c->report_ID; + for (j = 0; j < ITEMTYPES; j++) + s->last_pos[i].pos[j] = s->pos[j]; + } + + /* store next report ID */ + + c->report_ID = next_rID; + + /* lookup last position for next rID */ + + if (next_rID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == next_rID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = next_rID; + for (j = 0; j < ITEMTYPES; j++) + s->pos[j] = s->last_pos[i].pos[j]; + } else { + for (j = 0; j < ITEMTYPES; j++) + s->pos[j] = 0; /* Out of RID entries. */ + } +} + hid_data_t hid_start_parse(report_desc_t d, int kindset, int id) { @@ -98,7 +151,6 @@ hid_start_parse(report_desc_t d, int kindset, int id) s->end = d->data + d->size; s->kindset = kindset; s->reportid = id; - s->hassavedcoll = 0; return (s); } @@ -106,200 +158,193 @@ void hid_end_parse(hid_data_t s) { - while (s->cur.next) { - hid_item_t *hi = s->cur.next->next; - free(s->cur.next); - s->cur.next = hi; - } + if (s == NULL) + return; + free(s); } -int -hid_get_item(hid_data_t s, hid_item_t *h) +static uint8_t +hid_get_byte(struct hid_data *s, const uint16_t wSize) { - int r; + const uint8_t *ptr; + uint8_t retval; - for (;;) { - r = hid_get_item_raw(s, h); - if (r <= 0) - break; - if (h->report_ID == s->reportid || s->reportid == -1) - break; - } - return (r); -} + ptr = s->p; + + /* check if end is reached */ + if (ptr == s->end) + return (0); + + /* read out a byte */ + retval = *ptr; + + /* check if data pointer can be advanced by "wSize" bytes */ + if ((s->end - ptr) < wSize) + ptr = s->end; + else + ptr += wSize; -#define REPORT_SAVED_COLL \ - do { \ - if (s->hassavedcoll) { \ - *h = s->savedcoll; \ - h->report_ID = c->report_ID; \ - s->hassavedcoll = 0; \ - return (1); \ - } \ - } while(/*LINTED*/ 0) + /* update pointer */ + s->p = ptr; + + return (retval); +} static int hid_get_item_raw(hid_data_t s, hid_item_t *h) { - hid_item_t *c = &s->cur, *hi, nc; - unsigned int bTag = 0, bType = 0, bSize; - unsigned char *data; - hid_kind_t retkind; - unsigned char *p; - int dval, i; + hid_item_t *c; + unsigned int bTag, bType, bSize; + int32_t mask; + int32_t dval; + + if (s == NULL) + return (0); + + c = &s->cur[s->pushlevel]; top: - if (s->multimax) { - REPORT_SAVED_COLL; - if (c->logical_minimum >= c->logical_maximum) { - if (s->logminsize == 1) - c->logical_minimum =(int8_t)c->logical_minimum; - else if (s->logminsize == 2) - c->logical_minimum =(int16_t)c->logical_minimum; + /* check if there is an array of items */ + if (s->icount < s->ncount) { + /* get current usage */ + if (s->iusage < s->nusage) { + dval = s->usages_min[s->iusage] + s->ousage; + c->usage = dval; + s->usage_last = dval; + if (dval == s->usages_max[s->iusage]) { + s->iusage ++; + s->ousage = 0; + } else { + s->ousage ++; + } + } else { + /* Using last usage */ + dval = s->usage_last; } - if (s->multi < s->multimax) { - c->usage = s->usages[min(s->multi, s->nusage-1)]; - s->multi++; + s->icount ++; + /* + * Only copy HID item, increment position and return + * if correct kindset! + */ + if (s->kindset & (1 << c->kind)) { *h = *c; - /* - * 'multimax' is only non-zero if the current - * item kind is input/output/feature - */ - h->pos = s->kindpos[c->kind]; - s->kindpos[c->kind] += c->report_size; - h->next = 0; + h->pos = s->pos[c->kind]; + s->pos[c->kind] += c->report_size * c->report_count; return (1); - } else { - c->report_count = s->multimax; - s->multimax = 0; - s->nusage = 0; - hid_clear_local(c); } } - for (;;) { - p = s->p; - if (p >= s->end) - return (0); - bSize = *p++; + /* reset state variables */ + s->icount = 0; + s->ncount = 0; + s->iusage = 0; + s->nusage = 0; + s->susage = 0; + s->ousage = 0; + hid_clear_local(c); + + /* get next item */ + while (s->p != s->end) { + + bSize = hid_get_byte(s, 1); if (bSize == 0xfe) { /* long item */ - bSize = *p++; - bSize |= *p++ << 8; - bTag = *p++; - data = p; - p += bSize; + bSize = hid_get_byte(s, 1); + bSize |= hid_get_byte(s, 1) << 8; + bTag = hid_get_byte(s, 1); + bType = 0xff; /* XXX what should it be */ } else { /* short item */ bTag = bSize >> 4; bType = (bSize >> 2) & 3; bSize &= 3; - if (bSize == 3) bSize = 4; - data = p; - p += bSize; + if (bSize == 3) + bSize = 4; } - s->p = p; - /* - * The spec is unclear if the data is signed or unsigned. - */ + switch(bSize) { case 0: dval = 0; + mask = 0; break; case 1: - dval = /*(int8_t)*/*data++; + dval = (int8_t)hid_get_byte(s, 1); + mask = 0xFF; break; case 2: - dval = *data++; - dval |= *data++ << 8; - dval = /*(int16_t)*/dval; + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval = (int16_t)dval; + mask = 0xFFFF; break; case 4: - dval = *data++; - dval |= *data++ << 8; - dval |= *data++ << 16; - dval |= *data++ << 24; + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval |= hid_get_byte(s, 1) << 16; + dval |= hid_get_byte(s, 1) << 24; + mask = 0xFFFFFFFF; break; default: - return (-1); + dval = hid_get_byte(s, bSize); + continue; } switch (bType) { - case 0: /* Main */ + case 0: /* Main */ switch (bTag) { - case 8: /* Input */ - retkind = hid_input; - ret: - if (!(s->kindset & (1 << retkind))) { - /* Drop the items of this kind */ - s->nusage = 0; - continue; - } - c->kind = retkind; + case 8: /* Input */ + c->kind = hid_input; c->flags = dval; + ret: + c->report_count = s->loc_count; + c->report_size = s->loc_size; + if (c->flags & HIO_VARIABLE) { - s->multimax = c->report_count; - s->multi = 0; + /* range check usage count */ + if (c->report_count > 255) { + s->ncount = 255; + } else + s->ncount = c->report_count; + + /* + * The "top" loop will return + * one and one item: + */ c->report_count = 1; - if (s->minset) { - for (i = c->usage_minimum; - i <= c->usage_maximum; i++) { - s->usages[s->nusage] = i; - if (s->nusage < MAXUSAGE-1) - s->nusage++; - } - c->usage_minimum = 0; - c->usage_maximum = 0; - s->minset = 0; - } - goto top; + c->usage_minimum = 0; + c->usage_maximum = 0; } else { - if (s->minset) - c->usage = c->usage_minimum; - *h = *c; - h->next = 0; - h->pos = s->kindpos[c->kind]; - s->kindpos[c->kind] += - c->report_size * c->report_count; - hid_clear_local(c); - s->minset = 0; - return (1); + s->ncount = 1; } - case 9: /* Output */ - retkind = hid_output; + goto top; + + case 9: /* Output */ + c->kind = hid_output; + c->flags = dval; goto ret; case 10: /* Collection */ c->kind = hid_collection; c->collection = dval; c->collevel++; - nc = *c; - hid_clear_local(c); - /*c->report_ID = NO_REPORT_ID;*/ - s->nusage = 0; - if (s->hassavedcoll) { - *h = s->savedcoll; - h->report_ID = nc.report_ID; - s->savedcoll = nc; - return (1); - } else { - s->hassavedcoll = 1; - s->savedcoll = nc; - } - break; + c->usage = s->usage_last; + *h = *c; + return (1); case 11: /* Feature */ - retkind = hid_feature; + c->kind = hid_feature; + c->flags = dval; goto ret; case 12: /* End collection */ - REPORT_SAVED_COLL; c->kind = hid_endcollection; + if (c->collevel == 0) { + /* Invalid end collection. */ + return (0); + } c->collevel--; *h = *c; - /*hid_clear_local(c);*/ - s->nusage = 0; return (1); default: - return (-2); + break; } break; @@ -310,7 +355,6 @@ hid_get_item_raw(hid_data_t s, hid_item_t *h) break; case 1: c->logical_minimum = dval; - s->logminsize = bSize; break; case 2: c->logical_maximum = dval; @@ -328,46 +372,93 @@ hid_get_item_raw(hid_data_t s, hid_item_t *h) c->unit = dval; break; case 7: - c->report_size = dval; + /* mask because value is unsigned */ + s->loc_size = dval & mask; break; case 8: - c->report_ID = dval; - s->kindpos[hid_input] = 0; - s->kindpos[hid_output] = 0; - s->kindpos[hid_feature] = 0; + hid_switch_rid(s, c, dval & mask); break; case 9: - c->report_count = dval; + /* mask because value is unsigned */ + s->loc_count = dval & mask; break; - case 10: /* Push */ - hi = malloc(sizeof *hi); - /* XXX unchecked malloc */ - *hi = s->cur; - c->next = hi; + case 10: /* Push */ + s->pushlevel ++; + if (s->pushlevel < MAXPUSH) { + s->cur[s->pushlevel] = *c; + /* store size and count */ + c->report_size = s->loc_size; + c->report_count = s->loc_count; + /* update current item pointer */ + c = &s->cur[s->pushlevel]; + } break; - case 11: /* Pop */ - hi = c->next; - s->cur = *hi; - free(hi); + case 11: /* Pop */ + s->pushlevel --; + if (s->pushlevel < MAXPUSH) { + c = &s->cur[s->pushlevel]; + /* restore size and count */ + s->loc_size = c->report_size; + s->loc_count = c->report_count; + c->report_size = 0; + c->report_count = 0; + } break; default: - return (-3); + break; } break; case 2: /* Local */ switch (bTag) { case 0: - c->usage = c->_usage_page | dval; - if (s->nusage < MAXUSAGE) - s->usages[s->nusage++] = c->usage; + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + + /* set last usage, in case of a collection */ + s->usage_last = dval; + + if (s->nusage < MAXUSAGE) { + s->usages_min[s->nusage] = dval; + s->usages_max[s->nusage] = dval; + s->nusage ++; + } /* else XXX */ + + /* clear any pending usage sets */ + s->susage = 0; break; case 1: - s->minset = 1; - c->usage_minimum = c->_usage_page | dval; - break; + s->susage |= 1; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_minimum = dval; + + goto check_set; case 2: - c->usage_maximum = c->_usage_page | dval; + s->susage |= 2; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_maximum = dval; + + check_set: + if (s->susage != 3) + break; + + /* sanity check */ + if ((s->nusage < MAXUSAGE) && + (c->usage_minimum <= c->usage_maximum)) { + /* add usage range */ + s->usages_min[s->nusage] = + c->usage_minimum; + s->usages_max[s->nusage] = + c->usage_maximum; + s->nusage ++; + } + /* else XXX */ + + s->susage = 0; break; case 3: c->designator_index = dval; @@ -401,6 +492,19 @@ hid_get_item_raw(hid_data_t s, hid_item_t *h) return (0); } +int +hid_get_item(hid_data_t s, hid_item_t *h) +{ + int r; + + for (;;) { + r = hid_get_item_raw(s, h); + if (r <= 0 || s->reportid == -1 || h->report_ID == s->reportid) + break; + } + return (r); +} + int hid_report_size(report_desc_t r, enum hid_kind k, int id) { @@ -412,11 +516,11 @@ hid_report_size(report_desc_t r, enum hid_kind k, int id) int report_id = 0; hpos = 0; - lpos = 0xffffffff; + lpos = 0xFFFFFFFF; memset(&h, 0, sizeof h); - for (d = hid_start_parse(r, 1< h.pos) lpos = h.pos; @@ -438,7 +542,7 @@ hid_report_size(report_desc_t r, enum hid_kind k, int id) temp = hpos - lpos; /* return length in bytes rounded up */ - return ((temp + 7) / 8); + return ((temp + 7) / 8 + report_id); } int diff --git a/lib/libusbhid/shlib_version b/lib/libusbhid/shlib_version index 9c1551636c5..5b844bbf422 100644 --- a/lib/libusbhid/shlib_version +++ b/lib/libusbhid/shlib_version @@ -1,2 +1,2 @@ -major=6 +major=7 minor=0 diff --git a/lib/libusbhid/usbhid.3 b/lib/libusbhid/usbhid.3 index 3c0e6435392..271d296229d 100644 --- a/lib/libusbhid/usbhid.3 +++ b/lib/libusbhid/usbhid.3 @@ -1,4 +1,4 @@ -.\" $OpenBSD: usbhid.3,v 1.15 2014/01/21 03:15:45 schwarze Exp $ +.\" $OpenBSD: usbhid.3,v 1.16 2014/05/12 17:03:28 mpi Exp $ .\" $NetBSD: usbhid.3,v 1.5 2002/02/07 07:00:52 ross Exp $ .\" .\" Copyright (c) 1999, 2001 Lennart Augustsson @@ -25,7 +25,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd $Mdocdate: January 21 2014 $ +.Dd $Mdocdate: May 12 2014 $ .Dt USBHID 3 .Os .Sh NAME @@ -127,7 +127,10 @@ where .Fa k is an item of type .Fa hid_kind_t . -The report ID (if present) is given by +The desired report ID, or +.Dv -1 +to obtain items of all report IDs, +is given by .Fa id . The function returns .Fa NULL diff --git a/lib/libusbhid/usbhid.h b/lib/libusbhid/usbhid.h index dd0c98b73d6..ca70def6440 100644 --- a/lib/libusbhid/usbhid.h +++ b/lib/libusbhid/usbhid.h @@ -1,4 +1,4 @@ -/* $OpenBSD: usbhid.h,v 1.5 2012/07/16 19:57:17 jasper Exp $ */ +/* $OpenBSD: usbhid.h,v 1.6 2014/05/12 17:03:28 mpi Exp $ */ /* $NetBSD: usbhid.h,v 1.1 2001/12/28 17:45:27 augustss Exp $ */ /* @@ -32,45 +32,41 @@ typedef struct report_desc *report_desc_t; typedef struct hid_data *hid_data_t; typedef enum hid_kind { - hid_input = 0, - hid_output = 1, - hid_feature = 2, - hid_collection, - hid_endcollection + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection } hid_kind_t; typedef struct hid_item { /* Global */ - int _usage_page; - int logical_minimum; - int logical_maximum; - int physical_minimum; - int physical_maximum; - int unit_exponent; - int unit; - int report_size; - int report_ID; + uint32_t _usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + int32_t unit; + int32_t report_size; + int32_t report_ID; #define NO_REPORT_ID 0 - int report_count; + int32_t report_count; /* Local */ - unsigned int usage; - int usage_minimum; - int usage_maximum; - int designator_index; - int designator_minimum; - int designator_maximum; - int string_index; - int string_minimum; - int string_maximum; - int set_delimiter; + uint32_t usage; + int32_t usage_minimum; + int32_t usage_maximum; + int32_t designator_index; + int32_t designator_minimum; + int32_t designator_maximum; + int32_t string_index; + int32_t string_minimum; + int32_t string_maximum; + int32_t set_delimiter; /* Misc */ - int collection; - int collevel; + int32_t collection; + int collevel; enum hid_kind kind; - unsigned int flags; - /* Absolute data position (bits) */ - unsigned int pos; - /* */ + uint32_t flags; + /* Location */ + uint32_t pos; + /* unused */ struct hid_item *next; } hid_item_t; @@ -99,5 +95,5 @@ int hid_parse_usage_in_page(const char *name); int hid_parse_usage_page(const char *name); /* Extracting/insertion of data, data.c: */ -int hid_get_data(const void *p, const hid_item_t *h); -void hid_set_data(void *p, const hid_item_t *h, int data); +int32_t hid_get_data(const void *p, const hid_item_t *h); +void hid_set_data(void *p, const hid_item_t *h, int32_t data); diff --git a/lib/libusbhid/usbvar.h b/lib/libusbhid/usbvar.h index 224423ea99f..31602ad1930 100644 --- a/lib/libusbhid/usbvar.h +++ b/lib/libusbhid/usbvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: usbvar.h,v 1.2 2002/05/10 00:09:17 nate Exp $ */ +/* $OpenBSD: usbvar.h,v 1.3 2014/05/12 17:03:28 mpi Exp $ */ /* $NetBSD: usbvar.h,v 1.1 2001/12/28 17:45:27 augustss Exp $ */ /* @@ -28,7 +28,6 @@ */ struct report_desc { - unsigned int size; - unsigned char data[1]; + uint32_t size; + uint8_t data[1]; }; - -- 2.20.1