热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux设备驱动深入解析:设备模型与devicedriverbus机制(下篇)

在上篇文章的基础上,本文将继续探讨Linux设备驱动中的设备模型与`devicedriverbus`机制。在将设备注册到总线之前,需要先创建`device`对象。可以通过静态定义`device`结构体变量,并调用`device_register`函数来完成这一过程。此外,文章还将详细解析设备模型的内部工作机制,以及`devicedriverbus`机制如何实现设备与驱动的自动匹配和管理。

接上一篇文章,在往总线注册注册设备前要先创建device,我们可以静态的定义device结构变量,然后调用device_register()将其注册,或者通过内核提供的device_create()接口函数创建和注册device。先看看device的数据结构定义:

struct device {

struct device*parent;

struct device_private*p;

struct kobject kobj;

const char*init_name; /* initial name of the device */

struct device_type*type;

struct semaphoresem;/* semaphore 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 */

struct dev_pm_infopower;

#ifdef CONFIG_NUMA

intnuma_node;/* NUMA node this device is close to */

#endif

u64*dma_mask;/* dma mask (if dma'able device) */

u64coherent_dma_mask;/* Like dma_mask, but for

alloc_coherent mappings as

not all hardware supports

64 bit addresses for consistent

allocations such descriptors. */

struct device_dma_parameters *dma_parms;

struct list_headdma_pools;/* dma pools (if dma'ble) */

struct dma_coherent_mem*dma_mem; /* internal for coherent mem

override */

/* arch specific additions */

struct dev_archdataarchdata;

dev_tdevt;/* dev_t, creates the sysfs "dev" */

spinlock_tdevres_lock;

struct list_headdevres_head;

struct klist_nodeknode_class;

struct class*class;

const struct attribute_group **groups;/* optional groups */

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

};

和总线类型一样,device也把部分私有数据封装到device_private结构中:

struct device_private {

struct klist klist_children;

struct klist_node knode_parent;

struct klist_node knode_driver;

struct klist_node knode_bus;

void *driver_data;

struct device *device;

};

创建device的函数device_create()如下:

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_vargs():

struct device *device_create_vargs(struct class *class, struct device *parent,

dev_t devt, void *drvdata, 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;

}

dev->devt = devt;

dev->class = class;

dev->parent = parent;

dev->release = device_create_release;

dev_set_drvdata(dev, drvdata);

retval = kobject_set_name_vargs(&dev->kobj, fmt, args);

if (retval)

goto error;

retval = device_register(dev);

if (retval)

goto error;

return dev;

error:

put_device(dev);

return ERR_PTR(retval);

}

该函数为创建的device分配内存空间,根据输入参数和一些默认值对其初始化,然后调用device_register将其注册到相应的总线上。

int device_register(struct device *dev)

{

device_initialize(dev);

return device_add(dev);

}

device_initialize()先对dev初始化:

void device_initialize(struct device *dev)

{

dev-> kobj.kset = devices_kset;

为其内嵌kobjkset赋值,类似bus_kset,devices_kset也是系统启动是创建的:

int __init devices_init(void)

{

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;

}

对应sysfs中的/sys/devices/sys/devices/block/sys/devices/char

接着device_initialize()中的代码:

kobject_init(&dev-> kobj, &device_ktype);

初始化dev内嵌的kobj

INIT_LIST_HEAD(&dev->dma_pools);

init_MUTEX(&dev->sem);

spin_lock_init(&dev->devres_lock);

INIT_LIST_HEAD(&dev->devres_head);

初始化一些其它的字段。

device_init_wakeup(dev, 0);

device_pm_init(dev);

电源管理的一些初始化。

set_dev_node(dev, -1);

设置numa

}

初始化dev后调用device_add()往相应总线上添加设备。分段分析这个函数:

int device_add(struct device *dev)

