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内核不使用设备树时设备是板级文件创建并注册添加的,使用设备树后就是由系统启动从设备树中解析出来并动态创建的。

设备创建

       设备的创建有两种情况通过device 提供的接口 device_create 创建设备,此时的设备是通过动态内存创建的,创建的时候可以种指定class,parent父设备,drvdata驱动数等,这个接口最终会返回一个struct device设备,这个设备中的release接口是释放这个设备所占内存的方法。这个接口创建的设备他的init_name 字段是未初始化的。之后这个接口的后续调用会直接调用device_add(这个接口内会检查device init_name设置则会将其设置给device内涵的kobject保证名称一致,然后会将device的名称置空)将设备加入到指定的总线。第二种是通过直接定义或创建其他包含struct device实体的上层结构体时创建的device(其实都是分配内存)。

device_create 

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_create -->device_create_vargs这个函数里面最后会调用device的添加函数(device_add())将新创建的设备添加到内核。

设备注册添加

设备创建好并初始化好后需要注册添加进内核,一共有两种情况下将设备添加进内核。第一种就是上面的提到的设备创建和添加一起由创建的内部完成就是通过device_add接口,还有一种就是device_register,他的调用过程大致如下面的过程

device_register()

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

int device_add(struct device *dev)
{
    struct device *parent = NULL;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;
    /* 增加设备的引用次数 */
    dev = get_device(dev);
    if (!dev)
        goto done;
    /* 增加设备私有数据 私有数据包括指向当前device自己的指针和一个list用来链接设备下的子设备 */
    if (!dev->p) {
        error = device_private_init(dev);
        if (error)
            goto done;
    }

    /* Kobject 的设置 */
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL;
    }

    /* 设备明如果未设置 则按 %s%u,总线name,device->id形式格式化*/
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error;
    }

    pr_debug("device: ‘%s‘: %s\n", dev_name(dev), __func__);
    /* 增加父设备引用计数 */
    parent = get_device(dev->parent);
    /* 设置device 中的kobj的父设备obj为父设备obj */
    kobj = get_device_parent(dev, parent);
    if (kobj)
        dev->kobj.parent = kobj;

    /* use parent numa_node */
    if (parent)
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    /* kobject 操作 可以参考kobject的相关部分 */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
    if (error)
        goto Error;

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);
    /* sysfs操作 创建设备通用属性的文件虚拟文件系统相关*/
    error = device_create_file(dev, &dev_attr_uevent);
    if (error)
        goto attrError;
    /* 如果设备有设备号 则还要创建/dev 目录下的文件 */
    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &dev_attr_dev);
        if (error)
            goto ueventattrError;
        error = device_create_sys_dev_entry(dev);
        if (error)
            goto devtattrError;

        devtmpfs_create_node(dev);
    }
    /* 实际的设备按不同的分类可能同属多个分类父设备  但是真实的设备在文件系统中只要一份而其他的则是以符号链接的形式指向唯一的实体 */
    error = device_add_class_symlinks(dev);
    if (error)
        goto SymlinkError;
    /* sysfs操作 创建设备个性属性的文件*/    
    error = device_add_attrs(dev);
    if (error)
        goto AttrsError;
    /* 根据device 中的bus指针将设备添加到特定的总线,总线可以为空*/
    error = bus_add_device(dev);
    if (error)
        goto BusError;
    /* 电源管理相关 */
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    device_pm_add(dev);

    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    /* 一种客户端新设备发现机制 通知客户端新设备添加 */
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);
    /* kobject的事件机制 产生uevent事件 事件类型为ADD
    *  内核可以通过helper或netlink机制和用户空间的应用通信,热插拔机制
    */
    kobject_uevent(&dev->kobj, KOBJ_ADD);
    /*
     * 驱动开始匹配,后面详细分析
     */
    bus_probe_device(dev);
    /* 设备私有数据 将设备添加到父设备子设备list中*/
    if (parent)
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);
    /* 将设备添加到所属class设备子设备list中*/
    if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->klist_devices);

        /* 设备类通用的处理接口,如字符设备会在/dev目录下创建文件等 */
        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->mutex);
    }
done:
    put_device(dev);
    return error;
 。
。
。
。
。
。   
} 

从这个函数我们明白了

  • device结构体中的struct device_private *p 实际上就是设备自己的一个指针和一些关联对象的list。
  • device 中的init_name 字段实际上最后是给到了内核对象kobject,如果未指定device的init_name则会按照总线和设备ID的方式构造device的name。
  • 最后就是kobject的初始化和添加了,这一部分可以参考kobject的实现。
  • 创建sys目录下的文件。
  • 如果设备指定了设备号则还会在dev目录下创建对应的设备文件。
  • 然后就是设备所属类的sys文件操作。
  • uevent时间产生。
  • class 的通用接口处理,如cdev就包含自动在dev目录下创建文件。
  • 驱动探测的成功和失败的结构不影响设备的注册添加。

 中间的bus_probe_device就是开始probe驱动的过程

/*
 * 驱动匹配
 */
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;
}

分两种情况,一种是在设备注册时就直接指定了驱动程序则不用查找直接执行绑定,否则就需要探测。探测的主要过程如下

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);
}

这里就会执行驱动的正真的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\n",
         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\n",
         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\n",
            __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\n",
         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\n", 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\n",
               drv->name, dev_name(dev), ret);
    } else {
        pr_debug("%s: probe of %s rejects match %d\n",
               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;
}

具体的操作都在代码中注释了,由此可以看出来,设备注册时要是没有总线,就没有意义因为没有驱动的设备在系统中肯定时无法使用的。剩下就是device预留给驱动编辑的接口函数简单的罗列一下。

其他驱动会使用的接口

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\n", dev_name(dev), __func__);
    device_del(dev);
    put_device(dev);
}
设备删除

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

        后续还会在去看看driver的注册添加过程,驱动和设备的添加过程有相同的部分也有不同的,但是其中驱动匹配的那一部分特别的相似,一个是拿着device遍历总线上的driver,另一个则是拿着driver去遍历device,全部都依赖总线。这就是驱动的基本架构思路所以在Linux的发展过程中会出现platform_bus.他的出现就是为了同一linux内核的驱动框架。所以反向也说明platform的机制也就可以体现其他总线的一些相同机制。所以后续会在拿platform总线为实例在深入研究一下。也就能清楚设备匹配过程总线的mach接口的一般实现方式。

 

Linux 驱动框架---linux 设备

上一篇:xshell 隧道代理


下一篇:将shp文件导入PostgreSQL数据库