深度解析linux HID核心

描述

一、hid核心初始化

在linux内核中,HID核心是完成HID功能的关键组件,如果内核支持HID,在启动过程中,则会对HID进行初始化,完成该操作的函数是hid_init(),实现在/drivers/hid/hid-core.c中:

 

static int __init hid_init(void)
{
 int ret;

 ret = bus_register(&hid_bus_type);
 if (ret) {
  pr_err("can't register hid bus
");
  goto err;
 }

#ifdef CONFIG_HID_BPF
 hid_bpf_ops = &hid_ops;
#endif

 ret = hidraw_init();
 if (ret)
  goto err_bus;

 hid_debug_init();

 return 0;
err_bus:
 bus_unregister(&hid_bus_type);
err:
 return ret;
}

 

(1)调用bus_register()注册hid总线,在总线类型定义中指定了总线名称、dev_groups、drv_groups、.match、.probe、.remove和.uevent:

 

const struct bus_type hid_bus_type = {
 .name  = "hid",
 .dev_groups = hid_dev_groups,
 .drv_groups = hid_drv_groups,
 .match  = hid_bus_match,
 .probe  = hid_device_probe,
 .remove  = hid_device_remove,
 .uevent  = hid_uevent,
};

 

(2)调用hidraw_init()初始化hidraw模块支持,hidraw模块提供了对hid原始数据的直接访问接口。

(3)调用hid_debug_init()创建debugfs中的调试条目hid。

上述则是hid初始化的具体步骤,以模块的方式构建进内核,在内核启动过程中自动完成。

二、hid总线probe过程分析

从hid_bus_type总线可以知道,hid总线的probe是hid_device_probe(),函数定义如下:

 

static int hid_device_probe(struct device *dev)
{
 struct hid_device *hdev = to_hid_device(dev);
 struct hid_driver *hdrv = to_hid_driver(dev->driver);
 int ret = 0;

 if (down_interruptible(&hdev->driver_input_lock))
  return -EINTR;

 hdev->io_started = false;
 clear_bit(ffs(HID_STAT_REPROBED), &hdev->status);

 if (!hdev->driver)
  ret = __hid_device_probe(hdev, hdrv);

 if (!hdev->io_started)
  up(&hdev->driver_input_lock);

 return ret;
}

 

上述函数将对HID设备进行探测,如果设备没有驱动程序,则尝试调用 __hid_device_probe() 函数来进行探测。__hid_device_probe()实现如下:

 

static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
{
 const struct hid_device_id *id;
 int ret;

 if (!hid_check_device_match(hdev, hdrv, &id))
  return -ENODEV;

 hdev->devres_group_id = devres_open_group(&hdev->dev, NULL, GFP_KERNEL);
 if (!hdev->devres_group_id)
  return -ENOMEM;

 /* reset the quirks that has been previously set */
 hdev->quirks = hid_lookup_quirk(hdev);
 hdev->driver = hdrv;

 if (hdrv->probe) {
  ret = hdrv->probe(hdev, id);
 } else { /* default probe */
  ret = hid_open_report(hdev);
  if (!ret)
   ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
 }

 if (ret) {
  devres_release_group(&hdev->dev, hdev->devres_group_id);
  hid_close_report(hdev);
  hdev->driver = NULL;
 }

 return ret;
}

 

上述函数具体实现细节如下:

(1)调用 hid_check_device_match 函数来检查设备是否匹配给定的 HID 驱动程序。如果不匹配,则返回 -ENODEV,表示设备不存在。

(2)然后它打开了一个设备资源组(device resource group),用于管理与设备相关的资源。如果打开失败,则返回 -ENOMEM,表示内存不足。

(3)重置先前设置的 quirks(设备特性)。quirks 是用来处理一些设备特定的行为的标志。

(4)将设备的驱动程序指针设置为给定的驱动程序。

(5)如果给定的驱动程序有 probe 函数,则调用该函数进行设备探测。否则,调用默认的探测流程:先调用 hid_open_report 打开报告通道,然后调用 hid_hw_start 开始设备的硬件操作。

(6)如果探测过程中出现错误,则释放先前打开的设备资源组,关闭报告通道,并将设备的驱动程序指针设置为 NULL。

(7)最后返回探测的结果。

__hid_device_probe()主要负责设置设备的特性,打开report通道,并调用特定驱动程序的探测函数来启动该hid设备。

三、hid总线match过程分析

从hid_bus_type总线可以知道,hid总线的match是hid_bus_match(),函数定义如下:

 

static int hid_bus_match(struct device *dev, struct device_driver *drv)
{
 struct hid_driver *hdrv = to_hid_driver(drv);
 struct hid_device *hdev = to_hid_device(dev);

 return hid_match_device(hdev, hdrv) != NULL;
}

 

首先使用to_hid_driver()和to_hid_device()分别解出hid驱动hid_driver和hid设备hid_device,再调用hid_match_device()匹配设备和驱动。hid_match_device()实现如下:

 

const struct hid_device_id *hid_match_device(struct hid_device *hdev,
          struct hid_driver *hdrv)
{
 struct hid_dynid *dynid;

 spin_lock(&hdrv->dyn_lock);
 list_for_each_entry(dynid, &hdrv->dyn_list, list) {
  if (hid_match_one_id(hdev, &dynid->id)) {
   spin_unlock(&hdrv->dyn_lock);
   return &dynid->id;
  }
 }
 spin_unlock(&hdrv->dyn_lock);

 return hid_match_id(hdev, hdrv->id_table);
}

 

上述函数中,首先获取了 hdrv 的动态设备 ID 锁,并遍历 hdrv->dyn_list 中的每个动态设备 ID(通过一个名为 hid_dynid 的结构体链表实现)。在每次迭代中,调用 hid_match_one_id 函数来检查当前设备是否与当前动态设备 ID 匹配。如果匹配成功,它释放锁并返回找到的设备 ID。如果在动态设备 ID 列表中没有找到匹配的设备 ID,则释放锁,并调用 hid_match_id 函数来尝试在静态设备 ID 表中匹配设备。最终,hid_match_device()返回匹配的设备ID,如果没有找到匹配的设备 ID,则返回 NULL。

总的来说,hid_match_device()的作用是在给定的 HID 驱动程序中查找与给定 HID 设备匹配的设备 ID,它首先搜索动态设备 ID 列表,然后再搜索静态设备 ID 表。

四、hid总线的uevent过程

从hid_bus_type总线可以知道,hid总线对uevent的处理由hid_uevent()完成:

 

static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
 const struct hid_device *hdev = to_hid_device(dev);

 if (add_uevent_var(env, "HID_ID=%04X:%08X:%08X",
   hdev->bus, hdev->vendor, hdev->product))
  return -ENOMEM;

 if (add_uevent_var(env, "HID_NAME=%s", hdev->name))
  return -ENOMEM;

 if (add_uevent_var(env, "HID_PHYS=%s", hdev->phys))
  return -ENOMEM;

 if (add_uevent_var(env, "HID_UNIQ=%s", hdev->uniq))
  return -ENOMEM;

 if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X",
      hdev->bus, hdev->group, hdev->vendor, hdev->product))
  return -ENOMEM;

 return 0;
}

 

