该系列文章阅读顺序:

  1. linux驱动-设备驱动模型(属性文件 kobject )
  2. linux驱动-设备驱动模型(kset)
  3. linux驱动-设备驱动模型(bus总线)
  4. linux驱动-设备驱动模型(device设备)
  5. linux驱动-设备驱动模型(driver驱动)
  6. linux驱动-设备驱动模型(class类)
  7. linux驱动-设备驱动模型(platform设备)

device 用于抽象系统中所有的硬件设备,描述它的名字、属性、从属的 Bus、从属的 Class 等信息。

作者: baron

1、数据结构

1) device

struct device {
    struct device    *parent;	 //父设备

    struct device_private    *p;  //保存设备链表

    struct kobject kobj;	  //对应的kobj用于创建dev目录
    const char      *init_name;   //设备的名称,非常重要,如果不存在则使用"bus->name + device ID" 如果都不存在则不允许创建设备
    const struct device_type *type; //设备类型

    struct mutex        mutex;  /* mutex to synchronize calls toits driver. */

    struct bus_type *bus;       	/* 设备挂接的总线 */
    struct device_driver *driver;       /* 匹配成功后,链接匹配到的driver */
    void        *platform_data; 	/* 用于保存私有数据。 */
    void        *driver_data;   	/* Driver data, set and get with dev_set get_drvdata */
    struct dev_links_info   links;
    struct dev_pm_info  power;
    struct dev_pm_domain    *pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
    struct irq_domain   *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
    struct dev_pin_info *pins; //pinctrl对应的接口
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
    struct list_head    msi_list;
#endif

#ifdef CONFIG_NUMA
    int     numa_node;  /* NUMA node this device is close to */
#endif
    const struct dma_map_ops *dma_ops;
    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 fwnode_handle    *fwnode; /* firmware device node */

    dev_t           devt;   /* 设备号 */
    u32         id; /* device instance */

    spinlock_t      devres_lock;
    struct list_head    devres_head;

    struct klist_node   knode_class; //链接到dev->class->p->klist_devices节点
    struct class        *class;		//所属的class
    const struct attribute_group **groups;  /* optional groups */

    void    (*release)(struct device *dev);
    struct iommu_group  *iommu_group;
    struct iommu_fwspec *iommu_fwspec;

    bool            offline_disabled:1;
    bool            offline:1;
    bool            of_node_reused:1;
};

2) device_private

struct device_private {
    struct klist klist_children; 	//用于挂接子设备的 knode_parent
    struct klist_node knode_parent; 	//挂接到父设备的 klist_children
    struct klist_node knode_driver;     //链入连接的 driver 的 klist_devices 链表
    struct klist_node knode_bus; 	//链接进入 bus->p->klist_devices 链表
    struct list_head deferred_probe;
    struct device *device;		//指向所属的 dev
};

2、device 根目录的初始化

device文件目录初始化函数,在 driver_init 中被调用,内核初始化时被调用。

int __init devices_init(void)
{
   /*
    * 在 sys/ 下创建一个名为 devices 目录,deices_kset
    * 在 sys/ 下创建一个名为 dev 的目录,dev_kobj
    * 在 sys/dev/ 下创建一个名为 block 的目录,sysfs_dev_block_kobj
    * 在 sys/dev/ 下创建一个名为 char 的目录,sysfs_dev_char_kobj
    */    
    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
    if (!devices_kset)
        return -ENOMEM;
    dev_kobj = kobject_create_and_add("dev", NULL);
    if (!dev_kobj)
        goto dev_kobj_err;
    sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
    if (!sysfs_dev_block_kobj)
        goto block_kobj_err;
    sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
    if (!sysfs_dev_char_kobj)
        goto char_kobj_err;

    return 0;

 char_kobj_err:
    kobject_put(sysfs_dev_block_kobj);
 block_kobj_err:
    kobject_put(dev_kobj);
 dev_kobj_err:
    kset_unregister(devices_kset);
    return -ENOMEM;
}

devices_init 初始化了 device 的基本根目录,如下

/sys/devices
/sys/dev
/sys/dev/block
/sys/dev/char

bus总线管理着该总线下的所有设备和驱动,他们分别位于/sys/bus/xxx/device /sys/bus/xxx/driver

3、device的注册

1) device_register

使用 device_regster 注册 device ,这里以源码的形式分析。

int device_register(struct device *dev)
{
    device_initialize(dev); //对dev进行一些初始化
    return device_add(dev); //将dev注册进bus
}

2) device_initialize

void device_initialize(struct device *dev)
{
    dev->kobj.kset = devices_kset; //初始化kset
    kobject_init(&dev->kobj, &device_ktype); //初始化ktypoe
	
    // 对一些链表进行初始化
    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); 	//设置该设备节点为-1,一般未注册前默认为-1
#ifdef CONFIG_GENERIC_MSI_IRQ
    INIT_LIST_HEAD(&dev->msi_list);
#endif
}

从它的初始化可以看出,所有通过device_register注册的dev的dev->kobj->list,都将挂接在 devices_kset->list上(在kobj_add注册时链接) ,如果没有父设备则设备将使用 devices_kset 作为父设备目录节点,因此可以得出下面结论:

  • 所有通过 device_regster 创建的设备都应该是/sys/devices/的子目录

