incubator-nuttx/drivers/usbhost/hid_parser.c

528 lines
16 KiB
C

/****************************************************************************
* drivers/usbhost/hid_parser.c
*
* Copyright (C) 2011 Gregory Nutt. All rights reserved.
*
* Adapted from the LUFA Library:
*
* Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
* dean [at] fourwalledcubicle [dot] com, www.lufa-lib.org
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/usb/hid.h>
#include <nuttx/usb/hid_parser.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
struct hid_state_s
{
struct hid_rptitem_attributes_s attrib;
uint8_t rptcount;
uint8_t id;
};
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: hid_parsereport
*
* Description:
* Function to process a given HID report returned from an attached device,
* and store it into a given struct hid_rptinfo_s structure.
*
* Input Parameters:
* report Buffer containing the device's HID report table.
* rptlen Size in bytes of the HID report table.
* filter Callback function to decide if an item should be retained
* rptinfo Pointer to a struct hid_rptinfo_s instance for the parser output.
*
* Returned Value:
* Zero on success, otherwise a negated errno value.
****************************************************************************/
int hid_parsereport(FAR const uint8_t *report, int rptlen,
hid_rptfilter_t filter, FAR struct hid_rptinfo_s *rptinfo)
{
struct hid_state_s state[CONFIG_HID_STATEDEPTH];
struct hid_state_s *currstate = &state[0];
struct hid_collectionpath_s *collectionpath = NULL;
struct hid_rptsizeinfo_s *rptidinfo = &rptinfo->rptsize[0];
uint16_t usage[CONFIG_HID_USAGEDEPTH];
uint8_t nusage = 0;
struct hid_range_s usage_range = { 0, 0 };
int i;
DEBUGASSERT(report && filter && rptinfo);
memset(rptinfo, 0x00, sizeof(struct hid_rptinfo_s));
memset(currstate, 0x00, sizeof(struct hid_state_s));
memset(rptidinfo, 0x00, sizeof(struct hid_rptsizeinfo_s));
rptinfo->nreports = 1;
while (rptlen > 0)
{
uint8_t item = *report;
uint32_t data = 0;
report++;
rptlen--;
switch (item & USBHID_RPTITEM_SIZE_MASK)
{
case USBHID_RPTITEM_SIZE_4: /* 4 bytes of little endian data follow */
data = (uint32_t)(*report++);
data |= (uint32_t)(*report++) << 8;
data |= (uint32_t)(*report++) << 16;
data |= (uint32_t)(*report++) << 24;
rptlen -= 4;
break;
case USBHID_RPTITEM_SIZE_2: /* 2 bytes of little endian data follow */
data = (uint32_t)(*report++);
data |= (uint32_t)(*report++) << 8;
rptlen -= 2;
break;
case USBHID_RPTITEM_SIZE_1: /* 1 byte of data follows */
data = (uint32_t)(*report++);
rptlen -= 1;
break;
case USBHID_RPTITEM_SIZE_0: /* No data follows */
default:
break;
}
switch (item & ~USBHID_RPTITEM_SIZE_MASK)
{
case USBHID_GLOBAL_PUSH_PREFIX:
if (currstate == &state[CONFIG_HID_STATEDEPTH - 1])
{
return -E2BIG;
}
memcpy((currstate + 1), currstate, sizeof(struct hid_state_s));
currstate++;
break;
case USBHID_GLOBAL_POP_PREFIX:
if (currstate == &state[0])
{
return -EINVAL; /* Pop without push? */
}
currstate--;
break;
case USBHID_GLOBAL_USAGEPAGE_PREFIX:
if ((item & USBHID_RPTITEM_SIZE_MASK) == USBHID_RPTITEM_SIZE_4)
{
currstate->attrib.usage.page = (data >> 16);
}
currstate->attrib.usage.page = data;
break;
case USBHID_GLOBAL_LOGICALMIN_PREFIX:
currstate->attrib.logical.min = data;
break;
case USBHID_GLOBAL_LOGICALMAX_PREFIX:
currstate->attrib.logical.max = data;
break;
case USBHID_GLOBAL_PHYSICALMIN_PREFIX:
currstate->attrib.physical.min = data;
break;
case USBHID_GLOBAL_PHYSMICALAX_PREFIX:
currstate->attrib.physical.max = data;
break;
case USBHID_GLOBAL_UNITEXP_PREFIX:
currstate->attrib.unit.exponent = data;
break;
case USBHID_GLOBAL_UNIT_PREFIX:
currstate->attrib.unit.type = data;
break;
case USBHID_GLOBAL_REPORTSIZE_PREFIX:
currstate->attrib.bitsize = data;
break;
case USBHID_GLOBAL_REPORTCOUNT_PREFIX:
currstate->rptcount = data;
break;
case USBHID_GLOBAL_REPORTID_PREFIX:
currstate->id = data;
if (rptinfo->haverptid)
{
rptidinfo = NULL;
for (i = 0; i < rptinfo->nreports; i++)
{
if (rptinfo->rptsize[i].id == currstate->id)
{
rptidinfo = &rptinfo->rptsize[i];
break;
}
}
if (rptidinfo == NULL)
{
if (rptinfo->nreports == CONFIG_HID_MAXIDS)
{
return -EINVAL;
}
rptidinfo = &rptinfo->rptsize[rptinfo->nreports++];
memset(rptidinfo, 0x00, sizeof(struct hid_rptsizeinfo_s));
}
}
rptinfo->haverptid = true;
rptidinfo->id = currstate->id;
break;
case USBHID_LOCAL_USAGE_PREFIX:
if (nusage == CONFIG_HID_USAGEDEPTH)
{
return -E2BIG;
}
usage[nusage++] = data;
break;
case USBHID_LOCAL_USAGEMIN_PREFIX:
usage_range.min = data;
break;
case USBHID_LOCAL_USAGEMAX_PREFIX:
usage_range.max = data;
break;
case USBHID_MAIN_COLLECTION_PREFIX:
if (collectionpath == NULL)
{
collectionpath = &rptinfo->collectionpaths[0];
}
else
{
struct hid_collectionpath_s *ParentCollectionPath = collectionpath;
collectionpath = &rptinfo->collectionpaths[1];
while (collectionpath->parent != NULL)
{
if (collectionpath == &rptinfo->collectionpaths[CONFIG_HID_MAXCOLLECTIONS - 1])
{
return -EINVAL;
}
collectionpath++;
}
collectionpath->parent = ParentCollectionPath;
}
collectionpath->type = data;
collectionpath->usage.page = currstate->attrib.usage.page;
if (nusage)
{
collectionpath->usage.usage = usage[0];
for (i = 0; i < nusage; i++)
usage[i] = usage[i + 1];
nusage--;
}
else if (usage_range.min <= usage_range.max)
{
collectionpath->usage.usage = usage_range.min++;
}
break;
case USBHID_MAIN_ENDCOLLECTION_PREFIX:
if (collectionpath == NULL)
{
return -EINVAL;
}
collectionpath = collectionpath->parent;
break;
case USBHID_MAIN_INPUT_PREFIX:
case USBHID_MAIN_OUTPUT_PREFIX:
case USBHID_MAIN_FEATURE_PREFIX:
{
int itemno;
for (itemno = 0; itemno < currstate->rptcount; itemno++)
{
struct hid_rptitem_s newitem;
uint8_t tag;
memcpy(&newitem.attrib, &currstate->attrib,
sizeof(struct hid_rptitem_attributes_s));
newitem.flags = data;
newitem.collectionpath = collectionpath;
newitem.id = currstate->id;
if (nusage)
{
newitem.attrib.usage.usage = usage[0];
for (i = 0; i < nusage; i++)
{
usage[i] = usage[i + 1];
}
nusage--;
}
else if (usage_range.min <= usage_range.max)
{
newitem.attrib.usage.usage = usage_range.min++;
}
tag = (item & ~USBHID_RPTITEM_SIZE_MASK);
if (tag == USBHID_MAIN_INPUT_PREFIX)
{
newitem.type = HID_REPORT_ITEM_IN;
}
else if (tag == USBHID_MAIN_OUTPUT_PREFIX)
{
newitem.type = HID_REPORT_ITEM_OUT;
}
else
{
newitem.type = HID_REPORT_ITEM_FEATURE;
}
newitem.bitoffset = rptidinfo->size[newitem.type];
rptidinfo->size[newitem.type] += currstate->attrib.bitsize;
/* Accumulate the maximum report size */
if (rptinfo->maxrptsize < newitem.bitoffset)
{
rptinfo->maxrptsize = newitem.bitoffset;
}
if ((data & USBHID_MAIN_CONSTANT) == 0 && filter(&newitem))
{
if (rptinfo->nitems == CONFIG_HID_MAXITEMS)
{
return -EINVAL;
}
memcpy(&rptinfo->items[rptinfo->nitems],
&newitem, sizeof(struct hid_rptitem_s));
rptinfo->nitems++;
}
}
}
break;
}
if ((item & USBHID_RPTITEM_TYPE_MASK) == USBHID_RPTITEM_TYPE_MAIN)
{
usage_range.min = 0;
usage_range.max = 0;
nusage = 0;
}
}
if (!(rptinfo->nitems))
{
return -ENOENT;
}
return OK;
}
/****************************************************************************
* Name: hid_getitem
*
* Description:
* Extracts the given report item's value out of the given HID report and
* places it into the value member of the report item's struct hid_rptitem_s
* structure.
*
* When called on a report with an item that exists in that report, this
* copies the report item's Value to it's previous element for easy
* checking to see if an item's value has changed before processing a
* report. If the given item does not exist in the report, the function
* does not modify the report item's data.
*
* Input Parameters
* report Buffer containing an IN or FEATURE report from an attached
* device.
* item Pointer to the report item of interest in a struct hid_rptinfo_s
* item array.
*
* Returned Value:
* Zero on success, otherwise a negated errno value.
*
****************************************************************************/
int hid_getitem(FAR const uint8_t *report, FAR struct hid_rptitem_s *item)
{
uint16_t remaining = item->attrib.bitsize;
uint16_t offset = item->bitoffset;
uint32_t mask = (1 << 0);
if (item->id)
{
if (item->id != report[0])
{
return -ENOENT;
}
report++;
}
item->previous = item->value;
item->value = 0;
while (remaining--)
{
if (report[offset >> 3] & (1 << (offset & 7)))
{
item->value |= mask;
}
offset++;
mask <<= 1;
}
return OK;
}
/****************************************************************************
* Name: hid_putitem
*
* Desription:
* Retrieves the given report item's value out of the Value member of the
* report item's struct hid_rptitem_s structure and places it into the correct
* position in the HID report buffer. The report buffer is assumed to have
* the appropriate bits cleared before calling this function (i.e., the
* buffer should be explicitly cleared before report values are added).
*
* When called, this copies the report item's Value element to it's
* previous element for easy checking to see if an item's value has
* changed before sending a report.
*
* If the device has multiple HID reports, the first byte in the report is
* set to the report ID of the given item.
*
* Input Parameters:
* report Buffer holding the current OUT or FEATURE report data.
* item Pointer to the report item of interest in a struct hid_rptinfo_s
* item array.
*
****************************************************************************/
#if 0 /* Not needed by host */
void hid_putitem(FAR uint8_t *report, struct hid_rptitem_s *item)
{
uint16_t remaining = item->attrib.bitsize;
uint16_t offset = item->bitoffset;
uint32_t mask = (1 << 0);
if (item->id)
{
report[0] = item->id;
report++;
}
item->previous = item->value;
while (remaining--)
{
if (item->value & (1 << (offset & 7)))
{
report[offset >> 3] |= mask;
}
offset++;
mask <<= 1;
}
}
#endif
/****************************************************************************
* Name: hid_reportsize
*
* Description:
* Retrieves the size of a given HID report in bytes from it's Report ID.
*
* InputParameters:
* rptinfo Pointer to a struct hid_rptinfo_s instance containing the parser output.
* id Report ID of the report whose size is to be retrieved.
* rpttype Type of the report whose size is to be determined, a valued from the
* HID_ReportItemTypes_t enum.
*
* Size of the report in bytes, or 0 if the report does not exist.
*
****************************************************************************/
size_t hid_reportsize(FAR struct hid_rptinfo_s *rptinfo, uint8_t id, uint8_t rpttype)
{
int i;
for (i = 0; i < CONFIG_HID_MAXIDS; i++)
{
size_t size = rptinfo->rptsize[i].size[rpttype];
if (rptinfo->rptsize[i].id == id)
{
return ((size >> 3) + ((size & 0x07) ? 1 : 0));
}
}
return 0;
}