上述代码用于解析 HID(Human Interface Device,人机接口设备)相关的信息,并将这些信息填充到内核对象环境(struct kobj_uevent_env)中。这个函数可能在系统中发生 HID 设备事件时被调用,比如当连接了新的 HID 设备或移除了现有的 HID 设备时。具体步骤如下:

const struct hid_device *hdev = to_hid_device(dev);从给定的 device 结构指针 dev 中提取 hid_device 结构指针。通过使用 add_uevent_var函数向内核对象 uevent 环境中添加各种 HID 相关信息,该函数用于向环境中添加键值对,如果 add_uevent_var 失败(返回非零值),则返回 -ENOMEM,表示内存分配失败。

HID_ID=%04X:%08X:%08X:添加带有总线、厂商和产品 ID 的 HID ID 信息。

HID_NAME=%s:添加 HID 设备名称。

HID_PHYS=%s:添加 HID 设备的物理位置。

HID_UNIQ=%s:添加 HID 设备的唯一标识符。

MODALIAS=hid:b%04Xg%04Xv%08Xp%08X:添加 MODALIAS 字符串,用于 Linux 中的设备识别和驱动加载。它包括总线、组、厂商和产品。

五、usbhid驱动分析

在/drivers/hid/usbhid/路径下同样存在一个hid-core.c文件,从名称上很容易与/drivers/hid/hid-core.c混淆,但也不知道为什么内核中要用这个名称来命名。从该文件中代码可以知道,该份源码本质上是usbhid的驱动实现:

 

