1、簡介
本文基于內(nèi)核源碼4.19.4分析。
linux內(nèi)核設(shè)備的注冊由device_register()函數(shù)完成,這個函數(shù)是linux設(shè)備驅(qū)動模型的核心函數(shù),實現(xiàn)在/drivers/base/core.c中:
在device_register()函數(shù)中,分為兩個步驟:
(1)調(diào)用device_initialize():該步驟用于初始化一個device。
(2)調(diào)用device_add():該函數(shù)用于將device添加到linux內(nèi)核的device樹中。
2、device_initialize分析
該函數(shù)接收一個struct device *dev參數(shù),在該函數(shù)中初始化struct device結(jié)構(gòu)中的幾個重要成員:
設(shè)置dev->kobj.kset為device_kset。device_kset是一個struct kset類型的全局變量,用于向sysfs文件系統(tǒng)中導(dǎo)出目錄:/sys/device/* 。
初始化dev中的kobject,并指定與這個對象相關(guān)聯(lián)的ktype為device_type。
初始化dma_pools鏈表。
初始化struct device中的各種鎖:
初始化device的電源管理:
如果在NUMA下,還會初始化設(shè)置device的numa_node為-1。
接著初始化device下的links中的鏈表:
在struct device 中的links表示鏈接到該設(shè)備的suppliers和consumers,由struct dev_links_info表示:
設(shè)置device下的links.status值為DL_DEV_NO_DRIVER,表示此時還沒有對應(yīng)驅(qū)動attach到這個設(shè)備。
以上步驟則是device_initialize()初始化設(shè)備時完成的操作。
3、device_add分析
(1)調(diào)用get_device(dev)增加device的引用計數(shù)。
(2)如果dev->p為NULL,則調(diào)用device_private_init()設(shè)置device的私有數(shù)據(jù):
(3)設(shè)置device的name:
如果開啟支持pr_debug()函數(shù),則會打印出對應(yīng)的設(shè)備名稱。
(4)尋找父設(shè)備和父設(shè)備對應(yīng)的kobj,并調(diào)用kobject_add()將dev->kobj添加到dev->kobj.parent:
(5)使用device_create_file為device創(chuàng)建sysfs屬性文件:
error=device_create_file(dev,&dev_attr_uevent);
dev_attr_uevent是一個struct device_attribute類型的數(shù)據(jù),該結(jié)構(gòu)用于描述導(dǎo)出設(shè)備屬性的接口,定義如下:
structdevice_attribute{ structattributeattr; ssize_t(*show)(structdevice*dev,structdevice_attribute*attr, char*buf); ssize_t(*store)(structdevice*dev,structdevice_attribute*attr, constchar*buf,size_tcount); };
(6)添加類的符號鏈接:
error=device_add_class_symlinks(dev);
device_add_class_symlinks()的功能是將設(shè)備添加到指定的設(shè)備類中,并在/sys/class目錄下為設(shè)備創(chuàng)建符號鏈接,以便用戶空間程序能夠方便地訪問和管理設(shè)備。
(7)調(diào)用device_add_attrs()為設(shè)備添加屬性:
error=device_add_attrs(dev);
device_add_attrs()的功能是為設(shè)備添加屬性,并在/sys/devices目錄下創(chuàng)建相應(yīng)的屬性文件。這樣,用戶空間程序可以通過訪問設(shè)備的屬性文件來讀取和修改設(shè)備的屬性值。這個函數(shù)在設(shè)備驅(qū)動的初始化過程中常常被調(diào)用,以確保設(shè)備的屬性能夠正確地顯示和訪問。
(8)調(diào)用bus_add_device()添加設(shè)備到bus:
bus_add_device用于將設(shè)備添加到總線上。它的功能是將一個設(shè)備(struct device結(jié)構(gòu)體)添加到指定總線(struct bus_type結(jié)構(gòu)體)上,并進行相應(yīng)的初始化和注冊操作。
bus_add_device的執(zhí)行邏輯:
(1)從dev->bus中取得bus_type*類型的指針bus,如果獲取bus不成功,則函數(shù)直接返回;如果bus獲取成功,則會繼續(xù)后續(xù)的第(2)步操作。
(2)調(diào)用device_add_attrs接口,將由bus->dev_attrs指針定義的默認attribute添加到內(nèi)核中,這個操作會體現(xiàn)在sysfs文件系統(tǒng)中的/sys/devices/xxx/xxx_device/目錄中。
(3)調(diào)用device_add_groups將bus_dev_groups添加到內(nèi)核中。
(4)調(diào)用sysfs_create_link將該設(shè)備在sysfs中的目錄,鏈接到該bus的devices目錄下
(5)接著依然調(diào)用sysfs_create_link,在該設(shè)備的sysfs目錄中,創(chuàng)建一個指向該設(shè)備所在bus目錄的鏈接,命名為subsystem。
(6)前面幾個操作實則是向sysfs文件系統(tǒng)注冊關(guān)于設(shè)備的信息,向用戶空間拋出接口。最后步驟則是調(diào)用klist_add_tail()將該設(shè)備指針保存到bus->p->klist_devices中。
(9)調(diào)用device_pm_add()將一個設(shè)備添加到PM核心的active設(shè)備鏈表中。
(10)創(chuàng)建設(shè)備節(jié)點:
(11)通過bus_notifier告知系統(tǒng)設(shè)備已經(jīng)添加:
(12)調(diào)用bus_probe_device()為該設(shè)備probe一個驅(qū)動。該函數(shù)實現(xiàn)如下:
具體執(zhí)行流程如下:
(1)從dev中解析出該dev所在而bus,如果bus不存在,則退出該函數(shù)。
(2)如果設(shè)置了driver_autoprobe,則調(diào)用device_initial_probe(dev)。該函數(shù)本質(zhì)調(diào)用到device_attach(),嘗試將設(shè)備連接到驅(qū)動程序。
(3)遍歷bus上的子系統(tǒng)接口鏈表interfaces,如果add_dev函數(shù)指針存在,則調(diào)用對應(yīng)的函數(shù)。(從源碼來看有些驅(qū)動程序,會使用struct subsys_interface來實現(xiàn),在此處實現(xiàn)對注冊的subsys_interface下的add_dev的調(diào)用執(zhí)行)
(13)如果父設(shè)備存在,則會將該設(shè)備添加到父設(shè)備的klist_children鏈表中(klist_children是包含此設(shè)備的所有子節(jié)點的鏈表):
(14)如果設(shè)備的class不為NULL,則會將class綁定到device:
klist_add_tail(&dev->p->knode_class,&dev->class->p->klist_devices);
(15)通知所有的interface接口:
在內(nèi)核中,struct class_interface是用于表示設(shè)備類和設(shè)備驅(qū)動之間的接口的結(jié)構(gòu)體。它定義了設(shè)備類與設(shè)備驅(qū)動之間的關(guān)聯(lián)關(guān)系,允許設(shè)備驅(qū)動在注冊時與相應(yīng)的設(shè)備類進行關(guān)聯(lián),并提供了一組函數(shù)指針,用于設(shè)備類調(diào)用設(shè)備驅(qū)動中的操作。
struct class_interface結(jié)構(gòu)體定義如下:
structclass_interface{ structlist_headnode; structclass*class; int(*add)(structdevice*dev,structclass_interface*class_intf); void(*remove)(structdevice*dev,structclass_interface*class_intf); };
node: 用于將struct class_interface鏈接到設(shè)備類的接口鏈表中。
class: 指向與該接口相關(guān)聯(lián)的設(shè)備類。
add: 指向設(shè)備類調(diào)用設(shè)備驅(qū)動的添加操作的函數(shù)指針。當設(shè)備添加到設(shè)備類時,會調(diào)用此函數(shù)。
remove: 指向設(shè)備類調(diào)用設(shè)備驅(qū)動的移除操作的函數(shù)指針。當設(shè)備從設(shè)備類中移除時,會調(diào)用此函數(shù)。
通過使用struct class_interface,設(shè)備驅(qū)動可以與設(shè)備類進行交互,以便在設(shè)備添加或移除時執(zhí)行相應(yīng)的操作。這種機制允許設(shè)備驅(qū)動與設(shè)備類解耦,使得設(shè)備驅(qū)動可以在設(shè)備類的上下文中執(zhí)行一些操作,而無需直接操作設(shè)備類。
回到device_add()中,使用了list_for_each_entry()遍歷interfaces鏈表,如果設(shè)置了class_intf->add_dev,則調(diào)用該回調(diào)函數(shù)指針指向的函數(shù)。
4、總結(jié)
結(jié)合本文內(nèi)容和linux內(nèi)核源碼,得出以下結(jié)論:
(1)在設(shè)備驅(qū)動模型中,所有的設(shè)備注冊操作最后都會調(diào)用device_register()函數(shù)實現(xiàn)。
(2)在筆者分析的linux版本下的device_register()中,存在兩個數(shù)據(jù)結(jié)構(gòu):struct class_interface 和struct subsys_interface。從內(nèi)核源碼來看,這兩個結(jié)構(gòu)只在為數(shù)不多的幾個特定驅(qū)動程序中使用,那么可證明這可能是歷史發(fā)展遺留下來的代碼,在device_register中仍然保留了對這部分代碼的支持。
(3)在device_register()中調(diào)用了bus_probe_device(),從而證明在注冊設(shè)備的時候發(fā)生了『設(shè)備與驅(qū)動匹配』的過程。
審核編輯:劉清
-
電源管理
+關(guān)注
關(guān)注
115文章
6177瀏覽量
144443 -
Linux系統(tǒng)
+關(guān)注
關(guān)注
4文章
593瀏覽量
27392 -
dma
+關(guān)注
關(guān)注
3文章
560瀏覽量
100546 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21644 -
numa
+關(guān)注
關(guān)注
0文章
7瀏覽量
3837
原文標題:一臉懵,萬千設(shè)備,linux內(nèi)核如何知道?
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論