設備驅動開發是Linux開發領域一個非常重要的部分,在Linux源碼的85%都是驅動程序的代碼。設備驅動開發不僅需要了解硬件底層的知識,還需要擁有操作系統的背景。驅動程序追求的是高效,穩定,驅動程序發生的問題有可能直接導致整個系統的崩潰。
驅動程序不主動運行,意味着驅動程序是等待應用程序來調用的。應用程序通過內核來調用驅動程序,實現與實際硬件設備的通信。Linux外設通常可以分爲字符型設備、塊設備、網絡設備三種類型。
設備驅動開發很複雜,好的驅動不好寫,比如一個驅動程序被多個進程使用,這就需要考慮到併發和競態的問題。
驅動程序可以在編譯內核時靜態編譯到內核當中,也可以通過模塊化的方式在需要時進行加載。驅動程序在模塊化編譯完成後會以.ko作爲擴展名,我們可以使用insmod命令進行加載,使用rmmod命令進行卸載,可以使用lsmod命令查看當前運行的內核當中已經加載的模塊。
1、在通常的驅動程序代碼中,我們可以找到一下兩個函數:
module_init(irfpa_module_init);
module_exit(irfpa_module_exit);
當我們使用insmod命令加載模塊時,模塊化的初始化函數會自動被調用,用來向內核註冊驅動程序。系統使用module_init來標記初始化函數。當使用rmmod命令卸載模塊時,模塊的清除函數被調用。清除函數使用 module_exit來標記。
模塊化的結構是Linux推薦的一種方式,其方便系統的精簡和組織,這也是Linux很好的一個特性,實現了在運行時可擴展的目的。
**2、**sys文件系統簡介
在2.6內核之前,絕大多數的驅動程序最終都被映射到/dev目錄下,對於字符型驅動程序而言,採用file_operations類型的數據結構來組織驅動程序。這使得/dev目錄變得越來越繁瑣,對與普通用戶而言,變得不具可讀性。之後就採用sys文件系統來組織驅動。
sys文件系統是一個類似於proc文件系統的特殊文件系統,它由系統自動維護,用於將系統中的設備組織成層次結構,並向用戶模式程序提供詳細的內核數據結構信息。在sys目錄下有:
block:包含所有的塊設備
devices:包含系統所有的設備,並根據設備掛載的總線類型組織成層次結構。
bus:包含系統中所有的總線類型。
drivers:包含內核中所有已註冊的設備驅動程序。
class:包含系統中的設備類型(我們自定義的設備類型就在此目錄中)
對於用戶程序而言,若要對設備進行操作,只需在sys目錄下找到對應的文件,對其進行讀寫即可完成。
3、設備驅動程序的作用在於提供機制,而不是策略。簡單的說驅動程序是實現“需要什麼功能”(機制),而用戶程序利用驅動程序“如何使用這些功能”(策略)來完成不同的任務。我覺得就是我們編出一個模塊,用戶程序調用這個模塊。
在1、中兩個宏幫助我們向內核標記了模塊的初始化與清除函數。在驅動程序中主要的註冊信息都在irfpa_module_init這個函數中完成了。命令insmod將自動調用這個函數。
/**註冊設備配置,註冊驅動platform_driver_register
* irfpa_module_init - register the Device Configuration.
* Returns 0 on success, otherwise negative error.
*/
static int __init irfpa_module_init(void)
{
return platform_driver_register(&irfpa_platform_driver);
}
從Linux2.6內核起,引入一套新的驅動管理和註冊機制:platform_device 和 platform_driver 。Linux 中大部分的設備驅動,都可以使用這套機制,設備用 platform_device 表示;驅動用 platform_driver 進行註冊。platform機制將設備本身的資源註冊進內核,由內核統一管理,在驅動程序中用使用這些資源時,通過platform device提供的標準接口進行申請並使用。
platform 是一個虛擬的地址總線,相比 PCI、USB,它主要用於描述SOC上的片上資源。platform 所描述的資源有一個共同點:在CPU 的總線上直接取址。平臺設備會分到一個名稱(用在驅動綁定中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源。
platform_device_系列函數,實際上是註冊了一個叫platform的虛擬總線。使用約定是如果一個不屬於任何總線的設備,例如藍牙,串口等設備,都需要掛在這個虛擬總線上。
4、要用註冊一個platform驅動的步驟:
1,註冊設備platform_device_register
2,註冊驅動platform_driver_register,過程中在系統尋找註冊的設備(根據.name),找到後運行.probe進行初始化。
註冊時候的兩個名字必須一樣,才能match上,才能work。
在設備樹中,已經把FPGA實現的邏輯作爲設備掛載在ARM處理器的操作系統中,這裏就不用註冊設備了,只需要註冊驅動就好了。
irfpa: irfpa@43c00000 {
compatible = "sitp,irfpa-1.00.a";
reg = < 0x43c00000 0x100 0x43000000 0x100 0x18000000 0x8000000 0x40000000 0x800>; //irfpa vdma irfpa_buf irfpa_xinf
};
platform_driver_register(&irfpa_platform_driver) 會向系統註冊irfpa_platform_driver這個驅動程序,這個函數會根據 irfpa_platform_driver中的”sitp,irfpa-1.00.a”內容,搜索系統註冊的device中有沒有這個platform_device,如果有,就會執行 platform_driver(也就是irfpa_platform_driver的類型)中的.probe函數。
注意:對只需要初始化運行一次的函數都加上__init屬性,__init 宏告訴編譯器如果這個模塊被編譯到內核則把這個函數放到(.init.text)段,module_exit的參數卸載時同__init類似,如果驅動被編譯進內核,則__exit宏會忽略清理函數,因爲編譯進內核的模塊不需要做清理工作,顯然__init和__exit對動態加載的模塊是無效的,只支持完全編譯進內核。
這裏實現的是驅動:
驅動註冊中,需要實現的結構體是:platform_driver 。
/* Driver Structure */
static struct platform_driver irfpa_platform_driver = {
.probe = irfpa_drv_probe,
.remove = __devexit_p(irfpa_drv_remove),
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = irfpa_of_match, //好像置爲NULL了
},
};
#ifdef CONFIG_OF
static struct of_device_id irfpa_of_match[] __devinitdata = {
{ .compatible = "sitp,irfpa-1.00.a", },
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, irfpa_of_match);
#else
#define irfpa_of_match NULL
#endif /* CONFIG_OF */
在驅動程序的初始化函數中,調用了platform_driver_register()註冊 platform_driver 。需要注意的是:platform_driver 和 platform_device 中的 name 變量的值必須是相同的 。這樣在 platform_driver_register() 註冊時,會將當前註冊的 platform_driver 中的 name 變量的值和已註冊的所有 platform_device 中的 name 變量的值進行比較,只有找到具有相同名稱的 platform_device 才能註冊成功。當註冊成功時,會調用 platform_driver 結構元素 probe 函數指針。
platform_driver_register()的註冊過程:
1 platform_driver_register(&irfpa_platform_driver)
2 driver_register(&irfpa_platform_driver->driver)
3 bus_add_driver(&irfpa_platform_driver)
4 driver_attach(&irfpa_platform_driver)
5 bus_for_each_dev(&irfpa_platform_driver->bus, NULL, &irfpa_platform_driver, __driver_attach)
6 __driver_attach(irfpa, &irfpa_platform_driver)
7 driver_probe_device(&irfpa_platform_driver, irfpa)
8 really_probe(irfpa, &irfpa_platform_driver)
在really_probe()中:爲設備指派管理該設備的驅動:dev->driver = drv, 調用probe()函數初始化設備:drv->probe(dev)
這裏真正調用了probe函數。
典型的Platform device是系統中的各種自主設備,包括各種橋接在外圍總線上的port-based device和host,以及各種集成在SOC platform上的控制器。他們都有一個特點,那就是CPU BUS可以直接尋址,或者特殊的情況platform_device連接在其他總線上,但是它的寄存器是可以被直接尋址的。
Platform device有一個名字,用來進行driver的綁定;還有諸如中斷,地址之類的一些資源列表
Probe()函數必須驗證指定設備的硬件是否真的存在,probe()可以使用設備的資源,包括時鐘,platform_data等
a、platform_driver_register(&&irfpa_platform_driver) //註冊平臺驅動
int platform_driver_register(struct platform_driver *drv)
{
。。。
return driver_register(&drv->driver);
}
b、driver_register(&drv->driver); //註冊驅動到bus,@drv: 要註冊的驅動
這個函數開始先判斷bus->p是否爲空,如果不爲空然後判斷驅動跟驅動的總線是否有衝突的函數註冊,如果有衝突就給出警告信息,然後在註冊在bus上的driver尋找是否有跟要註冊的driver相同,有則表明驅動已被註冊過,返回錯誤。經過上面的驗證後,將驅動添加註冊到bus上,如果沒問題,則再將驅動添加到同一屬性的組中,在sysfs下表現爲同一個目錄。
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p); //判斷bus->p是否爲空,如果drv->bus->p爲空,則打印失敗信息以及panic信息。其實這個主要是判斷bus是否存在,這個結論還需要論證!
if ((drv->bus->probe && drv->probe) || //判斷驅動跟驅動的總線是否有衝突的函數註冊,給出警告信息
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus); //這個函數的功能就是查找bus上已經註冊的驅動,和要註冊的驅動比較,如果找到,則返回找到的驅動。bus->p->drivers_kset是bus上已經註冊的驅動的kobject的結合,會傳給kset_find_obj()作爲參數。kset_find_obj()它會查找在kset->list上的每一個kobject與改驅動的名字是否有同名字的,如果找到則返回改kobject。
if (other) {
put_driver(other);
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); //經過上面的驗證後,將驅動添加註冊到bus上,見下面函數分析
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups); //如果grop不爲空的話,將在驅動文件夾下創建以group名字的子文件夾,然後在子文件夾下添加group的屬性文件
if (ret)
bus_remove_driver(drv);
return ret;
}
c、bus_add_driver(drv); //添加驅動到總線
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus); //找到該drv所屬的bus,其實就是增加該bus->p->subsys->kobject->kref的引用計數
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL); //分配driver_private結構
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL); //初始化priv->klist_devices
priv->driver = drv; //將該drv賦值給priv->driver
drv->p = priv; //而drv的drv->p又等於priv
priv->kobj.kset = bus->p->drivers_kset; //指向bus的drvier容器
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name); //驅動的kobject初始化和添加dir到sysfs中,可以看出kobject_init()的功能就是初始化kobject結構中的成員狀態。kobject_add主要設置drvier的kobject和bus之間的層次關係,然後在sysfs中建立該驅動的文件夾。拿i2c總線舉個例子吧,i2c總線註冊好後將會有如下文件夾結構/sys/bus/i2c/,在/sys/bus/i2c/文件夾下會有如下文件夾uevent devices、drivers、drivers_probe、drivers_autoprobe,當你註冊驅動的時候,將會在/sys/bus/i2c/drivers/下注冊一個改驅動的文件夾,比如ov7675,那麼它將會註冊成/sys/bus/i2c/drivers/ov7675/,其實這些文件夾都對應一個kobject,通過kset容器組成一個很清晰的層次結構。
if (error)
goto out_unregister;
if (drv->bus->p->drivers_autoprobe) { //這個變量默認是爲1的
error = driver_attach(drv); //匹配函數,後面會分析
if (error)
goto out_unregister;
}
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); //將priv->knode_bus添加到bus->p->klist_drivers,見4-3部分
module_add_driver(drv->owner, drv); //添加drv的module,見4-4部分
error = driver_create_file(drv, &driver_attr_uevent); //在sysfs的目錄下創建文件uevent屬性文件,見4-5分析
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}
error = driver_add_attrs(bus, drv); //給driver添加bus上的所有屬性
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
__func__, drv->name);
}
error = add_bind_files(drv); //添加綁定文件,driver_attr_bind 和 driver_attr_unbind見4-5分析
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
kobject_uevent(&priv->kobj, KOBJ_ADD); //產生一個KOBJ_ADD uevent
return 0;
out_unregister:
kfree(drv->p);
drv->p = NULL;
kobject_put(&priv->kobj);
out_put_bus:
bus_put(bus);
return error;
}
d、driver_attach(drv);//爲驅動尋找相應的設備、//該函數將調用bus_for_each_dev()。
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
//該函數將調用bus_for_each_dev()。//監測到bus設備,調用__driver_attach( )
e、bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL)); //將bus中的已註冊的device列表放到迭代器中,方便索引
while ((dev = next_device(&i)) && !error) //將驅動逐個地與列表中每一個的device匹配,可能一個驅動匹配好幾個設備
error = fn(dev, data); //這個fn就是上面傳下來的__driver_attach
klist_iter_exit(&i);
return error;
}
f、__driver_attach(struct device * dev, void * data) //dev 爲使用驅動程式的設備結構體 //i2c總線根據設備client名字和id_table中的名字進行匹配的。如果匹配了,則返回id值,在i2c_device_match中則返回真。也就是bus的match函數將會返回真。那將會進入driver_probe_device()。
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
//*鎖定設備並嘗試綁定設備,出錯返回0,因爲我們需要一直嘗試綁定設備
//*若有些驅動不支持設備就返回錯誤
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (!driver_match_device(drv, dev)) //跟名字的意思一樣,driver跟device嘗試匹配,這裏看bus的總線的match函數是否已經註冊,如果沒註冊則直接返回1,如果註冊,則調用註冊的匹配函數。
return 0;
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
return 0;
}
g、driver_probe_device(drv, dev)
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;
if (!device_is_registered(dev)) //首先判斷這個device是否已經註冊
return -ENODEV;
pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
ret = really_probe(dev, drv); //轉而調用really_probe()
return ret;
}
h、really_probe(dev, drv) //調用driver的probe( ),dev爲設備結構體
static atomic_t probe_count = ATOMIC_INIT(0); //記錄probe數目
static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue); //probe隊列
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
atomic_inc(&probe_count); //原子增加計數
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv; //把驅動賦值給dev->drvier
if (driver_sysfs_add(dev)) { //主要是添加driver和dev之間的連接文件, 在driver目錄下添加以dev->kobj名字的連接文件,連接到device,或同樣在device目錄下添加‘driver’爲名字的連接文件連接到drvier
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) { //如果bus的probe註冊就執行,否則執行driver的probe,這也是函數開始時檢測的原因!
ret = dev->bus->probe(dev);//調用driver的probe( ),dev爲設備結構體
if (ret)
goto probe_failed;
} else if (drv->probe) { //這裏才真正調用了驅動的probe
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
driver_bound(dev); //driver綁定dev, 將設備的驅動node添加到diver的klist_devices中,初始化一個klist_node,並將klist聯繫起來
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
至此纔開始執行程序中的probe函數。