向/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