linux设备驱动程序--bus

linux 中bus驱动解析

总线(bus)是linux发展过程中抽象出来的一种设备模型,为了统一管理所有的设备,内核中每个设备都会被挂载在总线上,这个bus可以是对应硬件的bus(i2c bus、spi bus)、可以是虚拟bus(platform bus)。

简述bus的工作流程

bus将所有挂在上面的具体设备抽象成两部分,driver和device。

driver实现了同类型设备的驱动程序实现,而device则向系统注册具体的设备需要的资源,每当添加一个新的driver(device)到bus中时,都将调用bus的match函数,试图寻找匹配的device(driver)。

总线大概是这样的:

如果匹配成功,就调用probe函数,在probe函数中实现设备的初始化、各种配置以及生成用户空间的文件接口。

举个例子,针对AT24CXX(一种常用的存储设备)这种同系列产品,他们的操作方式都是非常相似的,不同的无非是容量大小。

那么我们就没有必要为AT24C01、AT24C02去分别写一份驱动程序,而是统一为其写一份兼容所有AT24CXX的驱动程序,然后再传入不同的参数以对应具体的型号。

在linux驱动管理模型中的体现就是:驱动程序对应driver、需要的具体型号的硬件资源对应device,将其挂在bus上。

将driver注册到bus上,当用户需要使用AT24C01时,以AT24C01的参数构建一个对应device,注册到bus中,bus的match函数匹配上之后,调用probe函数,即可完成AT24C01的初始化,完成在用户空间的文件接口注册。

以此类推,添加所有的AT24CXX设备都可以以这样的形式实现,只需要构建一份device,而不用为每个设备重写一份驱动,提高了复用性,节省了内存空间。

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不沾身.

猜你喜欢

转载自www.cnblogs.com/downey-blog/p/10507703.html