PCI裝置建立過程
這部分分成兩部分學習,一是參考前輩的文章,分析lddbus和sculld兩部分源碼,二是參考ldd3學習pci部分
第一部分
ldd_bus聲明了一個bus_type結構的ldd_bus_type:
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
.uevent = ldd_uevent,
};
將ldd_bus_type添加到核心和從核心卸載的代碼如下:
static int _init ldd_bus_init(void)
{
int ret;
ret =bus_register(&ldd_bus_type);/*註冊匯流排,在調用這個函數之後ldd_bus_type
結構體將向核心註冊,在/sys/bus中出現ldd檔案夾,其中包含兩個目錄:devices 和 drivers */
if(ret)
return ret;
if(bus_create_file(&ldd_bus_type,&bus_attribute_version)) /*添加匯流排屬性,添加成功後會在/sys/bus/ldd中出現version屬性檔案
printk(KERN_NOTICE "unable to create this attribute/n");
ret = device_register(&ldd_bus);/*將匯流排作為裝置註冊,因為處理器的外圍各種控制器相對於arm核心來說,都可以看成是一種外設,註冊成功之後在sys/device目錄下出現ldd0目錄。
if(ret)
printk(KERN_NOTICE "unable to register ldd/n");
printk(KERN_NOTICE "Mount lddbus ok !/nBus device is ldd0 !/nYou can see me in sys/module/ , sys/devices/ and sys/bus/ !
/n");
return ret;
}
static void ldd_bus_exit(void)
{
device_unregister(&ldd_bus);
bus_unregister(&ldd_bus_type);
}
module_init(ldd_bus_init);
module_exit(ldd_bus_exit);
lddbus模組的主要部分就是這些,很簡單。因為這隻不過是一個虛擬匯流排,沒有實際的驅動。模組還匯出了載入匯流排裝置和匯流排驅動時需要用到的註冊和登出函數。對於實際的匯流排,應該還要匯出匯流排的讀寫常式。
將匯流排裝置和驅動註冊函數放在lddbus模組,並匯出給其他的匯流排驅動程式使用,是因為註冊匯流排裝置和驅動需要匯流排結構體的資訊,而且這些註冊函數對於所有匯流排裝置和驅動都一樣。只要這個匯流排驅動一載入,其他的匯流排驅動程式就可以通過調用這些函數註冊匯流排裝置和驅動,方便了匯流排裝置驅動的作者,減少了代碼的冗餘。
這些註冊函數內部調用driver_register、device_register 和
driver_unregister、device_unregister 這些函數。
二、sculld模組:在scull的基礎上添加裝置和驅動註冊和登出函數。
//*******在源碼的聲明階段添加如下代碼,以增加裝置和驅動的結構體***** struct sculld_dev *sculld_devices; /* allocated in scull_init_module */
/* Device model stuff */ static struct ldd_driver sculld_driver = { .version = "$Revision: 1.21-tekkamanninja $", .module = THIS_MODULE, .driver = { //在內部嵌入一個driver .name = "sculld", }, }; //**************************************************************
//******增加裝置註冊函數和裝置號屬性******************************** static ssize_t sculld_show_dev(struct device *ddev, struct device_attribute *attr , char *buf) { struct sculld_dev *dev = ddev->driver_data; return print_dev_t(buf, dev->cdev.dev); }
static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);
static void sculld_register_dev(struct sculld_dev *dev, int index) { sprintf(dev->devname, "sculld%d", index); dev->ldev.name = dev->devname; dev->ldev.driver = &sculld_driver; dev->ldev.dev.driver_data = dev; register_ldd_device(&dev->ldev); if (device_create_file(&dev->ldev.dev, &dev_attr_dev))
printk( "Unable to create dev attribute ! /n"); } //*****************************************************************
/*還要在模組的初始化函數和模組清除函數中添加裝置和驅動的註冊和登出函數*/ sculld_register_dev(sculld_devices + i, i); register_ldd_driver(&sculld_driver); unregister_ldd_device(&sculld_devices[i].ldev); unregister_ldd_driver(&sculld_driver);
|
修改後好模組就可以實現向sysfs檔案系統匯出資訊。
三、分析裝置和驅動註冊和登出核心函數,瞭解一般的註冊、登出過程。
(1)裝置的註冊
在驅動程式中對裝置進行註冊的核心函數是:
int device_register(struct device *dev) { device_initialize(dev); return device_add(dev); }
|
在 device_register 函數中, 驅動核心初始化 device 結構體中的許多成員, 向 kobject 核心註冊裝置的 kobject
( 導致熱插拔事件產生,當裝置核心添加或刪除一個kobject時都會產生熱插拔事件), 接著添加裝置到其 parent 節點所擁有的裝置鏈表中。此後所有的裝置都可通過正確的順序被訪問,
並知道其位於裝置層次中的哪一點。
裝置接著被添加到匯流排相關的裝置鏈表(包含了所有向匯流排註冊的裝置)中。接著驅動核心遍曆這個鏈表, 為每個驅動程式調用該匯流排的match函數。
match函數主要是將驅動核心傳遞給它的 struct device 和 struct
device_driver轉換為特定的裝置、驅動結構體 ,檢查裝置的特定資訊, 以確定驅動程式是否支援該裝置:
若不支援, 函數返回 0 給驅動核心,這樣驅動核心移向鏈表中的下一個驅動;
若支援, 函數返回 1 給驅動核心,使驅動核心設定struct device 中的 driver 指標指向這個驅動, 並調用在 struct
device_driver 中指定的 probe 函數.
probe 函數(又一次) 將驅動核心傳遞給它的 struct device 和 struct
device_driver轉換為特定的裝置、驅動結構體 ,並再次驗證這個驅動是否支援這個裝置, 遞增裝置的引用計數, 接著調用匯流排驅動的 probe
函數:
若匯流排 probe 函數認為它不能處理這個裝置,則返回一個負的錯誤值給驅動核心,這樣驅動核心移向鏈表中的下一個裝置;
若這個 probe 函數能夠處理這個裝置, 則初始化這個裝置, 並返回 0 給驅動核心。這會使驅動核心添加裝置到與這個特定驅動所連結的裝置鏈表中, 並在
/sys/bus的匯流排目錄中的 drivers
目錄中建立一個到這個裝置符號連結(指向/sys/devices中的裝置),使使用者準確知道哪個驅動被綁定到了哪個裝置。
(2)裝置的登出 在驅動程式中對裝置進行登出的核心函數是:
void device_unregister(struct device * dev)
|
在
device_unregister 函數中, 驅動核心將刪除這個裝置的驅動程式(如果有)指向這個裝置的符號連結, 並從它的內部裝置鏈表中刪除該裝置, 再以
device 結構中的 struct kobject 指標為參數,調用 kobject_del。kobject_del 函數引起使用者空間的 hotplug
調用,表明 kobject 現在從系統中刪除, 接著刪除所有該 kobject 以前建立的、與之相關聯的 sysfs 檔案和目錄。kobject_del
函數也去除裝置自身的 kobject 引用。此後, 所有的和這個裝置關聯的 sysfs 入口被去除, 並且和這個裝置關聯的記憶體被釋放。
(3)驅動程式的註冊
在驅動程式中對驅動程式進行註冊的核心函數是:
int driver_register(struct device_driver * drv)
|
driver_register 函數初始化 struct device_driver 結構體(包括 一個裝置鏈表及其增刪對象函數 和 一個自旋鎖),
然後調用 bus_add_driver 函數。
bus_add_driver進行如下操作:
(1)尋找驅動關聯的匯流排:若未找到, 立刻返回負的錯誤值;
(2)根據驅動的名字和關聯的匯流排,建立驅動的 sysfs
目錄;
(3)擷取匯流排的內部鎖, 遍曆所有的已經註冊到匯流排的裝置,為這些裝置調用match函數,
若成功,進行剩下的綁定過程。(類似註冊裝置,不再贅述)
(4)驅動程式的登出
刪除驅動程式是一個簡單的過程,在驅動程式中對驅動程式進行登出的核心函數是:
void driver_unregister(struct device_driver * drv)
|
deiver_unregister
函數通過清理在 sysfs 樹中串連到這個驅動入口的 sysfs 屬性,來完成一些基本的管理工作。然後遍曆所有屬於該驅動的裝置,為其調用 release
函數(類似裝置從系統中刪除時調用 release 函數)。
在所有的裝置與驅動程式脫離後,通常在驅動程式中會使用下面兩個函數: down(&drv->unload_sem); up(&drv->unload_sem); 它們在函數返回給調用者之前完成。這樣做是因為在安全返回前,代碼需要等待所有的對這個驅動的引用計數為 0。 模組卸載時,通常都要調用 driver_unregister 函數作為退出的方法。 只要驅動程式被裝置引用並且等待這個鎖時,模組就需要保留在記憶體中。這使得核心知道何時可以安全從記憶體刪除驅動。 |