static int __init hid_init(void)
{
 int retval = -ENOMEM;

 retval = usbhid_quirks_init(quirks_param);
 if (retval)
  goto usbhid_quirks_init_fail;
 retval = usb_register(&hid_driver);
 if (retval)
  goto usb_register_fail;
 printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "
");

 return 0;
usb_register_fail:
 usbhid_quirks_exit();
usbhid_quirks_init_fail:
 return retval;
}

static void __exit hid_exit(void)
{
 usb_deregister(&hid_driver);
 usbhid_quirks_exit();
}

module_init(hid_init);
module_exit(hid_exit);

 

在hid_init()中完成了以下操作:

1、调用 usbhid_quirks_init() 函数来初始化 USB HID 设备的特殊处理,quirks_param 是参数,用来传递特定的配置或修正信息。

2、注册 HID 驱动程序。hid_driver 是一个结构体,它包含了驱动程序的相关信息,usb_register() 函数将这个驱动程序注册到 USB 子系统中。

(1)struct hid_driver

struct hid_driver实现如下:

 

static struct usb_driver hid_driver = {
 .name =  "usbhid",
 .probe = usbhid_probe,
 .disconnect = usbhid_disconnect,
#ifdef CONFIG_PM
 .suspend = hid_suspend,
 .resume = hid_resume,
 .reset_resume = hid_reset_resume,
#endif
 .pre_reset = hid_pre_reset,
 .post_reset = hid_post_reset,
 .id_table = hid_usb_ids,
 .supports_autosuspend = 1,
};

 

(2)usbhid的探测行为

usbhid的探测行为由usbhid_probe()完成:

 

static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
 struct usb_host_interface *interface = intf->cur_altsetting;
 struct usb_device *dev = interface_to_usbdev(intf);
 struct usbhid_device *usbhid;
 struct hid_device *hid;
 unsigned int n, has_in = 0;
 size_t len;
 int ret;

 dbg_hid("HID probe called for ifnum %d
",
   intf->altsetting->desc.bInterfaceNumber);

 for (n = 0; n < interface->desc.bNumEndpoints; n++)
  if (usb_endpoint_is_int_in(&interface->endpoint[n].desc))
   has_in++;
 if (!has_in) {
  hid_err(intf, "couldn't find an input interrupt endpoint
");
  return -ENODEV;
 }
    
//分配一个 hid_device 结构体并将其指针赋给 hid,用于表示 HID 设备。
 hid = hid_allocate_device();
 if (IS_ERR(hid))
  return PTR_ERR(hid);
        
//将 hid 结构体指针与 USB 接口相关联,以便后续可以通过接口访问 HID 设备。
 usb_set_intfdata(intf, hid);
    
//指定 hid_device 结构体的底层驱动为usb_hid_driver驱动。
 hid->ll_driver = &usb_hid_driver;
 hid->ff_init = hid_pidff_init;
#ifdef CONFIG_USB_HIDDEV
 hid->hiddev_connect = hiddev_connect;
 hid->hiddev_disconnect = hiddev_disconnect;
 hid->hiddev_hid_event = hiddev_hid_event;
 hid->hiddev_report_event = hiddev_report_event;
#endif
 hid->dev.parent = &intf->dev;
 hid->bus = BUS_USB; //指定hid设备位于usb总线上
 hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
 hid->product = le16_to_cpu(dev->descriptor.idProduct);
 hid->name[0] = 0;
 hid->quirks = usbhid_lookup_quirk(hid->vendor, hid->product);
 if (intf->cur_altsetting->desc.bInterfaceProtocol ==
   USB_INTERFACE_PROTOCOL_MOUSE)
  hid->type = HID_TYPE_USBMOUSE;
 else if (intf->cur_altsetting->desc.bInterfaceProtocol == 0)
  hid->type = HID_TYPE_USBNONE;

 if (dev->manufacturer)
  strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));

 if (dev->product) {
  if (dev->manufacturer)
   strlcat(hid->name, " ", sizeof(hid->name));
  strlcat(hid->name, dev->product, sizeof(hid->name));
 }

 if (!strlen(hid->name))
  snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",
    le16_to_cpu(dev->descriptor.idVendor),
    le16_to_cpu(dev->descriptor.idProduct));