3) device_add

这个函数将 device 注册进 bus,同时它也做了很多事情非常多的事情,具体做了什么事情我们跟着源码来看一下。

int device_add(struct device *dev)
{
    struct device *parent;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;
    struct kobject *glue_dir = NULL;

    dev = get_device(dev); //增加dev引用计数
    if (!dev)
        goto done;

    if (!dev->p) {
        /* 动态分配一个 device_private 并且初始化 dev成员*/
        error = device_private_init(dev);
        if (error)
            goto done;
    }

    /*
     * for statically allocated devices, which should all be converted
     * some day, we need to initialize the name. We prevent reading back
     * the name, and force the use of dev_name()
     */
    if (dev->init_name) {	// 如果有init_name则设置,设置dev->kobj->name 为 init_name
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL; //这里需要注意的是,这个 init_name 被设置为空
    }

    /* subsystems can specify simple device enumeration */
    //如果 dev->kobj->name 为空且对应的bus设置了dev_name 则使用这个名字+dev->id作为设备名
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
	
    //如果这没有设置dev->kobj-name 则直接返回error
    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error;
    }

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    //增加 dev-parent 的kobj引用计数
    parent = get_device(dev->parent);

    //返回parent->kobj
    kobj = get_device_parent(dev, parent);
    if (IS_ERR(kobj)) {
        error = PTR_ERR(kobj);
        goto parent_error;
    }
    if (kobj)
        dev->kobj.parent = kobj;

    /* use parent numa_node */
    if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) // 一般未注册前默认为-1
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    // 注册 dev->kobj,在 sys/ 下创建相关目录
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
    if (error) {
        glue_dir = get_glue_dir(dev);
        goto Error;
    }

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);

    //在dev目录下创建属性文件uevent
    error = device_create_file(dev, &dev_attr_uevent);
    if (error)
        goto attrError;

   /* 在dev所在目录创建三个链接文件
    * of_node     ---> 设备树生成的节点
    * subsystem   ---> dev->class
    * device      ---> 父设备目录
    * 同时在所属的class目录下创建指向 dev 目录的链接文件,dev->init_name
    */
    error = device_add_class_symlinks(dev);
    if (error)
        goto SymlinkError;
  
    //该函数用于在 dev下创建所属的 class->dev_groups, 所属的 type->groups 和 dev->groups 指向的属性文件以及属性文件 online
    error = device_add_attrs(dev);
    if (error)
        goto AttrsError;

    //将设备注册进bus,详细分析见后文
    error = bus_add_device(dev);
    if (error)
        goto BusError;

    //电源相关暂时忽略
    error = dpm_sysfs_add(dev); 
    if (error)
        goto DPMError;

    //电源相关暂时忽略
    device_pm_add(dev);

    if (MAJOR(dev->devt)) {
        //在设备所在目录下创建属性文件 dev
        error = device_create_file(dev, &dev_attr_dev);
        if (error)
            goto DevAttrError;
        //如果存在class则在class所在目录下创建指向dev->kobj的链接文件"major+minor",否则在 /sys/char 下创建
        error = device_create_sys_dev_entry(dev);
        if (error)
            goto SysEntryError;

        //创建设备文件节点 /dev/dev->init_name
        devtmpfs_create_node(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);
    //向用户空间发送 uevent 事件 KOBJ_ADD
    kobject_uevent(&dev->kobj, KOBJ_ADD);

    //遍历所挂接的bus上的所有drv,对所有的drv进行匹配,匹配成功则调用相对应的probe函数,详细分析见后文
    bus_probe_device(dev);
 
    if (parent) //将 dev->p->knode_parent 节点加入 parent->p->klist_children
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);

    if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        // 绑定device和class,将dev->knode_class 节点链接到dev->class->p->klist_devices
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->klist_devices);

        /* 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;
 SysEntryError:
    if (MAJOR(dev->devt))
        device_remove_file(dev, &dev_attr_dev);
 DevAttrError:
    device_pm_remove(dev);
    dpm_sysfs_remove(dev);
 DPMError:
    bus_remove_device(dev);
 BusError:
    device_remove_attrs(dev);
 AttrsError:
    device_remove_class_symlinks(dev);
 SymlinkError:
    device_remove_file(dev, &dev_attr_uevent);
 attrError:
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);
    glue_dir = get_glue_dir(dev);
    kobject_del(&dev->kobj);
 Error:
    cleanup_glue_dir(dev, glue_dir);
parent_error:
    put_device(parent);
name_error:
    kfree(dev->p);
    dev->p = NULL;
    goto done;
}

4) get_device_parent

这个函数用于获取 dev->kobj->parent

static struct kobject *get_device_parent(struct device *dev, struct device *parent)
{
    if (dev->class) {	//是否设置class
        struct kobject *kobj = NULL;
        struct kobject *parent_kobj;
        struct kobject *k;

#ifdef CONFIG_BLOCK
        /* block disks show up in /sys/block */
        if (sysfs_deprecated && dev->class == &block_class) {
            if (parent && parent->class == &block_class)
                return &parent->kobj;
            return &block_class.p->subsys.kobj;
        }
