0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

深度解析linux HID核心

嵌入式小生 来源:嵌入式小生 2024-09-29 17:04 次阅读

一、hid核心初始化

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

staticint__inithid_init(void)
{
intret;

ret=bus_register(&hid_bus_type);
if(ret){
pr_err("can'tregisterhidbus
");
gotoerr;
}

#ifdefCONFIG_HID_BPF
hid_bpf_ops=&hid_ops;
#endif

ret=hidraw_init();
if(ret)
gotoerr_bus;

hid_debug_init();

return0;
err_bus:
bus_unregister(&hid_bus_type);
err:
returnret;
}

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

conststructbus_typehid_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(),函数定义如下:

staticinthid_device_probe(structdevice*dev)
{
structhid_device*hdev=to_hid_device(dev);
structhid_driver*hdrv=to_hid_driver(dev->driver);
intret=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);

returnret;
}

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

staticint__hid_device_probe(structhid_device*hdev,structhid_driver*hdrv)
{
conststructhid_device_id*id;
intret;

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;

/*resetthequirksthathasbeenpreviouslyset*/
hdev->quirks=hid_lookup_quirk(hdev);
hdev->driver=hdrv;

if(hdrv->probe){
ret=hdrv->probe(hdev,id);
}else{/*defaultprobe*/
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;
}

returnret;
}

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

