一、導讀
在linux設備驅(qū)動模型中,總線是一個抽象的概念,是一類特殊的設備。在設備模型的實現(xiàn)中,內(nèi)核規(guī)定了系統(tǒng)中的每個設備都需要連接到一個總線上,這個總線可以是一個內(nèi)部的Bus、虛擬的Bus或者Platform 總線。在內(nèi)核中通過struct but_type結(jié)構(gòu)來描述總線,定義在include/linux/device.h中。
本文首先描述與總線相關(guān)的數(shù)據(jù)結(jié)構(gòu),重點描述struct bus_type結(jié)構(gòu)體內(nèi)部各個元素的含義以及內(nèi)部之間的聯(lián)系。接著會描述linux設備驅(qū)動模型初始化過程中關(guān)于總線的初始化流程,這部分由buses_init()完成,最后會描述對總線的幾個操作接口函數(shù)。
本文所有源碼分析基于linux內(nèi)核版本:4.1.15。
一、導讀
二、與總線相關(guān)的數(shù)據(jù)結(jié)構(gòu)
(2-1)struct bus_type
(2-2)struct subsys_private
三、總線的初始化
四、總線的操作接口
(4-1)總線的注冊
(4-2)總線的注銷
(4-3)device和device_driver的添加
(4-4)driver的probe
二、與總線相關(guān)的數(shù)據(jù)結(jié)構(gòu)
(2-1)struct bus_type
總線是處理器和更多設備之間的通道,對于linux的設備模型,所有的設備都通過總線連接在一起。總線之間可以互相連接,例如:USB控制器通常是一個PCI設備,設備模型表示總線和它們控制的設備之間的實際連接。總線由struct bus_type結(jié)構(gòu)表示,該結(jié)構(gòu)包含了總線名稱、默認屬性、總線的方法、PM操作和驅(qū)動核心的私有數(shù)據(jù)。sturct bus_type定義如下:
structbus_type{
constchar*name;
constchar*dev_name;
structdevice*dev_root;
structdevice_attribute*dev_attrs;/*usedev_groupsinstead*/
conststructattribute_group**bus_groups;
conststructattribute_group**dev_groups;
conststructattribute_group**drv_groups;
int(*match)(structdevice*dev,structdevice_driver*drv);
int(*uevent)(structdevice*dev,structkobj_uevent_env*env);
int(*probe)(structdevice*dev);
int(*remove)(structdevice*dev);
void(*shutdown)(structdevice*dev);
int(*online)(structdevice*dev);
int(*offline)(structdevice*dev);
int(*suspend)(structdevice*dev,pm_message_tstate);
int(*resume)(structdevice*dev);
conststructdev_pm_ops*pm;
conststructiommu_ops*iommu_ops;
structsubsys_private*p;
structlock_class_keylock_key;
};
name :總線的名稱。
dev_name : 用于子系統(tǒng)枚舉設備等,例如("foo%u", dev->id)。
dev_root : 表示要用于父設備的默認設備。
dev_attrs: 設備屬性組。
bus_groups: bus屬性組。
dev_groups: dev屬性組。
drv_groups: drv屬性組。
match: 是一個需要由具體bus驅(qū)動實現(xiàn)的回調(diào)函數(shù),當屬于該bus的所有device和驅(qū)動添加到內(nèi)核時,內(nèi)核都會調(diào)用該接口函數(shù)。
uevent:也是一個由具體的bus驅(qū)動實現(xiàn)的回到函數(shù),當屬于該bus的設備,觸發(fā)添加、移除或者其他動作時,bus模塊核心就會調(diào)用該接口,這樣可以讓bus的驅(qū)動能夠修改環(huán)境變量。
probe、 remove:這兩個也是回調(diào)函數(shù),當有新的設備或者驅(qū)動添加到這個bus時,內(nèi)核則會首先調(diào)用這個bus的probe,然后再調(diào)用具體驅(qū)動程序的probe去初始化匹配設備;當有設備從這個bus上移除的時候則會調(diào)用remove,所以這個兩個回調(diào)函數(shù)非常重要。
shutdown:在需要shutdown的時候調(diào)用該回調(diào)函數(shù),以讓設備停止工作。該函數(shù)與電源管理相關(guān)。
online:當設備再脫機后重新聯(lián)機時調(diào)用該函數(shù)。該函數(shù)與電源管理相關(guān)。
offline:當讓設備脫機以便進行熱插拔時調(diào)用該函數(shù)。
suspend:當總線上的設備想要進入睡眠模式時調(diào)用。
resume:讓這個bus上的一個設備退出睡眠模式時調(diào)用該函數(shù)。
pm:是與之對應的bus的電源管理操作,會去回調(diào)執(zhí)行特定驅(qū)動程序的pm的ops。
iommu_ops:該總線的IOMMU特定操作,用于將IOMMU驅(qū)動程序?qū)崿F(xiàn)附加到總線上,并允許驅(qū)動程序進行總線上特殊的設定操作。
p: 一個struct subsys_private類型的指針,是驅(qū)動核心的私有數(shù)據(jù),只有驅(qū)動核心可以使用,
lock_key: 該參數(shù)供鎖驗證器使用。
(2-2)struct subsys_private
在bus_type和class結(jié)構(gòu)中都有一個指向struct subsys_private的指針,用于保存bus_type和class結(jié)構(gòu)的驅(qū)動程序核心部分的私有數(shù)據(jù)。從命名上似乎不容易理解struct subsys_private,由于bus_type和clsss結(jié)構(gòu)中都有一個struct subsys_private指針,所以可以將subsys_private理解成bus_type和class的上層,包含了bus和class。
struct subsys_private結(jié)構(gòu)定義如下(/drivers/base/base.h):
structsubsys_private{
structksetsubsys;
structkset*devices_kset;
structlist_headinterfaces;
structmutexmutex;
structkset*drivers_kset;
structklistklist_devices;
structklistklist_drivers;
structblocking_notifier_headbus_notifier;
unsignedintdrivers_autoprobe:1;
structbus_type*bus;
structksetglue_dirs;
structclass*class;
};
subsys:用于描述本subsystem的kset,用于代表其自身。
devices_kset: 表示subsystem的device目錄。
interfaces: interfaces是一個list_head類型數(shù)據(jù),用于保存與之相關(guān)的interface。在內(nèi)核中interface用于抽象bus下所有關(guān)聯(lián)設備的一些特殊的功能。
mutex: mutex類型鎖,用于保護設備和interface鏈表。
drivers_kset: 表示subsystem中驅(qū)動相關(guān)鏈表。
klist_devices: 設備鏈表,用于保存本bus下所有的device的指針,以方便查找。
klist_drivers: 驅(qū)動鏈表,用于保存本bus下所有的device_driver的指針,以方便查找。
bus_notifier: bus_notifier是一個總線通知列表,用于監(jiān)測bus上發(fā)生的任何事情。
drivers_autoprobe:用于控制該bus下的drivers或者device是否具有自動probe屬性。
bus:是一個指向與之關(guān)聯(lián)的struct bus_type類型的指針。用于保存上層的bus。
glue_dirs:表示glue目錄,用于放在父設備之間,以避免名稱空間出現(xiàn)沖突。
class:是一個指向與之關(guān)聯(lián)的struct class類型的指針。用于保存上層的class。
三、總線的初始化
總線屬于linux驅(qū)動模型的一部分,所以在內(nèi)核啟動過程中,在driver_init()函數(shù)中會調(diào)用buses_init(),完成總線相關(guān)的初始化操作:
在buses_init()的操作邏輯中,完成了以下幾件事情:
(1)動態(tài)創(chuàng)建bus內(nèi)核kset,并指定其事件操作函數(shù),然后添加到sysfs中。
(2)動態(tài)創(chuàng)建system內(nèi)核kset,并指定其父級kset為devices_kset->kobj,然后添加到sysfs中。
四、總線的操作接口
本小節(jié)描述linux內(nèi)核中對總線的操作API接口:
//添加設備到總線 externintbus_add_device(structdevice*dev); //為新的設備探測驅(qū)動 externvoidbus_probe_device(structdevice*dev); //從總線中將設備移除 externvoidbus_remove_device(structdevice*dev); //添加一個驅(qū)動到總線 externintbus_add_driver(structdevice_driver*drv); //從總線將移除驅(qū)動 externvoidbus_remove_driver(structdevice_driver*drv); //將驅(qū)動程序從該驅(qū)動控制的所有設備中分離 externvoiddriver_detach(structdevice_driver*drv); //嘗試將設備和驅(qū)動程序綁定在一起 externintdriver_probe_device(structdevice_driver*drv,structdevice*dev);
//注冊一個驅(qū)動核心總線子系統(tǒng) externint__must_checkbus_register(structbus_type*bus); //注銷驅(qū)動核心總線子系統(tǒng) externvoidbus_unregister(structbus_type*bus); /*注冊總線通知器。總線通知器,用于獲取當設備添加/移除或者驅(qū)動程序與 設備綁定/解綁定時的通知。*/ externintbus_register_notifier(structbus_type*bus, structnotifier_block*nb); //注銷總線通知器 externintbus_unregister_notifier(structbus_type*bus, structnotifier_block*nb);
(4-1)總線的注冊
調(diào)用bus_register執(zhí)行具體總線的注冊操作,該函數(shù)實現(xiàn)在/drivers/base/bus.c中,具體執(zhí)行邏輯如下:
(1)調(diào)用kzalloc()為struct subsys_private創(chuàng)建內(nèi)存,設置priv->bus的值為想要注冊的總線類型,然后將bus->p賦值為priv。
(2)初始化總線通知器。
(3)為priv->subsys.kobj重新設置名稱,即總線的名稱。
(4)初始化priv->subsys.kobj的kset和ktype字段。
(5)調(diào)用kset_register將private->subsys.kobj注冊到內(nèi)核中。
(6)調(diào)用bus_create_file向sysfs文件系統(tǒng)中的bus目錄下添加一個uevnet attribute:
(7)調(diào)用kset_create_and_add()向內(nèi)核分別添加devices和drivers kset,這樣便可以在sysfs中查看了:
(8)初始化priv指針中的mutex、klist_devices和klist_drivers等變量。
(9)調(diào)用add_probe_files函數(shù),在bus下添加bus_attr_drivers_probe和bus_attr_drivers_autoprobe兩個attribute:
(10)調(diào)用bus_add_groups添加bus_groups屬性組。
(4-2)總線的注銷
調(diào)用bus_unregister執(zhí)行具體總線的注銷操作,該函數(shù)同樣實現(xiàn)在/drivers/base/bus.c中:
voidbus_unregister(structbus_type*bus)
{
pr_debug("bus:'%s':unregistering
",bus->name);
if(bus->dev_root)
device_unregister(bus->dev_root);
bus_remove_groups(bus,bus->bus_groups);
remove_probe_files(bus);
kset_unregister(bus->p->drivers_kset);
kset_unregister(bus->p->devices_kset);
bus_remove_file(bus,&bus_attr_uevent);
kset_unregister(&bus->p->subsys);
}
(4-3)device和device_driver的添加
linux內(nèi)核的驅(qū)動模型中,提供了device_register()和driver_register()兩個接口,供各個驅(qū)動模塊使用。從linux內(nèi)核多個子系統(tǒng)的源碼中可以發(fā)現(xiàn),對于各種驅(qū)動程序的注冊最終都會調(diào)用到driver_register()。然而這兩個接口函數(shù)的核心邏輯中,是通過調(diào)用總線的bus_add_device()和bus_add_driver()實現(xiàn)的:在driver_register()中調(diào)用driver_find()在給定的總線中查找給定名稱的驅(qū)動,如果驅(qū)動已經(jīng)存在則返回-EBUSY;如果驅(qū)動在總線中不存在,則調(diào)用bus_add_driver()注冊驅(qū)動。在device_register()中則首先調(diào)用device_initialize初始化設備(本質(zhì)上是對sturct device結(jié)構(gòu)賦值),然后調(diào)用device_add向linux內(nèi)核驅(qū)動模型注冊設備。
device_register()和driver_register()兩個接口都在/drivers/base/bus.c文件中實現(xiàn)。下文來具體看看這兩個接口的執(zhí)行邏輯:
1、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將該設備在sysfs中的目錄,鏈接到該bus的devices目錄下
(5)接著依然調(diào)用sysfs_create_link,在該設備的sysfs目錄中,創(chuàng)建一個指向該設備所在bus目錄的鏈接,命名為subsystem。
(6)前面幾個操作實則是向sysfs文件系統(tǒng)注冊關(guān)于設備的信息,向用戶空間拋出接口。最后步驟則是調(diào)用klist_add_tail()將該設備指針保存到bus->p->klist_devices中。
2、bus_add_driver的執(zhí)行邏輯:
(1)首先調(diào)用bus_get從驅(qū)動程序中獲取到該驅(qū)動程序所屬的bus_type指針。如果該指針為零(即獲取失敗)則返回-EINVAL;反之則繼續(xù)執(zhí)行后續(xù)操作。
(2)為該驅(qū)動的struct driver_private指針(priv)分配空間,并初始化其中的priv->klist_devices、priv->driver、priv->kobj.kset等變量,同時將priv保存到device_driver的p中。
(3)調(diào)用kobject_init_and_add,并傳入該驅(qū)動的名稱作為參數(shù),向sysfs中注冊driver的kobject。該操作體現(xiàn)在sysfs文件中的/sys/bus/xxx/drivers/目錄下。
(4)調(diào)用klist_add_tail將驅(qū)動添加到總線的klist_drivers鏈表中,如果該驅(qū)動的drivers_autoprobe為真,還將調(diào)用driver_attach嘗試將驅(qū)動綁定到設備。
(5)調(diào)用module_add_driver()將驅(qū)動添加到drv->owner中,咱不過多分析。
(6)調(diào)用driver_create_file,在sysfs文件系統(tǒng)中的driver目錄下,創(chuàng)建uevent attribute。
(7)調(diào)用driver_add_groups將bus->drv_groups屬性組添加到驅(qū)動中。
(4-4)driver的probe
當一個driver在進行probe的時候,大部分邏輯都會依賴總線的具體實現(xiàn),核心操作是bus_probe_device()和driver_attach()兩個接口,這兩個接口都是在drivers/base/base.h中聲明,在drivers/base/bus.c中實現(xiàn)。
bus_probe_device()實現(xiàn)如下:
driver_attach()實現(xiàn)如下:
從上圖可知,driver_attach中會調(diào)用bus_for_each_dev遍歷設備,該函數(shù)用于遍歷drv->bus的設備列表,并為每個設備調(diào)用__driver_attach(),并將drv傳遞給__driver_attach,故而可以明確知道對每個設備執(zhí)行的操作則是__driver_attach(),該函數(shù)定義在/drivers/base/dd.c文件中,具體的執(zhí)行邏輯如下:
(1)使用driver_match_device()判斷驅(qū)動和設備是否匹配(本質(zhì)上是判斷是否指定了drv->bus->match,如果指定了則執(zhí)行與之對應的函數(shù),否則返回1),如果驅(qū)動和設備已經(jīng)匹配了則直接返回;否則,則繼續(xù)執(zhí)行后續(xù)的操作。
(2)調(diào)用driver_probe_device()嘗試將驅(qū)動和設備綁定在一起。
總體上,bus_probe_device()和driver_attach()這兩個接口的操作流程類似,即:搜索所在的總線,比對是否有同名的device_driver(或device),如果有并且該設備沒有綁定Driver(注:這一點很重要,則可以使同一個Driver驅(qū)動相同名稱的多個設備)則調(diào)用device_driver的probe接口。
審核編輯:劉清
-
處理器
+關(guān)注
關(guān)注
68文章
20300瀏覽量
253670 -
USB控制器
+關(guān)注
關(guān)注
1文章
38瀏覽量
12341 -
PCI
+關(guān)注
關(guān)注
5文章
689瀏覽量
134420 -
platform
+關(guān)注
關(guān)注
0文章
19瀏覽量
17792 -
Linux驅(qū)動
+關(guān)注
關(guān)注
0文章
47瀏覽量
10511
原文標題:Linux設備與驅(qū)動的“鵲橋” | Bus
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
淺談Linux發(fā)行版之間的聯(lián)系和區(qū)別
嵌入式Linux字符設備驅(qū)動的設計與應用
嵌入式Linux字符設備驅(qū)動的設計與應用
《Linux設備驅(qū)動開發(fā)詳解》第23章、Linux設備驅(qū)動的移植
《Linux設備驅(qū)動開發(fā)詳解》第17章、Linux音頻設備驅(qū)動
《Linux設備驅(qū)動開發(fā)詳解》第16章、Linux網(wǎng)絡設備驅(qū)動
《Linux設備驅(qū)動開發(fā)詳解》第14章、Linux終端設備驅(qū)動
《Linux設備驅(qū)動開發(fā)詳解》第13章、Linux塊設備驅(qū)動
《Linux設備驅(qū)動開發(fā)詳解》第9章、Linux設備驅(qū)動中的異步通知與異步IO
《Linux設備驅(qū)動開發(fā)詳解》第8章、Linux設備驅(qū)動中的阻塞與非阻塞IO
《Linux設備驅(qū)動開發(fā)詳解》第7章、Linux設備驅(qū)動中的并發(fā)控制
Linux設備與驅(qū)動之間的聯(lián)系描述
評論