一、内核中通用hid触摸驱动
在linux内核中,为HID触摸面板实现了一个通用的驱动程序,位于/drivers/hid/hid-multitouch.c文件中。hid触摸驱动是以struct hid_driver实现,首先定义一个描述hid触摸驱动的结构mt_driver:
static struct hid_driver mt_driver = { .name = "hid-multitouch", .id_table = mt_devices, .probe = mt_probe, .remove = mt_remove, .input_mapping = mt_input_mapping, .input_mapped = mt_input_mapped, .input_configured = mt_input_configured, .feature_mapping = mt_feature_mapping, .usage_table = mt_grabbed_usages, .event = mt_event, .report = mt_report, .suspend = pm_ptr(mt_suspend), .reset_resume = pm_ptr(mt_reset_resume), .resume = pm_ptr(mt_resume), };
并实现了struct hid_driver结构中关键的函数。接着使用module_hdi_driver()将该驱动以模块方式构建:
module_hid_driver(mt_driver);
mt_devices是一个truct hid_device_id类型的数组,定义了hid备的设备参数,这些参数根据不同的厂家进行划分,划分的准则符合USB-HID协议,例如(
static const struct hid_device_id mt_devices[] = { /* 3M panels */ { .driver_data = MT_CLS_3M, MT_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) }, { .driver_data = MT_CLS_3M, MT_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) }, { .driver_data = MT_CLS_3M, MT_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M3266) }, /* Anton devices */ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, MT_USB_DEVICE(USB_VENDOR_ID_ANTON, USB_DEVICE_ID_ANTON_TOUCH_PAD) }, /* Asus T101HA */ { .driver_data = MT_CLS_WIN_8_DISABLE_WAKEUP, HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) }, /* 省略大量内容 */ }
上述元素实则是填充struct hid_device_id的各个元素,HID_DEVICE宏包装对.bus、.group、.vendor、.product赋值操作:
在mt_devices数组中,直接使用HID_DEVICE以及衍生宏为其各个字段赋值。
二、probe过程剖析
从struct hid_driver mt_driver可以知道mt_drvier的.probe为mt_probe():
static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret, i; struct mt_device *td; const struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */ for (i = 0; mt_classes[i].name ; i++) { if (id->driver_data == mt_classes[i].name) { mtclass = &(mt_classes[i]); break; } } td = devm_kzalloc(&hdev->dev, sizeof(struct mt_device), GFP_KERNEL); if (!td) { dev_err(&hdev->dev, "cannot allocate multitouch data "); return -ENOMEM; } td->hdev = hdev; td->mtclass = *mtclass; td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; hid_set_drvdata(hdev, td); INIT_LIST_HEAD(&td->applications); INIT_LIST_HEAD(&td->reports); if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID) td->serial_maybe = true; /* Orientation is inverted if the X or Y axes are * flipped, but normalized if both are inverted. */ if (hdev->quirks & (HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT) && !((hdev->quirks & HID_QUIRK_X_INVERT) && (hdev->quirks & HID_QUIRK_Y_INVERT))) td->mtclass.quirks = MT_QUIRK_ORIENTATION_INVERT; /* This allows the driver to correctly support devices * that emit events over several HID messages. */ hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC; /* * This allows the driver to handle different input sensors * that emits events through different applications on the same HID * device. */ hdev->quirks |= HID_QUIRK_INPUT_PER_APP; if (id->group != HID_GROUP_MULTITOUCH_WIN_8) hdev->quirks |= HID_QUIRK_MULTI_INPUT; if (mtclass->quirks & MT_QUIRK_FORCE_MULTI_INPUT) { hdev->quirks &= ~HID_QUIRK_INPUT_PER_APP; hdev->quirks |= HID_QUIRK_MULTI_INPUT; } timer_setup(&td->release_timer, mt_expired_timeout, 0); ret = hid_parse(hdev); if (ret != 0) return ret; if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) mt_fix_const_fields(hdev, HID_DG_CONTACTID); ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) return ret; ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); if (ret) dev_warn(&hdev->dev, "Cannot allocate sysfs group for %s ", hdev->name); mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); return 0; }
1、首先定义了一个结构体指针 td,用于存储多点触摸设备的数据。
2、使用 devm_kzalloc 分配一个 mt_device 结构体大小的内存,并初始化相关字段。如果内存分配失败,则返回错误码 -ENOMEM。
3、调用hid_set_drvdata()设置多点触摸设备的数据指针,以便后续可以在其他函数中访问到该设备的数据。
4、初始化设备数据结构中的链表头,用于管理多点触摸应用程序和报告。
5、根据设备的特性和属性设置一些HID属性。例如:根据设备的 ID 和 HID 类别设置了一些特殊的属性和标志。
6、使用 timer_setup 函数初始化了一个定时器,用于处理触摸设备的释放操作。
7、调用 hid_parse() 函数解析设备的报告描述符,并进行相应的初始化。
8、 如果设备具有特定的修复需求,例如修复常量接触 ID 的问题,则调用 mt_fix_const_fields() 函数进行修复。
9、调用hid_hw_start()函数启动设备的硬件,并指定默认连接模式。
10、调用sysfs_create_group()函数创建sysfs组,以便在sysfs中创建设备属性。
11、调用mt_set_modes()函数设置设备的模式,包括延迟模式和输入模式。
12、如果一切顺利,返回 0 表示成功,否则返回相应的错误码。
总而言之,mt_probe()是一个用于初始化和配置多点触摸设备的函数,它会根据设备的特性和属性进行相应的设置,并启动设备的硬件以及创建相应的 sysfs 属性组。
(1)hid_parse()函数
hid_parse()实现在/include/linux/hid.h中,本质上是调用hid_open_report()解析HW的report:
hid_open_report()实现如下:
int hid_open_report(struct hid_device *device) { struct hid_parser *parser; struct hid_item item; unsigned int size; __u8 *start; __u8 *buf; __u8 *end; __u8 *next; int ret; int i; static int (*dispatch_type[])(struct hid_parser *parser, struct hid_item *item) = { hid_parser_main, hid_parser_global, hid_parser_local, hid_parser_reserved }; if (WARN_ON(device->status & HID_STAT_PARSED)) return -EBUSY; start = device->dev_rdesc; if (WARN_ON(!start)) return -ENODEV; size = device->dev_rsize; /* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */ buf = call_hid_bpf_rdesc_fixup(device, start, &size); if (buf == NULL) return -ENOMEM; if (device->driver->report_fixup) start = device->driver->report_fixup(device, buf, &size); else start = buf; start = kmemdup(start, size, GFP_KERNEL); kfree(buf); if (start == NULL) return -ENOMEM; device->rdesc = start; device->rsize = size; parser = vzalloc(sizeof(struct hid_parser)); if (!parser) { ret = -ENOMEM; goto alloc_err; } parser->device = device; end = start + size; device->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS, sizeof(struct hid_collection), GFP_KERNEL); if (!device->collection) { ret = -ENOMEM; goto err; } device->collection_size = HID_DEFAULT_NUM_COLLECTIONS; for (i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++) device->collection[i].parent_idx = -1; ret = -EINVAL; while ((next = fetch_item(start, end, &item)) != NULL) { start = next; if (item.format != HID_ITEM_FORMAT_SHORT) { hid_err(device, "unexpected long global item "); goto err; } if (dispatch_type[item.type](parser, &item)) { hid_err(device, "item %u %u %u %u parsing failed ", item.format, (unsigned)item.size, (unsigned)item.type, (unsigned)item.tag); goto err; } if (start == end) { if (parser->collection_stack_ptr) { hid_err(device, "unbalanced collection at end of report description "); goto err; } if (parser->local.delimiter_depth) { hid_err(device, "unbalanced delimiter at end of report description "); goto err; } /* * fetch initial values in case the device's * default multiplier isn't the recommended 1 */ hid_setup_resolution_multiplier(device); kfree(parser->collection_stack); vfree(parser); device->status |= HID_STAT_PARSED; return 0; } } hid_err(device, "item fetching failed at offset %u/%u ", size - (unsigned int)(end - start), size); err: kfree(parser->collection_stack); alloc_err: vfree(parser); hid_close_report(device); return ret; }
上述函数遍历hid数据,然后调用dispatch_type函数指针数组指定的四个函数解析HID report:
1、hid_parser_main():解析main Item。
2、hid_parser_global():解析Global Item。
3、hid_parser_local():解析local Item。
4、hid_parser_reserved():解析预留Item。
(2)hid_hw_start()函数
hid_hw_start()用于开始一个底层HID硬件:
int hid_hw_start(struct hid_device *hdev, unsigned int connect_mask) { int error; error = hdev->ll_driver->start(hdev); if (error) return error; if (connect_mask) { error = hid_connect(hdev, connect_mask); if (error) { hdev->ll_driver->stop(hdev); return error; } } return 0; }
1、首先调用hdev->ll_driver->start(hdev),hdev 是一个指向 hid_device 结构体的指针,ll_driver 则是指向底层驱动的指针。这行代码调用了底层驱动中的start 函数,启动了 HID 设备的硬件。如果启动失败,start 函数可能会返回一个错误码。
2、接着检查 connect_mask是否为非零值,如果connect_mask不为零,表示需要连接 HID 设备的某些部分。调用 hid_connect()函数连接 HID 设备,并将connect_mask 作为参数传递给它。如果连接失败,hid_connect` 函数可能会返回一个错误码。
3、如果上述步骤2中出现了错误,则调用hdev->ll_driver->stop(hdev),停止HID设备的硬件,然后函数返回之前发生的错误码。
4、如果启动和连接都成功,函数返回0,表示 HID 设备已经成功启动并连接。
(3)hid_connect()函数
hid_connect()实现如下:
int hid_connect(struct hid_device *hdev, unsigned int connect_mask) { static const char *types[] = { "Device", "Pointer", "Mouse", "Device", "Joystick", "Gamepad", "Keyboard", "Keypad", "Multi-Axis Controller" }; const char *type, *bus; char buf[64] = ""; unsigned int i; int len; int ret; //连接 HID 设备到 BPF(Berkeley Packet Filter)。如果连接失败,则返回相应的错误码。 ret = hid_bpf_connect_device(hdev); if (ret) return ret; //根据设备的特性和属性,设置了一些连接标志位 if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE) connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV); if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE) connect_mask |= HID_CONNECT_HIDINPUT_FORCE; if (hdev->bus != BUS_USB) connect_mask &= ~HID_CONNECT_HIDDEV; if (hid_hiddev(hdev)) connect_mask |= HID_CONNECT_HIDDEV_FORCE; if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev, connect_mask & HID_CONNECT_HIDINPUT_FORCE)) hdev->claimed |= HID_CLAIMED_INPUT; if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect && !hdev->hiddev_connect(hdev, connect_mask & HID_CONNECT_HIDDEV_FORCE)) hdev->claimed |= HID_CLAIMED_HIDDEV; if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev)) hdev->claimed |= HID_CLAIMED_HIDRAW; if (connect_mask & HID_CONNECT_DRIVER) hdev->claimed |= HID_CLAIMED_DRIVER; /* Drivers with the ->raw_event callback set are not required to connect * to any other listener. */ if (!hdev->claimed && !hdev->driver->raw_event) { hid_err(hdev, "device has no listeners, quitting "); return -ENODEV; } //处理设备的数据报文顺序 hid_process_ordering(hdev); //如果设备被输入子系统声明,并且需要连接到力反馈(Force Feedback),则调用设备的力反馈初始化函数。 if ((hdev->claimed & HID_CLAIMED_INPUT) && (connect_mask & HID_CONNECT_FF) && hdev->ff_init) hdev->ff_init(hdev); len = 0; if (hdev->claimed & HID_CLAIMED_INPUT) len += sprintf(buf + len, "input"); if (hdev->claimed & HID_CLAIMED_HIDDEV) len += sprintf(buf + len, "%shiddev%d", len ? "," : "", ((struct hiddev *)hdev->hiddev)->minor); if (hdev->claimed & HID_CLAIMED_HIDRAW) len += sprintf(buf + len, "%shidraw%d", len ? "," : "", ((struct hidraw *)hdev->hidraw)->minor); type = "Device"; for (i = 0; i < hdev->maxcollection; i++) { struct hid_collection *col = &hdev->collection[i]; if (col->type == HID_COLLECTION_APPLICATION && (col->usage & HID_USAGE_PAGE) == HID_UP_GENDESK && (col->usage & 0xffff) < ARRAY_SIZE(types)) { type = types[col->usage & 0xffff]; break; } } switch (hdev->bus) { case BUS_USB: bus = "USB"; break; case BUS_BLUETOOTH: bus = "BLUETOOTH"; break; case BUS_I2C: bus = "I2C"; break; case BUS_VIRTUAL: bus = "VIRTUAL"; break; case BUS_INTEL_ISHTP: case BUS_AMD_SFH: bus = "SENSOR HUB"; break; default: bus = ""; } //创建设备的sysfs文件。 ret = device_create_file(&hdev->dev, &dev_attr_country); if (ret) hid_warn(hdev, "can't create sysfs country code attribute err: %d ", ret); //通过hid_info()函数打印设备的连接信息。 hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s ", buf, bus, hdev->version >> 8, hdev->version & 0xff, type, hdev->name, hdev->phys); return 0; }
三、hid-multitouch.c应用场景
笔者最近需要通过usb方式接入触摸面板,且该触摸面板满足hid协议,故而使用了内核提供的hid-multitouch.c:
然后为板卡接入了两个hid触摸面板,如果HID触摸面板识别成功,在hid-multitouch目录下生成了对应的设备节点链接:
经验证,内核对目前手里的两块hid触摸面板的数据解析正常,触摸事件上报正常。
全部0条评论
快来发表一下你的评论吧 !