一、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;ndesc.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(lenphys)-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()這個接口中調用執行:
六、總結
hid核心的功能大致可總結如下:
(1)設備注冊和注銷:包括設備的注冊、注銷和初始化函數,用于將 HID 設備與對應的驅動程序關聯起來,并在設備連接或斷開時進行處理。
(2)事件處理:包括從 HID 設備接收事件數據的函數和處理這些事件數據的代碼,這些事件數據可能來自于鍵盤、鼠標、游戲手柄等輸入設備。
(3)設備識別與匹配:包括用于識別和匹配 HID 設備的函數,這些函數通常用于確定哪個驅動程序適合于連接到系統的特定設備。
(4)報告解析:包括解析 HID 設備發送的報告數據的函數,以便將其轉換為更易于理解的格式,并將其傳遞給適當的用戶空間或內核空間應用程序。
(5)驅動程序注冊:包括注冊和注銷 HID 驅動程序的函數,這些函數用于將驅動程序添加到系統中以處理特定類型的 HID 設備。
(6)錯誤處理和調試功能:包括用于處理設備連接和通信中出現的錯誤以及調試信息輸出的函數。
-
內核
+關注
關注
3文章
1366瀏覽量
40234 -
Linux
+關注
關注
87文章
11232瀏覽量
208949 -
函數
+關注
關注
3文章
4308瀏覽量
62444
原文標題:一文探秘linux HID核心
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論