#endif

        /*
         * If we have no parent, we live in "virtual".
         * Class-devices with a non class-device as parent, live
         * in a "glue" directory to prevent namespace collisions.
         */
        if (parent == NULL) //parent为空
            parent_kobj = virtual_device_parent(dev); //在/sys/devices/目录下创建 virtual 目录
        else if (parent->class && !dev->class->ns_type) //dev->class->ns_type
            return &parent->kobj;
        else
            parent_kobj = &parent->kobj; //设置parent_kobj

        mutex_lock(&gdp_mutex);

        /* find our class-directory at the parent and reference it */
        //如果已经在dev->class->p->glue_dirs下注册了 parent_kobj 则增加它的引用计数,并直接返回
        spin_lock(&dev->class->p->glue_dirs.list_lock);
        list_for_each_entry(k, &dev->class->p->glue_dirs.list, entry)
            if (k->parent == parent_kobj) {
                kobj = kobject_get(k);
                break;
            }
        spin_unlock(&dev->class->p->glue_dirs.list_lock);
        if (kobj) { //直接返回
            mutex_unlock(&gdp_mutex);
            return kobj;
        }

        /* or create a new class-directory at the parent device */
        /* 在 parent_kobj 并没有在 dev->class->p->glue_dirs 中,
         * 则在父目录下创建一个名为 dev->class->name 的目录,
         * 并将 class->p->glue_dirs 作为其kset。
         */
        k = class_dir_create_and_add(dev->class, parent_kobj);
        /* do not emit an uevent for this simple "glue" directory */
        mutex_unlock(&gdp_mutex);
        return k; //返回dir->kobj
    }

    /* subsystems can specify a default root directory for their devices */
	//如果parent为空则且dev->bus->dev_root不为空则使用dev->bus->dev_root->kobj作为父节点
    if (!parent && dev->bus && dev->bus->dev_root)
        return &dev->bus->dev_root->kobj;

    if (parent)
        return &parent->kobj; //返回父节点
    return NULL;
}

class_dir_create_and_add

static struct kobject* class_dir_create_and_add(struct class *class, struct kobject *parent_kobj)
{
    struct class_dir *dir;
    int retval;

    dir = kzalloc(sizeof(*dir), GFP_KERNEL);
    if (!dir)
        return ERR_PTR(-ENOMEM);

    dir->class = class;
    kobject_init(&dir->kobj, &class_dir_ktype);

    dir->kobj.kset = &class->p->glue_dirs;

    retval = kobject_add(&dir->kobj, parent_kobj, "%s", class->name);
    if (retval < 0) {
        kobject_put(&dir->kobj);
        return ERR_PTR(retval);
    }
    return &dir->kobj;
}

get_device_parent函数需要分为情况讨论
设备属于某个class且parent为空

  1. 创建 /sys/devices/virtual目录,并将其作为父目录parent_kobj
  2. 遍历 dev->class->p->glue_dirs 下的所有kobj, 如果存在 parent_kobj,则增加其引用计数并直接返回 parent_kobj
  3. 否则在parent_kobj下创建一个dir->kobj (/sys/dev/vitual/dir->kobj->name),使用传入的class->name 作为其dir->kobj->name, 并将class->p->glue_dirs作为其kset。并返回dir->kobj作为parent_kobj,这种情况会曾加如下目录
/sys/devices/virtual 
/sys/devices/virtual/class->name //dev->class->p->glue_dirs 下不存在 parent_kobj 则创建这个节点

设备属于某个class且parent不为空

  1. 如果存在dev->class->ns_type则直接返回 parent_kobj (这种情况不增加目录)
  2. 如果上述不存在,则遍历 dev->class->p->glue_dirs 下的所有 kobj, 如果存在parent_kobj,则增加其引用计数并直接返回 parent_kobj
  3. 否则在 parent_kobj 下创建一个dir->kobj,使用传入的 class->name 作为其 dir->kobj->name, 并将 class->p->glue_dirs 作为其 kset。并返回 dir->kobj作为parent_kobj,这种情况会增加如下目录
..../parent_kobj->name/class->name

设备属不属于某个class
如果 parent 为空则且 dev->bus->dev_root 不为空则使用 dev->bus->dev_root->kobj 作为父节点,否则直接返回 parent->kobj

5) device_add_class_symlinks

该函数会创建 class 与 dev 之间的连接,首先在传入的 dev 所在目录创建三个链接文件,of_node、subsystem、device,同时在所属的 class 目录下创建指向 dev 目录的链接文件,dev->init_name

static int device_add_class_symlinks(struct device *dev)
{
    struct device_node *of_node = dev_of_node(dev);
    int error;

    if (of_node) { //设备树生成的节点
        //在 dev->kobj 下创建指向of_node->kobj的链接文件of_node
        error = sysfs_create_link(&dev->kobj, &of_node->kobj,"of_node");
        if (error)
            dev_warn(dev, "Error %d creating of_node link\n",error);
        /* An error here doesn't warrant bringing down the device */
    }

    if (!dev->class)
        return 0;
    //在 dev->kobj 下创建指向 dev->class->p->subsys.kobj 的链接文件"subsystem"
    error = sysfs_create_link(&dev->kobj,
                  &dev->class->p->subsys.kobj,
                  "subsystem");
    if (error)
        goto out_devnode;

    //在 dev->kobj 下创建指向 dev->parent->kobj 的链接文件 device
    if (dev->parent && device_is_not_partition(dev)) { //默认情况满足条件
        error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
                      "device");
        if (error)
            goto out_subsys;
    }

