嵌入式Linux設備驅動開發(一)

設備驅動開發是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函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章