目录
二、device_create()、device_register()、device_add()使用中的区别
前言:
Linux设备驱动开发中,经常会用到device_create()函数创建设备节点,除了device_create()内核源码中也经常用device_register()和device_add()创建设备节点。
本文主要讲解device_create()、device_register()、deivce_add()的区别。
一、源码分析
1、device_create()
device_create函数是我们写设备驱动经常用到的函数,它的功能是创建一个设备,并注册它到sysfs中,源码如下:
/* 源码路径:drivers/base/core.c */
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_groups_vargs()函数,device_create_groups_vargs功能是创建一个device并且将它注册到sysfs中
device_create()
--->device_create_vargs()
--->--->device_create_groups_vargs()
device_create_groups_vargs()函数源码:
static __printf(6, 0) 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); //初始化device
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); //设置device节点名字
if (retval)
goto error;
retval = device_add(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
可以看到,device_create()是device_add()的封装,最终还是调用 device_add()
2、device_register
/* drivers/base/core.c */
int device_register(struct device *dev)
{
device_initialize(dev); //初始化device
return device_add(dev);
}
device_register()也是device_add()的封装。
3、device_add
device_add()会在/sys目录对应设备目录下创建uevent属性节点,应用层的udev则会根据uevent来创建/dev目录下的设备节点,这里关于udev的部分不再赘述
/* drivers/base/core.c */
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);
if (!dev)
goto done;
if (!dev->p) {
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) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
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);
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))
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error) {
glue_dir = get_glue_dir(dev);
goto Error;
}
/* notify platform of device entry */
error = device_platform_notify(dev, KOBJ_ADD);
if (error)
goto platform_error;
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
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)) {
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;
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);
kobject_uevent(&dev->kobj, KOBJ_ADD);
/*
* Check if any of the other devices (consumers) have been waiting for
* this device (supplier) to be added so that they can create a device
* link to it.
*
* This needs to happen after device_pm_add() because device_link_add()
* requires the supplier be registered before it's called.
*
* But this also needs to happen before bus_probe_device() to make sure
* waiting consumers can link to it before the driver is bound to the
* device and the driver sync_state callback is called for this device.
*/
if (dev->fwnode && !dev->fwnode->dev) {
dev->fwnode->dev = dev;
fw_devlink_link_device(dev);
}
bus_probe_device(dev);
if (parent)
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 */
klist_add_tail(&dev->p->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:
device_platform_notify(dev, KOBJ_REMOVE);
platform_error:
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;
}
二、device_create()、device_register()、device_add()使用中的区别
分别用device_create()、device_register()、device_add()来创建设备节点“/dev/led”,实现相同的功能能直观看到使用它们的区别。
1、device_create()
static class *led_class;
static int __init led_init(void)
{
int ret;
dev_t devno;
struct cdev *cdev;
struct dev *dev;
/* 注册设备号 */
ret = alloc_chrdev_region(&devno, 0, 1, "led");
if (ret < 0)
return ret;
/* 分配、初始化、注册cdev*/
cdev = cdev_alloc();
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
goto out_unregister_devno;
}
cdev_init(&cdev, &led_fops);
cdev.owner = THIS_MODULE;
ret = cdev_add(&cdev, devno, 1);
if (ret)
goto out_free_cdev;
/* 创建设备类 */
led_class = class_create(THIS_MODULE, "led_class");
if (IS_ERR(led_class)) {
ret = PTR_ERR(led_class);
goto out_unregister_cdev;
}
/* 创建设备节点 */
dev = device_create(led_class, NULL, devno, NULL, "led");
if (IS_ERR(dev)) {
ret = PTR_ERR(dev);
goto out_del_class;
}
return 0;
}
module_init(led_init);
2、device_register()
static class *led_class;
static int __init led_init(void)
{
......
/* 注册设备号 */
......
/* 分配、初始化、注册cdev*/
......
/* 创建设备类 */
......
/* 创建设备节点 */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
ret = -ENOMEM;
goto out_del_class;
}
dev->class = led_class; // 关联设备类
dev->parent = NULL;
dev->devt = devno; // 关联设备号
dev_set_drvdata(dev, NULL);
dev_set_name(dev, "led"); // 设置节点名字
dev->release = device_create_release;
ret = device_register(dev);
if (ret)
goto out_put_dev;
return 0;
}
module_init(led_init);
3、device_add()
static class *led_class;
static int __init led_init(void)
{
......
/* 注册设备号 */
......
/* 分配、初始化、注册cdev*/
......
/* 创建设备类 */
......
/* 创建设备节点 */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
ret = -ENOMEM;
goto out_del_class;
}
dev->class = led_class; // 关联设备类
dev->parent = NULL;
dev->devt = devno; // 关联设备号
dev_set_drvdata(dev, NULL);
dev_set_name(dev, "led"); // 设置节点名字
dev->release = device_create_release;
device_initialize(dev);
ret = device_add(dev);
if (ret)
goto out_put_dev;
return 0;
}
module_init(led_init);
最后看下 dev_set_name 函数源码,它调用kobject_set_name_vargs去设置device节点名字
/* drivers/base/core.c */
int dev_set_name(struct device *dev, const char *fmt, ...)
{
va_list vargs;
int err;
va_start(vargs, fmt);
err = kobject_set_name_vargs(&dev->kobj, fmt, vargs);
va_end(vargs);
return err;
}