#ifdef CONFIG_BLOCK
    /* /sys/block has directories and does not need symlinks */
    if (sysfs_deprecated && dev->class == &block_class)
        return 0;
#endif

    /* link in the class directory pointing to the device */
	//在 dev->class->p->subsys.kobj 下创建指向 dev->kobj 链接文件 "dev->init_name"
    error = sysfs_create_link(&dev->class->p->subsys.kobj,
                  &dev->kobj, dev_name(dev));
    if (error)
        goto out_device;

    return 0;

out_device:
    sysfs_remove_link(&dev->kobj, "device");

out_subsys:
    sysfs_remove_link(&dev->kobj, "subsystem");
out_devnode:
    sysfs_remove_link(&dev->kobj, "of_node");
    return error;
}

6) device_add_attrs

该函数用于在 dev 下创建所属的 class->dev_groups, 所属的 type->groups dev->groups指向的属性文件以及属性文件 online

static int device_add_attrs(struct device *dev)
{
    struct class *class = dev->class;
    const struct device_type *type = dev->type;
    int error;

    if (class) {
        //创建 class->dev_groups 属性文件
        error = device_add_groups(dev, class->dev_groups);
        if (error)
            return error;
    }

    if (type) {
        //创建 type->groups 属性文件
        error = device_add_groups(dev, type->groups);
        if (error)
            goto err_remove_class_groups;
    }

    //创建 dev->groups 属性文件
    error = device_add_groups(dev, dev->groups);
    if (error)
        goto err_remove_type_groups;
	
    //创建属性文件 online
    if (device_supports_offline(dev) && !dev->offline_disabled) {
        error = device_create_file(dev, &dev_attr_online);
        if (error)
            goto err_remove_dev_groups;
    }

    return 0;

 err_remove_dev_groups:
    device_remove_groups(dev, dev->groups);
 err_remove_type_groups:
    if (type)
        device_remove_groups(dev, type->groups);
 err_remove_class_groups:
    if (class)
        device_remove_groups(dev, class->dev_groups);

    return error;
}

7) bus_add_device

这个函数将 device 注册进入bus

int bus_add_device(struct device *dev)
{
    struct bus_type *bus = bus_get(dev->bus);
    int error = 0;

    if (bus) {
        pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
        /* 在当前设备所在目录下创建 dev->dev_attrs 默认属性文件(有就创建没有就不创建) */
        error = device_add_attrs(bus, dev);
        if (error)
            goto out_put;
        /* 在当前设备所在目录下创建属性文件  bus->dev_groups */
        error = device_add_groups(dev, bus->dev_groups);
        if (error)
            goto out_id;
        /* 在设备所在bus的devices目录下创建指向设备所在目录的软链接,名字为dev->name */
        error = sysfs_create_link(&bus->p->devices_kset->kobj,
                        &dev->kobj, dev_name(dev));
        if (error)
            goto out_groups;
        /* 在设备所在目录下创建指向设备所在总线的的软链接名为 subsystem */
        error = sysfs_create_link(&dev->kobj,
                &dev->bus->p->subsys.kobj, "subsystem");
        if (error)
            goto out_subsys;
        /* 将设备链入 bus->p->klist_devices 链表 */
        klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
    }
    return 0;

out_subsys:
    sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
    device_remove_groups(dev, bus->dev_groups);
out_id:
    device_remove_attrs(bus, dev);
out_put:
    bus_put(dev->bus);
    return error;
}

bus_add_device 会创建以下节点

/sys/bus/xxx/devices/dev->name ----> ..../dev->name
..../dev->name/subsystem ----> /sys/bus/xxx

8) bus_probe_device

如果drivers_autoprobe为1,即可以自动匹配则调用 device_initial_probe(dev)

void bus_probe_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;
    struct subsys_interface *sif;

    if (!bus)
        return;
	
    //如果设置了 bus->p->drivers_autoprobe 则进行匹配
    if (bus->p->drivers_autoprobe) 
        device_initial_probe(dev);

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

device_initial_probe

void device_initial_probe(struct device *dev)
{
    __device_attach(dev, true);
}

__device_attach

