7778ab7e0c
Import the rest of HID improvements from the branch: - improve report descriptor parser in libusbhid to handle several kinds of reports same time; - add to the libusbhid API two functions wrapping respective kernel IOCTLs for reading and writing reports; - tune uhid IOCTL interface to allow reading and writing arbitrary report, when multiple supported by the device; - teach usbhidctl to set output and feature reports; - make usbhidaction support all the same item names as bhidctl. Sponsored by: iXsystems, inc.
567 lines
12 KiB
C
567 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
|
|
#define ITEMTYPES 3
|
|
|
|
struct hid_pos_data {
|
|
int32_t rid;
|
|
uint32_t pos[ITEMTYPES];
|
|
};
|
|
|
|
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 */
|
|
};
|
|
|
|
/*------------------------------------------------------------------------*
|
|
* 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, 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_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;
|
|
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;
|
|
h->pos = s->pos[c->kind];
|
|
s->pos[c->kind] += 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;
|
|
c->usage_minimum = 0;
|
|
c->usage_maximum = 0;
|
|
} 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) {
|
|
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:
|
|
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;
|
|
int report_id = 0;
|
|
|
|
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 || id < 0) && 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;
|
|
if (h.report_ID != 0)
|
|
report_id = 1;
|
|
}
|
|
}
|
|
hid_end_parse(d);
|
|
|
|
/* safety check - can happen in case of currupt descriptors */
|
|
if (lpos > hpos)
|
|
temp = 0;
|
|
else
|
|
temp = hpos - lpos;
|
|
|
|
/* return length in bytes rounded up */
|
|
return ((temp + 7) / 8 + report_id);
|
|
}
|
|
|
|
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);
|
|
}
|