(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(),函数定义如下:

staticinthid_bus_match(structdevice*dev,structdevice_driver*drv)
{
structhid_driver*hdrv=to_hid_driver(drv);
structhid_device*hdev=to_hid_device(dev);

returnhid_match_device(hdev,hdrv)!=NULL;
}

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

conststructhid_device_id*hid_match_device(structhid_device*hdev,
structhid_driver*hdrv)
{
structhid_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);

returnhid_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()完成:

staticinthid_uevent(conststructdevice*dev,structkobj_uevent_env*env)
{
conststructhid_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;

return0;
}

上述代码用于解析 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的驱动实现:

staticint__inithid_init(void)
{
intretval=-ENOMEM;

retval=usbhid_quirks_init(quirks_param);
if(retval)
gotousbhid_quirks_init_fail;
retval=usb_register(&hid_driver);
if(retval)
gotousb_register_fail;
printk(KERN_INFOKBUILD_MODNAME":"DRIVER_DESC"
");

return0;
usb_register_fail:
usbhid_quirks_exit();
usbhid_quirks_init_fail:
returnretval;
}

staticvoid__exithid_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实现如下:

staticstructusb_driverhid_driver={
.name="usbhid",
.probe=usbhid_probe,
.disconnect=usbhid_disconnect,
#ifdefCONFIG_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()完成:

staticintusbhid_probe(structusb_interface*intf,conststructusb_device_id*id)
{
structusb_host_interface*interface=intf->cur_altsetting;
structusb_device*dev=interface_to_usbdev(intf);
structusbhid_device*usbhid;
structhid_device*hid;
unsignedintn,has_in=0;
size_tlen;
intret;

dbg_hid("HIDprobecalledforifnum%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'tfindaninputinterruptendpoint
");
return-ENODEV;
}

//分配一个hid_device结构体并将其指针赋给hid,用于表示HID设备。
hid=hid_allocate_device();
if(IS_ERR(hid))
returnPTR_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;
#ifdefCONFIG_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;
elseif(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;
gotoerr;
}

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,(unsignedlong)hid);
spin_lock_init(&usbhid->lock);

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

return0;
err_free:
kfree(usbhid);
err:
hid_destroy_device(hid);
returnret;
}

(3)usb_hid_driver实现

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

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

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

15ae12901545cf4bc689c9d425102c87.png

六、总结

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

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

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

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

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

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

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

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

    关注

    3

    文章

    1341

    浏览量

    40111
  • Linux
    +关注

    关注

    87

    文章

    11136

    浏览量

    208097
  • 函数
    +关注

    关注

    3

    文章

    4245

    浏览量

    62042

原文标题:一文探秘linux HID核心

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Zstack中串口操作的深度解析(一)

    本帖最后由 eehome 于 2013-1-5 10:06 编辑 Zstack中串口操作的深度解析(一)欢迎研究ZigBee的朋友和我交流。。。
    发表于 08-12 21:11

    I2C通信设计深度解析

    I2C通信设计深度解析
    发表于 08-12 21:31

    深度搜索Linux操作系统:系统构建和原理解析

    深度搜索Linux操作系统:系统构建和原理解析!比较好的一本Linux内核书籍,从另一个角度去解释!
    发表于 09-16 16:40

    java经典面试题深度解析

    免费视频教程:java经典面试题深度解析对于很多初学者来说,学好java在后期面试的阶段都没什么经验,为了让大家更好的了解面试相关知识,今天在这里给大家分享了一个java经典面试题深度解析
    发表于 06-20 15:16

    linux名称解析

    linux常用名称解析
    发表于 09-02 08:47

    【银杏科技ARM+FPGA双核心应用】STM32H7系列28——USB_HID

    `一、硬件平台二、实验简介 USB HID是HumanInterface Device的缩写,由其名称可以了解HID设备是直接与人交互的设备,例如键盘、鼠标与游戏杆等。不过HID设备并不一定
    发表于 04-20 17:31

    【银杏科技ARM+FPGA双核心应用】STM32H7系列37——USB_HID_FS

    `一、硬件平台二、实验简介 USB HID是HumanInterface Device的缩写,由其名称可以了解HID设备是直接与人交互的设备,例如键盘、鼠标与游戏杆等。不过HID设备并不一定
    发表于 04-30 12:12

    解析深度学习:卷积神经网络原理与视觉实践

    解析深度学习:卷积神经网络原理与视觉实践
    发表于 06-14 22:21

    功能安全---AUTOSAR架构深度解析 精选资料分享

    AUTOSAR架构深度解析本文转载于:AUTOSAR架构深度解析AUTOSAR的分层式设计,用于支持完整的软件和硬件模块的独立性(Independence),中间RTE(Runtime
    发表于 07-23 08:34

    AUTOSAR架构深度解析 精选资料推荐

    AUTOSAR架构深度解析本文转载于:AUTOSAR架构深度解析目录AUTOSAR架构深度解析A
    发表于 07-28 07:40

    AUTOSAR架构深度解析 精选资料分享

    AUTOSAR架构深度解析本文转载于:AUTOSAR架构深度解析AUTOSAR的分层式设计,用于支持完整的软件和硬件模块的独立性(Independence),中间RTE(Runtime
    发表于 07-28 07:02

    C语言深度解析

    C语言深度解析,本资料来源于网络,对C语言的学习有很大的帮助,有着较为深刻的解析,可能会对读者有一定的帮助。
    发表于 09-28 07:00

    TCL HiD系列HiD34189H电路原理图

    TCL HiD彩电电路图TCL HiD彩色电视机电路图,TCL HiD彩电图纸,TCL HiD原理图。
    发表于 05-06 13:58 335次下载
    TCL <b class='flag-5'>HiD</b>系列<b class='flag-5'>HiD</b>34189H电路原理图

    嵌入式Linux与物联网软件开发C语言内核深度解析书籍的介绍

    嵌入式Linux与物联网软件开发——C语言内核深度解析 C语言是嵌入式Linux领域的主要开发语言。对于学习嵌入式、单片机、Linux驱动开
    发表于 05-15 18:10 8次下载
    嵌入式<b class='flag-5'>Linux</b>与物联网软件开发C语言内核<b class='flag-5'>深度</b><b class='flag-5'>解析</b>书籍的介绍

    linux无法解析域名怎么办

    由于linux中没有DNS导致无法解析域名。
    发表于 05-21 09:23 2281次阅读
    <b class='flag-5'>linux</b>无法<b class='flag-5'>解析</b>域名怎么办