static int __device_attach(struct device *dev, bool allow_async)
{
    int ret = 0;

    device_lock(dev);
    if (dev->driver) { //在初始化dev的时候手动指定了driver,则在这里进行绑定
        if (device_is_bound(dev)) {  //判断设备和驱动是否已经绑定
            ret = 1;
            goto out_unlock;
        }

        // 手动绑定设备和驱动
        ret = device_bind_driver(dev); 
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {
        struct device_attach_data data = {
            .dev = dev,
            .check_async = allow_async,
            .want_async = false,
        };

        if (dev->parent)
            pm_runtime_get_sync(dev->parent);
		
        //遍历 dev->bus 上所有的 drv ,对每一个drv 调用 __device_attach_driver 函数
        ret = bus_for_each_drv(dev->bus, NULL, &data,
                    __device_attach_driver);
        if (!ret && allow_async && data.have_async) {
            /*
             * If we could not find appropriate driver
             * synchronously and we are allowed to do
             * async probes and there are drivers that
             * want to probe asynchronously, we'll
             * try them.
             */
            dev_dbg(dev, "scheduling asynchronous probe\n");
            get_device(dev);
            async_schedule(__device_attach_async_helper, dev);
        } else {
            pm_request_idle(dev);
        }

        if (dev->parent)
            pm_runtime_put(dev->parent);
    }
out_unlock:
    device_unlock(dev);
    return ret;
}

device_bind_driver

int device_bind_driver(struct device *dev)
{
    int ret;

   /*
    * 创建链接文件 .../dev->driver->p->kobj/dev->kobj->name
    * 创建链接文件 .../dev->kobj/driver
    */
    ret = driver_sysfs_add(dev);
    if (!ret)
        driver_bound(dev); //手动绑定
    else if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
    return ret;
}

__device_attach_driver

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    struct device_attach_data *data = _data;
    struct device *dev = data->dev;
    bool async_allowed;
    int ret;

    /*
     * Check if device has already been claimed. This may
     * happen with driver loading, device discovery/registration,
     * and deferred probe processing happens all at once with
     * multiple threads.
     */
    if (dev->driver)
        return -EBUSY; 
   /*
    * 调用 drv 所属的 bus 的 match 函数,这里可以看出来如果不初始化 drv->bus->match 函数则默认匹配成功。
      static inline int driver_match_device(struct device_driver *drv, struct device *dev)
      {
          return drv->bus->match ? drv->bus->match(dev, drv) : 1;
      }
    */
    ret = driver_match_device(drv, dev);
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
    } /* ret > 0 means positive match */

    //获取驱动加载方式是同步加载还是异步加载
    async_allowed = driver_allows_async_probing(drv);

    if (async_allowed)
        data->have_async = true;

    if (data->check_async && async_allowed != data->want_async)
        return 0;
	
    //调用 driver_probe_device
    return driver_probe_device(drv, dev);
}

driver_probe_device

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

    if (dev->parent)
        pm_runtime_get_sync(dev->parent);

    pm_runtime_barrier(dev);

    //调用 really_probe 函数
    ret = really_probe(dev, drv);
    pm_request_idle(dev);

    if (dev->parent)
        pm_runtime_put(dev->parent);

    return ret;
}

really_probe

static int really_probe(struct device *dev, struct device_driver *drv)
{
    int ret = -EPROBE_DEFER;
    int local_trigger_count = atomic_read(&deferred_trigger_count);
    bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
               !drv->suppress_bind_attrs;
#ifdef CONFIG_MTPROF
    unsigned long long ts = 0;
#endif
    if (defer_all_probes) {
        /*
         * Value of defer_all_probes can be set only by
         * device_defer_all_probes_enable() which, in turn, will call
         * wait_for_device_probe() right after that to avoid any races.
         */
        dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);
        driver_deferred_probe_add(dev);
        return ret;
    }

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

re_probe:
    dev->driver = drv;

    /* If using pinctrl, bind pins now before probing */
    // 如果使用了 pinctrl 则会在这里先加载一次,如果 dts 写错了但能编译过了,就是不probe
    // 当移植代码的时候,当我们注册的驱动无法 prob e到设备的时候记得检查一下是否是 dts 的问题,
    // 因为不同个平台的 dts 可能由微小的差别,我就踩过这个坑 =_=
    ret = pinctrl_bind_pins(dev);
    if (ret)
        goto pinctrl_bind_failed;
   /*
    * 在drv所在目录创建指向dev目录的链接文件,名字使用 dev->kobj->name
    * 在dev所在目录创建指向drv目录的连接文件,名字使用 "driver"
    */
    if (driver_sysfs_add(dev)) {
        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
            __func__, dev_name(dev));
        goto probe_failed;
    }

    if (dev->pm_domain && dev->pm_domain->activate) {
        ret = dev->pm_domain->activate(dev);
        if (ret)
            goto probe_failed;
    }
    
    //调用 dev->bus->probe
    if (dev->bus->probe) {
        TIME_LOG_START();
        ret = dev->bus->probe(dev);
        TIME_LOG_END();
        bootprof_probe(ts, dev, drv, (unsigned long)dev->bus->probe);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) { //如果 dev->bus->probe 不存在则调用 drv->probe
       TIME_LOG_START();
        ret = drv->probe(dev);
        TIME_LOG_END();
        bootprof_probe(ts, dev, drv, (unsigned long)drv->probe);
        if (ret)
            goto probe_failed;
    }

    if (test_remove) {
        test_remove = false;

        if (dev->bus->remove)
            dev->bus->remove(dev);
        else if (drv->remove)
            drv->remove(dev);

        devres_release_all(dev);
        driver_sysfs_remove(dev);
        dev->driver = NULL;
        dev_set_drvdata(dev, NULL);
        if (dev->pm_domain && dev->pm_domain->dismiss)
            dev->pm_domain->dismiss(dev);
        pm_runtime_reinit(dev);

        goto re_probe;
    }

    pinctrl_init_done(dev);

    if (dev->pm_domain && dev->pm_domain->sync)
        dev->pm_domain->sync(dev);
    
    //建立设备与驱动的连接
    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:
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
pinctrl_bind_failed:
    devres_release_all(dev);
    driver_sysfs_remove(dev);
    dev->driver = NULL;
    dev_set_drvdata(dev, NULL);
    if (dev->pm_domain && dev->pm_domain->dismiss)
        dev->pm_domain->dismiss(dev);
    pm_runtime_reinit(dev);

    switch (ret) {
    case -EPROBE_DEFER:
        /* Driver requested deferred probing */
        dev_dbg(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();
        break;
    case -ENODEV:
    case -ENXIO:
        pr_debug("%s: probe of %s rejects match %d\n",
             drv->name, dev_name(dev), ret);
        break;
    default:
        /* driver matched but the probe failed */
        printk(KERN_WARNING
               "%s: probe of %s failed with error %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;
}

4、device_register总结

1) 检查设备名的合法性

从代码可以看出对于 dev 来说名字是一个非常重要的参数,首先使用 init_name 作为dev->kobj 的名字同时将 init_name 设置为空,如果 init_name 初始为空则使用 “bus->dev_nam + dev->id” 作为dev->kobj 的名字,如果设备没有设置名字则直接返回错误。

2)在sys/创建文件目录的层次关系的创建

