歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> Linux設備驅動模型

Linux設備驅動模型

日期:2017/3/3 12:56:01   编辑:Linux技術

驅動程序在計算機系統中有兩個作用,一是直接控制硬件,並給上層提供操作硬件的接口,二是主動向上層上報數據,總的來說是承上啟下。

Linux內核是大內核,所謂大內核是指內核中包含了設備驅動程序;而windows內核是小內核,不包含驅動。

驅動中module_init和module_exit兩個宏只是在kernel的編譯階段起作用,作用是將驅動的init函數和exit函數指針分別放到兩個指定的section代碼段裡面。Kernel初始化驅動的時候會從這個section代碼段開頭依次調用放到此處的所有驅動初始化函數指針。這兩個宏一般放在驅動代碼的最下面。某些驅動之間可能存在依賴關系,所以驅動的初始化需要有先後順序。內核中提供了設定驅動模塊初始化優先級的宏。

[code]#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn

#define pure_initcall(fn)       __define_initcall(fn, 0)

#define core_initcall(fn)       __define_initcall(fn, 1)
#define core_initcall_sync(fn)      __define_initcall(fn, 1s)
#define postcore_initcall(fn)       __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
#define arch_initcall(fn)       __define_initcall(fn, 3)
#define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
#define subsys_initcall(fn)     __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
#define fs_initcall(fn)         __define_initcall(fn, 5)
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
#define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
#define device_initcall(fn)     __define_initcall(fn, 6)
#define device_initcall_sync(fn)    __define_initcall(fn, 6s)
#define late_initcall(fn)       __define_initcall(fn, 7)
#define late_initcall_sync(fn)      __define_initcall(fn, 7s)
Linux在啟動過程中會開一個內核線程去初始化所有編譯到內核裡面的驅動程序,流程是:

[code]start_kernel --> rest_init --> kernel_thread --> kernel_init --> kernel_init_freeable --> do_basic_setup。
[code]static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    shmem_init();
    driver_init();
    init_irq_proc();
    do_ctors();
    usermodehelper_enable();
    do_initcalls();
    random_int_secret_init();
}
void __init driver_init(void)
{
    /* These are the core pieces */
    devtmpfs_init();
    devices_init();
    buses_init();
    classes_init();
    firmware_init();
    hypervisor_init();

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    platform_bus_init();
    cpu_dev_init();
    memory_dev_init();
    container_dev_init();
    of_core_init();
}

設備驅動程序初始化入口是

static int __init xxx_init(void)
函數,卸載函數為
static void __exit yyy_exit(void)
。這兩個函數一般位於驅動代碼的
module_init
module_exit
兩個宏之前。 Linux設備驅動模型的三個核心概念:bus、device和driver,即總線、設備和驅動。下面詳細說一下我的理解。

總線bus:一般指數據傳輸的通道,比如I2C總線就是I2C master跟I2C slave數據相互的通道,其他的還有I2S總線,SPI總線,MMC總線等等等等,這些總線都是物理中實實在在的線路。內核中還有一種是虛擬總線,純軟件的東西,最重要的一個就是platform總線,為啥要引入這個虛擬的platform總線呢?原因就是很多設備並沒有掛載在任何一種總線上,比如鍵盤,內核為了更好的管理設備和驅動,將這些不是總線設備的設備和它的驅動也當做一種總線設備和驅動去處理。總線在內核中對應的數據結構是struct bus_type,這是所有總線的一個抽象。所有總線的結構體變量都用這個類型定義。

設備device:一般指功能獨立的硬件模塊,控制器master是設備,被控制的slave也是設備。比如每一個I2C控制器都是一個設備,同時,每一個I2C總線上的slave也都是一個設備。內核中也有一種虛擬設備,純軟件的東西,這種虛擬設備挺多的,隨便都可以創建一個。設備在內核中對應的數據結構是struct device,這是所有設備的一個抽象,也是所有具體設備數據類型的基類。這個結構體裡面都是一些描述設備屬性的成員。

驅動driver:一般指控制設備的軟件,驅動和設備是孿生兄弟,誰也離不開誰。每一種設備都對應一套驅動程序,不同設備的驅動是不一樣的。比如現在的ARM架構的芯片都有3個左右的I2C控制器,所有I2C控制器共用一套驅動程序i2c_driver。驅動在內核中對應的數據結構是

