zoukankan      html  css  js  c++  java
  • 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
    ", 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不沾身.

  • 相关阅读:
    Could not load file or assembly Microsoft.SqlServer.management.sdk.sfc version 11.0.0.0
    代码覆盖率 (Code Coverage)从简到繁 (一)
    vscode配置自动格式化eslint 配置模板
    无效的源发行版: 10
    java读取一个文件写入另外一个文件
    notepad++使用正则表达式匹配
    jsp页面返回字符串而非方法执行后取得的数据?
    maven的原始setting.xml文件,自带阿里云镜像,之前配的时候出错,保存一下,注意可以在localRepository处设置存储依赖的地址,大概在49到54行,我的是<localRepository>F:/MavenRepository</localRepository>,F盘要先有这个文件夹
    Java 8 lambda Stream list to Map key 重复 value合并到Collection
    BufferedReader.readLine()读取文件
  • 原文地址:https://www.cnblogs.com/downey-blog/p/10507703.html
Copyright © 2011-2022 走看看