linux設備驅動程序--bus

linux 中bus驅動解析

總線(bus)是linux發展過程當中抽象出來的一種設備模型,爲了統一管理全部的設備,內核中每一個設備都會被掛載在總線上,這個bus能夠是對應硬件的bus(i2c bus、spi bus)、能夠是虛擬bus(platform bus)。html

簡述bus的工做流程

bus將全部掛在上面的具體設備抽象成兩部分,driver和device。node

driver實現了同類型設備的驅動程序實現,而device則向系統註冊具體的設備須要的資源,每當添加一個新的driver(device)到bus中時,都將調用bus的match函數,試圖尋找匹配的device(driver)。linux

總線大概是這樣的:框架

若是匹配成功,就調用probe函數,在probe函數中實現設備的初始化、各類配置以及生成用戶空間的文件接口。函數

舉個例子,針對AT24CXX(一種經常使用的存儲設備)這種同系列產品,他們的操做方式都是很是類似的,不一樣的無非是容量大小。post

那麼咱們就沒有必要爲AT24C0一、AT24C02去分別寫一份驅動程序,而是統一爲其寫一份兼容全部AT24CXX的驅動程序,而後再傳入不一樣的參數以對應具體的型號。debug

在linux驅動管理模型中的體現就是:驅動程序對應driver、須要的具體型號的硬件資源對應device,將其掛在bus上。rest

將driver註冊到bus上,當用戶須要使用AT24C01時,以AT24C01的參數構建一個對應device,註冊到bus中,bus的match函數匹配上以後,調用probe函數,便可完成AT24C01的初始化,完成在用戶空間的文件接口註冊。code

以此類推,添加全部的AT24CXX設備均可以以這樣的形式實現,只須要構建一份device,而不用爲每一個設備重寫一份驅動,提升了複用性,節省了內存空間。orm

linux bus結構體

linux將設備掛在總線上,對應設備的註冊和匹配流程由總線進行管理,在linux內核中,每個bus,都由struct bus_type來描述:

struct bus_type {
    const char      *name;
    const char      *dev_name;
    struct device       *dev_root;
    ...
    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);
    struct subsys_private *p;
    ...

};
爲了突出重點,省去了一些暫時不須要深刻了解的成員,咱們來看看其中最主要的幾個成員:

name : 該bus的名字,這個名字是這個bus在sysfs文件系統中的體現,對應/sys/bus/$name.
dev_name : 這個dev_name並不對應bus的名稱,而是對應bus所包含的struct device的名字,即對應dev_root。
dev_root:bus對應的device結構,每一個設備都須要對應一個相應的struct device.
match:bus的device鏈表和driver鏈表進行匹配的實際執行回調函數,每當有device或者driver添加到bus中時,調用match函數,爲device(driver)尋找匹配的driver(device)。
uevent:bus時間回調函數,當屬於這個bus的設備發生添加、刪除、修改等行爲時,都將出發uvent事件。
probe:當device和driver經由match匹配成功時,將會調用總線的probe函數實現具體driver的初始化。事實上每一個driver也會提供相應的probe函數,先調用總線的probe函數,在總線probe函數中調用driver的probe函數。
remove:移除掛載在設備上的driver,bus上的driver部分也會提供remove函數,在執行移除時,先調用driver的remove,而後再調用bus的remove以清除資源。
struct subsys_private *p:見下文

struct subsys_private p
struct subsys_private
p主要實現了對bus中數據的管理:

struct subsys_private {
    struct kset subsys;
    struct kset *devices_kset;

    struct kset *drivers_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    ...
};

其中struct kset subsys、struct kset devices_kset、struct kset drivers_kset都是在sysfs文件系統中建立對應的目錄。

struct klist klist_devices、struct klist klist_drivers是兩個主要的數據部分,klist_devices是存儲全部註冊到bus的device的鏈表,而klist_drivers是存儲全部註冊到bus的driver的鏈表。

bus的註冊

瞭解了bus的結構,那麼,bus是怎麼註冊的呢?

spi bus的註冊過程在KERNEL/drivers/spi/spi.c中:

static int __init spi_init(void)
{ 
    ...
    status = bus_register(&spi_bus_type);
    ...
    return 0;
}

postcore_initcall(spi_init);

i2c bus的註冊過程在KERNEL/drivers/i2c/i2c-core-base.c中:

static int __init i2c_init(void)
{
    ...
    bus_register(&i2c_bus_type);
    ...
}
postcore_initcall(i2c_init);

而platform bus的註冊過程在KERNEL/drivers/base/platform.c中:

int __init platform_bus_init(void)
{
    ...
    bus_register(&platform_bus_type);
    ...
}

i2c和spi爲物理總線,這兩種總線經過postcore_initcall()將各自的init函數註冊到系統中,postcore_initcall的詳解能夠參考另外一篇博客:linux init機制