{

struct device *parent = NULL;

struct class_interface *class_intf;

int error = -EINVAL;

dev = get_device(dev);

if (!dev)

goto done;

if (!dev->p) {

error = device_private_init(dev);

if (error)

goto done;

}

增加dev的计数,初始化其私有结构,实际上面初始化调用的dev_set_drvdata(dev, drvdata)里边已经包含了device_private_init(),但如果是直接调用device_register()而不是device_create()时,必须在这里再初始化一次,显然第一个调用device_private_init()是不必要id。所以在这里才分析device_private_init(),函数如下:

int device_private_init(struct device *dev)

{

dev->p = kzalloc(sizeof(*dev->p), GFP_KERNEL);

if (!dev->p)

return -ENOMEM;

为其分配内存。

dev->p->device = dev;

关联到dev

klist_init(&dev->p-> klist_children, klist_children_get,

klist_children_put);

初始化klist_children

return 0;

}

接着device_add()函数。

/*

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

}

if (!dev_name(dev))

goto name_error;

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

dev->init_name设置dev->kobj.name,而后dev->init_name指向NULL,如果dev->kobj.name则跳到name_error

parent = get_device(dev->parent);

增加dev->parent-> kobj的计数。

setup_parent(dev, parent);

看下这个函数:

static void setup_parent(struct device *dev, struct device *parent)

{

struct kobject *kobj;

kobj = get_device_parent(dev, parent);

if (kobj)

dev->kobj.parent = kobj;

}

static struct kobject *get_device_parent(struct device *dev,

struct device *parent)

{

int retval;

if (dev->class) {

struct kobject *kobj = NULL;

struct kobject *parent_kobj;

struct kobject *k;

/*

* 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_kobj = virtual_device_parent(dev);

else if (parent->class)

return &parent->kobj;

else

parent_kobj = &parent->kobj;

/* find our class-directory at the parent and reference it */

spin_lock(&dev->class->p->class_dirs.list_lock);

list_for_each_entry(k, &dev->class->p->class_dirs.list, entry)

if (k->parent == parent_kobj) {

kobj = kobject_get(k);

break;

}

spin_unlock(&dev->class->p->class_dirs.list_lock);

if (kobj)

return kobj;

/* or create a new class-directory at the parent device */

k = kobject_create();

if (!k)

return NULL;

k->kset = &dev->class->p->class_dirs;

retval = kobject_add(k, parent_kobj, "%s", dev->class->name);

if (retval <0) {

kobject_put(k);

return NULL;

}

/* do not emit an uevent for this simple "glue" directory */

return k;

}

if (parent)

return &parent->kobj;

return NULL;

}

dev-> kobj.parent的设置如下&#xff1a;如果dev->parent&#xff0c;则将dev->parent->kobj赋给它&#xff0c;否则如果device_create()class参数不为为NULL时&#xff0c;则通过virtual_device_parent(dev)获得其parent&#xff0c;否则指向NULL,在调用kobject_add()时使其kset字段的kset内嵌的object

继续device_add()函数

/* 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 */

error &#61; kobject_add(&dev->kobj, dev->kobj.parent, NULL);

if (error)

goto Error;

dev->kobj添加到系统中。

/* notify platform of device entry */

if (platform_notify)

platform_notify(dev);

如果定义了platform_notify&#xff08;&#xff09;函数&#xff0c;则调用它&#xff0c;在drivers/base/core.c中有&#xff1a;

int (*platform_notify)(struct device *dev) &#61; NULL;

说明默认将不会调用它。

error &#61; device_create_file(dev, &uevent_attr);

if (error)

goto attrError;

建立devuevent_attr属性文件&#xff0c;这里的uevent_attr定义如下&#xff1a;

static struct device_attribute uevent_attr &#61;

__ATTR(uevent, S_IRUGO | S_IWUSR, show_uevent, store_uevent);

show_uevent()函数如下&#xff1a;

static ssize_t show_uevent(struct device *dev, struct device_attribute *attr,

char *buf)

{

struct kobject *top_kobj;

struct kset *kset;

struct kobj_uevent_env *env &#61; NULL;

int i;

size_t count &#61; 0;

int retval;

/* search the kset, the device belongs to */

top_kobj &#61; &dev->kobj;

while (!top_kobj->kset && top_kobj->parent)

top_kobj &#61; top_kobj->parent;

if (!top_kobj->kset)

goto out;

kset &#61; top_kobj->kset;

if (!kset->uevent_ops || !kset->uevent_ops->uevent)

goto out;

/* respect filter */

if (kset->uevent_ops && kset->uevent_ops->filter)

if (!kset->uevent_ops->filter(kset, &dev->kobj))

goto out;

env &#61; kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);

if (!env)

return -ENOMEM;

/* let the kset specific function add its keys */

retval &#61; kset->uevent_ops->uevent(kset, &dev->kobj, env);

if (retval)

goto out;

/* copy keys to file */

for (i &#61; 0; i envp_idx; i&#43;&#43;)

count &#43;&#61; sprintf(&buf[count], "%s\n", env->envp[i]);

out:

kfree(env);

return count;

}

改函数表明如果读devuevent则会显示dev-> kobj所属kset产生的环境变量。

store_uevent定义如下&#xff1a;

static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,

const char *buf, size_t count)

{

enum kobject_action action;

if (kobject_action_type(buf, count, &action) &#61;&#61; 0) {

kobject_uevent(&dev->kobj, action);

goto out;

}

dev_err(dev, "uevent: unsupported action-string; this will "

"be ignored in a future kernel version\n");

kobject_uevent(&dev->kobj, KOBJ_ADD);

out:

return count;

}

从程序可以看出对这个文件的作用写则会产生相应的事件。如果输入的字符串不合法&#xff0c;则就会产生一个add事件。

接着device_add()函数中的代码&#xff1a;