下面列出所有可能出现的情况
设备的 bus 为空,class 为空,parent 为空

/sys/devices/xxx
/sys/devices/xxx/power
/sys/devices/xxx/uevent
/sys/devices/xxx/of_node  //如果存在设备树节点则创建

/sys/devices/xxx/dev //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

设备的 bus 为空,class 为空,parent 不为空

/sys/devices/.../xxx->parent/xxx
/sys/devices/.../xxx->parent/xxx/power
/sys/devices/.../xxx->parent/xxx/uevent
/sys/devices/.../xxx->parent/xxx/of_node  //如果存在设备树节点则创建

/sys/devices/.../xxx->parent/xxx/dev //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/.../xxx->parent/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

设备的 bus 不为空,class 为空,parent 为空

/sys/devices/xxx
/sys/devices/xxx/power
/sys/devices/xxx/uevent
/sys/devices/xxx/of_node  //如果存在设备树节点则创建

/* bus_add_device 创建下面目录 */
/sys/bus/xxx->bus/devices/xxx --->/sys/devices/xxx	 //指向设备
/sys/devices/xxx/subsystem ---> /sys/bus/xxx->bus //指向所挂接的bus总线

/sys/devices/xxx/dev  //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

//和drv匹配成功创建下面目录
/sys/devices/xxx/driver ---> /sys/bus/drivers/xxx->driver/
/sys/bus/drivers/xxx->driver/xxx ---> /sys/devices/xxx

设备的bus不为空,class为空,parent不为空

/sys/devices/.../xxx->parent/xxx
/sys/devices/.../xxx->parent/xxx/power
/sys/devices/.../xxx->parent/xxx/uevent
/sys/devices/.../xxx->parent/xxx/of_node  //如果存在设备树节点则创建

/* bus_add_device 创建下面目录 */
/sys/bus/xxx->bus/devices/xxx --->/sys/devices/.../xxx->parent/xxx	 //指向设备
/sys/devices/.../xxx->parent/xxx/subsystem ---> /sys/bus/xxx->bus //指向所挂接的bus总线

/sys/devices/.../xxx->parent/xxx/dev  //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/.../xxx->parent/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

//和drv匹配成功创建下面目录
/sys/devices/.../xxx->parent/xxx/driver ---> /sys/bus/drivers/xxx->driver/
/sys/bus/drivers/xxx->driver/xxx ---> /sys/devices/.../xxx->parent/xxx

设备的 bus 为空,class 不为空,parent 为空

//dir->kobj使用xxx->class->name做名字

/sys/devices/virtual
/sys/devices/virtual/dir->kobj
/sys/devices/virtual/dir->kobj/xxx
/sys/devices/virtual/dir->kobj/xxx/uevent
/sys/devices/virtual/dir->kobj/xxx/of_node //如果存在设备树节点则创建

/* device_add_class_symlinks 创建下面目录 */
/sys/devices/virtual/dir->kobj/xxx/subsystem ---> /sys/class/xxx->class/xxx
/sys/class/dev->class/xxx ---> /sys/devices/virtual/dir->kobj/xxx

/sys/devices/xxx/dev  //有设备号的设备会创建这个节点

/*
 * .../dev->class->dev_kobj/"major+minor" ---> /sys/devices/virtual/dir->kobj/xxx 有dev->class->dev_kobj和设备号的设备会创建这个节点
 * dev->class->dev_kobj 这个属性在class被注册的时候一般被默认设置为 sysfs_dev_char_kobj,即 /sys/dev/char节点
 */
/sys/dev/char/"major+minor" ---> /sys/devices/virtual/dir->kobj/xxx //有设备号会默认创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