而platform做爲虛擬總線,platform_bus_init被系統初始化時直接調用,調用流程爲:

start_kernel  
-> rest_init();
    -> kernel_thread(kernel_init, NULL, CLONE_FS);
        -> kernel_init()
            -> kernel_init_freeable();
                -> do_basic_setup();
                    -> driver_init();  
                        ->platform_bus_init();

spi_bus_type、i2c_bus_type、platform_bus_type分別爲對應的struct bus_type描述結構體。

對應的spi_bus_type、i2c_bus_type、platform_bus_type實現我將分別在spi、i2c、platform具體框架解析中介紹。

bus_register()

能夠看到,這三種總線都是由bus_register()接口註冊的,那麼這個接口到底作了什麼呢?

int bus_register(struct bus_type *bus)
{
    int retval;
    struct subsys_private *priv;
    struct lock_class_key *key = &bus->lock_key;

    priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->bus = bus;
    bus->p = priv;

    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
    if (retval)
        goto out;

    priv->subsys.kobj.kset = bus_kset;
    priv->subsys.kobj.ktype = &bus_ktype;
    priv->drivers_autoprobe = 1;

    retval = kset_register(&priv->subsys);
    if (retval)
        goto out;

    retval = bus_create_file(bus, &bus_attr_uevent);
    if (retval)
        goto bus_uevent_fail;

    priv->devices_kset = kset_create_and_add("devices", NULL,
                        &priv->subsys.kobj);
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }

    priv->drivers_kset = kset_create_and_add("drivers", NULL,
                        &priv->subsys.kobj);
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }

    INIT_LIST_HEAD(&priv->interfaces);
    __mutex_init(&priv->mutex, "subsys mutex", key);
    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
    klist_init(&priv->klist_drivers, NULL, NULL);

    retval = add_probe_files(bus);
    if (retval)
        goto bus_probe_files_fail;

    retval = bus_add_groups(bus, bus->bus_groups);
    if (retval)
        goto bus_groups_fail;

    pr_debug("bus: '%s': registered\n", bus->name);
    return 0;

}

在上面貼出的代碼中,能夠看出,bus_register()其實也沒作什麼特別的事,主要是兩個:

  • 在sysfs系統中註冊各類用戶文件接口,將bus的信息和操做接口導出到用戶接口。
  • 初始化device和driver鏈表。

向總線中添加driver/device

既然bus_register只是初始化了相應的資源,在/sys下導出接口文件,那整個bus是如何工做的呢?

以i2c爲例,咱們來看看這整個過程:

首先使用i2c_new_device接口來添加一個device:

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client   *client;
    client = kzalloc(sizeof *client, GFP_KERNEL);
    client->adapter = adap;

    client->dev.platform_data = info->platform_data;
    if (info->archdata)
        client->dev.archdata = *info->archdata;
    client->flags = info->flags;
    client->addr = info->addr;

    client->irq = info->irq;
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
    client->dev.of_node = info->of_node;
    client->dev.fwnode = info->fwnode;
    ...
    device_register(&client->dev);
    ...
}

申請一個i2c_client並對其賦值,而後以這個爲參數調用device_register(&client->dev),將dangqiandevice添加到bus中。

int device_register(struct device *dev)
{
    device_initialize(dev);
    return device_add(dev);
}

int device_add(struct device *dev)
{
    ...
     bus_add_device(dev);
    ...
}
int bus_add_device(struct device *dev){
    ...
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
    ...
    bus_probe_device(dev);
    ...
}

在device_register中,調用了device_add,緊接着調用bus_add_device,根據函數名稱能夠看出是將這個device添加到對應的bus中。

果真,根據bus_add_device()的源代碼,能夠看到,將當前device連接到其對應bus的devices鏈表,而後在下面調用bus_probe_device();這個函數的做用就是輪詢對應bus的drivers連接,查看新添加的device是否存在匹配的driver。

對應的,i2c_driver_register()將i2c driver部分添加到bus中,再輪詢檢查bus的devices鏈表是否有對應的device能匹配上,有興趣的能夠從i2c_driver_register()開始研究源代碼。

device和driver的匹配

上文中提到當bus中有新的device和driver添加時,會調用bus的match函數進行匹配,那麼究竟是怎麼匹配的呢?

簡單來講,在靜態定義的device中,通常會有.name屬性,與driver的.id_table屬性相匹配。

device部分還有可能從設備樹轉換而來,就有設備樹中相應的.compatible屬性和driver的of_match_table.compatible屬性相匹配。

事實上對於匹配這一部分,能夠直接參考每一個bus的match函數實現。

這一章節只是對linux中的總線作一個概念性的說明,在以後的博客中會詳細介紹到相應bus的框架,同時也會詳解對應的match()函數實現。

敬請期待!

好了,關於linux的bus討論就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言

原創博客,轉載請註明出處!

祝各位早日實現項目叢中過,bug不沾身.

相關文章
相關標籤/搜索