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

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

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;icollection[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;imaxcollection;i++){
structhid_collection*col=&hdev->collection[i];
if(col->type==HID_COLLECTION_APPLICATION&&
(col->usage&HID_USAGE_PAGE)==HID_UP_GENDESK&&
(col->usage&0xffff)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:
然后為板卡接入了兩個hid觸摸面板,如果HID觸摸面板識別成功,在hid-multitouch目錄下生成了對應的設備節點鏈接:
經驗證,內核對目前手里的兩塊hid觸摸面板的數據解析正常,觸摸事件上報正常。
-
內核
+關注
關注
4文章
1466瀏覽量
42769 -
Linux
+關注
關注
88文章
11746瀏覽量
218874 -
驅動程序
+關注
關注
19文章
869瀏覽量
50318 -
HID
+關注
關注
2文章
139瀏覽量
48835 -
觸摸面板
+關注
關注
0文章
16瀏覽量
13800
原文標題:原來linux內核對觸摸的支持這么溜
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
基于Linux內核輸入子系統的驅動研究
如何使用Linux內核實現USB驅動程序框架
什么是通用HID燈鎮流器
linux內核中通用HID觸摸驅動
評論