向/dev/uhid写输入数据时,uhid_char_write调用uhid_dev_input
static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev) { if (!uhid->running) return -EINVAL; hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0); return 0; } // 写入数据格式为 struct uhid_event { __u32 type; union { struct uhid_create_req create; struct uhid_input_req input; struct uhid_output_req output; struct uhid_output_ev_req output_ev; struct uhid_feature_req feature; struct uhid_feature_answer_req feature_answer; } u; } __attribute__((__packed__)); struct uhid_input_req { __u8 data[UHID_DATA_MAX]; __u16 size; } __attribute__((__packed__));
从uhid-example.c中可以看到
写入的数据size = 4,data[0]的bit0、bit1、bit2为button1、2、3的按下状态,data[1]为鼠标水平方向值,data[2]为垂直方向值,data[3]为滚轮值
由于hid_device对应的hid_driver->raw_event为NULL(该hid_driver就是drivers/hid/usbhid/hid-core.c中注册的generic-usb driver),hid_input_report调用hid_report_raw_event
int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size, int interrupt) { struct hid_report_enum *report_enum = hid->report_enum + type; struct hid_report *report; unsigned int a; int rsize, csize = size; u8 *cdata = data; int ret = 0; report = hid_get_report(report_enum, data); if (!report) goto out; if (report_enum->numbered) { cdata++; csize--; } rsize = ((report->size - 1) >> 3) + 1; if (rsize > HID_MAX_BUFFER_SIZE) rsize = HID_MAX_BUFFER_SIZE; if (csize < rsize) { dbg_hid("report %d is too short, (%d < %d)\n", report->id, csize, rsize); memset(cdata + csize, 0, rsize - csize); } if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event) hid->hiddev_report_event(hid, report); if (hid->claimed & HID_CLAIMED_HIDRAW) { ret = hidraw_report_event(hid, data, size); if (ret) goto out; } for (a = 0; a < report->maxfield; a++) hid_input_field(hid, report->field[a], cdata, interrupt); if (hid->claimed & HID_CLAIMED_INPUT) hidinput_report_event(hid, report); out: return ret; }
hid_report_raw_event会向hiddev和hidraw report(hid->hiddev_report_event、hidraw_report_event)
然后通过一个for循环,遍历report的所有field,对每一个field通过hid_input_field -> hid_process_event -> hidinput_hid_event -> input_event 的调用处理该field相关的输入数据,将输入事件注入到input子系统
for循环结束后调用hidinput_report_event向input子系统发送sync事件
void hidinput_report_event(struct hid_device *hid, struct hid_report *report) { struct hid_input *hidinput; if (hid->quirks & HID_QUIRK_NO_INPUT_SYNC) return; list_for_each_entry(hidinput, &hid->inputs, list) input_sync(hidinput->input); }
接下来在详细分析hid_inputfield之前,先看一下uhid-example.c中的<a href=”http://img.pickbox.cc/wp-content/uploads/mouse.hid.rar”>HID Report Desciptor
USAGE_PAGE (Generic Desktop) 05 01 USAGE (Mouse) 09 02 COLLECTION (Application) A1 01 USAGE (Pointer) 09 01 COLLECTION (Physical) A1 00 USAGE_PAGE (Button) 05 09 USAGE_MINIMUM (Button 1) 19 01 USAGE_MAXIMUM (Button 3) 29 03 LOGICAL_MINIMUM (0) 15 00 LOGICAL_MAXIMUM (1) 25 01 REPORT_COUNT (3) 95 03 REPORT_SIZE (1) 75 01 INPUT (Data,Var,Abs) 81 02 REPORT_COUNT (1) 95 01 REPORT_SIZE (5) 75 05 INPUT (Cnst,Ary,Abs) 81 01 USAGE_PAGE (Generic Desktop) 05 01 USAGE (X) 09 30 USAGE (Y) 09 31 USAGE (Wheel) 09 38 LOGICAL_MINIMUM (-128) 15 80 LOGICAL_MAXIMUM (127) 25 7F REPORT_SIZE (8) 75 08 REPORT_COUNT (3) 95 03 INPUT (Data,Var,Rel) 81 06 END_COLLECTION C0 END_COLLECTION C0
这里面只有一个Physical Collection输入,没有report id(如果有report id,那么在USAGE_PAGE (Button)这行前面应该有REPORT_ID (1)),对应linux的hid_report;这个Collection又包含有三个INPUT,对应着linux的3个field
field[0]表述3个button(REPORT_COUNT=3),每个button用1个bit(REPORT_SIZE=1)表示按下状态;field[1]表述用5个constant bit占位;field[2]表述3个(REPORT_COUNT=3)Relative坐标,分别为X、Y、Wheel,每个坐标值占8bit(REPORT_SIZE=8)
这3个field构成的数据刚好和通过uhid_input_req传递进来的数据的顺序一致,只需要按照report descriptor解析,就能知道每个数据代表的意义
更多report descriptor介绍参考tutorial-about-usb-hid-report-descriptors(PDF)
report descriptor在linux内核数据结构的对应关系,可以看看下面这张图
回到hid_input_field
static void hid_input_field(struct hid_device *hid, struct hid_field *field, __u8 *data, int interrupt) { unsigned n; unsigned count = field->report_count; unsigned offset = field->report_offset; unsigned size = field->report_size; __s32 min = field->logical_minimum; __s32 max = field->logical_maximum; __s32 *value; value = kmalloc(sizeof(__s32) * count, GFP_ATOMIC); if (!value) return; // 取出 for (n = 0; n < count; n++) { value[n] = min < 0 ? snto32(extract(hid, data, offset + n * size, size), size) : extract(hid, data, offset + n * size, size); /* Ignore report if ErrorRollOver */ if (!(field->flags & HID_MAIN_ITEM_VARIABLE) && value[n] >= min && value[n] <= max && field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) goto exit; } for (n = 0; n < count; n++) { if (HID_MAIN_ITEM_VARIABLE & field->flags) { hid_process_event(hid, field, &field->usage[n], value[n], interrupt); continue; } if (field->value[n] >= min && field->value[n] <= max && field->usage[field->value[n] - min].hid && search(value, field->value[n], count)) hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, interrupt); if (value[n] >= min && value[n] <= max && field->usage[value[n] - min].hid && search(field->value, value[n], count)) hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt); } memcpy(field->value, value, count * sizeof(__s32)); exit: kfree(value); }
hid_input_field对field中的REPORT_COUNT个输入数据通过extract取出,并存放在value数组,又依次调用hid_process_event处理
static void hid_process_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value, int interrupt) { struct hid_driver *hdrv = hid->driver; int ret; hid_dump_input(hid, usage, value); if (hdrv && hdrv->event && hid_match_usage(hid, usage)) { ret = hdrv->event(hid, field, usage, value); if (ret != 0) { if (ret < 0) hid_err(hid, "%s's event failed with %d\n", hdrv->name, ret); return; } } if (hid->claimed & HID_CLAIMED_INPUT) hidinput_hid_event(hid, field, usage, value); if (hid->claimed & HID_CLAIMED_HIDDEV && interrupt && hid->hiddev_hid_event) hid->hiddev_hid_event(hid, field, usage, value); }
最终调用hidinput_hid_event进行处理,hidinput_hid_event处理各种case,但最后还是要调用input_event将输入事件注入到input子系统
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value) { struct input_dev *input; unsigned *quirks = &hid->quirks; if (!field->hidinput) return; input = field->hidinput->input; if (!usage->type) return; if (usage->hat_min < usage->hat_max || usage->hat_dir) { int hat_dir = usage->hat_dir; if (!hat_dir) hat_dir = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1; if (hat_dir < 0 || hat_dir > 8) hat_dir = 0; input_event(input, usage->type, usage->code , hid_hat_to_axis[hat_dir].x); input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[hat_dir].y); return; } if (usage->hid == (HID_UP_DIGITIZER | 0x003c)) { /* Invert */ *quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT); return; } if (usage->hid == (HID_UP_DIGITIZER | 0x0032)) { /* InRange */ if (value) { input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1); return; } input_event(input, usage->type, usage->code, 0); input_event(input, usage->type, BTN_TOOL_RUBBER, 0); return; } if (usage->hid == (HID_UP_DIGITIZER | 0x0030) && (*quirks & HID_QUIRK_NOTOUCH)) { /* Pressure */ int a = field->logical_minimum; int b = field->logical_maximum; input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3)); } if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */ dbg_hid("Maximum Effects - %d\n",value); return; } if (usage->hid == (HID_UP_PID | 0x7fUL)) { dbg_hid("PID Pool Report\n"); return; } if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */ return; if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) && (usage->code == ABS_VOLUME)) { int count = abs(value); int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; int i; for (i = 0; i < count; i++) { input_event(input, EV_KEY, direction, 1); input_sync(input); input_event(input, EV_KEY, direction, 0); input_sync(input); } return; } /* * Ignore out-of-range values as per HID specification, * section 5.10 and 6.2.25 */ if ((field->flags & HID_MAIN_ITEM_VARIABLE) && (value < field->logical_minimum || value > field->logical_maximum)) { dbg_hid("Ignoring out-of-range value %x\n", value); return; } /* report the usage code as scancode if the key status has changed */ if (usage->type == EV_KEY && !!test_bit(usage->code, input->key) != value) input_event(input, EV_MSC, MSC_SCAN, usage->hid); input_event(input, usage->type, usage->code, value); if ((field->flags & HID_MAIN_ITEM_RELATIVE) && (usage->type == EV_KEY)) input_event(input, usage->type, usage->code, 0); }
input_event的参数value就是前面extract出的值,也是我们在uhid-example中传入的button的1 bit按下状态和坐标或者8 bit数值,而usage->code对应button的keycode或者坐标的axis
usage的type和code是在哪里赋值的?
它是在hidinput_connect时调用hidinput_configure_usage进行赋值的
前一篇文章《Linux uhid分析之创建HID设备》中提到,hid_device_probe时会先调用hid_parse -> hid_parse_report
它在hid_parse_local时,将USAGE_PAGE << 16 + USAGE的值保存到parser->local.usage数组;然后在hid_parse_main时调用 hid_add_field -> hid_register_field 分配field->usage结构体,并将field->usage[i].hid设置为parser->local.usage[j](也就是前面hid_parse_local保存的usage值)
static int hid_parser_local(struct hid_parser *parser, struct hid_item *item) { ... case HID_LOCAL_ITEM_TAG_USAGE: if (item->size <= 2) data = (parser->global.usage_page << 16) + data; return hid_add_usage(parser, data); ... } static int hid_parser_main(struct hid_parser *parser, struct hid_item *item) { __u32 data; int ret; data = item_udata(item); switch (item->tag) { ... case HID_MAIN_ITEM_TAG_INPUT: ret = hid_add_field(parser, HID_INPUT_REPORT, data); break; ... } return ret; } static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsigned flags) { struct hid_report *report; struct hid_field *field; unsigned usages; unsigned offset; unsigned i; report = hid_register_report(parser->device, report_type, parser->global.report_id); ... usages = max_t(unsigned, parser->local.usage_index, parser->global.report_count); field = hid_register_field(report, usages, parser->global.report_count); ... for (i = 0; i < usages; i++) { unsigned j = i; /* Duplicate the last usage we parsed if we have excess values */ if (i >= parser->local.usage_index) j = parser->local.usage_index - 1; field->usage[i].hid = parser->local.usage[j]; field->usage[i].collection_index = parser->local.collection_index[j]; } field->maxusage = usages; field->flags = flags; ... return 0; }
parse结束后,hid_device_probe 调用 hid_hw_start -> hid_connect -> hidinput_connect
int hidinput_connect(struct hid_device *hid, unsigned int force) { struct hid_report *report; struct hid_input *hidinput = NULL; struct input_dev *input_dev; int i, j, k; INIT_LIST_HEAD(&hid->inputs); if (!force) { for (i = 0; i < hid->maxcollection; i++) { struct hid_collection *col = &hid->collection[i]; if (col->type == HID_COLLECTION_APPLICATION || col->type == HID_COLLECTION_PHYSICAL) if (IS_INPUT_APPLICATION(col->usage)) break; } if (i == hid->maxcollection) return -1; } report_features(hid); for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) { if (k == HID_OUTPUT_REPORT && hid->quirks & HID_QUIRK_SKIP_OUTPUT_REPORTS) continue; list_for_each_entry(report, &hid->report_enum[k].report_list, list) { if (!report->maxfield) continue; if (!hidinput) { hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL); input_dev = input_allocate_device(); if (!hidinput || !input_dev) { kfree(hidinput); input_free_device(input_dev); hid_err(hid, "Out of memory during hid input probe\n"); goto out_unwind; } input_set_drvdata(input_dev, hid); input_dev->event = hid->ll_driver->hidinput_input_event; input_dev->open = hidinput_open; input_dev->close = hidinput_close; input_dev->setkeycode = hidinput_setkeycode; input_dev->getkeycode = hidinput_getkeycode; input_dev->name = hid->name; input_dev->phys = hid->phys; input_dev->uniq = hid->uniq; input_dev->id.bustype = hid->bus; input_dev->id.vendor = hid->vendor; input_dev->id.product = hid->product; input_dev->id.version = hid->version; input_dev->dev.parent = hid->dev.parent; hidinput->input = input_dev; list_add_tail(&hidinput->list, &hid->inputs); } for (i = 0; i < report->maxfield; i++) for (j = 0; j < report->field[i]->maxusage; j++) hidinput_configure_usage(hidinput, report->field[i], report->field[i]->usage + j); if (hid->quirks & HID_QUIRK_MULTI_INPUT) { /* This will leave hidinput NULL, so that it * allocates another one if we have more inputs on * the same interface. Some devices (e.g. Happ's * UGCI) cram a lot of unrelated inputs into the * same interface. */ hidinput->report = report; if (hid->driver->input_register && hid->driver->input_register(hid, hidinput)) goto out_cleanup; if (input_register_device(hidinput->input)) goto out_cleanup; hidinput = NULL; } } } if (hid->quirks & HID_QUIRK_MULTITOUCH) { /* generic hid does not know how to handle multitouch devices */ if (hidinput) goto out_cleanup; goto out_unwind; } if (hidinput && hid->driver->input_register && hid->driver->input_register(hid, hidinput)) goto out_cleanup; if (hidinput && input_register_device(hidinput->input)) goto out_cleanup; return 0; out_cleanup: list_del(&hidinput->list); input_free_device(hidinput->input); kfree(hidinput); out_unwind: /* unwind the ones we already registered */ hidinput_disconnect(hid); return -1; }hidinput_connect对hid_device->report_enum中的每一个report的每一个field,通过hidinput_configure_usage对usage进行配置
static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field, struct hid_usage *usage) { struct input_dev *input = hidinput->input; struct hid_device *device = input_get_drvdata(input); int max = 0, code; unsigned long *bit = NULL; field->hidinput = hidinput; if (field->flags & HID_MAIN_ITEM_CONSTANT) goto ignore; /* Ignore if report count is out of bounds. */ if (field->report_count < 1) goto ignore; /* only LED usages are supported in output fields */ if (field->report_type == HID_OUTPUT_REPORT && (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) { goto ignore; } if (device->driver->input_mapping) { int ret = device->driver->input_mapping(device, hidinput, field, usage, &bit, &max); if (ret > 0) goto mapped; if (ret < 0) goto ignore; } switch (usage->hid & HID_USAGE_PAGE) { case HID_UP_UNDEFINED: goto ignore; case HID_UP_KEYBOARD: set_bit(EV_REP, input->evbit); if ((usage->hid & HID_USAGE) < 256) { if (!hid_keyboard[usage->hid & HID_USAGE]) goto ignore; map_key_clear(hid_keyboard[usage->hid & HID_USAGE]); } else map_key(KEY_UNKNOWN); break; case HID_UP_BUTTON: code = ((usage->hid - 1) & HID_USAGE); switch (field->application) { case HID_GD_MOUSE: case HID_GD_POINTER: code += BTN_MOUSE; break; case HID_GD_JOYSTICK: if (code <= 0xf) code += BTN_JOYSTICK; else code += BTN_TRIGGER_HAPPY; break; case HID_GD_GAMEPAD: code += BTN_GAMEPAD; break; default: switch (field->physical) { case HID_GD_MOUSE: case HID_GD_POINTER: code += BTN_MOUSE; break; case HID_GD_JOYSTICK: code += BTN_JOYSTICK; break; case HID_GD_GAMEPAD: code += BTN_GAMEPAD; break; default: code += BTN_MISC; } } map_key(code); break; ... case HID_UP_GENDESK: ... switch (usage->hid) { /* These usage IDs map directly to the usage codes. */ case HID_GD_X: case HID_GD_Y: case HID_GD_Z: case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ: case HID_GD_SLIDER: case HID_GD_DIAL: case HID_GD_WHEEL: if (field->flags & HID_MAIN_ITEM_RELATIVE) map_rel(usage->hid & 0xf); else map_abs(usage->hid & 0xf); break; case HID_GD_HATSWITCH: usage->hat_min = field->logical_minimum; usage->hat_max = field->logical_maximum; map_abs(ABS_HAT0X); break; case HID_GD_START: map_key_clear(BTN_START); break; case HID_GD_SELECT: map_key_clear(BTN_SELECT); break; default: goto unknown; } break; ... default: unknown: if (field->report_size == 1) { if (field->report->type == HID_OUTPUT_REPORT) { map_led(LED_MISC); break; } map_key(BTN_MISC); break; } if (field->flags & HID_MAIN_ITEM_RELATIVE) { map_rel(REL_MISC); break; } map_abs(ABS_MISC); break; } mapped: if (device->driver->input_mapped && device->driver->input_mapped(device, hidinput, field, usage, &bit, &max) < 0) goto ignore; set_bit(usage->type, input->evbit); while (usage->code <= max && test_and_set_bit(usage->code, bit)) usage->code = find_next_zero_bit(bit, max + 1, usage->code); if (usage->code > max) goto ignore; if (usage->type == EV_ABS) { int a = field->logical_minimum; int b = field->logical_maximum; if ((device->quirks & HID_QUIRK_BADPAD) && (usage->code == ABS_X || usage->code == ABS_Y)) { a = field->logical_minimum = 0; b = field->logical_maximum = 255; } if (field->application == HID_GD_GAMEPAD || field->application == HID_GD_JOYSTICK) input_set_abs_params(input, usage->code, a, b, (b - a) >> 8, (b - a) >> 4); else input_set_abs_params(input, usage->code, a, b, 0, 0); input_abs_set_res(input, usage->code, hidinput_calc_abs_res(field, usage->code)); /* use a larger default input buffer for MT devices */ if (usage->code == ABS_MT_POSITION_X && input->hint_events_per_packet == 0) input_set_events_per_packet(input, 60); } if (usage->type == EV_ABS && (usage->hat_min < usage->hat_max || usage->hat_dir)) { int i; for (i = usage->code; i < usage->code + 2 && i <= max; i++) { input_set_abs_params(input, i, -1, 1, 0, 0); set_bit(i, input->absbit); } if (usage->hat_dir && !field->dpad) field->dpad = usage->code; } ... ignore: return; }
USAGE_PAGE为Button对应case HID_UP_BUTTON:
code = ((usage->hid - 1) & HID_USAGE)计算出Button 1对应code=0,Button 2对应code=1,Button 3对应code=2;
然后code += BTN_MOUSE(0x110)计算出三个Button的值分别为0x110、0x111、0x112,对应linux/input.h的BTN_LEFT、BTN_RIGHT、BTN_MIDDLE
map_key(code)将usage->type设置为EV_KEY,usage->code分别设置为前面计算出的BTN_LEFT、BTN_RIGHT、BTN_MIDDLE
USAGE_PAGE为Generic Desktop对应case HID_UP_GENDESK:
USAGE的X(0x30)对应HID_GD_X、Y(0x31)对应HID_GD_Y、Wheel(0x38)对应HID_GD_WHEEL
调用map_rel(usage->hid & 0xf)指定usage->type为EV_REL,usage->code分别为REL_X: 0x0(0x30 & 0xf)、REL_Y: 0x1(0x31 & 0xf)、REL_WHEEL: 0x8(0x38 & 0xf)
#define map_abs(c) hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c)) #define map_rel(c) hid_map_usage(hidinput, usage, &bit, &max, EV_REL, (c)) #define map_key(c) hid_map_usage(hidinput, usage, &bit, &max, EV_KEY, (c)) #define map_led(c) hid_map_usage(hidinput, usage, &bit, &max, EV_LED, (c)) static inline void hid_map_usage(struct hid_input *hidinput, struct hid_usage *usage, unsigned long **bit, int *max, __u8 type, __u16 c) { struct input_dev *input = hidinput->input; usage->type = type; usage->code = c; switch (type) { case EV_ABS: *bit = input->absbit; *max = ABS_MAX; break; case EV_REL: *bit = input->relbit; *max = REL_MAX; break; case EV_KEY: *bit = input->keybit; *max = KEY_MAX; break; case EV_LED: *bit = input->ledbit; *max = LED_MAX; break; } }
最后再贴一个logitech手柄的hid report descriptor
0x05, 0x01, /* Usage Page (Desktop), */ 0x09, 0x05, /* Usage (Gamepad), */ 0xA1, 0x01, /* Collection (Application), */ 0xA1, 0x02, /* Collection (Logical), */ 0x85, 0x01, /* Report ID (1), */ 0x75, 0x08, /* Report Size (8), */ 0x95, 0x04, /* Report Count (4), */ 0x15, 0x00, /* Logical Minimum (0), */ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ 0x35, 0x00, /* Physical Minimum (0), */ 0x46, 0xFF, 0x00, /* Physical Maximum (255), */ 0x09, 0x30, /* Usage (X), */ 0x09, 0x31, /* Usage (Y), */ 0x09, 0x32, /* Usage (Z), */ 0x09, 0x35, /* Usage (Rz), */ 0x81, 0x02, /* Input (Variable), */ 0x75, 0x04, /* Report Size (4), */ 0x95, 0x01, /* Report Count (1), */ 0x25, 0x07, /* Logical Maximum (7), */ 0x46, 0x3B, 0x01, /* Physical Maximum (315), */ 0x66, 0x14, 0x00, /* Unit (Degrees), */ 0x09, 0x39, /* Usage (Hat Switch), */ 0x81, 0x42, /* Input (Variable, Null State), */ 0x66, 0x00, 0x00, /* Unit, */ 0x75, 0x01, /* Report Size (1), */ 0x95, 0x0C, /* Report Count (12), */ 0x25, 0x01, /* Logical Maximum (1), */ 0x45, 0x01, /* Physical Maximum (1), */ 0x05, 0x09, /* Usage Page (Button), */ 0x19, 0x01, /* Usage Minimum (01h), */ 0x29, 0x0C, /* Usage Maximum (0Ch), */ 0x81, 0x02, /* Input (Variable), */ 0x95, 0x01, /* Report Count (1), */ 0x75, 0x08, /* Report Size (8), */ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ 0x46, 0xFF, 0x00, /* Physical Maximum (255), */ 0x09, 0x00, /* Usage (00h), */ 0x81, 0x02, /* Input (Variable), */ 0xC0, /* End Collection, */ 0xA1, 0x02, /* Collection (Logical), */ 0x85, 0x02, /* Report ID (2), */ 0x95, 0x07, /* Report Count (7), */ 0x75, 0x08, /* Report Size (8), */ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ 0x46, 0xFF, 0x00, /* Physical Maximum (255), */ 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ 0x09, 0x03, /* Usage (03h), */ 0x81, 0x02, /* Input (Variable), */ 0xC0, /* End Collection, */ 0xA1, 0x02, /* Collection (Logical), */ 0x85, 0x03, /* Report ID (3), */ 0x09, 0x04, /* Usage (04h), */ 0x91, 0x02, /* Output (Variable), */ 0xC0, /* End Collection, */ 0xC0 /* End Collection */
hid report descriptor查看工具:
https://github.com/jfojfo/usbhid-dump
https://github.com/jfojfo/hidrd