freebsd-skq/lib/libusbhid/parse.c
kaiw 28e4fb3b8e Merge improvements from kernel HID parser to the userland usbhid(3)
parser.  This merge does not change any API and should not break any
native or thirdparty applications.

Changes include:

* Merge multiple report ID support and other improvements from kernel
  HID parser.
* Ignore rid argument in hid_start_parser, parse all the report items since
  we now support multiple report ID.
* Skip report ID byte in hid_get_data() and set report ID byte in
  hid_set_data(), if report ID is non-zero.
* Reimplement hid_get_report_id: instead get report id from uhid device
  (which is always 0), try parsing the report descriptor and return the
  first report ID encountered.

Reviewed by:	hps
Silent on:	-usb mailing list
2010-03-27 08:00:16 +00:00

563 lines
12 KiB
C

/* $NetBSD: parse.c,v 1.11 2000/09/24 02:19:54 augustss Exp $ */
/*
* Copyright (c) 1999, 2001 Lennart Augustsson <augustss@netbsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include "usbhid.h"
#include "usbvar.h"
#define MAXUSAGE 100
#define MAXPUSH 4
#define MAXID 64
struct hid_pos_data {
int32_t rid;
uint32_t pos;
};
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];
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 */
};
/*------------------------------------------------------------------------*
* hid_clear_local
*------------------------------------------------------------------------*/
static void
hid_clear_local(hid_item_t *c)
{
c->usage = 0;
c->usage_minimum = 0;
c->usage_maximum = 0;
c->designator_index = 0;
c->designator_minimum = 0;
c->designator_maximum = 0;
c->string_index = 0;
c->string_minimum = 0;
c->string_maximum = 0;
c->set_delimiter = 0;
}
static void
hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID)
{
uint8_t i;
/* 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;
s->last_pos[i].pos = c->pos;
}
/* 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;
c->pos = s->last_pos[i].pos;
} else
c->pos = 0; /* Out of RID entries. */
}
/*------------------------------------------------------------------------*
* hid_start_parse
*------------------------------------------------------------------------*/
hid_data_t
hid_start_parse(report_desc_t d, int kindset, int id __unused)
{
struct hid_data *s;
s = malloc(sizeof *s);
memset(s, 0, sizeof *s);
s->start = s->p = d->data;
s->end = d->data + d->size;
s->kindset = kindset;
return (s);
}
/*------------------------------------------------------------------------*
* hid_end_parse
*------------------------------------------------------------------------*/
void
hid_end_parse(hid_data_t s)
{
if (s == NULL)
return;
free(s);
}
/*------------------------------------------------------------------------*
* get byte from HID descriptor
*------------------------------------------------------------------------*/
static uint8_t
hid_get_byte(struct hid_data *s, const uint16_t wSize)
{
const uint8_t *ptr;
uint8_t retval;
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;
/* update pointer */
s->p = ptr;
return (retval);
}
/*------------------------------------------------------------------------*
* hid_get_item
*------------------------------------------------------------------------*/
int
hid_get_item(hid_data_t s, hid_item_t *h)
{
hid_item_t *c;
unsigned int bTag, bType, bSize;
uint32_t oldpos;
int32_t mask;
int32_t dval;
if (s == NULL)
return (0);
c = &s->cur[s->pushlevel];
top:
/* 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;
}
s->icount ++;
/*
* Only copy HID item, increment position and return
* if correct kindset!
*/
if (s->kindset & (1 << c->kind)) {
*h = *c;
c->pos += c->report_size * c->report_count;
return (1);
}
}
/* 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 = 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;
}
switch(bSize) {
case 0:
dval = 0;
mask = 0;
break;
case 1:
dval = (int8_t)hid_get_byte(s, 1);
mask = 0xFF;
break;
case 2:
dval = hid_get_byte(s, 1);
dval |= hid_get_byte(s, 1) << 8;
dval = (int16_t)dval;
mask = 0xFFFF;
break;
case 4:
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:
dval = hid_get_byte(s, bSize);
continue;
}
switch (bType) {
case 0: /* Main */
switch (bTag) {
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) {
/* 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;
} else {
s->ncount = 1;
}
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++;
c->usage = s->usage_last;
*h = *c;
return (1);
case 11: /* Feature */
c->kind = hid_feature;
c->flags = dval;
goto ret;
case 12: /* End collection */
c->kind = hid_endcollection;
if (c->collevel == 0) {
/* Invalid end collection. */
return (0);
}
c->collevel--;
*h = *c;
return (1);
default:
break;
}
break;
case 1: /* Global */
switch (bTag) {
case 0:
c->_usage_page = dval << 16;
break;
case 1:
c->logical_minimum = dval;
break;
case 2:
c->logical_maximum = dval;
break;
case 3:
c->physical_minimum = dval;
break;
case 4:
c->physical_maximum = dval;
break;
case 5:
c->unit_exponent = dval;
break;
case 6:
c->unit = dval;
break;
case 7:
/* mask because value is unsigned */
s->loc_size = dval & mask;
break;
case 8:
hid_switch_rid(s, c, dval);
break;
case 9:
/* mask because value is unsigned */
s->loc_count = dval & mask;
break;
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 */
s->pushlevel --;
if (s->pushlevel < MAXPUSH) {
/* preserve position */
oldpos = c->pos;
c = &s->cur[s->pushlevel];
/* restore size and count */
s->loc_size = c->report_size;
s->loc_count = c->report_count;
/* set default item location */
c->pos = oldpos;
c->report_size = 0;
c->report_count = 0;
}
break;
default:
break;
}
break;
case 2: /* Local */
switch (bTag) {
case 0:
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->susage |= 1;
if (bSize != 4)
dval = (dval & mask) | c->_usage_page;
c->usage_minimum = dval;
goto check_set;
case 2:
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;
break;
case 4:
c->designator_minimum = dval;
break;
case 5:
c->designator_maximum = dval;
break;
case 7:
c->string_index = dval;
break;
case 8:
c->string_minimum = dval;
break;
case 9:
c->string_maximum = dval;
break;
case 10:
c->set_delimiter = dval;
break;
default:
break;
}
break;
default:
break;
}
}
return (0);
}
int
hid_report_size(report_desc_t r, enum hid_kind k, int id)
{
struct hid_data *d;
struct hid_item h;
uint32_t temp;
uint32_t hpos;
uint32_t lpos;
hpos = 0;
lpos = 0xFFFFFFFF;
memset(&h, 0, sizeof h);
for (d = hid_start_parse(r, 1 << k, id); hid_get_item(d, &h); ) {
if (h.report_ID == id && h.kind == k) {
/* compute minimum */
if (lpos > h.pos)
lpos = h.pos;
/* compute end position */
temp = h.pos + (h.report_size * h.report_count);
/* compute maximum */
if (hpos < temp)
hpos = temp;
}
}
hid_end_parse(d);
/* safety check - can happen in case of currupt descriptors */
if (lpos > hpos)
temp = 0;
else
temp = hpos - lpos;
if (id)
temp += 8;
/* return length in bytes rounded up */
return ((temp + 7) / 8);
}
int
hid_locate(report_desc_t desc, unsigned int u, enum hid_kind k,
hid_item_t *h, int id)
{
struct hid_data *d;
for (d = hid_start_parse(desc, 1 << k, id); hid_get_item(d, h); ) {
if (h->kind == k && !(h->flags & HIO_CONST) && h->usage == u) {
hid_end_parse(d);
return (1);
}
}
hid_end_parse(d);
h->report_size = 0;
return (0);
}