Linux uhid分析之输入事件传递

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

report descriptor在linux内核数据结构的对应关系,可以看看下面这张图
hid_device的report_description结构图

回到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