linux驱动-设备驱动模型(device设备)
从代码可以看出对于 dev 来说名字是一个非常重要的参数,首先使用 init_name 作为dev->kobj 的名字同时将 init_name 设置为空,如果 init_name 初始为空则使用 “bus->dev_nam + dev->id” 作为dev->kobj 的名字,如果设备没有设置名字则直接返回错误。
该系列文章阅读顺序:
- linux驱动-设备驱动模型(属性文件 kobject )
- linux驱动-设备驱动模型(kset)
- linux驱动-设备驱动模型(bus总线)
- linux驱动-设备驱动模型(device设备)
- linux驱动-设备驱动模型(driver驱动)
- linux驱动-设备驱动模型(class类)
- 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为空
- 创建 /sys/devices/virtual目录,并将其作为父目录parent_kobj
- 遍历 dev->class->p->glue_dirs 下的所有kobj, 如果存在 parent_kobj,则增加其引用计数并直接返回 parent_kobj
- 否则在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不为空
- 如果存在
dev->class->ns_type
则直接返回 parent_kobj (这种情况不增加目录) - 如果上述不存在,则遍历
dev->class->p->glue_dirs
下的所有 kobj, 如果存在parent_kobj,则增加其引用计数并直接返回 parent_kobj - 否则在 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 //内核打印

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