// 通过 USB 设备获取 HID 设备的物理路径。
 usb_make_path(dev, hid->phys, sizeof(hid->phys));
 strlcat(hid->phys, "/input", sizeof(hid->phys));
 len = strlen(hid->phys);
 if (len < sizeof(hid->phys) - 1)
  snprintf(hid->phys + len, sizeof(hid->phys) - len,
    "%d", intf->altsetting[0].desc.bInterfaceNumber);

 if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)
  hid->uniq[0] = 0;

 usbhid = kzalloc(sizeof(*usbhid), GFP_KERNEL);
 if (usbhid == NULL) {
  ret = -ENOMEM;
  goto err;
 }

 hid->driver_data = usbhid;
 usbhid->hid = hid;
 usbhid->intf = intf;
 usbhid->ifnum = interface->desc.bInterfaceNumber;

 init_waitqueue_head(&usbhid->wait);
 INIT_WORK(&usbhid->reset_work, hid_reset);
 setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
 spin_lock_init(&usbhid->lock);

// 将 HID 设备添加到系统中。
 ret = hid_add_device(hid);
 if (ret) {
  if (ret != -ENODEV)
   hid_err(intf, "can't add hid device: %d
", ret);
  goto err_free;
 }

 return 0;
err_free:
 kfree(usbhid);
err:
 hid_destroy_device(hid);
 return ret;
}

 

(3)usb_hid_driver实现

 

static struct hid_ll_driver usb_hid_driver = {
 .parse = usbhid_parse, //用于解析 USB HID 设备的输入报告。
 .start = usbhid_start, //用于启动 USB HID 设备。
 .stop = usbhid_stop,  //用于停止 USB HID 设备。
 .open = usbhid_open,  //用于打开 USB HID 设备。
 .close = usbhid_close, //用于关闭 USB HID 设备。
 .power = usbhid_power, //用于控制 USB HID 设备的电源状态。
 .request = usbhid_request, //用于向 USB HID 设备发送请求。
 .wait = usbhid_wait_io, //用于等待 USB HID 设备的输入/输出操作完成。
 .raw_request = usbhid_raw_request, //用于向 USB HID 设备发送原始请求。
 .output_report = usbhid_output_report, //用于向 USB HID 设备发送输出报告。
 .idle = usbhid_idle, //用于设置 USB HID 设备的空闲状态。
};

 

struct hid_ll_driver描述了hid底层驱动具体的回调函数,在这里则实现了对于底层hid设备的具体操作实现,通过这些接口函数可以实现对 USB HID设备的控制、数据传输等功能。

上述usb_hid_driver结构中的函数指会在使用HID的API时被间接调用,例如对于.parse指定的回调函数usbhid_parse(),则会在usbhid_parse()这个接口中调用执行:

Linux

六、总结

hid核心的功能大致可总结如下:

(1)设备注册和注销:包括设备的注册、注销和初始化函数,用于将 HID 设备与对应的驱动程序关联起来,并在设备连接或断开时进行处理。

(2)事件处理:包括从 HID 设备接收事件数据的函数和处理这些事件数据的代码,这些事件数据可能来自于键盘、鼠标、游戏手柄等输入设备。

(3)设备识别与匹配:包括用于识别和匹配 HID 设备的函数,这些函数通常用于确定哪个驱动程序适合于连接到系统的特定设备。

(4)报告解析:包括解析 HID 设备发送的报告数据的函数,以便将其转换为更易于理解的格式,并将其传递给适当的用户空间或内核空间应用程序。

(5)驱动程序注册:包括注册和注销 HID 驱动程序的函数,这些函数用于将驱动程序添加到系统中以处理特定类型的 HID 设备。

(6)错误处理和调试功能:包括用于处理设备连接和通信中出现的错误以及调试信息输出的函数。

 

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

全部0条评论

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

×
20
完善资料,
赚取积分