HelenOS sources
This source file includes following definitions.
- usb_hid_report_path_try_insert
- usb_hid_report_init
- usb_hid_report_append_fields
- usb_hid_report_find_description
- usb_hid_parse_report_descriptor
- usb_hid_report_parse_tag
- usb_hid_report_parse_main_tag
- usb_hid_report_parse_global_tag
- usb_hid_report_parse_local_tag
- usb_hid_report_tag_data_uint32
- usb_hid_descriptor_print_list
- usb_hid_descriptor_print
- usb_hid_report_deinit
#include <usb/hid/hidparser.h>
#include <errno.h>
#include <stdio.h>
#include <mem.h>
#include <usb/debug.h>
#include <assert.h>
#include <stdlib.h>
#define OUTSIDE_DELIMITER_SET 0
#define START_DELIMITER_SET 1
#define INSIDE_DELIMITER_SET 2
#define USB_HID_NEW_REPORT_ITEM 1
#define USB_HID_NO_ACTION 2
#define USB_HID_RESET_OFFSET 3
#define USB_HID_INVALID -98
#define USB_HID_UNKNOWN_TAG -99
usb_hid_report_path_t *usb_hid_report_path_try_insert(usb_hid_report_t *report,
usb_hid_report_path_t *cmp_path)
{
link_t *path_it = report->collection_paths.head.next;
usb_hid_report_path_t *path = NULL;
if ((report == NULL) || (cmp_path == NULL)) {
return NULL;
}
while (path_it != &report->collection_paths.head) {
path = list_get_instance(path_it, usb_hid_report_path_t,
cpath_link);
if (usb_hid_report_compare_usage_path(path, cmp_path,
USB_HID_PATH_COMPARE_STRICT) == 0) {
break;
}
path_it = path_it->next;
}
if (path_it == &report->collection_paths.head) {
path = usb_hid_report_path_clone(cmp_path);
if (path == NULL) {
return NULL;
}
list_append(&path->cpath_link, &report->collection_paths);
report->collection_paths_count++;
return path;
} else {
return list_get_instance(path_it, usb_hid_report_path_t,
cpath_link);
}
}
errno_t usb_hid_report_init(usb_hid_report_t *report)
{
if (report == NULL) {
return EINVAL;
}
memset(report, 0, sizeof(usb_hid_report_t));
list_initialize(&report->reports);
list_initialize(&report->collection_paths);
report->use_report_ids = 0;
return EOK;
}
errno_t usb_hid_report_append_fields(usb_hid_report_t *report,
usb_hid_report_item_t *report_item)
{
usb_hid_report_field_t *field;
int i;
uint32_t *usages;
int usages_used = 0;
if ((report == NULL) || (report_item == NULL)) {
return EINVAL;
}
if (report_item->usages_count > 0) {
usages = malloc(sizeof(uint32_t) * report_item->usages_count);
memcpy(usages, report_item->usages, sizeof(int32_t) *
report_item->usages_count);
} else {
usages = NULL;
}
usb_hid_report_path_t *path = report_item->usage_path;
for (i = 0; i < report_item->count; i++) {
field = malloc(sizeof(usb_hid_report_field_t));
if (field == NULL) {
return ENOMEM;
}
memset(field, 0, sizeof(usb_hid_report_field_t));
link_initialize(&field->ritems_link);
field->logical_minimum = report_item->logical_minimum;
field->logical_maximum = report_item->logical_maximum;
field->physical_minimum = report_item->physical_minimum;
field->physical_maximum = report_item->physical_maximum;
if (USB_HID_ITEM_FLAG_VARIABLE(report_item->item_flags) == 0) {
field->usage = 0;
field->usage_page = 0;
field->usages_count = report_item->usages_count;
field->usages = usages;
usages_used = 1;
} else {
int32_t usage;
if (i < report_item->usages_count) {
usage = report_item->usages[i];
} else {
usage = report_item->usages[report_item->usages_count - 1];
}
if (USB_HID_IS_EXTENDED_USAGE(usage)) {
field->usage = USB_HID_EXTENDED_USAGE(usage);
field->usage_page =
USB_HID_EXTENDED_USAGE_PAGE(usage);
} else {
field->usage = usage;
field->usage_page = report_item->usage_page;
}
}
usb_hid_report_set_last_item(path, USB_HID_TAG_CLASS_GLOBAL,
field->usage_page);
usb_hid_report_set_last_item(path, USB_HID_TAG_CLASS_LOCAL,
field->usage);
field->collection_path =
usb_hid_report_path_try_insert(report, path);
field->size = report_item->size;
field->offset = report_item->offset + (i * report_item->size);
if (report->use_report_ids != 0) {
field->offset += 8;
report->use_report_ids = 1;
}
field->item_flags = report_item->item_flags;
usb_hid_report_description_t *report_des;
report_des = usb_hid_report_find_description(report,
report_item->id, report_item->type);
if (report_des == NULL) {
report_des = malloc(
sizeof(usb_hid_report_description_t));
if (report_des == NULL) {
return ENOMEM;
}
memset(report_des, 0,
sizeof(usb_hid_report_description_t));
report_des->type = report_item->type;
report_des->report_id = report_item->id;
if (report_des->report_id != 0) {
report_des->bit_length = 8;
}
link_initialize (&report_des->reports_link);
list_initialize (&report_des->report_items);
list_append(&report_des->reports_link, &report->reports);
report->report_count++;
}
list_append(&field->ritems_link, &report_des->report_items);
report_des->bit_length += field->size;
report_des->item_length++;
}
if (usages && usages_used == 0) {
free(usages);
}
return EOK;
}
usb_hid_report_description_t *usb_hid_report_find_description(
const usb_hid_report_t *report, uint8_t report_id,
usb_hid_report_type_t type)
{
if (report == NULL) {
return NULL;
}
list_foreach(report->reports, reports_link,
usb_hid_report_description_t, report_des) {
if (((report_des->report_id == report_id) || (report_id == 0)) &&
(report_des->type == type)) {
return report_des;
}
}
return NULL;
}
errno_t usb_hid_parse_report_descriptor(usb_hid_report_t *report,
const uint8_t *data, size_t size)
{
size_t i = 0;
uint8_t tag = 0;
uint8_t item_size = 0;
int class = 0;
int ret;
usb_hid_report_item_t *report_item = 0;
usb_hid_report_item_t *new_report_item;
usb_hid_report_path_t *usage_path;
size_t offset_input = 0;
size_t offset_output = 0;
size_t offset_feature = 0;
link_t *item_link;
list_t stack;
list_initialize(&stack);
if (usb_hid_report_init(report) != EOK) {
return EINVAL;
}
if (!(report_item = malloc(sizeof(usb_hid_report_item_t)))) {
return ENOMEM;
}
memset(report_item, 0, sizeof(usb_hid_report_item_t));
link_initialize(&(report_item->link));
if (!(usage_path = usb_hid_report_path())) {
free(report_item);
return ENOMEM;
}
usb_hid_report_path_append_item(usage_path, 0, 0);
while (i < size) {
if (!USB_HID_ITEM_IS_LONG(data[i])) {
if ((i + USB_HID_ITEM_SIZE(data[i])) >= size) {
return EINVAL;
}
tag = USB_HID_ITEM_TAG(data[i]);
item_size = USB_HID_ITEM_SIZE(data[i]);
class = USB_HID_ITEM_TAG_CLASS(data[i]);
ret = usb_hid_report_parse_tag(tag, class, data + i + 1,
item_size, report_item, usage_path);
switch (ret) {
case USB_HID_NEW_REPORT_ITEM:
report_item->usage_path = usage_path;
usb_hid_report_path_set_report_id(
report_item->usage_path, report_item->id);
if (report_item->id != 0) {
report->use_report_ids = 1;
}
switch (tag) {
case USB_HID_REPORT_TAG_INPUT:
report_item->type =
USB_HID_REPORT_TYPE_INPUT;
report_item->offset = offset_input;
offset_input += report_item->count *
report_item->size;
break;
case USB_HID_REPORT_TAG_OUTPUT:
report_item->type =
USB_HID_REPORT_TYPE_OUTPUT;
report_item->offset = offset_output;
offset_output += report_item->count *
report_item->size;
break;
case USB_HID_REPORT_TAG_FEATURE:
report_item->type =
USB_HID_REPORT_TYPE_FEATURE;
report_item->offset = offset_feature;
offset_feature += report_item->count *
report_item->size;
break;
default:
usb_log_debug2(
"\tjump over - tag %X\n", tag);
break;
}
usb_hid_report_append_fields(report,
report_item);
usb_hid_report_reset_local_items (report_item);
break;
case USB_HID_RESET_OFFSET:
offset_input = 0;
offset_output = 0;
offset_feature = 0;
usb_hid_report_path_set_report_id (usage_path,
report_item->id);
break;
case USB_HID_REPORT_TAG_PUSH:
new_report_item = usb_hid_report_item_clone(
report_item);
usb_hid_report_path_t *tmp_path =
usb_hid_report_path_clone(usage_path);
new_report_item->usage_path = tmp_path;
list_prepend (&new_report_item->link, &stack);
break;
case USB_HID_REPORT_TAG_POP:
item_link = list_first(&stack);
if (item_link == NULL) {
return EINVAL;
}
free(report_item);
report_item = list_get_instance(item_link,
usb_hid_report_item_t, link);
usb_hid_report_usage_path_t *tmp_usage_path;
tmp_usage_path = list_get_instance(
report_item->usage_path->cpath_link.prev,
usb_hid_report_usage_path_t, rpath_items_link);
usb_hid_report_set_last_item(usage_path,
USB_HID_TAG_CLASS_GLOBAL, tmp_usage_path->usage_page);
usb_hid_report_set_last_item(usage_path,
USB_HID_TAG_CLASS_LOCAL, tmp_usage_path->usage);
usb_hid_report_path_free(report_item->usage_path);
list_remove (item_link);
break;
default:
break;
}
i += 1 + USB_HID_ITEM_SIZE(data[i]);
} else {
i += 3 + USB_HID_ITEM_SIZE(data[i + 1]);
}
}
return EOK;
}
int usb_hid_report_parse_tag(uint8_t tag, uint8_t class, const uint8_t *data,
size_t item_size, usb_hid_report_item_t *report_item,
usb_hid_report_path_t *usage_path)
{
int ret;
switch (class) {
case USB_HID_TAG_CLASS_MAIN:
if ((ret = usb_hid_report_parse_main_tag(tag, data, item_size,
report_item, usage_path)) == 0) {
return USB_HID_NEW_REPORT_ITEM;
} else {
return ret;
}
break;
case USB_HID_TAG_CLASS_GLOBAL:
return usb_hid_report_parse_global_tag(tag, data, item_size,
report_item, usage_path);
break;
case USB_HID_TAG_CLASS_LOCAL:
return usb_hid_report_parse_local_tag(tag, data, item_size,
report_item, usage_path);
break;
default:
return USB_HID_NO_ACTION;
}
}
int usb_hid_report_parse_main_tag(uint8_t tag, const uint8_t *data,
size_t item_size, usb_hid_report_item_t *report_item,
usb_hid_report_path_t *usage_path)
{
usb_hid_report_usage_path_t *path_item;
switch (tag) {
case USB_HID_REPORT_TAG_INPUT:
case USB_HID_REPORT_TAG_OUTPUT:
case USB_HID_REPORT_TAG_FEATURE:
report_item->item_flags = *data;
return 0;
break;
case USB_HID_REPORT_TAG_COLLECTION:
path_item = list_get_instance(list_first(&usage_path->items),
usb_hid_report_usage_path_t, rpath_items_link);
path_item->flags = *data;
usb_hid_report_set_last_item(usage_path,
USB_HID_TAG_CLASS_GLOBAL,
USB_HID_EXTENDED_USAGE_PAGE(report_item->usages[report_item->usages_count - 1]));
usb_hid_report_set_last_item(usage_path,
USB_HID_TAG_CLASS_LOCAL,
USB_HID_EXTENDED_USAGE(report_item->usages[report_item->usages_count - 1]));
usb_hid_report_path_append_item(usage_path,
report_item->usage_page,
report_item->usages[report_item->usages_count - 1]);
usb_hid_report_reset_local_items (report_item);
return USB_HID_NO_ACTION;
break;
case USB_HID_REPORT_TAG_END_COLLECTION:
usb_hid_report_remove_last_item(usage_path);
return USB_HID_NO_ACTION;
break;
default:
return USB_HID_NO_ACTION;
}
return 0;
}
int usb_hid_report_parse_global_tag(uint8_t tag, const uint8_t *data,
size_t item_size, usb_hid_report_item_t *report_item,
usb_hid_report_path_t *usage_path)
{
switch (tag) {
case USB_HID_REPORT_TAG_USAGE_PAGE:
report_item->usage_page =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_LOGICAL_MINIMUM:
report_item->logical_minimum = USB_HID_UINT32_TO_INT32(
usb_hid_report_tag_data_uint32(data, item_size),
item_size * 8);
break;
case USB_HID_REPORT_TAG_LOGICAL_MAXIMUM:
report_item->logical_maximum = USB_HID_UINT32_TO_INT32(
usb_hid_report_tag_data_uint32(data, item_size),
item_size * 8);
break;
case USB_HID_REPORT_TAG_PHYSICAL_MINIMUM:
report_item->physical_minimum = USB_HID_UINT32_TO_INT32(
usb_hid_report_tag_data_uint32(data, item_size),
item_size * 8);
break;
case USB_HID_REPORT_TAG_PHYSICAL_MAXIMUM:
report_item->physical_maximum = USB_HID_UINT32_TO_INT32(
usb_hid_report_tag_data_uint32(data, item_size),
item_size * 8);
break;
case USB_HID_REPORT_TAG_UNIT_EXPONENT:
report_item->unit_exponent = usb_hid_report_tag_data_uint32(
data, item_size);
break;
case USB_HID_REPORT_TAG_UNIT:
report_item->unit = usb_hid_report_tag_data_uint32(
data, item_size);
break;
case USB_HID_REPORT_TAG_REPORT_SIZE:
report_item->size = usb_hid_report_tag_data_uint32(
data, item_size);
break;
case USB_HID_REPORT_TAG_REPORT_COUNT:
report_item->count = usb_hid_report_tag_data_uint32(
data, item_size);
break;
case USB_HID_REPORT_TAG_REPORT_ID:
report_item->id = usb_hid_report_tag_data_uint32(data,
item_size);
return USB_HID_RESET_OFFSET;
break;
case USB_HID_REPORT_TAG_PUSH:
case USB_HID_REPORT_TAG_POP:
return tag;
break;
default:
return USB_HID_NO_ACTION;
}
return 0;
}
int usb_hid_report_parse_local_tag(uint8_t tag, const uint8_t *data,
size_t item_size, usb_hid_report_item_t *report_item,
usb_hid_report_path_t *usage_path)
{
int32_t extended_usage;
switch (tag) {
case USB_HID_REPORT_TAG_USAGE:
switch (report_item->in_delimiter) {
case INSIDE_DELIMITER_SET:
break;
case START_DELIMITER_SET:
report_item->in_delimiter = INSIDE_DELIMITER_SET;
case OUTSIDE_DELIMITER_SET:
extended_usage = ((report_item->usage_page) << 16);
extended_usage +=
usb_hid_report_tag_data_uint32(data, item_size);
report_item->usages[report_item->usages_count] =
extended_usage;
report_item->usages_count++;
break;
}
break;
case USB_HID_REPORT_TAG_USAGE_MINIMUM:
if (item_size == 3) {
report_item->extended_usage_page =
USB_HID_EXTENDED_USAGE_PAGE(
usb_hid_report_tag_data_uint32(data, item_size));
report_item->usage_minimum =
USB_HID_EXTENDED_USAGE(
usb_hid_report_tag_data_uint32(data, item_size));
} else {
report_item->usage_minimum =
usb_hid_report_tag_data_uint32(data, item_size);
}
break;
case USB_HID_REPORT_TAG_USAGE_MAXIMUM:
if (item_size == 3) {
if (report_item->extended_usage_page !=
USB_HID_EXTENDED_USAGE_PAGE(
usb_hid_report_tag_data_uint32(data, item_size))) {
return USB_HID_INVALID;
}
report_item->extended_usage_page =
USB_HID_EXTENDED_USAGE_PAGE(
usb_hid_report_tag_data_uint32(data, item_size));
report_item->usage_maximum =
USB_HID_EXTENDED_USAGE(
usb_hid_report_tag_data_uint32(data, item_size));
} else {
report_item->usage_maximum =
usb_hid_report_tag_data_uint32(data, item_size);
}
for (int32_t i = report_item->usage_minimum;
i <= report_item->usage_maximum; i++) {
if (report_item->extended_usage_page) {
report_item->usages[report_item->usages_count++] =
(report_item->extended_usage_page << 16) + i;
} else {
report_item->usages[report_item->usages_count++] =
(report_item->usage_page << 16) + i;
}
}
report_item->extended_usage_page = 0;
break;
case USB_HID_REPORT_TAG_DESIGNATOR_INDEX:
report_item->designator_index =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_DESIGNATOR_MINIMUM:
report_item->designator_minimum =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_DESIGNATOR_MAXIMUM:
report_item->designator_maximum =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_STRING_INDEX:
report_item->string_index =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_STRING_MINIMUM:
report_item->string_minimum =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_STRING_MAXIMUM:
report_item->string_maximum =
usb_hid_report_tag_data_uint32(data, item_size);
break;
case USB_HID_REPORT_TAG_DELIMITER:
report_item->in_delimiter =
usb_hid_report_tag_data_uint32(data, item_size);
break;
default:
return USB_HID_NO_ACTION;
}
return 0;
}
uint32_t usb_hid_report_tag_data_uint32(const uint8_t *data, size_t size)
{
unsigned int i;
uint32_t result;
result = 0;
for (i = 0; i < size; i++) {
result = (result | (data[i]) << (i * 8));
}
return result;
}
void usb_hid_descriptor_print_list(list_t *list)
{
if (list == NULL || list_empty(list)) {
usb_log_debug("\tempty");
return;
}
list_foreach(*list, ritems_link, usb_hid_report_field_t,
report_item) {
usb_log_debug("\t\tOFFSET: %u", report_item->offset);
usb_log_debug("\t\tSIZE: %zu", report_item->size);
usb_log_debug("\t\tLOGMIN: %d",
report_item->logical_minimum);
usb_log_debug("\t\tLOGMAX: %d",
report_item->logical_maximum);
usb_log_debug("\t\tPHYMIN: %d",
report_item->physical_minimum);
usb_log_debug("\t\tPHYMAX: %d",
report_item->physical_maximum);
usb_log_debug("\t\ttUSAGEMIN: %X",
report_item->usage_minimum);
usb_log_debug("\t\tUSAGEMAX: %X",
report_item->usage_maximum);
usb_log_debug("\t\tUSAGES COUNT: %zu",
report_item->usages_count);
usb_log_debug("\t\tVALUE: %X", report_item->value);
usb_log_debug("\t\ttUSAGE: %X", report_item->usage);
usb_log_debug("\t\tUSAGE PAGE: %X", report_item->usage_page);
usb_hid_print_usage_path(report_item->collection_path);
}
}
void usb_hid_descriptor_print(usb_hid_report_t *report)
{
if (report == NULL)
return;
list_foreach(report->reports, reports_link,
usb_hid_report_description_t, report_des) {
usb_log_debug("Report ID: %d", report_des->report_id);
usb_log_debug("\tType: %d", report_des->type);
usb_log_debug("\tLength: %zu", report_des->bit_length);
usb_log_debug("\tB Size: %zu",
usb_hid_report_byte_size(report,
report_des->report_id,
report_des->type));
usb_log_debug("\tItems: %zu", report_des->item_length);
usb_hid_descriptor_print_list(&report_des->report_items);
}
}
void usb_hid_report_deinit(usb_hid_report_t *report)
{
if (report == NULL) {
return;
}
link_t *path_link;
usb_hid_report_path_t *path;
while (!list_empty(&report->collection_paths)) {
path_link = list_first(&report->collection_paths);
path = list_get_instance(path_link,
usb_hid_report_path_t, cpath_link);
list_remove(path_link);
usb_hid_report_path_free(path);
}
usb_hid_report_description_t *report_des;
usb_hid_report_field_t *field;
while (!list_empty(&report->reports)) {
report_des = list_get_instance(list_first(&report->reports),
usb_hid_report_description_t, reports_link);
list_remove(&report_des->reports_link);
while (!list_empty(&report_des->report_items)) {
field = list_get_instance(
list_first(&report_des->report_items),
usb_hid_report_field_t, ritems_link);
list_remove(&field->ritems_link);
free(field);
}
free(report_des);
}
return;
}
HelenOS homepage, sources at GitHub