struct device_driver
,這個數據類型裡面大多都是一些函數的指針,寫設備驅動代碼主要就是去實現裡面的這些函數。所有設備驅動的類型都是這個數據類型的派生類。比如
struct i2c_driver
裡面包含
struct device_driver
。下面以platform總線為例來說明Linux設備驅動模型。

[code]struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
struct platform_device {
    const char  *name;
    int     id;
    bool        id_auto;
    struct device   dev;
    u32     num_resources;
    struct resource *resource;

    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};
struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
};
probe函數,是驅動加載的入口函數,對於大部分設備驅動是必須的,簡單的設備驅動可能不需要,直接在init裡面把probe的事情都做了。之前說到的init函數是驅動初始化的入口函數,兩個概念是不一樣的。Init肯定是在probe之前調用,init肯定會被調用,但是probe不一樣會調用。Probe在什麼情況下才會被調用呢?Init函數一般是向總線中去注冊設備或驅動,把
xxx_driver
xxx_device
注冊到總線上。
platform_driver_register(&xxx_driver)
,這個函數是注冊platform總線驅動的API函數,相應的還有
platform_device_register(&xxx_device)
,注冊設備的API函數。這裡涉及到Linux驅動模型的核心思想,即注冊到總線上的設備和注冊到該總線上的驅動要match起來,然後probe才會被調用。所以,probe函數不是手動去調用的,而是match成功之後自動調用的。如果match不成功probe就不會被調用,驅動就不會被加載,然後設備和驅動就都無法使用。無論是真實的總線還是虛擬的platform總線,都分別使用兩條鏈表去管理掛載在其上的設備和驅動,鏈表上的每個節點都代表一個設備模塊或者一個驅動模塊。這兩條鏈表的位置分別是
struct bus_type --> struct subsys_private --> struct klist klist_devices
struct bus_type --> struct subsys_private --> struct klist klist_drivers

[code]struct bus_type {
    const char      *name;
    const char      *dev_name;
    struct device       *dev_root;
    struct bus_attribute    *bus_attrs;
    struct device_attribute *dev_attrs;
    struct driver_attribute *drv_attrs;

    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;

    struct iommu_ops *iommu_ops;

    struct subsys_private *p;
    struct lock_class_key lock_key;
};

struct subsys_private {
    struct kset subsys;
    struct kset *devices_kset;
    struct list_head interfaces;
    struct mutex mutex;

    struct kset *drivers_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;

    struct kset glue_dirs;
    struct class *class;
};
設備結構體中代表鏈表節點的是
struct platform_device --> struct device dev --> struct device_private *p –-> struct klist_node knode_bus
,這個knode_bus就是這個設備在設備鏈表裡面的節點。驅動結構體中代表鏈表節點的是
struct platform_driver --> struct device_driver driver --> struct driver_private *p --> struct klist_node knode_bus
,這個knode_bus就是這個驅動在驅動鏈表裡面的節點。無論是注冊設備,還是注冊驅動,過程中都會去遍歷另一條鏈表(注冊設備時遍歷驅動鏈表,注冊驅動時遍歷設備鏈表),首先要檢查之前有沒有注冊過,如果注冊過了就直接返回,目的就是要去尋找自己的另一半。尋找的依據是什麼呢?看看match函數。

[code]/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}
Match函數裡面提供了5種方法,任何一種匹配成功都算OK。其實主要就是對比device結構體和driver結構體裡面幾個關鍵成員是否一致。所以寫驅動的時候要注意這些成員,不能隨便寫。

驅動注冊和加載的流程是這樣的:

platform_driver_register –> driver_register –> bus_add_driver –> driver_attach –> bus_for_each_dev –> __driver_attach –> driver_match_device? –> driver_probe_device –> really_probe –> bus->probe/driver->probe。

最後如果bus的probe函數不為NULL,就執行bus的probe,其實bus的probe裡面也會調用drv的probe,所以最後都會調到drv的probe。

設備注冊的流程是這樣的:

platform_device_register –> platform_device_add –> device_add –> bus_probe_device –> device_initial_probe –> __device_attach –> bus_for_each_drv –> __device_attach_driver –> driver_match_device? –> driver_probe_device –> really_probe –> bus->probe/driver->probe。

Copyright © Linux教程網 All Rights Reserved