if (MAJOR(dev->devt)) {

error &#61; device_create_file(dev, & devt_attr);

if (error)

goto ueventattrError;

error &#61; device_create_sys_dev_entry(dev);

if (error)

goto devtattrError;

devtmpfs_create_node(dev);

}

如果dev->devt的煮设备号不为空&#xff0c;则创建devt_attr属性文件和连接文件&#xff0c;devt_attr定义如下

static struct device_attribute devt_attr &#61;

__ATTR(dev, S_IRUGO, show_dev, NULL);

该属性的store函数为NULL&#xff0c;说明为只读。读操作函数show_dev&#xff08;&#xff09;&#xff1a;

static ssize_t show_dev(struct device *dev, struct device_attribute *attr,

char *buf)

{

return print_dev_t(buf, dev->devt);

}

#define print_dev_t(buffer, dev)\

sprintf((buffer), "%u:%u\n", MAJOR(dev), MINOR(dev))

读操作为打印dev的设备号。

static int device_create_sys_dev_entry(struct device *dev)

{

struct kobject *kobj &#61; device_to_dev_kobj(dev);

int error &#61; 0;

char devt_str[15];

if (kobj) {

format_dev_t(devt_str, dev->devt);

error &#61; sysfs_create_link(kobj, &dev->kobj, devt_str);

}

return error;

}

static struct kobject *device_to_dev_kobj(struct device *dev)

{

struct kobject *kobj;

if (dev->class)

kobj &#61; dev->class->dev_kobj;

else

kobj &#61; sysfs_dev_char_kobj;

return kobj;

}

如果定义了dev->class&#xff0c;则在相应的目录下建立链接文件&#xff0c;否则默认在/sys/devices/char下建立链接文件。

接着device_add ()函数&#xff1a;

error &#61; device_add_class_symlinks(dev);

if (error)

goto SymlinkError;

error &#61; device_add_attrs(dev);

if (error)

goto AttrsError;

同样在class子系统下建立链接文件、class->dev_attrs属性文件、group等。

接下一篇文章。



推荐阅读
  • 在项目部署后,Node.js 进程可能会遇到不可预见的错误并崩溃。为了及时通知开发人员进行问题排查,我们可以利用 nodemailer 插件来发送邮件提醒。本文将详细介绍如何配置和使用 nodemailer 实现这一功能。 ... [详细]
  • 本文将详细探讨Linux pinctrl子系统的各个关键数据结构,帮助读者深入了解其内部机制。通过分析这些数据结构及其相互关系,我们将进一步理解pinctrl子系统的工作原理和设计思路。 ... [详细]
  • 本文详细介绍如何利用已搭建的LAMP(Linux、Apache、MySQL、PHP)环境,快速创建一个基于WordPress的内容管理系统(CMS)。WordPress是一款流行的开源博客平台,适用于个人或小型团队使用。 ... [详细]
  • 本文详细介绍如何在Linux系统中配置SSH密钥对,以实现从一台主机到另一台主机的无密码登录。内容涵盖密钥对生成、公钥分发及权限设置等关键步骤。 ... [详细]
  • 本文探讨了在C++中如何有效地清空输入缓冲区,确保程序只处理最近的输入并丢弃多余的输入。我们将介绍一种不阻塞的方法,并提供一个具体的实现方案。 ... [详细]
  • 本文详细介绍了如何通过RPM包在Linux系统(如CentOS)上安装MySQL 5.6。涵盖了检查现有安装、下载和安装RPM包、配置MySQL以及设置远程访问和开机自启动等步骤。 ... [详细]
  • This pull request introduces the ability to provide comprehensive paragraph configurations directly within the Create Note and Create Paragraph REST endpoints, reducing the need for additional configuration calls. ... [详细]
  • 问题描述:通过添加最少数量的括号,使得给定的括号序列变为合法,并输出最终的合法序列。数据范围:字符串长度不超过100。涉及算法:区间动态规划(Interval DP)。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 异常要理解Java异常处理是如何工作的,需要掌握一下三种异常类型:检查性异常:最具代表性的检查性异常是用户错误或问题引起的异常ÿ ... [详细]
  • 本文介绍了一个SQL Server自定义函数,用于从字符串中提取仅包含数字和小数点的子串。该函数通过循环删除非数字字符来实现,并附带创建测试表、存储过程以演示其应用。 ... [详细]
  • 本文详细解析了Java中hashCode()和equals()方法的实现原理及其在哈希表结构中的应用,探讨了两者之间的关系及其实现时需要注意的问题。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 配置多VLAN环境下的透明SQUID代理
    本文介绍如何在包含多个VLAN的网络环境中配置SQUID作为透明网关。网络拓扑包括Cisco 3750交换机、PANABIT防火墙和SQUID服务器,所有设备均部署在ESXi虚拟化平台上。 ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
author-avatar
尕心疼TammyY
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有