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

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

3天内不再提示

linux内核中通用HID触摸驱动

嵌入式小生 来源:嵌入式小生 2024-10-29 10:55 次阅读

一、内核中通用hid触摸驱动

linux内核中,为HID触摸面板实现了一个通用的驱动程序,位于/drivers/hid/hid-multitouch.c文件中。hid触摸驱动是以struct hid_driver实现,首先定义一个描述hid触摸驱动的结构mt_driver:

staticstructhid_drivermt_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协议,例如(

staticconststructhid_device_idmt_devices[]={

/*3Mpanels*/
{.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)},

/*Antondevices*/
{.driver_data=MT_CLS_EXPORT_ALL_INPUTS,
MT_USB_DEVICE(USB_VENDOR_ID_ANTON,
USB_DEVICE_ID_ANTON_TOUCH_PAD)},

/*AsusT101HA*/
{.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赋值操作:

4ed84076-90d2-11ef-a511-92fbcf53809c.png

在mt_devices数组中,直接使用HID_DEVICE以及衍生宏为其各个字段赋值。

二、probe过程剖析

从struct hid_driver mt_driver可以知道mt_drvier的.probe为mt_probe():

staticintmt_probe(structhid_device*hdev,conststructhid_device_id*id)
{
intret,i;
structmt_device*td;
conststructmt_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(structmt_device),GFP_KERNEL);
if(!td){
dev_err(&hdev->dev,"cannotallocatemultitouchdata
");
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;


/*OrientationisinvertediftheXorYaxesare
*flipped,butnormalizedifbothareinverted.
*/
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;

/*Thisallowsthedrivertocorrectlysupportdevices
*thatemiteventsoverseveralHIDmessages.
*/
hdev->quirks|=HID_QUIRK_NO_INPUT_SYNC;

/*
*Thisallowsthedrivertohandledifferentinputsensors
*thatemitseventsthroughdifferentapplicationsonthesameHID
*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)
returnret;

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)
returnret;

ret=sysfs_create_group(&hdev->dev.kobj,&mt_attribute_group);
if(ret)
dev_warn(&hdev->dev,"Cannotallocatesysfsgroupfor%s
",
hdev->name);

mt_set_modes(hdev,HID_LATENCY_NORMAL,true,true);

return0;
}

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:

4f10c810-90d2-11ef-a511-92fbcf53809c.png

hid_open_report()实现如下:

inthid_open_report(structhid_device*device)
{
structhid_parser*parser;
structhid_itemitem;
unsignedintsize;
__u8*start;
__u8*buf;
__u8*end;
__u8*next;
intret;
inti;
staticint(*dispatch_type[])(structhid_parser*parser,
structhid_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()ensuresweworkonacopyofrdesc*/
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(structhid_parser));
if(!parser){
ret=-ENOMEM;
gotoalloc_err;
}

parser->device=device;

end=start+size;

device->collection=kcalloc(HID_DEFAULT_NUM_COLLECTIONS,
sizeof(structhid_collection),GFP_KERNEL);
if(!device->collection){
ret=-ENOMEM;
gotoerr;
}
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,"unexpectedlongglobalitem
");
gotoerr;
}

if(dispatch_type[item.type](parser,&item)){
hid_err(device,"item%u%u%u%uparsingfailed
",
item.format,(unsigned)item.size,
(unsigned)item.type,(unsigned)item.tag);
gotoerr;
}

if(start==end){
if(parser->collection_stack_ptr){
hid_err(device,"unbalancedcollectionatendofreportdescription
");
gotoerr;
}
if(parser->local.delimiter_depth){
hid_err(device,"unbalanceddelimiteratendofreportdescription
");
gotoerr;
}

/*
*fetchinitialvaluesincasethedevice's
*defaultmultiplierisn'ttherecommended1
*/
hid_setup_resolution_multiplier(device);

kfree(parser->collection_stack);
vfree(parser);
device->status|=HID_STAT_PARSED;

return0;
}
}

hid_err(device,"itemfetchingfailedatoffset%u/%u
",
size-(unsignedint)(end-start),size);
err:
kfree(parser->collection_stack);
alloc_err:
vfree(parser);
hid_close_report(device);
returnret;
}

上述函数遍历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硬件:

inthid_hw_start(structhid_device*hdev,unsignedintconnect_mask)
{
interror;

error=hdev->ll_driver->start(hdev);
if(error)
returnerror;

if(connect_mask){
error=hid_connect(hdev,connect_mask);
if(error){
hdev->ll_driver->stop(hdev);
returnerror;
}
}

return0;
}

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

inthid_connect(structhid_device*hdev,unsignedintconnect_mask)
{
staticconstchar*types[]={"Device","Pointer","Mouse","Device",
"Joystick","Gamepad","Keyboard","Keypad",
"Multi-AxisController"
};
constchar*type,*bus;
charbuf[64]="";
unsignedinti;
intlen;
intret;

//连接HID设备到BPF(BerkeleyPacketFilter)。如果连接失败,则返回相应的错误码。
ret=hid_bpf_connect_device(hdev);
if(ret)
returnret;

//根据设备的特性和属性,设置了一些连接标志位
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;

/*Driverswiththe->raw_eventcallbacksetarenotrequiredtoconnect
*toanyotherlistener.*/
if(!hdev->claimed&&!hdev->driver->raw_event){
hid_err(hdev,"devicehasnolisteners,quitting
");
return-ENODEV;
}

//处理设备的数据报文顺序
hid_process_ordering(hdev);

//如果设备被输入子系统声明,并且需要连接到力反馈(ForceFeedback),则调用设备的力反馈初始化函数。
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?",":"",
((structhiddev*)hdev->hiddev)->minor);
if(hdev->claimed&HID_CLAIMED_HIDRAW)
len+=sprintf(buf+len,"%shidraw%d",len?",":"",
((structhidraw*)hdev->hidraw)->minor);

type="Device";
for(i=0;i< hdev->maxcollection;i++){
structhid_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){
caseBUS_USB:
bus="USB";
break;
caseBUS_BLUETOOTH:
bus="BLUETOOTH";
break;
caseBUS_I2C:
bus="I2C";
break;
caseBUS_VIRTUAL:
bus="VIRTUAL";
break;
caseBUS_INTEL_ISHTP:
caseBUS_AMD_SFH:
bus="SENSORHUB";
break;
default:
bus="";
}

//创建设备的sysfs文件。
ret=device_create_file(&hdev->dev,&dev_attr_country);
if(ret)
hid_warn(hdev,
"can'tcreatesysfscountrycodeattributeerr:%d
",ret);

//通过hid_info()函数打印设备的连接信息。
hid_info(hdev,"%s:%sHIDv%x.%02x%s[%s]on%s
",
buf,bus,hdev->version>>8,hdev->version&0xff,
type,hdev->name,hdev->phys);

return0;
}

三、hid-multitouch.c应用场景

笔者最近需要通过usb方式接入触摸面板,且该触摸面板满足hid协议,故而使用了内核提供的hid-multitouch.c:4f44a5cc-90d2-11ef-a511-92fbcf53809c.png

然后为板卡接入了两个hid触摸面板,如果HID触摸面板识别成功,在hid-multitouch目录下生成了对应的设备节点链接:4f700442-90d2-11ef-a511-92fbcf53809c.png

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

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

    关注

    3

    文章

    1372

    浏览量

    40281
  • Linux
    +关注

    关注

    87

    文章

    11294

    浏览量

    209341
  • 驱动程序
    +关注

    关注

    19

    文章

    831

    浏览量

    48026
  • HID
    HID
    +关注

    关注

    2

    文章

    130

    浏览量

    46606
  • 触摸面板
    +关注

    关注

    0

    文章

    15

    浏览量

    13558

原文标题:原来linux内核对触摸的支持这么溜

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

收藏 人收藏

    评论

    相关推荐

    Linux clock子系统及驱动实例

    Linux驱动,操作时钟只需要简单调用内核提供的通用接口即可,clock驱动通常是由芯片厂商
    发表于 05-31 16:10 812次阅读
    <b class='flag-5'>Linux</b> clock子系统及<b class='flag-5'>驱动</b>实例

    Linux内核container_of原理详解

    Linux内核中经常可见container_of的身影,它在实际驱动的编写也是广泛应用。
    发表于 07-14 15:19 312次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>中</b>container_of原理详解

    #硬声创作季 #LinuxLinux-4.25.7 触摸驱动文件添加到Linux内核

    LinuxLINUX内核
    水管工
    发布于 :2022年11月10日 21:51:29

    Linux内核添加wifi驱动

    Linux内核添加wifi驱动Linux WIFI驱动实验rtl8723 Wifi联网测试
    发表于 02-05 07:59

    LINUX内核驱动第三版(中文)

    LINUX内核驱动第三版(中文)
    发表于 03-11 09:18 0次下载

    基于Linux内核输入子系统的驱动研究

    Linux因其完全开放的特性和稳定优良的性能深受欢迎,当推出了内核输入子系统后,更方便了嵌入式领域的驱动开放。介绍了Linux的设备驱动基础
    发表于 09-12 16:38 23次下载

    linux内核驱动第三版

    电子发烧友网站提供《linux内核驱动第三版.txt》资料免费下载
    发表于 04-04 23:40 0次下载

    linux2.6内核设备驱动模型精华

    linux 内核驱动部分详解
    发表于 04-27 10:43 20次下载

    Linux内核输入子系统的驱动研究

    Linux内核输入子系统的驱动研究
    发表于 10-31 14:41 14次下载
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>输入子系统的<b class='flag-5'>驱动</b>研究

    如何使用Linux内核实现USB驱动程序框架

    Linux内核提供了完整的USB驱动程序框架。USB总线采用树形结构,在一条总线上只能有唯一的主机设备。 Linux内核从主机和设备两个角度
    发表于 11-06 17:59 20次下载
    如何使用<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>实现USB<b class='flag-5'>驱动</b>程序框架

    Linux内核代码60%都是驱动

    为什么Linux内核代码60%都是驱动? 如果每支持新的设备就加入驱动内核会不会变得越来越臃肿?
    的头像 发表于 07-11 11:48 938次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>代码60%都是<b class='flag-5'>驱动</b>?

    linux内核的driver_register介绍

    linux内核注册驱动由driver_register()完成。它将驱动程序的信息添加到内核驱动
    的头像 发表于 07-14 09:17 2825次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>内核</b><b class='flag-5'>中</b>的driver_register介绍

    什么是通用HID灯镇流器

    荧光灯镇流器中有一种典型的简单高频驱动方法,但是具有高弧光管压力的 HID 灯存在声共振现象(*) 的问题。为了避免这个问题,HID 灯需要以低于 1kHz 的低频或直流驱动
    发表于 02-02 17:05 617次阅读
    什么是<b class='flag-5'>通用</b><b class='flag-5'>HID</b>灯镇流器

    linux驱动程序如何加载进内核

    Linux系统驱动程序是内核与硬件设备之间的桥梁。它们允许内核与硬件设备进行通信,从而实现对硬件设备的控制和管理。
    的头像 发表于 08-30 15:02 444次阅读

    深度解析linux HID核心

    linux内核HID核心是完成HID功能的关键组件,如果内核支持
    的头像 发表于 09-29 17:04 424次阅读
    深度解析<b class='flag-5'>linux</b> <b class='flag-5'>HID</b>核心