设备的 bus 为空,class 不为空,parent 不为空

//dir->kobj使用xxx->class->name做名字

/sys/devices/.../xxx->parent/dir->kobj/
/sys/devices/.../xxx->parent/dir->kobj/xxx
/sys/devices/.../xxx->parent/dir->kobj/xxx/uevent
/sys/devices/.../xxx->parent/dir->kobj/xxx/of_node //如果存在设备树节点则创建

/* device_add_class_symlinks 创建下面目录 */
/sys/devices/.../xxx->parent/dir->kobj/xxx/subsystem ---> /sys/class/xxx->class
/sys/devices/xxx->parent/dir->kobj/xxx/device ---> dev->parent->kobj 
/sys/class/dev->class/xxx ---> /sys/devices/xxx->parent/dir->kobj/xxx

/sys/devices/xxx/dev  //有设备号的设备会创建这个节点

/* 
 * .../dev->class->dev_kobj/"major+minor" ---> /sys/devices/xxx->parent/dir->kobj/xxx 有dev->class->dev_kobj和设备号的设备会创建这个节点
 * dev->class->dev_kobj 这个属性在class被注册的时候一般被默认设置为 sysfs_dev_char_kobj,即 /sys/dev/char节点
 */
/sys/dev/char/"major+minor" ---> /sys/devices/xxx->parent/dir->kobj/xxx //有设备号会默认创建这个节点

/dev/xxx //有设备号的设备会创建这个节点

为什么没有同时出现一个设备同时属于 bus 和 class 的情况,通过整理 device_add 创建的目录层次可以发现,bus(class)下的设备都会在注册的的时候在设备目录创建 subsystem ,这个链接文件指向 bus(class),同时也会在 bus(class) 目录创建指向设备的链接文件。在 device_add_class_symlinks 函数和 bus_add_device 函数中都会在当前设备下创建 “subsystem” 这个属性文件,很明显存在着冲突,这得出一个结论:内核在向 bus 注册设备的时候,如果设备同属于 class 和 bus 时,设备是无法注册的

3)和挂接的bus上的所有drv进行匹配

匹配流程如下

bus_probe_device--->
   device_initial_probe(dev) ---> //如果bus->p->drivers_autoprobe被置位则调用这个函数
      ---> __device_attach(dev, true);
               ---> bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); //对于该bus上的每一个drv都调用__device_attach_driver函数
                        ---> __device_attach_driver(drv, data) 
                                ---> driver_match_device(drv, dev);
                                         ---> drv->bus->match(dev, drv)
                                         ---> driver_probe_device(drv, dev) //如果匹配成功则调用这个函数
                                                 ---> really_probe(dev, drv);
                                                         ---> dev->driver = drv; //将匹配成功的驱动赋值给对应的设备
                                                         ---> dev->bus->probe(dev) //默认调用这个
                                                         ---> drv->probe(dev) //如果没有设置 dev->bus->probe 函数,则调用这

dev 在注册的时候会和所挂接 bus上 的所有 drv 进行匹配,即调用 drv->bus->match(dev, drv) 函数,如果匹配成功则调用所在总线上的 probe 函数,即 dev->bus->probe(dev) 函数 , 从这里也可以看出匹配的规则是灵活的,它由总线决定,由总线来决定设备和驱动的匹配规则, 比如 platform 总线就规定了5种匹配规则,这里只是提一下,后面的platform设备详述。

4)建立与字符设备的联系

常用的字符设备就是通过设备号与设备模型建立联系的,当我们在设备模型中注册一个 dev 时,如果存在设备号则会在/dev下创建对应的设备文件,我们可以通过这个文件的设备号,在已经注册的字符设备链表中查询到我们已经注册的字符设备。

5、创建我们自己的设备

我们之前已经创建了一个 my_bus 总线,现在在这个基础之上再创建一个 my_dev

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>

extern struct bus_type my_bus;

struct device my_dev = {
    .init_name = "my_dev",
    .bus = &my_bus,
};

static int my_device_init(void)
{
    device_register(&my_dev);
    return 0;
}

static void my_device_exit(void)
{
    device_unregister(&my_dev);
}

module_init(my_device_init);
module_exit(my_device_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

XF-X2:/sys/bus/my_bus/devices # ls
XF-X2:/sys/bus/my_bus/devices #
XF-X2:/sys/bus/my_bus/devices # insmod /cache/my_device.ko
XF-X2:/sys/bus/my_bus/devices #
XF-X2:/sys/bus/my_bus/devices # ls
my_dev
XF-X2:/sys/bus/my_bus/devices # cd my_dev/
XF-X2:/sys/bus/my_bus/devices/my_dev # ls
power subsystem uevent

6、在dev下创建属性文件

属性文件作为用户空间和内核空间交互的常用手段之一,它的重要性不言而喻。 device_register 在 device_initialize 中会将 ktype 初始化为 device_ktype

static struct kobj_type device_ktype = {
    .release    = device_release,
    .sysfs_ops  = &dev_sysfs_ops,
    .namespace  = device_namespace,
};

static const struct sysfs_ops dev_sysfs_ops = {
    .show   = dev_attr_show,
    .store  = dev_attr_store,
};

可以看到内核已经实现了 device_release 函数当 dev 引用计数为 0 时会自动释放掉自己。同样的内核也实现了属性文件中间层的函数 dev_attr_show 和 dev_attr_store

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
                 char *buf)
{
    struct device_attribute *dev_attr = to_dev_attr(attr); //获取到更大的结构 device_attribute
    struct device *dev = kobj_to_dev(kobj);
    ssize_t ret = -EIO;

    if (dev_attr->show)
        ret = dev_attr->show(dev, dev_attr, buf); //调用 device_attribute 的成员 show 函数
    if (ret >= (ssize_t)PAGE_SIZE) {
        print_symbol("dev_attr_show: %s returned bad count\n",
                (unsigned long)dev_attr->show);
    }
    return ret;
}

