一、内核中通用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条评论
快来发表一下你的评论吧 !