• Linux 驱动框架---linux 设备


    Linux 设备

    Linux驱动中的三大主要基础成员主要是设备,总线和驱动。今天先来从设备开始分析先把设备相关的数据结构放到这里方便后面看到来查,其中有些进行了简单的注释。

    struct device {
        struct device        *parent;/*父设备*/
    
        struct device_private    *p;/*设备私有数据 包含子设备,父设备,总线等的链表*/
    
        struct kobject kobj;/*内核对象*/
        const char        *init_name; /* initial name of the device */
        const struct device_type *type; /* 设备类型抽象共同的部分*/
    
        struct mutex        mutex;    /* mutex 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        *platform_data;    /* Platform specific data, device
                           core doesn't touch it */
        void        *driver_data;    /* Driver data, set and get with
                           dev_set/get_drvdata */
        struct dev_pm_info    power;
        struct dev_pm_domain    *pm_domain;
    
    #ifdef CONFIG_PINCTRL
        struct dev_pin_info    *pins;
    #endif
    
    #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. */
        unsigned long    dma_pfn_offset;
    
        struct device_dma_parameters *dma_parms;
    
        struct list_head    dma_pools;    /* dma pools (if dma'ble) */
    
        struct dma_coherent_mem    *dma_mem; /* internal for coherent mem
                             override */
    #ifdef CONFIG_DMA_CMA
        struct cma *cma_area;        /* contiguous memory area for dma
                           allocations */
    #endif
        /* arch specific additions */
        struct dev_archdata    archdata;
    
        struct device_node    *of_node; /* associated device tree node */
        struct acpi_dev_node    acpi_node; /* associated ACPI device node */
    
        dev_t            devt;    /* dev_t, creates the sysfs "dev" */
        u32            id;    /* device instance */
    
        spinlock_t        devres_lock;
        struct list_head    devres_head;
    
        struct klist_node    knode_class;
        struct class        *class;
        const struct attribute_group **groups;    /* optional groups */
    
        void    (*release)(struct device *dev);
        struct iommu_group    *iommu_group;
    
        bool            offline_disabled:1;
        bool            offline:1;
    };

    有了设备就需要考虑设备创建和加入的部分了,Linux内核不使用设备树时设备是板级文件创建并注册添加的,使用设备树后就是由系统启动从设备树中解析出来并动态创建的。

    设备创建

    struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)

    上面这个函数实际内部调用是

    struct device *device_create(struct class *class, struct device *parent,
                     dev_t devt, void *drvdata, const char *fmt, ...)
    {
        va_list vargs;
        struct device *dev;
    
        va_start(vargs, fmt);
        dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
        va_end(vargs);
        return dev;
    }

           设备的创建有两种情况通过device 提供的接口 device_create 创建设备,此时的设备是通过动态申请内存创建的,创建的时候可以种指定设备类class父设备parent,设备携带的驱动数据drvdata等除此之外还需要提供设备号dev_t,这个接口最终会返回一个struct device 的设备句柄。只要是通过这种方式创建的设备这个设备中的release接口就会自动绑定一个释放这个设备所占内存的方法。

    static void device_create_release(struct device *dev)
    {
        pr_debug("device: '%s': %s
    ", dev_name(dev), __func__);
        kfree(dev);
    }
    
    static struct device *
    device_create_groups_vargs(struct class *class, struct device *parent,
                   dev_t devt, void *drvdata,
                   const struct attribute_group **groups,
                   const char *fmt, va_list args)
    {
        struct device *dev = NULL;
        int retval = -ENODEV;
    
        if (class == NULL || IS_ERR(class))
            goto error;
    
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev) {
            retval = -ENOMEM;
            goto error;
        }
    
        device_initialize(dev);
        dev->devt = devt;
        dev->class = class;
        dev->parent = parent;
        dev->groups = groups;
        dev->release = device_create_release;
        dev_set_drvdata(dev, drvdata);
    
        retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
        if (retval)
            goto error;
    
        retval = device_add(dev);
        if (retval)
            goto error;
    
        return dev;
    
    error:
        put_device(dev);
        return ERR_PTR(retval);
    }

    这个接口创建的设备他的init_name 字段是未初始化的,之后这个接口的后续会调用device_add(这个接口内会检查device init_name是否设置,如果有则会将其设置给device name字段,然后会将device init_name置空;反之如果没有指定这个字段则系统将按照总线名称和设备号的方式给他一个name,总之设备不能没有未初始化名称然后添加到系统中)将设备加入到指定的总线。第二种是静态创建常见包含在platform_device中在芯片的板级文件中静态创建platform_device设备的时候创建,具体可以platform_device的结构体定义就能发现其中定义了一个设备实体成员而非句柄。

    由上面可以看出实际是device_create -->device_create_vargs这个函数里面最后会调用device的添加函数(device_add())将新创建的设备添加到内核。

    设备注册添加

    设备创建好并初始化好后需要注册添加进内核,一共有两种情况下将设备添加进内核。第一种就是上面的提到的设备创建和添加一起由创建的内部完成就是通过device_add接口,还有一种就是device_register,他的调用过程大致如下面的过程。因为很多地方有时候是调用的注册接口(device_register)有时调用的是接口(device_add),所以这里就顺带把这个接口也拿出来说一下区分一下区别。直接看代码就很直观了 

    device_register

    1 int device_register(struct device *dev)
    2 {
    3     device_initialize(dev);
    4     return device_add(dev);
    5 }

    调用-->device_initialize() 初始化必要的成员后最终也是调用device_add将设备添加进内核。接下来开始细看device_add 过程(关键)。在这之前需要明确在Linux中设备都是挂在总线下的没有总线的设备没有意义。下面就是device_add接口的内部实现,加了一些注释,异常处理删除了。

      1 int device_add(struct device *dev)
      2 {
      3     struct device *parent = NULL;
      4     struct kobject *kobj;
      5     struct class_interface *class_intf;
      6     int error = -EINVAL;
      7     /* 增加设备的引用次数 */
      8     dev = get_device(dev);
      9     if (!dev)
     10         goto done;
     11     /* 增加设备私有数据 私有数据包括指向当前device自己的指针和一个list用来链接设备下的子设备 */
     12     if (!dev->p) {
     13         error = device_private_init(dev);
     14         if (error)
     15             goto done;
     16     }
     17 
     18     /* Kobject 的设置 */
     19     if (dev->init_name) {
     20         dev_set_name(dev, "%s", dev->init_name);
     21         dev->init_name = NULL;
     22     }
     23 
     24     /* 设备name如果未设置 则按 %s%u,总线name,device->id形式格式化*/
     25     if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
     26         dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
     27     /* 没有设备名的设备是不允许添加到系统的 */
     28     if (!dev_name(dev)) {
     29         error = -EINVAL;
     30         goto name_error;
     31     }
     32 
     33     pr_debug("device: '%s': %s
    ", dev_name(dev), __func__);
     34     /* 增加父设备引用计数 */
     35     parent = get_device(dev->parent);
     36     /* 参考 https://blog.csdn.net/qq_20678703/article/details/52846064 */
     37     kobj = get_device_parent(dev, parent);
     38     if (kobj)
     39         dev->kobj.parent = kobj;
     40 
     41     /* use parent numa_node */
     42     if (parent)
     43         set_dev_node(dev, dev_to_node(parent));
     44 
     45     /* first, register with generic layer. */
     46     /* we require the name to be set before, and pass NULL */
     47     /* kobject 操作 可以参考kobject的相关部分 */
     48     error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
     49     if (error)
     50         goto Error;
     51 
     52     /* notify platform of device entry */
     53     if (platform_notify)
     54         platform_notify(dev);
     55     /* sysfs操作 创建设备通用属性的文件虚拟文件系统相关*/
     56     error = device_create_file(dev, &dev_attr_uevent);
     57     if (error)
     58         goto attrError;
     59     /* sysfs下创建虚拟文件 */
     60     if (MAJOR(dev->devt)) {
     61         error = device_create_file(dev, &dev_attr_dev);
     62         if (error)
     63             goto ueventattrError;
     64         error = device_create_sys_dev_entry(dev);
     65         if (error)
     66             goto devtattrError;
     67 
     68         devtmpfs_create_node(dev);
     69     }
     70     /* 实际的设备按不同的分类可能同属多个分类父设备  但是真实的设备在文件系统中只要
     71     一份而其他的则是以符号链接的形式指向唯一的实体 */
     72     error = device_add_class_symlinks(dev);
     73     if (error)
     74         goto SymlinkError;
     75     /* sysfs操作 创建设备个性属性的文件*/    
     76     error = device_add_attrs(dev);
     77     if (error)
     78         goto AttrsError;
     79     /* 根据device 中的bus指针将设备添加到特定的总线*/
     80     error = bus_add_device(dev);
     81     if (error)
     82         goto BusError;
     83     /* 电源管理相关 */
     84     error = dpm_sysfs_add(dev);
     85     if (error)
     86         goto DPMError;
     87     /* 将设备加入到了一个list 中 电源管理子系统?*/
     88     device_pm_add(dev);
     89 
     90     /* Notify clients of device addition.  This call must come
     91      * after dpm_sysfs_add() and before kobject_uevent().
     92      */
     93     /* 一种客户端新设备发现机制 通知客户端新设备添加 */
     94     if (dev->bus)
     95         blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
     96                          BUS_NOTIFY_ADD_DEVICE, dev);
     97     /* kobject的事件机制 产生uevent事件 事件类型为ADD
     98     *  内核可以通过helper或netlink机制和用户空间的应用通信,热插拔机制
     99     */
    100     kobject_uevent(&dev->kobj, KOBJ_ADD);
    101     /*
    102      * 驱动开始匹配,后面详细分析
    103      */
    104     bus_probe_device(dev);
    105     /* 设备私有数据 将设备添加到父设备子设备list中*/
    106     if (parent)
    107         klist_add_tail(&dev->p->knode_parent,
    108                    &parent->p->klist_children);
    109     /* 将设备添加到所属class设备子设备list中*/
    110     if (dev->class) {
    111         mutex_lock(&dev->class->p->mutex);
    112         /* tie the class to the device */
    113         klist_add_tail(&dev->knode_class,
    114                    &dev->class->p->klist_devices);
    115 
    116         /*  */
    117         /* notify any interfaces that the device is here */
    118         list_for_each_entry(class_intf,
    119                     &dev->class->p->interfaces, node)
    120             if (class_intf->add_dev)
    121                 class_intf->add_dev(dev, class_intf);
    122         mutex_unlock(&dev->class->p->mutex);
    123     }
    124 done:
    125     put_device(dev);
    126     return error;
    127 128 129 130 131 132 133 }

    从这个函数我们了解了设备添加的大致过程:

    • device结构体中的struct device_private *p 实际上就是设备私有数据用来管理设备相关的子设备list,总线等信息。
    • device 中的init_name 字段实际上最后是给到了内核对象kobject,如果未指定device的init_name则会按照总线和设备ID的方式构造device的name。
    • 没有名字的设备是不允许添加到系统的。
    • 最后就是kobject的初始化和添加了,这一部分可以参考kobject的实现。
    • 大多数的设备虚拟文件系统中的文件创建和设备是否匹配到驱动无关,至于/dev目录下的还是个疑问。
    • uevent 设备事件产生。
    • 驱动探测的成功和失败的结果不影响设备的注册添加,因为驱动可以晚于设备注册。
    • 是否自动进行驱动匹配取决于系统的总系的属性。

     中间的bus_probe_device就是开始探测驱动的过程。

    /*
     * 驱动匹配
     */
    void bus_probe_device(struct device *dev)
    {
        struct bus_type *bus = dev->bus;
        struct subsys_interface *sif;
        int ret;
    
        if (!bus)
            return;
        //是否自动探测
        if (bus->p->drivers_autoprobe) {
            ret = device_attach(dev);
            WARN_ON(ret < 0);
        }
    
        mutex_lock(&bus->p->mutex);
        list_for_each_entry(sif, &bus->p->interfaces, node)
            if (sif->add_dev)
                sif->add_dev(dev, sif);
        mutex_unlock(&bus->p->mutex);
    }
    /**
    *
    **/
    int device_attach(struct device *dev) { int ret = 0; device_lock(dev); /* 如果设备已经指定驱动 则直接执行驱动和设备的绑定操作,在sys文件系统下建立对应文件 */ if (dev->driver) { if (klist_node_attached(&dev->p->knode_driver)) { ret = 1; goto out_unlock; } ret = device_bind_driver(dev); if (ret == 0) ret = 1; else { dev->driver = NULL; ret = 0; } } else { /* 需要匹配 */ ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); pm_request_idle(dev); } out_unlock: device_unlock(dev); return ret; }

    分两种情况,一种是这个接口被调用仅仅是来绑定驱动和设备的;而我们这里显然不是,通过bus_for_each_drv 遍历总线上的每一个驱动和当前设备进行适配,尝试能否匹配。匹配用的接口是__device_attach这个接口代码如下,处理的过程大致是

    如果总线有实现匹配match接口则调用总线的match接口传入的参数就是驱动和设备的句柄。否则调用driver_probe_device进行进一步的尝试。

    int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
                 void *data, int (*fn)(struct device_driver *, void *))
    {
        struct klist_iter i;
        struct device_driver *drv;
        int error = 0;
    
        if (!bus)
            return -EINVAL;
        /* 遍历总线中的驱动list 挨个执行__device_attach函数 */
        klist_iter_init_node(&bus->p->klist_drivers, &i,
                     start ? &start->p->knode_bus : NULL);
        while ((drv = next_driver(&i)) && !error)
            error = fn(drv, data);
        klist_iter_exit(&i);
        return error;
    }
    
    static int __device_attach(struct device_driver *drv, void *data)
    {
        struct device *dev = data;
        /* 如果总线有定义mach函数则 调用总线的mach函数 这个接口由总线提供  */
        if (!driver_match_device(drv, dev))
            return 0;
        /* 前面的匹配未成功 进一步探测*/
        return driver_probe_device(drv, dev);
    }
    

    如果总线的match接口成功匹配了就直接返回,否则继续执行 driver_probe_device接口,这个接口如下又调用了really_probe。

    /* 这里会通过调用really_probe 然后执行真正的驱动的probe函数*/
    int driver_probe_device(struct device_driver *drv, struct device *dev)
    {
        int ret = 0;
    
        if (!device_is_registered(dev))
            return -ENODEV;
    
        pr_debug("bus: '%s': %s: matched device %s with driver %s
    ",
             drv->bus->name, __func__, dev_name(dev), drv->name);
    
        pm_runtime_barrier(dev);
        ret = really_probe(dev, drv);
        pm_request_idle(dev);
    
        return ret;
    }

    继续看really_probe这个接口的实现如下:

    static int really_probe(struct device *dev, struct device_driver *drv)
    {
        int ret = 0;
        int local_trigger_count = atomic_read(&deferred_trigger_count);
    
        atomic_inc(&probe_count);
        pr_debug("bus: '%s': %s: probing driver %s with device %s
    ",
             drv->bus->name, __func__, drv->name, dev_name(dev));
        WARN_ON(!list_empty(&dev->devres_head));
        /* 先绑定驱动和设备*/
        dev->driver = drv;
        /* 如果需要绑定pin 则绑定pin*/
        /* If using pinctrl, bind pins now before probing */
        ret = pinctrl_bind_pins(dev);
        if (ret)
            goto probe_failed;
        /* 设备射驱动的sys目录下的操作 */
        if (driver_sysfs_add(dev)) {
            printk(KERN_ERR "%s: driver_sysfs_add(%s) failed
    ",
                __func__, dev_name(dev));
            goto probe_failed;
        }
       /* 如果总线提供了probe接口则执行总线的接口否则执行驱动的probe,这也
       *  就解释了driver_register过程检查总线和drv的probe接口都在时会给出警告,因为他俩只能二选一且总线优先
       */
        if (dev->bus->probe) {
            ret = dev->bus->probe(dev);
            if (ret)
                goto probe_failed;
        } else if (drv->probe) {
            ret = drv->probe(dev);
            if (ret)
                goto probe_failed;
        }
        /* 执行到这里就是匹配成功了,所以执行剩下的驱动绑定操作,至此驱动匹配结束 */
        driver_bound(dev);
        ret = 1;
        pr_debug("bus: '%s': %s: bound device %s to driver %s
    ",
             drv->bus->name, __func__, dev_name(dev), drv->name);
        goto done;
        /* 如果上面的操作失败了都会执行下面的操作进行开始匹配前一些操作的逆操作 */
    probe_failed:
        devres_release_all(dev);
        driver_sysfs_remove(dev);
        dev->driver = NULL;
        dev_set_drvdata(dev, NULL);
    
        if (ret == -EPROBE_DEFER) {
            /* Driver requested deferred probing */
            dev_info(dev, "Driver %s requests probe deferral
    ", drv->name);
            driver_deferred_probe_add(dev);
            /* Did a trigger occur while probing? Need to re-trigger if yes */
            if (local_trigger_count != atomic_read(&deferred_trigger_count))
                driver_deferred_probe_trigger();
        } else if (ret != -ENODEV && ret != -ENXIO) {
            /* driver matched but the probe failed */
            printk(KERN_WARNING
                   "%s: probe of %s failed with error %d
    ",
                   drv->name, dev_name(dev), ret);
        } else {
            pr_debug("%s: probe of %s rejects match %d
    ",
                   drv->name, dev_name(dev), ret);
        }
        /*
         * Ignore errors returned by ->probe so that the next driver can try
         * its luck.
         */
        ret = 0;
    done:
        atomic_dec(&probe_count);
        wake_up(&probe_waitqueue);
        return ret;
    }

    先判断总线是否实现了探测函数,是则执行总线的探测接口传入的参数为当前设备句柄,反之如果总线没有实现探测接口则执行判断驱动是否实现了探测接口是则调用传入的参数也是设备句柄。这里还暗含了一个机制如果总线提供了probe接口则执行总线的接口否则执行驱动的probe,这也就解释了driver_register过程检查总线和drv的probe接口都在时会给出警告,因为他俩只能二选一且总线优先具体的操作都在代码中注释了。并且更加肯定在驱动和总线都有探测接口时是优先使用总线的probe接口。可以明白设备注册时指定总线对于驱动的匹配十分重要,没有总线设备添加了也无法自动探测驱动---字符设备和杂项设备是个例外因为他们好像没有强化驱动和设备的概念。

    其他会使用的接口

    void device_initialize(struct device *dev) 设备初始化
    {
        dev->kobj.kset = devices_kset;
        kobject_init(&dev->kobj, &device_ktype);
        INIT_LIST_HEAD(&dev->dma_pools);
        mutex_init(&dev->mutex);
        lockdep_set_novalidate_class(&dev->mutex);
        spin_lock_init(&dev->devres_lock);
        INIT_LIST_HEAD(&dev->devres_head);
        device_pm_init(dev);
        set_dev_node(dev, -1);
    }
    
    int dev_set_name(struct device *dev, const char *fmt, ...)设置设备名称并不是init_name 字段
    {
        va_list vargs;
        int err;
    
        va_start(vargs, fmt);
        err = kobject_set_name_vargs(&dev->kobj, fmt, vargs);
        va_end(vargs);
        return err;
    }
    //device 使用计数增加,实际操作的是内涵的kobject 
    struct device *get_device(struct device *dev)
    {
        return dev ? kobj_to_dev(kobject_get(&dev->kobj)) : NULL;
    }
    device 释放一次
    void put_device(struct device *dev)
    {
        /* might_sleep(); */
        if (dev)
            kobject_put(&dev->kobj);
    }

    设备的删除 

    void device_unregister(struct device *dev)
    {
        pr_debug("device: '%s': %s
    ", dev_name(dev), __func__);
        device_del(dev);
        put_device(dev);
    }

    这里还是比较复杂一点的,因为内核中可能有和设备匹配的驱动,删除设备需要吧驱动匹配时增加的文件删除和绑定操作删除进行解绑等。将设备从系统中注销,注意和删除的区别,但是也都是设备添加过程的一些操作的逆向操作。

    总结     

          这里总结一下设备添加的驱动匹配过程:从设备添加接口开始 bus_probe_device---》device_attach---》bus_for_each_drv 这个接口传入了一个接口函数是 __device_attach,然后bus_for_each_drv 这个接口的伪代码就是

    bus_for_each_drv
    {
        遍历总线上的驱动drv
        执行 __device_attach(drv,dev);
        退出条件是 上面的接口返回非0或者总线上的drv全都尝试过了
    }

    __device_attach接口的执行过程大致就是先尝试总线是否有match接口有则调用,当总线的match接口返回0时则代表匹配成功则直接返回0 然后bus_for_each_drv 接口也会退出匹配完成;否则执行driver_probe_device---》really_probe 这个接口会调用总线或者驱动的probe接口进行驱动的探测且总线probe接口优先,返回0匹配成功。由此可见总线实现了match接口对于驱动匹配效率是有提升的。后续还会在去看看driver的注册添加过程,驱动和设备的添加过程有相同的部分也有不同的,但是其中驱动匹配的那一部分特别的相似,一个是拿着device遍历总线上的driver,另一个则是拿着driver去遍历device,全部都依赖总线。这就是驱动的基本架构思路,所以在Linux的发展过程中会出现platform_bus.他的出现就是为了统一linux内核的驱动框架。反向也说明platform的机制也就可以体现其他总线的一些相同机制。所以后续会在拿platform总线为实例在深入研究一下。也就能清楚设备匹配过程总线的mach接口的一般实现方式。

  • 相关阅读:
    javaSE笔记-多态
    javaSE笔记-接口
    javaSE笔记-static关键字
    javaSE笔记-fianl关键字
    javaSE笔记-抽象类
    javaSE笔记-继承
    javaSE笔记-JKD、JRE、JVM各自的作用和关系
    搭建网络验证RIP协议
    计算机网络学习
    python itertools 模块讲解
  • 原文地址:https://www.cnblogs.com/w-smile/p/13289775.html
Copyright © 2020-2023  润新知