• linux设备驱动——andriod平台wlan驱动


     

    linux设备驱动——andriod平台wlan驱动

        关于这一部分的blog,所有的内容均摘自自己的工作总结笔记,在很多网站都发现了自己写的技术总结的转载感到很高兴,虽然我还是个菜鸟,但是我会继续努力。另外关于wlan驱动这方面的资料真的很少,我基本上是靠自己读代码来理解那些繁琐的寄存器读写、802.11/e/h/d等标准的,真的比较辛苦。不过好在算是慢慢的搞清楚了这个流程,在此之前我们仍然要补习一下关于在2.6版本内核中写驱动的知识。
        有关linux设备模型这一块比较复杂,我不敢断定自己理解的肯定正确,但是我会在做这个驱动的过程中回过头来修改自己的笔记并且纠正自己在blog上贴的并不正确的地方。另外,我的无线网卡是挂接在SDIO总线上的,所以呢,我们之前会先介绍一点SDIO的驱动,当然并不在这篇blog上,这篇blog会是总领性的关于基础知识的介绍。下面是笔记:
        在进入正式的驱动代码之前,我们不得不补充一点基础知识,也就是在2.6版本内核的现在,内核是如何管理总线,驱动,设备之间的关系的,关于bus_typedevice_driverdevice这三个内核结构在内核代码中可以找到。由于这三个结构的重要性,我们在这里先将它们贴出来,我会用红色的注释标注。我在笔记中将会提到的一些结构成员以其代表的意义,这样便于对照。(我不得不承认,这三个结构的代码枯燥复杂,但我可以保证,在看完我总结的笔记之后,你会对这三个结构中重要的数据成员有个非常不错的了解,当然你得对内核和驱动有基本的了解。如果我误导了你,请指正我,所以我的建议是不妨先看完了笔记再来看这些结构)
      1、设备结构的定义:
    struct device {
     struct klist  klist_children;         
     struct klist_node knode_parent;  /* node in sibling list */
     struct klist_node knode_driver;
     struct klist_node knode_bus;
     struct device  *parent;        
     struct kobject kobj;            //kobject结构,关于这个结构与kset结构以及subsystem结构,笔记中会有描述。
     char bus_id[BUS_ID_SIZE]; /* position on parent bus */
     struct device_type *type;
     unsigned  is_registered:1;
     unsigned  uevent_suppress:1;
     struct semaphore sem; /* semaphore to synchronize calls to
          * its driver.
          */
     struct bus_type * bus;  /* type of bus device is on */                  //这个设备挂接的总线的类型
     struct device_driver *driver; /* which driver has allocated this device */      //这个设备挂接的驱动
     void  *driver_data; /* data private to the driver */
     void  *platform_data; /* Platform specific data, device core doesn't touch it */
     struct dev_pm_info power;
    #ifdef CONFIG_NUMA
     int  numa_node; /* NUMA node this device is close to */
    #endif
     u64  *dma_mask; /* dma mask (if dma'able device) */
     u64  coherent_dma_mask;/* Like dma_mask, but for
              alloc_coherent mappings as
              not all hardware supports
              64 bit addresses for consistent
              allocations such descriptors. */
     struct list_head dma_pools; /* dma pools (if dma'ble) */
     struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
     /* arch specific additions */
     struct dev_archdata archdata;
     spinlock_t  devres_lock;
     struct list_head devres_head;
     /* class_device migration path */
     struct list_head node;
     struct class  *class;
     dev_t   devt;  /* dev_t, creates the sysfs "dev" */
     struct attribute_group **groups; /* optional groups */
     void (*release)(struct device * dev);
    };
      2、设备驱动的结构:
    struct device_driver {
     const char  * name;                                        //设备驱动的名字
     struct bus_type  * bus;                                    //设备驱动挂接的总线的类型
     struct kobject  kobj;                                      //kobject结构
     struct klist  klist_devices;                               //这个驱动对应的设备的链表
     struct klist_node knode_bus;
     struct module  * owner;
     const char   * mod_name; /* used for built-in modules */
     struct module_kobject * mkobj;
     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);
    };
     
      3、总线结构:
    struct bus_type {
     const char  * name;                                 //总线的名字
     struct module  * owner;
     struct kset  subsys;                                //与该总线相关的subsystem
     struct kset  drivers;                               //挂接在该总线上的驱动的集合
     struct kset  devices;                               //挂接在该总线上的设备的集合
     struct klist  klist_devices;                        
     struct klist  klist_drivers;
     struct blocking_notifier_head bus_notifier;
     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 (*suspend_late)(struct device * dev, pm_message_t state);
     int (*resume_early)(struct device * dev);
     int (*resume)(struct device * dev);
     unsigned int drivers_autoprobe:1;
    };
       我们注意到只有在bus_type结构中有kset结构,其他两个结构中则没有,我们知道kset结构是用于存放相同类型的kobject的,这究竟是个什么意思呢?kset又是为什么而存在的呢?为什么不能就是kobject呢?(关于kobject结构,我们很难抽象的形容,尽管它就是一个抽象的概念,我们将留待看代码的时候介绍,这里可以将kobject看成一个基类,kset就是容器了),下面我会按照自己的理解来回答这个问题(建议通读一下《linux设备驱动第三版》的linux 设备模型章节)
       首先不管是设备还是驱动,都是挂接在某条总线上的,也就是说我们根据总线类型的不同来区分各种设备和驱动。(但是我们也要注意到,一个设备和驱动是可以挂接在不同的总线上的,比如网卡可以挂接在pcisdio总线上,但这并不是说在linux设备模型中就可以同时挂接在两个总线上,我们只能选择其中的一种挂接)。
       在内核代码中我们找到了bus_type结构,我们发现了它使用了三个kset结构,分别是:       struct kset  subsys  struct kset  drivers struct kset devices。我们先抛开subsys因为它是用来向上挂接的。这里我们先看driversdevices两个kset结构的意义。我们从发现一个设备或者驱动说起吧,一个设备或者驱动向内核注册的时候(对于设备来说就是被插入了;对于驱动来说就是.ko模块被加载了),对于每一次设备注册或者驱动注册,我们都得分配一个device结构或者device_drive结构,每一次我们都需要将device结构挂入driversdevices(kset结构)链表中,这样我们能通过总线找到挂接在这个总线上的所有设备和驱动。但是这里我们注意到仅仅将设备们和驱动们挂接在总线上并不能表明设备和驱动之间的关系,这样的处理仅仅表明了驱动、设备与总线的关系,它们申明了我现在挂接在这条总线下,以后操作我就请通过这条总线。
       那么设备如何认出驱动,或者驱动如何认出设备呢?是的,我们是使用的probe函数。那么需要完成哪些过程了?在这些结构总是如何关联的?这才是我们关心的问题。所以这里我们将不得不在说明一下klist结构的作用。在内核代码中我们再次找到了device结构和device_drive结构。我们注意到在device结构中存在一个struct device_driver *driver这样的声明,而在device_drive中却并没有同样的包含device结构。我们这样想就明白了:对于一个设备来说,我们只能绑定一个驱动;而对于一个驱动来说,我们是可以对应于多个设备的。 也就是说这里device中的driver指针将会指向其绑定的驱动。那么回到probe探测函数,在我们对一个设备驱动进行注册的过程中,我们会在其相应的总线(也就是其挂接的总线)上发出一个探测,这个探测会搜寻所有挂接在这个总线上的尚未被绑定的设备(也就是driver指针为NULL),然后将driver指针指向这个驱动的结构,同时将这个设备的device结构挂接在device_driver结构中的klist链表中。 另外要提及一点就是我居然发现在device结构中也发现了一个这样的结构struct klist_node    knode_driver,它看起来跟klist有关,但是我得说我确实不认为device需要一个klist来挂上它能使用的driver。同样,当一个设备被注册时,它也会去寻找挂接在同一条总线上的驱动,并将自己与这个驱动联系起来。
     
       关于基础知识大致就这么些,如果需要知道的更细,可以查看《linux设备驱动第三版》,后面的我们会结合代码叙述注册设备,发现设备(对于驱动来说这里的工作其实很少,如果想知道内核做了些什么,就请跟着我一起看看内核的奥秘),以及数据传输的驱动代码。
    sdio_register_driver(&sdio) 函数被调用从而注册sdio驱动.这里已经进入内核部分代码,他存在于内核的drivers/mmc/core/sdio_bus.c文件中,噢,忘了说了,我看的内核代码版本是2.6.30.1.
        贴一下
    sdio_register_driver函数:    
     int sdio_register_driver(struct sdio_driver *drv)
    {
            drv->drv.name = drv->name;                          //首先忽略下面两行,直接进入
    driver_register函数.
            drv->drv.bus = &sdio_bus_type;                      //实际上这行代码是关键,而下面的函数中我们要找的仅仅是调用probe函数的地方而已,稍后分析
            return driver_register(&drv->drv);

    }
    来看driver_register函数的内容,由于其中涉及较多有关klist、kobject结构的内容,如果有不明白的地方,请查看同为《linux设备驱动——andriod平台wlan驱动》系列的介绍klist、kobject内容的章节,这里希望有的放矢.它的代码在drivers/base/driver.c中.
    int driver_register(struct device_driver *drv)
    {
            int ret;
            struct device_driver *other;

            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);        //在kobject结构组成的链表中查找是否已经存在这个驱动,以前的blog讲过,驱动必然挂接在某个总线上.请复习,返回值是
    device_driver结构的指针
            if (other) {
                    put_driver(other);                //由于之前增加了引用计数,这里在减1                        
                    printk(KERN_ERR "Error: Driver '%s' is already registered, "
                            "aborting.../n", drv->name);

                    return -EEXIST;
            }

            ret = bus_add_driver(drv);                                          //此函数是重点!
            if (ret)
                    return ret;
            ret = driver_add_groups(drv, drv->groups);               //这两个函数不用介绍也猜的出来
            if (ret)
                    bus_remove_driver(drv);
            return ret;
    }
    那么我们接着看
    bus_add_driver函数,这个函数的作用是:如果驱动还未挂接在总线上,挂接它并且调用probe函数进行探测.它的代码在drivers/base/bus.c中.
    int bus_add_driver(struct device_driver *drv)             //这个函数我不记得我的blog中是否有讲过(我记得我写过注释,如果没写过,就请自己看吧  呵呵 )
    {
           .......................................
            if (drv->bus->p->drivers_autoprobe) {
                    error = driver_attach(drv);                           //这个函数是重点.
                    if (error)
                            goto out_unregister;
            }
            ......................................       
     
    }
    driver_attach函数在drivers/base/dd.c,很简单的一句话:
     int driver_attach(struct device_driver *drv)
    {
            return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
    }

    这个函数会调用__driver_attach函数,我们已经接近目标了.
    static int __driver_attach(struct device *dev, void *data)
    {
            struct device_driver *drv = data;

            /*
             * 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 (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;
    }

    driver_probe_device函数中有一个really_probe函数,这是我们的最终目的地:
    static int really_probe(struct device *dev, struct device_driver *drv)
    {
            ...................................................
               if (dev->bus->probe) {                                         //我们看到了这里会调用probe函数,同样这里也会决定你的probe函数是使用一个参数还是两个参数,
                    ret = dev->bus->probe(dev);                          因为这取决于你是如何注册的.也就是是否调用了(仅仅针对于网友的提问)platform_device_register
                    if (ret)                                                                函数来注册设备.具体看下面的总结.
                            goto probe_failed;
            } else if (drv->probe) {
                    ret = drv->probe(dev);
                    if (ret)
                            goto probe_failed;
            }
             ....................................................
    }

    好了,这里来总结一下得失,同时根据以上列的代码回答网友的提问: if (dev->bus->probe)这个表示是否注册了device结构,如果注册了并且给device结构挂接上了驱动和总线,那么调用挂接在device结构中的总线的probe函数.这里的device结构从哪里冒出来的?它在 bus_for_each_dev函数中:
    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->knode_bus : NULL));
            while ((dev = next_device(&i)) && !error)                                      //查找每个挂接在sdio总线上的设备,看他们是否有注册,并调用相应的probe函数也就是
                    error = fn(dev, data);                                                                
    __driver_attach函数.实际上就是查找device结构.
            klist_iter_exit(&i); 
            return error;
    }

    关于platform_device结构与device结构的关系,就不用我在解释了吧!probe函数的调用,就取决于你在你注册的device结构中挂接的总线的类型了.因为调用 dev->bus->probe(dev); 所以清查看一下你注册是挂接的总线的probe函数的参数即可. 一般来说,参数会是两个,因为一类总线上总是可以挂接多个设备,所以我们还需要一个device_id. 如果行到else部分: else if (drv->probe) .这里调用驱动的probe函数,由于我们注册的是sdio_driver结构.看一看sdio_driver结构的申明,在include/linux/mmc/sdio_func.h中:
    struct sdio_driver {
            char *name;
            const struct sdio_device_id *id_table;

            int (*probe)(struct sdio_func *, const struct sdio_device_id *);                     //很显然probe就是两个参数,而不是一个.
            void (*remove)(struct sdio_func *);

            struct device_driver drv;
    };

    至于为什么你的probe是一个参数,我希望你能给出完整的注册流程才能分析,但是我认为根据我刚才分析的流程应该可以自己找出相应的代码了.
    ////////////////////////////////////////
    首先我要做的是总领一下kobject、kset、subsystem这三个结构之间的关系。同样以下内容摘自我的工作笔记,但顺序颠倒了,这里先行介绍可能对我们之后的代码介绍要有些许帮助。
        关于kobject结构,首先每个目录代表一个kobject对象,每个文件代表kobject的属性(这里我实在不敢肯定!)。kobject是组成设备模型的基本结构,它只是一个类似c++中基类的东西。
       我想给出这样一个比喻(我不知道这个比喻是否恰当):对于每一个目录,它们都有一些共同的特点,比如都有名字,都有父子目录。而对于“方法”来说,它们都有用来实现引用计数的方法。我们将这些共同点封装起来形成了基类。但是这个基类并不能真正的代表一个目录,加上了其它的特征之后才能成为一个真正的目录,所以我们需要将kobject这个结构嵌入所有的sysfs下的目录中以获得kobject提供的一些特征,在加上每个目录自己所独有特征从而形成一个sysfs中的目录。这也就是我理解的kobject结构的意义。那么kobject结构是否真的就对应于一个目录呢?回答是肯定的,因为我在代码中找到了这样的答案,每一次调用kobject_add函数,都会在这个函数中调用create_dir来创建一个目录。
        那么kset又是什么呢?为什么需要kset?还有subsystem呢?kobject、kset、subsystem这三个结构到底在设备树中各自承担什么样的角色?kset是是嵌入相同类型结构的kobject集合,对于c++来说意味着将继承自所有基类的子类放在一起,这有什么意义呢?这样想是否就会觉得很合理了,kset是用来将所有有着共同特点的目录联系在一起的东西。可能你还是要在内心不停的问,这太抽象了 太抽象了,能举个例子吗?是的,比如我们的设备树下的pci总线目录/sys/bus/pci下挂接着很多的设备和驱动(当然有很多的设备和驱动可以挂着在pci总线下),那么我们如何将它们联系在一起呢?是的,是通过kset结构,那么kset结构是个什么样的地位呢?噢,我想之前我理解错了,它不是/sys/bus/pci目录,也不是/sys/bus/pci/drivers目录中的某个具体驱动,它刚好就是/sys/bus/pci/drivers目录。不相信? 那么打开你们的linux操作系统,看一看是否每一条总线都有着drivesdevices两个目录?它们两个都是嵌入了kset结构;那么/sys/bus/pci/drivers目录中的某个具体驱动呢?它就是嵌入了kobject结构的目录;/sys/bus/pci目录呢?猜对了,它就是subsystem结构的嵌入。 还有要补充的吗?是呢,现在subsystem貌似被取消了,取代它的就是kset结构。这样也就是说一系列的kset就组成了subsystem
        我似乎忘记了自己问了三个问题,对的,我还需要问答自己看《linux设备驱动第三版》提出来的第三个问题:为什么需要kset?提出这个问题的想法很幼稚,难道我们不能够只用kobject结构将挂接在一条总线上的驱动或者设备都链接在一起吗?只要我们有list_head!linux内核的作者回答了我这个问题,你需要一个容器来管理它们,就像c++中的容器一样。但这至少说明了kset并不是必要的存在。那么kset是如何扮演容器的角色的呢?这个问题,我们需要看后面的图解来回答。
        以上是摘自我穿插在工作笔记中的关于kobject、ket、subsystem三个结构的描述,可能大家读完这段解释仍然无法形成具体的概念,仍然觉得很抽象。没关系,因为我在代码分析之后还会有总结性的举例。那时还不明白我就没辙了!!!
        好了,现在我们得费劲心思的捋一遍我们的驱动注册代码,以便找到设备树添加的关键部分。我想我又得强调一下,我的介绍是SDIO驱动,所以请大家看着linux内核代码drivers/mmc中关于sdio的驱动来理解我下面的笔记中的内容(想不看内核代码就理解设备树,我想太难太难)
        有关sbi_register函数(这是在我的wlan驱动代码中的函数,并不需要你太多的关注)中sdio_register_driver函数(从现在开始就都是内核函数了)注册驱动的介绍,在sdio_register_driver中将会指明驱动的名称(这里是wlan_sdio),此函数的参数为sdio_driver结构。驱动所挂接的总线sdio_bus_type,其结构类型为:
      static struct bus_type sdio_bus_type = {
                 .name             = "sdio",                 //总线类型
                 .dev_attrs       = sdio_dev_attrs,              //属性
                 .match           = sdio_bus_match,         //ops
                 .uevent           = sdio_bus_uevent,
                 .probe            = sdio_bus_probe, 
                 .remove          = sdio_bus_remove,
    };
        这个结构将在sdio_register_driver函数中被赋值以产生device_driver结构。也就是说device_driver被包含在sdio_driver中。随后调用函数driver_register,其参数为device_driver(此结构中定义了bus_type,也就是驱动挂接的总线类型)。至此将转入所有驱动(不止是sdio卡驱动)的注册代码中。此时的驱动结构已经变为device_drive(内核定义的驱动结构)
        driver_register将会完成挂接驱动至总线及生成设备树的过程,其完成的任务大致包括:
        1、设置设备驱动中kobject的名字
        2、将kobject添加至sysfs中,也就是在sysfs树中创建一个目录(kobject中有一个函数create_dir用于创建目录。)
          3、调用driver_attach函数完成probe 
          4driver_create_file创建文件属性,会生成一个属性为driver_attr_uevent的属性文件。
          5add_bind_files生成属性为driver_attr_unbinddriver_attr_bind的属性文件,关于文件的属性,它定义了文件的读写方式。
        如此抽象的描述及流水账般的记述我发现还是很没有说服力,因此我决定给出一个具体的例子,并给出代码的实现。先看例子——让我们看一下/sys/bus/下的目录,然后来个具体的描述:已知我们的总线的目录名字为”sdio”。也就是说在/sys/bus目录下有一个目录叫sdio,即/sys/bus/sdio。它是怎么形成的?内核中有”sdio”总线的驱动,找到这个函数bus_register(&sdio_bus_type);就是用来注册总线的。我们在前面不也看到了driver_register函数吗,是的,你猜对了,还有一个函数叫device_register。在这个函数调用完成后,就会得到/sys/bus/目录下的sdio目录了。那么驱动的名字”wlan_sdio”又是如何插入到/sys/bus/drivers/wlan_sdio目录下的呢。在driver_register->driver_register->bus_add_driver函数中有个重要的语句drv->kobj.kset = &bus->drivers;想象一下我们折腾了那么长时间的kobjectkset的意义,是的,这里就是将driverkobj所属的kset挂接上总线的kset。我们这里显得很绕对吗?幸运的是我在网上找到了一个非常棒的示意图:
     
                         
     

       我该怎么样来形容这个图呢? 它把依赖关系说的已经足够清楚了!!我这里唯一要解释的仅仅是在驱动文件夹被正确后所谓的文件属性有在那里。同样我们顺着目录/sys/bus/sdio/driver/wlan_sdio/下我们发现了bindunbindnew_id三个文件。

       好了,别忘了我们在前面提过的问题,kset是如何扮演容器的角色的呢?图中很清楚吧,看看粉红色的箭头,kset children list用来将将同类型的kobject连接起来以达到容器的效果。

     

    但是我们的驱动还没有结束,关于这个图的建立,我们势必要用代码才能说得明白。

    我们还是花费一点时间来看一下内核中的代码,关于sdio总线注册的代码部分,其它的部分,大家类举就可以了。懂了这段自然就懂了全部:

    int bus_register(struct bus_type * bus)

    {

           int retval;

     

           BLOCKING_INIT_NOTIFIER_HEAD(&bus->bus_notifier);

     

           retval = kobject_set_name(&bus->subsys.kobj, "%s", bus->name);    //总线的名字”sdio”,我们说过了一个kobject对应一个目录,这时会为这个目录赋值名字。

           if (retval)

                  goto out;

     

           bus->subsys.kobj.kset = &bus_subsys;                        //将其kset指向bus_subsys.,如何理解? 看看ldd_bus_type指向bus_subsys的那条蓝线

     

           retval = subsystem_register(&bus->subsys);              //bus->subsys的注册,实际上是用kset指针将其链接在一起。好吧 我得承认实际上这里取消了subsysem结构的概念,用kset代替了。这里会创建一个目录。它是一个kset也是一个kobject,因为kset包含了kobject

           if (retval)

                  goto out;

     

           retval = bus_create_file(bus, &bus_attr_uevent);            //创建属性文件

           if (retval)

                  goto bus_uevent_fail;

     

           kobject_set_name(&bus->devices.kobj, "devices");         //设置devices kset的名字为devices

           bus->devices.kobj.parent = &bus->subsys.kobj;            //参见ldd_bus_type->devices指向ldd_bus_type->sub_sys的红色箭头(注意这里也不是subsystem,而是kset

           retval = kset_register(&bus->devices);                 //创建devices命名的目录

           if (retval)

                  goto bus_devices_fail;

     

           kobject_set_name(&bus->drivers.kobj, "drivers");       //设置devices kset的名字为drivers

           bus->drivers.kobj.parent = &bus->subsys.kobj;         //同样参见ldd_bus_type->drivers指向ldd_bus_type->sub_sys的红色箭头(注意这里也不是subsystem,而是kset)。

           bus->drivers.ktype = &driver_ktype;                   //kobject默认属性的赋值

           retval = kset_register(&bus->drivers);                 //创建drivers命名的目录

           if (retval)

                  goto bus_drivers_fail;

     

           klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);    //klist结构的初始化,关于klist链接的作用我们已经说得很清楚了

           klist_init(&bus->klist_drivers, NULL, NULL);

     

           bus->drivers_autoprobe = 1;

           retval = add_probe_files(bus);                      //添加探测属性

           if (retval)

                  goto bus_probe_files_fail;

     

           retval = bus_add_attrs(bus);                       //添加其他属性

           if (retval)

                  goto bus_attrs_fail;

     

           pr_debug("bus type '%s' registered/n", bus->name);

           return 0;

     

    bus_attrs_fail:

           remove_probe_files(bus);

    bus_probe_files_fail:

           kset_unregister(&bus->drivers);

    bus_drivers_fail:

           kset_unregister(&bus->devices);

    bus_devices_fail:

           bus_remove_file(bus, &bus_attr_uevent);

    bus_uevent_fail:

           subsystem_unregister(&bus->subsys);

    out:

           return retval;

    }

    至此 我们终于理顺了整个过程

  • 相关阅读:
    linux查看用户组所有成员
    navicat for mysql 在Mac上安装后没有连接列表,就是左边的那一列连接项目怎么办?
    mysql启动问题access denied for user 'root'@'localhost'(using password:YES)
    phpcms多站点表单统一到主站点管理的解决方案
    thinkphp5.0 session驱动方式问题汇总
    Python__开启进程的两种方式
    Python并发编程之操作系统理论部分
    操作系统简介
    Python__基于udp的套接字
    网络编程1
  • 原文地址:https://www.cnblogs.com/yuzaipiaofei/p/4124159.html
Copyright © 2020-2023  润新知