You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
529 lines
16 KiB
529 lines
16 KiB
/**************************************************************************** |
|
* 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_rptitem_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; |
|
}
|
|
|