freebsd-dev/lib/libusbhid/parse.c
Pedro F. Giffuni 5e53a4f90f lib: further adoption of SPDX licensing ID tags.
Mainly focus on files that use BSD 2-Clause license, however the tool I
was using mis-identified many licenses so this was mostly a manual - error
prone - task.

The Software Package Data Exchange (SPDX) group provides a specification
to make it easier for automated tools to detect and summarize well known
opensource licenses. We are gradually adopting the specification, noting
that the tags are considered only advisory and do not, in any way,
superceed or replace the license texts.
2017-11-26 02:00:33 +00:00

584 lines
13 KiB
C

/* $NetBSD: parse.c,v 1.11 2000/09/24 02:19:54 augustss Exp $ */
/*-
* SPDX-License-Identifier: BSD-2-Clause-NetBSD
*
* 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 */
int32_t reportid; /* requested report ID */
};
/*------------------------------------------------------------------------*
* 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)
{
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;
s->reportid = id;
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
*------------------------------------------------------------------------*/
static int
hid_get_item_raw(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 & mask);
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_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)
{
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.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);
}