static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
                  const char *buf, size_t count)
{
    struct device_attribute *dev_attr = to_dev_attr(attr); //获取到更大的结构 device_attribute
    struct device *dev = kobj_to_dev(kobj);
    ssize_t ret = -EIO;

    if (dev_attr->store)
        ret = dev_attr->store(dev, dev_attr, buf, count);//调用 device_attribute 的成员 store 函数
    return ret;
}

struct device_attribute {
    struct attribute    attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
            char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
             const char *buf, size_t count);
};

这个框架看起来是不是很熟悉,其实前面我们自己在my_dir就已经实现了这个框架了,这里只是换了个壳而已,像前面的 bus,以及后面的 driver 等模型,内核已经帮我们实现了相关框架,我们只需要直接用就行了。创建属性结构的方法太麻烦了,没关系内核也为我们封装了快速创建并初始化 device_attribute 结构的宏了

/* 
 * 快速创建一个的 device_attribute 属性结构 dev_attr_name
 */
#define DEVICE_ATTR(_name, _mode, _show, _store) \
    struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* 
 * 快速创建一个可读可写的 device_attribute 属性结构 dev_attr_name
 * 属性操作函数为, name_show、name_store
 */
#define DEVICE_ATTR_RW(_name) \
    struct device_attribute dev_attr_##_name = __ATTR_RW(_name)


/* 
 * 快速创建一个只读的 device_attribute 属性结构 dev_attr_name
 * 属性操作函数为, name_show
 */
#define DEVICE_ATTR_RO(_name) \
    struct device_attribute dev_attr_##_name = __ATTR_RO(_name)

/* 
 * 快速创建一个只写的 device_attribute 属性结构 dev_attr_name
 * 属性操作函数为, name_store
 */
#define DEVICE_ATTR_WO(_name) \
    struct device_attribute dev_attr_##_name = __ATTR_WO(_name)

#define DEVICE_ULONG_ATTR(_name, _mode, _var) \
    struct dev_ext_attribute dev_attr_##_name = \
        { __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) }

#define DEVICE_INT_ATTR(_name, _mode, _var) \
    struct dev_ext_attribute dev_attr_##_name = \
        { __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) }

#define DEVICE_BOOL_ATTR(_name, _mode, _var) \
    struct dev_ext_attribute dev_attr_##_name = \
        { __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) }

#define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \
    struct device_attribute dev_attr_##_name =      \
        __ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store)

创建出了 device_attribute 结构,再调用 device_attribute 函数就可以在当前设备下快速创建属性文件。

int device_create_file(struct device *dev,
               const struct device_attribute *attr)
{
    int error = 0;

    if (dev) {
        WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
            "Attribute %s: write permission without 'store'\n",
            attr->attr.name);
        WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
            "Attribute %s: read permission without 'show'\n",
            attr->attr.name);
        error = sysfs_create_file(&dev->kobj, &attr->attr); //注册属性文件
    }

    return error;
}
EXPORT_SYMBOL_GPL(device_create_file);

编程实现

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>

extern struct bus_type my_bus;
extern struct class *my_class;

struct device my_dev = {
    .init_name = "my_dev",
    .bus = &my_bus,
};

ssize_t my_attr_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    printk("%s\n",attr->attr.name);
    sprintf(buf, "%s\n", attr->attr.name);
    return strlen((char*)attr->attr.name) +2;
}

ssize_t my_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    printk("%s store : %s\n",attr->attr.name, buf);
    return count;
}

DEVICE_ATTR(my_attr_test, 0664, my_attr_show, my_attr_store);

static int my_device_init(void)
{
    device_register(&my_dev);
    device_create_file(&my_dev,&dev_attr_my_attr_test); //注意这里需要添加自己的前缀 dev_attr_
    return 0;
}

static void my_device_exit(void)
{
    device_unregister(&my_dev);
}

module_init(my_device_init);
module_exit(my_device_exit);
MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

//cat自己创建出的设备节点
XF-X2:/sys/bus/my_bus/devices/my_dev # cat my_attr_test
my_attr_test
[  501.905519] <6>.(6)[2845:cat]my_attr_test //内核打印

//ehco自己创建的设备节点
XF-X2:/sys/bus/my_bus/devices/my_dev # echo 123>my_attr_test
[  545.227562] <4>.(4)[2821:sh]my_attr_test store : 123 //内核打印
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