/**************************************************************************** * 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 #include #include #include #include #include #include /**************************************************************************** * 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 *parent_collection_path = collectionpath; collectionpath = &rptinfo->collectionpaths[1]; while (collectionpath->parent != NULL) { if (collectionpath == &rptinfo->collectionpaths[ CONFIG_HID_MAXCOLLECTIONS - 1]) { return -EINVAL; } collectionpath++; } collectionpath->parent = parent_collection_path; } collectionpath->type = data; collectionpath->usage.page = currstate->attrib.usage.page; if (nusage) { collectionpath->usage.usage = usage[0]; for (i = 1; i < nusage; i++) usage[i - 1] = usage[i]; 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 = 1; i < nusage; i++) { usage[i - 1] = usage[i]; } 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 * * Description: * 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. * * Input Parameters: * 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; }