source: mainline/uspace/lib/usbhid/src/hidparser.c

Last change on this file was dd19446, checked in by Jiri Svoboda <jiri@…>, 16 months ago

Fix bug in usb_hid_translate_data()

Fixes Lenovo wireless mouse

  • Property mode set to 100644
File size: 15.2 KB
RevLine 
[bf2063e9]1/*
[2571089]2 * Copyright (c) 2011 Matej Klonfar
[e0a5d4c]3 * Copyright (c) 2018 Ondrej Hlavaty
[bf2063e9]4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
[160b75e]30/** @addtogroup libusbhid
[bf2063e9]31 * @{
32 */
33/** @file
[5499a8b]34 * USB HID report data parser implementation.
[bf2063e9]35 */
[faa44e58]36#include <usb/hid/hidparser.h>
[bf2063e9]37#include <errno.h>
[976f546]38#include <stdio.h>
[38d150e]39#include <stdlib.h>
[e24e7b1]40#include <mem.h>
[b7d9606]41#include <usb/debug.h>
[681f24b3]42#include <assert.h>
[6283cefb]43#include <bitops.h>
44#include <macros.h>
[976f546]45
[57d9c05e]46/*
47 * Data translation private functions
48 */
[1553cbf]49uint32_t usb_hid_report_tag_data_uint32(const uint8_t *data, size_t size);
[5499a8b]50
[cfbbe1d3]51int usb_hid_translate_data(usb_hid_report_field_t *item, const uint8_t *data);
[5499a8b]52
[ae3a941]53uint32_t usb_hid_translate_data_reverse(usb_hid_report_field_t *item,
54 int32_t value);
[5499a8b]55
[069b80d]56static int usb_pow(int a, int b)
[fad14d7]57{
[069b80d]58 switch (b) {
[5499a8b]59 case 0:
60 return 1;
61 break;
62 case 1:
63 return a;
64 break;
65 default:
[069b80d]66 return a * usb_pow(a, b - 1);
[5499a8b]67 break;
[fad14d7]68 }
69}
[a76b01b4]70
[3a6e423]71/** Returns size of report of specified report id and type in items
72 *
73 * @param parser Opaque report parser structure
[ae3a941]74 * @param report_id
[3a6e423]75 * @param type
76 * @return Number of items in specified report
77 */
[ae3a941]78size_t usb_hid_report_size(usb_hid_report_t *report, uint8_t report_id,
[069b80d]79 usb_hid_report_type_t type)
[3a6e423]80{
81 usb_hid_report_description_t *report_des;
82
[069b80d]83 if (report == NULL) {
[3a6e423]84 return 0;
85 }
86
[069b80d]87 report_des = usb_hid_report_find_description(report, report_id, type);
88 if (report_des == NULL) {
[3a6e423]89 return 0;
[069b80d]90 } else {
[3a6e423]91 return report_des->item_length;
92 }
93}
[1eee99f2]94
95/** Returns size of report of specified report id and type in bytes
96 *
97 * @param parser Opaque report parser structure
[ae3a941]98 * @param report_id
[1eee99f2]99 * @param type
100 * @return Number of items in specified report
101 */
[ae3a941]102size_t usb_hid_report_byte_size(usb_hid_report_t *report, uint8_t report_id,
[069b80d]103 usb_hid_report_type_t type)
[1eee99f2]104{
105 usb_hid_report_description_t *report_des;
106
[069b80d]107 if (report == NULL) {
[1eee99f2]108 return 0;
109 }
110
[069b80d]111 report_des = usb_hid_report_find_description(report, report_id, type);
112 if (report_des == NULL) {
[1eee99f2]113 return 0;
[069b80d]114 } else {
[ae3a941]115 return ((report_des->bit_length + 7) / 8);
[1eee99f2]116 }
117}
[a76b01b4]118
[fad14d7]119/** Parse and act upon a HID report.
120 *
121 * @see usb_hid_parse_report_descriptor
122 *
123 * @param parser Opaque HID report parser structure.
124 * @param data Data for the report.
125 * @return Error code.
[ae3a941]126 */
127errno_t usb_hid_parse_report(const usb_hid_report_t *report, const uint8_t *data,
[069b80d]128 size_t size, uint8_t *report_id)
[fad14d7]129{
[175ad13e]130 usb_hid_report_description_t *report_des;
131 usb_hid_report_type_t type = USB_HID_REPORT_TYPE_INPUT;
[ae3a941]132
[069b80d]133 if (report == NULL) {
[55e388a1]134 return EINVAL;
135 }
[c156c2d]136
[069b80d]137 if (report->use_report_ids != 0) {
[cfbbe1d3]138 *report_id = data[0];
[069b80d]139 } else {
[cfbbe1d3]140 *report_id = 0;
[c156c2d]141 }
142
[ae3a941]143 report_des = usb_hid_report_find_description(report, *report_id,
[069b80d]144 type);
[8242dd86]145
[069b80d]146 if (report_des == NULL) {
[14e1bcc]147 return EINVAL;
148 }
[c156c2d]149
[175ad13e]150 /* read data */
[feeac0d]151 list_foreach(report_des->report_items, ritems_link,
152 usb_hid_report_field_t, item) {
[fad14d7]153
[069b80d]154 if (USB_HID_ITEM_FLAG_CONSTANT(item->item_flags) == 0) {
[ae3a941]155
[069b80d]156 if (USB_HID_ITEM_FLAG_VARIABLE(item->item_flags) == 0) {
157 /* array */
[ae3a941]158 item->value =
159 usb_hid_translate_data(item, data);
160
[5499a8b]161 item->usage = USB_HID_EXTENDED_USAGE(
[069b80d]162 item->usages[item->value -
163 item->physical_minimum]);
[f3b39b4]164
[ae3a941]165 item->usage_page =
[8242dd86]166 USB_HID_EXTENDED_USAGE_PAGE(
[069b80d]167 item->usages[item->value -
168 item->physical_minimum]);
[3a6e423]169
[069b80d]170 usb_hid_report_set_last_item(
[ae3a941]171 item->collection_path,
172 USB_HID_TAG_CLASS_GLOBAL,
[8242dd86]173 item->usage_page);
[f3b39b4]174
[069b80d]175 usb_hid_report_set_last_item(
[ae3a941]176 item->collection_path,
[5499a8b]177 USB_HID_TAG_CLASS_LOCAL, item->usage);
[069b80d]178 } else {
179 /* variable item */
[ae3a941]180 item->value = usb_hid_translate_data(item,
[feeac0d]181 data);
182 }
[fad14d7]183 }
184 }
[ae3a941]185
[fad14d7]186 return EOK;
187}
188
[57d9c05e]189/**
[c156c2d]190 * Translate data from the report as specified in report descriptor item
[57d9c05e]191 *
192 * @param item Report descriptor item with definition of translation
193 * @param data Data to translate
194 * @return Translated data
195 */
[cfbbe1d3]196int usb_hid_translate_data(usb_hid_report_field_t *item, const uint8_t *data)
[c7a2e7e]197{
[069b80d]198 /* now only short tags are allowed */
199 if (item->size > 32) {
[fad14d7]200 return 0;
201 }
202
[069b80d]203 if ((item->physical_minimum == 0) && (item->physical_maximum == 0)) {
[fad14d7]204 item->physical_minimum = item->logical_minimum;
[6283cefb]205 item->physical_maximum = item->logical_maximum;
[fad14d7]206 }
207
[6283cefb]208 int resolution;
[069b80d]209 if (item->physical_maximum == item->physical_minimum) {
[ae3a941]210 resolution = 1;
[069b80d]211 } else {
[ae3a941]212 resolution = (item->logical_maximum - item->logical_minimum) /
213 ((item->physical_maximum - item->physical_minimum) *
214 (usb_pow(10, (item->unit_exponent))));
[60a228f]215 }
[bda06a3]216
[6283cefb]217 int32_t value = 0;
218
219 /* First, skip all bytes we don't care */
220 data += item->offset / 8;
221
222 int bits = item->size;
223 int taken = 0;
224
225 /* Than we take the higher bits from the LSB */
226 const unsigned bit_offset = item->offset % 8;
[dd19446]227 const int lsb_bits = min((unsigned)bits, 8 - bit_offset);
[6283cefb]228
229 value |= (*data >> bit_offset) & BIT_RRANGE(uint8_t, lsb_bits);
230 bits -= lsb_bits;
231 taken += lsb_bits;
232 data++;
233
234 /* Then there may be bytes, which we take as a whole. */
235 while (bits > 8) {
236 value |= *data << taken;
237 taken += 8;
238 bits -= 8;
239 data++;
240 }
241
242 /* And, finally, lower bits from HSB. */
243 if (bits > 0) {
244 value |= (*data & BIT_RRANGE(uint8_t, bits)) << taken;
[175ad13e]245 }
[fad14d7]246
[069b80d]247 if ((item->logical_minimum < 0) || (item->logical_maximum < 0)) {
[1553cbf]248 value = USB_HID_UINT32_TO_INT32(value, item->size);
[fad14d7]249 }
250
[ae3a941]251 return (int) (((value - item->logical_minimum) / resolution) +
[069b80d]252 item->physical_minimum);
[c7a2e7e]253}
[0bd4810c]254
[5499a8b]255/* OUTPUT API */
[57d9c05e]256
[ae3a941]257/**
[a694a58]258 * Allocates output report buffer for output report
[57d9c05e]259 *
[a694a58]260 * @param parser Report parsed structure
261 * @param size Size of returned buffer
262 * @param report_id Report id of created output report
263 * @return Returns allocated output buffer for specified output
[57d9c05e]264 */
[ae3a941]265uint8_t *usb_hid_report_output(usb_hid_report_t *report, size_t *size,
[069b80d]266 uint8_t report_id)
[57d9c05e]267{
[069b80d]268 if (report == NULL) {
[57d9c05e]269 *size = 0;
270 return NULL;
271 }
272
[175ad13e]273 usb_hid_report_description_t *report_des = NULL;
[b72efe8]274
[feeac0d]275 list_foreach(report->reports, reports_link,
[1e371cf]276 usb_hid_report_description_t, rdes) {
277 if ((rdes->report_id == report_id) &&
278 (rdes->type == USB_HID_REPORT_TYPE_OUTPUT)) {
279 report_des = rdes;
[a694a58]280 break;
[c156c2d]281 }
[175ad13e]282 }
[841e6e5]283
[069b80d]284 if (report_des == NULL) {
[175ad13e]285 *size = 0;
286 return NULL;
[069b80d]287 } else {
288 *size = (report_des->bit_length + (8 - 1)) / 8;
[175ad13e]289 uint8_t *ret = malloc((*size) * sizeof(uint8_t));
290 memset(ret, 0, (*size) * sizeof(uint8_t));
291 return ret;
[57d9c05e]292 }
293}
294
295/** Frees output report buffer
296 *
297 * @param output Output report buffer
[a694a58]298 * @return void
[57d9c05e]299 */
300void usb_hid_report_output_free(uint8_t *output)
301{
[069b80d]302 if (output != NULL) {
303 free(output);
[57d9c05e]304 }
305}
306
[175ad13e]307/** Makes the output report buffer for data given in the report structure
[57d9c05e]308 *
[a694a58]309 * @param parser Opaque report parser structure
310 * @param path Usage path specifing which parts of output will be set
311 * @param flags Usage path structure comparison flags
312 * @param buffer Output buffer
313 * @param size Size of output buffer
314 * @return Error code
[57d9c05e]315 */
[ae3a941]316errno_t usb_hid_report_output_translate(usb_hid_report_t *report,
[069b80d]317 uint8_t report_id, uint8_t *buffer, size_t size)
[57d9c05e]318{
[069b80d]319 int32_t value = 0;
[841e6e5]320 int offset;
321 int length;
322 int32_t tmp_value;
[ae3a941]323
[069b80d]324 if (report == NULL) {
[57d9c05e]325 return EINVAL;
326 }
327
[069b80d]328 if (report->use_report_ids != 0) {
[feeac0d]329 buffer[0] = report_id;
[bda06a3]330 }
331
[175ad13e]332 usb_hid_report_description_t *report_des;
[ae3a941]333 report_des = usb_hid_report_find_description(report, report_id,
[069b80d]334 USB_HID_REPORT_TYPE_OUTPUT);
[ae3a941]335
[069b80d]336 if (report_des == NULL) {
[175ad13e]337 return EINVAL;
338 }
[57d9c05e]339
[feeac0d]340 list_foreach(report_des->report_items, ritems_link,
341 usb_hid_report_field_t, report_item) {
[ae3a941]342 value = usb_hid_translate_data_reverse(report_item,
[069b80d]343 report_item->value);
[841e6e5]344
[574f276]345 offset = report_des->bit_length - report_item->offset - 1;
346 length = report_item->size;
[ae3a941]347
[a1732929]348 usb_log_debug("\ttranslated value: %x", value);
[841e6e5]349
[069b80d]350 if ((offset / 8) == ((offset + length - 1) / 8)) {
[ae3a941]351 if (((size_t) (offset / 8) >= size) ||
[069b80d]352 ((size_t) (offset + length - 1) / 8) >= size) {
[9be8669]353 break; // TODO ErrorCode
[841e6e5]354 }
[069b80d]355 size_t shift = 8 - offset % 8 - length;
[feeac0d]356 value = value << shift;
[069b80d]357 value = value & (((1 << length) - 1) << shift);
[ae3a941]358
[9be8669]359 uint8_t mask = 0;
360 mask = 0xff - (((1 << length) - 1) << shift);
[069b80d]361 buffer[offset / 8] = (buffer[offset / 8] & mask) |
362 value;
363 } else {
[9be8669]364 int i = 0;
365 uint8_t mask = 0;
[069b80d]366 for (i = (offset / 8);
367 i <= ((offset + length - 1) / 8); i++) {
368 if (i == (offset / 8)) {
[9be8669]369 tmp_value = value;
[ae3a941]370 tmp_value = tmp_value &
[069b80d]371 ((1 << (8 - (offset % 8))) - 1);
[f3b39b4]372
[069b80d]373 tmp_value = tmp_value << (offset % 8);
[ae3a941]374
[1433ecda]375 mask = ~(((1 << (8 - (offset % 8))) - 1) << (offset % 8));
[f3b39b4]376
[ae3a941]377 buffer[i] = (buffer[i] & mask) |
[069b80d]378 tmp_value;
379 } else if (i == ((offset + length - 1) / 8)) {
[ae3a941]380
381 value = value >> (length -
[069b80d]382 ((offset + length) % 8));
[f3b39b4]383
[ae3a941]384 value = value & ((1 << (length -
[069b80d]385 ((offset + length) % 8))) - 1);
[ae3a941]386
387 mask = (1 << (length -
[069b80d]388 ((offset + length) % 8))) - 1;
[f3b39b4]389
[9be8669]390 buffer[i] = (buffer[i] & mask) | value;
[069b80d]391 } else {
392 buffer[i] = value & (0xff << i);
[175ad13e]393 }
[841e6e5]394 }
[9be8669]395 }
[841e6e5]396
[069b80d]397 /* reset value */
[cfbbe1d3]398 report_item->value = 0;
[57d9c05e]399 }
[ae3a941]400
[57d9c05e]401 return EOK;
402}
403
[841e6e5]404/**
[a694a58]405 * Translate given data for putting them into the outoput report
406 * @param item Report item structure
407 * @param value Value to translate
408 * @return ranslated value
[841e6e5]409 */
[ae3a941]410uint32_t usb_hid_translate_data_reverse(usb_hid_report_field_t *item,
[069b80d]411 int value)
[841e6e5]412{
[069b80d]413 int ret = 0;
[841e6e5]414 int resolution;
415
[069b80d]416 if (USB_HID_ITEM_FLAG_CONSTANT(item->item_flags)) {
[4172db4a]417 return item->logical_minimum;
[70a71e5]418 }
419
[069b80d]420 if ((item->physical_minimum == 0) && (item->physical_maximum == 0)) {
[cfbbe1d3]421 item->physical_minimum = item->logical_minimum;
[ae3a941]422 item->physical_maximum = item->logical_maximum;
[cfbbe1d3]423 }
[ae3a941]424
[069b80d]425 /* variable item */
426 if (item->physical_maximum == item->physical_minimum) {
[ae3a941]427 resolution = 1;
[069b80d]428 } else {
[ae3a941]429 resolution = (item->logical_maximum - item->logical_minimum) /
430 ((item->physical_maximum - item->physical_minimum) *
431 (usb_pow(10, (item->unit_exponent))));
[841e6e5]432 }
433
[ae3a941]434 ret = ((value - item->physical_minimum) * resolution) +
[069b80d]435 item->logical_minimum;
[5499a8b]436
[069b80d]437 usb_log_debug("\tvalue(%x), resolution(%x), phymin(%x) logmin(%x), "
[ae3a941]438 "ret(%x)\n", value, resolution, item->physical_minimum,
[069b80d]439 item->logical_minimum, ret);
[ae3a941]440
[069b80d]441 if ((item->logical_minimum < 0) || (item->logical_maximum < 0)) {
[1553cbf]442 return USB_HID_INT32_TO_UINT32(ret, item->size);
443 }
[069b80d]444
445 return (int32_t) 0 + ret;
[841e6e5]446}
447
[5499a8b]448/**
449 * Clones given state table
450 *
451 * @param item State table to clone
452 * @return Pointer to the cloned item
453 */
454usb_hid_report_item_t *usb_hid_report_item_clone(
[069b80d]455 const usb_hid_report_item_t *item)
[64dbc83]456{
457 usb_hid_report_item_t *new_report_item;
[ae3a941]458
[069b80d]459 if (!(new_report_item = malloc(sizeof(usb_hid_report_item_t)))) {
[64dbc83]460 return NULL;
[ae3a941]461 }
[1433ecda]462 memcpy(new_report_item, item, sizeof(usb_hid_report_item_t));
[64dbc83]463 link_initialize(&(new_report_item->link));
464
465 return new_report_item;
466}
467
[5499a8b]468/**
469 * Function for sequence walking through the report. Returns next field in the
470 * report or the first one when no field is given.
471 *
472 * @param report Searched report structure
473 * @param field Current field. If NULL is given, the first one in the report
474 * is returned. Otherwise the next one i nthe list is returned.
[ae3a941]475 * @param path Usage path specifying which fields wa are interested in.
[5499a8b]476 * @param flags Flags defining mode of usage paths comparison
477 * @param type Type of report we search.
478 * @retval NULL if no field is founded
479 * @retval Pointer to the founded report structure when founded
480 */
[ae3a941]481usb_hid_report_field_t *usb_hid_report_get_sibling(usb_hid_report_t *report,
482 usb_hid_report_field_t *field, usb_hid_report_path_t *path, int flags,
[069b80d]483 usb_hid_report_type_t type)
[e50cd7f]484{
[ae3a941]485 usb_hid_report_description_t *report_des =
[069b80d]486 usb_hid_report_find_description(report, path->report_id, type);
[5499a8b]487
[e50cd7f]488 link_t *field_it;
[ae3a941]489
[069b80d]490 if (report_des == NULL) {
[e50cd7f]491 return NULL;
492 }
493
[069b80d]494 if (field == NULL) {
[b72efe8]495 field_it = report_des->report_items.head.next;
[069b80d]496 } else {
[b72efe8]497 field_it = field->ritems_link.next;
[e50cd7f]498 }
499
[069b80d]500 while (field_it != &report_des->report_items.head) {
[ae3a941]501 field = list_get_instance(field_it, usb_hid_report_field_t,
[069b80d]502 ritems_link);
[f3b39b4]503
[069b80d]504 if (USB_HID_ITEM_FLAG_CONSTANT(field->item_flags) == 0) {
505 usb_hid_report_path_append_item(field->collection_path,
506 field->usage_page, field->usage);
[5499a8b]507
[069b80d]508 if (usb_hid_report_compare_usage_path(
[3ca4ae9]509 field->collection_path, path, flags) == 0) {
[f3b39b4]510 usb_hid_report_remove_last_item(
[069b80d]511 field->collection_path);
[c7c0984a]512 return field;
513 }
[069b80d]514 usb_hid_report_remove_last_item(field->collection_path);
[e50cd7f]515 }
516 field_it = field_it->next;
517 }
518
519 return NULL;
520}
[cfbbe1d3]521
[5499a8b]522/**
[1d10ca1]523 * Returns next report_id of report of specified type. If zero is given than
524 * first report_id of specified type is returned (0 is not legal value for
525 * repotr_id)
[5499a8b]526 *
[1d10ca1]527 * @param report_id Current report_id, 0 if there is no current report_id
[5499a8b]528 * @param type Type of searched report
529 * @param report Report structure inwhich we search
530 * @retval 0 if report structure is null or there is no specified report
531 * @retval report_id otherwise
532 */
[069b80d]533uint8_t usb_hid_get_next_report_id(usb_hid_report_t *report, uint8_t report_id,
534 usb_hid_report_type_t type)
[cfbbe1d3]535{
[069b80d]536 if (report == NULL) {
[cfbbe1d3]537 return 0;
538 }
539
540 usb_hid_report_description_t *report_des;
541 link_t *report_it;
[ae3a941]542
[069b80d]543 if (report_id > 0) {
[ae3a941]544 report_des = usb_hid_report_find_description(report, report_id,
[069b80d]545 type);
546 if (report_des == NULL) {
[0dd3e49]547 return 0;
[069b80d]548 } else {
[b72efe8]549 report_it = report_des->reports_link.next;
[ae3a941]550 }
[069b80d]551 } else {
[b72efe8]552 report_it = report->reports.head.next;
[cfbbe1d3]553 }
554
[069b80d]555 while (report_it != &report->reports.head) {
[ae3a941]556 report_des = list_get_instance(report_it,
[069b80d]557 usb_hid_report_description_t, reports_link);
[5499a8b]558
[069b80d]559 if (report_des->type == type) {
[cfbbe1d3]560 return report_des->report_id;
561 }
[0c904a3]562
563 report_it = report_it->next;
[cfbbe1d3]564 }
565
566 return 0;
567}
568
[5499a8b]569/**
570 * Reset all local items in given state table
571 *
572 * @param report_item State table containing current state of report
573 * descriptor parsing
574 *
575 * @return void
576 */
[2020927]577void usb_hid_report_reset_local_items(usb_hid_report_item_t *report_item)
578{
[069b80d]579 if (report_item == NULL) {
[2020927]580 return;
581 }
[ae3a941]582
[2020927]583 report_item->usages_count = 0;
[92cf9a4]584 memset(report_item->usages, 0, sizeof(report_item->usages));
[ae3a941]585
[2020927]586 report_item->extended_usage_page = 0;
587 report_item->usage_minimum = 0;
588 report_item->usage_maximum = 0;
589 report_item->designator_index = 0;
590 report_item->designator_minimum = 0;
591 report_item->designator_maximum = 0;
592 report_item->string_index = 0;
593 report_item->string_minimum = 0;
594 report_item->string_maximum = 0;
595}
[069b80d]596
[976f546]597/**
598 * @}
[c7a2e7e]599 */
Note: See TracBrowser for help on using the repository browser.