linux内核中通用HID触摸驱动

描述

一、内核中通用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赋值操作:

Linux

在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:

Linux

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:Linux

然后为板卡接入了两个hid触摸面板,如果HID触摸面板识别成功,在hid-multitouch目录下生成了对应的设备节点链接:Linux

经验证,内核对目前手里的两块hid触摸面板的数据解析正常,触摸事件上报正常。

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分