一、內(nèi)核中通用hid觸摸驅(qū)動(dòng)
在linux內(nèi)核中,為HID觸摸面板實(shí)現(xiàn)了一個(gè)通用的驅(qū)動(dòng)程序,位于/drivers/hid/hid-multitouch.c文件中。hid觸摸驅(qū)動(dòng)是以struct hid_driver實(shí)現(xiàn),首先定義一個(gè)描述hid觸摸驅(qū)動(dòng)的結(jié)構(gòu)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), };
并實(shí)現(xiàn)了struct hid_driver結(jié)構(gòu)中關(guān)鍵的函數(shù)。接著使用module_hdi_driver()將該驅(qū)動(dòng)以模塊方式構(gòu)建:
module_hid_driver(mt_driver);
mt_devices是一個(gè)truct hid_device_id類型的數(shù)組,定義了hid備的設(shè)備參數(shù),這些參數(shù)根據(jù)不同的廠家進(jìn)行劃分,劃分的準(zhǔn)則符合USB-HID協(xié)議,例如(
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)}, /*省略大量內(nèi)容*/ }
上述元素實(shí)則是填充struct hid_device_id的各個(gè)元素,HID_DEVICE宏包裝對(duì).bus、.group、.vendor、.product賦值操作:
在mt_devices數(shù)組中,直接使用HID_DEVICE以及衍生宏為其各個(gè)字段賦值。
二、probe過程剖析
從struct hid_driver mt_driver可以知道m(xù)t_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、首先定義了一個(gè)結(jié)構(gòu)體指針 td,用于存儲(chǔ)多點(diǎn)觸摸設(shè)備的數(shù)據(jù)。
2、使用 devm_kzalloc 分配一個(gè) mt_device 結(jié)構(gòu)體大小的內(nèi)存,并初始化相關(guān)字段。如果內(nèi)存分配失敗,則返回錯(cuò)誤碼 -ENOMEM。
3、調(diào)用hid_set_drvdata()設(shè)置多點(diǎn)觸摸設(shè)備的數(shù)據(jù)指針,以便后續(xù)可以在其他函數(shù)中訪問到該設(shè)備的數(shù)據(jù)。
4、初始化設(shè)備數(shù)據(jù)結(jié)構(gòu)中的鏈表頭,用于管理多點(diǎn)觸摸應(yīng)用程序和報(bào)告。
5、根據(jù)設(shè)備的特性和屬性設(shè)置一些HID屬性。例如:根據(jù)設(shè)備的 ID 和 HID 類別設(shè)置了一些特殊的屬性和標(biāo)志。
6、使用 timer_setup 函數(shù)初始化了一個(gè)定時(shí)器,用于處理觸摸設(shè)備的釋放操作。
7、調(diào)用 hid_parse() 函數(shù)解析設(shè)備的報(bào)告描述符,并進(jìn)行相應(yīng)的初始化。
8、 如果設(shè)備具有特定的修復(fù)需求,例如修復(fù)常量接觸 ID 的問題,則調(diào)用 mt_fix_const_fields() 函數(shù)進(jìn)行修復(fù)。
9、調(diào)用hid_hw_start()函數(shù)啟動(dòng)設(shè)備的硬件,并指定默認(rèn)連接模式。
10、調(diào)用sysfs_create_group()函數(shù)創(chuàng)建sysfs組,以便在sysfs中創(chuàng)建設(shè)備屬性。
11、調(diào)用mt_set_modes()函數(shù)設(shè)置設(shè)備的模式,包括延遲模式和輸入模式。
12、如果一切順利,返回 0 表示成功,否則返回相應(yīng)的錯(cuò)誤碼。
總而言之,mt_probe()是一個(gè)用于初始化和配置多點(diǎn)觸摸設(shè)備的函數(shù),它會(huì)根據(jù)設(shè)備的特性和屬性進(jìn)行相應(yīng)的設(shè)置,并啟動(dòng)設(shè)備的硬件以及創(chuàng)建相應(yīng)的 sysfs 屬性組。
(1)hid_parse()函數(shù)
hid_parse()實(shí)現(xiàn)在/include/linux/hid.h中,本質(zhì)上是調(diào)用hid_open_report()解析HW的report:
hid_open_report()實(shí)現(xiàn)如下:
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; }
上述函數(shù)遍歷hid數(shù)據(jù),然后調(diào)用dispatch_type函數(shù)指針數(shù)組指定的四個(gè)函數(shù)解析HID report:
1、hid_parser_main():解析main Item。
2、hid_parser_global():解析Global Item。
3、hid_parser_local():解析local Item。
4、hid_parser_reserved():解析預(yù)留Item。
(2)hid_hw_start()函數(shù)
hid_hw_start()用于開始一個(gè)底層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、首先調(diào)用hdev->ll_driver->start(hdev),hdev 是一個(gè)指向 hid_device 結(jié)構(gòu)體的指針,ll_driver 則是指向底層驅(qū)動(dòng)的指針。這行代碼調(diào)用了底層驅(qū)動(dòng)中的start 函數(shù),啟動(dòng)了 HID 設(shè)備的硬件。如果啟動(dòng)失敗,start 函數(shù)可能會(huì)返回一個(gè)錯(cuò)誤碼。
2、接著檢查 connect_mask是否為非零值,如果connect_mask不為零,表示需要連接 HID 設(shè)備的某些部分。調(diào)用 hid_connect()函數(shù)連接 HID 設(shè)備,并將connect_mask 作為參數(shù)傳遞給它。如果連接失敗,hid_connect` 函數(shù)可能會(huì)返回一個(gè)錯(cuò)誤碼。
3、如果上述步驟2中出現(xiàn)了錯(cuò)誤,則調(diào)用hdev->ll_driver->stop(hdev),停止HID設(shè)備的硬件,然后函數(shù)返回之前發(fā)生的錯(cuò)誤碼。
4、如果啟動(dòng)和連接都成功,函數(shù)返回0,表示 HID 設(shè)備已經(jīng)成功啟動(dòng)并連接。
(3)hid_connect()函數(shù)
hid_connect()實(shí)現(xiàn)如下:
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設(shè)備到BPF(BerkeleyPacketFilter)。如果連接失敗,則返回相應(yīng)的錯(cuò)誤碼。 ret=hid_bpf_connect_device(hdev); if(ret) returnret; //根據(jù)設(shè)備的特性和屬性,設(shè)置了一些連接標(biāo)志位 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; } //處理設(shè)備的數(shù)據(jù)報(bào)文順序 hid_process_ordering(hdev); //如果設(shè)備被輸入子系統(tǒng)聲明,并且需要連接到力反饋(ForceFeedback),則調(diào)用設(shè)備的力反饋初始化函數(shù)。 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=""; } //創(chuàng)建設(shè)備的sysfs文件。 ret=device_create_file(&hdev->dev,&dev_attr_country); if(ret) hid_warn(hdev, "can'tcreatesysfscountrycodeattributeerr:%d ",ret); //通過hid_info()函數(shù)打印設(shè)備的連接信息。 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應(yīng)用場(chǎng)景
筆者最近需要通過usb方式接入觸摸面板,且該觸摸面板滿足hid協(xié)議,故而使用了內(nèi)核提供的hid-multitouch.c:
然后為板卡接入了兩個(gè)hid觸摸面板,如果HID觸摸面板識(shí)別成功,在hid-multitouch目錄下生成了對(duì)應(yīng)的設(shè)備節(jié)點(diǎn)鏈接:
經(jīng)驗(yàn)證,內(nèi)核對(duì)目前手里的兩塊hid觸摸面板的數(shù)據(jù)解析正常,觸摸事件上報(bào)正常。
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1361瀏覽量
40191 -
Linux
+關(guān)注
關(guān)注
87文章
11212瀏覽量
208734 -
驅(qū)動(dòng)程序
+關(guān)注
關(guān)注
19文章
818瀏覽量
47909 -
HID
+關(guān)注
關(guān)注
2文章
129瀏覽量
46542 -
觸摸面板
+關(guān)注
關(guān)注
0文章
15瀏覽量
13553
原文標(biāo)題:原來linux內(nèi)核對(duì)觸摸的支持這么溜
文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論