总述

Linux 系统下的驱动最后都是以如下这个结构体呈现在系统中的,注意其中的dev_pm_ops是内核新增的内容来准备替换platform_driver中的电源管理相关的内容。这里内容是先进行总体的流程梳理后面再结合Linux内核代码的内容来学习。

struct device_driver {
const char *name;
struct bus_type *bus; /*Linux 下驱动都是应该挂在总线地下的*/ struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* 如果他为1 则取消暴露到文件系统中的绑定相关操作接口 */ const struct of_device_id *of_match_table; /* 设备树兼容属性表 */
const struct acpi_device_id *acpi_match_table;/* ACPI兼容属性表 */ int (*probe) (struct device *dev); /* 设备探测接口由总线调用 */
int (*remove) (struct device *dev);/* 设备卸载接口由总线调用 */
void (*shutdown) (struct device *dev); /* 电源管理相关 */
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; /* 驱动属性 */ const struct dev_pm_ops *pm;/* 设备电源管理 */ struct driver_private *p; /* 驱动私有数据 */
};

系统给驱动开发提供了一些接口,其中driver_register和driver_unregister两个接口常使用以完成驱动的注册和卸载。

驱动注册流程

这里先进行总体的执行过程分析,后续参考源码进行分析(后面有时间会补上),3.x的内核和4.0的内核区别会有一些但是不是很大。依然有参考意义

1、driver_register
1、驱动如果有probe函数并且总线也有就需要出一个警告,
要求使用总线提供的方法(也就是说以总线的探测接口优先)
2、driver_find 检查目标总线上是否已经有同名驱动
(因为驱动和设备的match有时是靠名字进行匹配的)
3、bus_add_driver(drv)
1、bus_get() 其实kobject.refcnt++
2、申请分配驱动私有管理数据结构内存并和驱动双向绑定
这部分私有数据用于驱动管理驱动上的设备和所属总线等逻辑      
主要是klist_devices和kobj相关的。
3、kobject_init_and_add 主要是kobject相关调用
4、klist_add_tail 将驱动添加到bus的klist中去
5、driver_attach(drv);/*驱动和设备的匹配 新内核新增的新异步匹配
机制这里和旧内核有区别 */
1、bus_for_each_dev(bus,device_start,data,fn(struct device*,void))
这个函数就是会遍历总线上的设备依次执行其中的fn传入的是接口函数
__driver_attach这一部分处理和设备的注册十分相似
2、遍历bus上的klist_devices 依次__driver_attach(dev,drv)进行匹配
1、__driver_attach
1、如果驱动总线的mach函数存则在调用驱动总线的mach函数
match(dev,drv)返回0就是匹配成功
2、前一步未匹配成功继续调用driver_probe_device(drv,dev)做了一堆检查
后实际调用really_probe(dev,drv)正如函数名一样他会真正执行probe函数。
这里有个机制内核会优先执行对应总线的probe函数,如果它不存在则会查看对
应驱动的probe函数是否存在,存在就调用驱动的probe操作,在这之前这个函数
还将sysfs下建立好了对应的文件,如果失败后面会删除并返回同时将dev的driver
句柄置空这次的驱动和设备匹配失败反之成功后将进行驱动和设备的绑定 执行driver_bound
6、上一步如果失败就退出驱动注册,成功了继续。
7、module_add_driver
8、前面成功匹配以将在sysfs下建立了驱动的文件夹和文件,后的
操作就是继续创建一些驱动文件接口和属性文件在驱动目录下。
9、至此驱动注册完成,在这之中驱动的探测和绑定均以执行完毕。

下面来仔细看看驱动的注册过程

driver_register

 1 int driver_register(struct device_driver *drv)
2 {
3 int ret;
4 struct device_driver *other;
5
6 BUG_ON(!drv->bus->p);
7 /*
8 这里给出警告是因为在驱动和设备的探测过程和卸载过程中
9 如果总线和驱动同时实现了同名的方法则只会执行总线的方法。
10 */
11 if ((drv->bus->probe && drv->probe) ||
12 (drv->bus->remove && drv->remove) ||
13 (drv->bus->shutdown && drv->shutdown))
14 printk(KERN_WARNING "Driver '%s' needs updating - please use "
15 "bus_type methods\n", drv->name);
16 /*
17 找总线上是否有同名的驱动,驱动不允许重名
18 */
19 other = driver_find(drv->name, drv->bus);
20 if (other) {
21 printk(KERN_ERR "Error: Driver '%s' is already registered, "
22 "aborting...\n", drv->name);
23 return -EBUSY;
24 }
25 /*
26 给总线上添加驱动,依附同一总线的驱动和设备才会进行匹配
27 */
28 ret = bus_add_driver(drv);
29 if (ret)
30 return ret;
31 /*
32 在sys目录地下创建 驱动文件;如果失败就需要从总线上删除驱动
33 */
34 ret = driver_add_groups(drv, drv->groups);
35 if (ret) {
36 bus_remove_driver(drv);
37 return ret;
38 }
39 /*
40 产生并发送一个内核对象增加事件,这里参考设备添加过程或kobject相关的内容
41 */
42 kobject_uevent(&drv->p->kobj, KOBJ_ADD);
43
44 return ret;
45 }

最重要的是其中的bus_add_driver 接口:

 1 int bus_add_driver(struct device_driver *drv)
2 {
3 struct bus_type *bus;
4 struct driver_private *priv;
5 int error = 0;
6 /*Kobject 相关*/
7 bus = bus_get(drv->bus);
8 if (!bus)
9 return -EINVAL;
10
11 pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
12 /* 申请一块内存作为驱动的私有数据块 用来管理驱动地下的设备等属性 */
13 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
14 if (!priv) {
15 error = -ENOMEM;
16 goto out_put_bus;
17 }
18 //设备list初始化,一个驱动可以和多个设备匹配
19 klist_init(&priv->klist_devices, NULL, NULL);
20 priv->driver = drv;
21 drv->p = priv;
22 //C实现的继承
23 priv->kobj.kset = bus->p->drivers_kset;
24 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
25 "%s", drv->name);
26 if (error)
27 goto out_unregister;
28 /*
29 将驱动添加到总线的drvlist中
30 */
31 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
32 /*
33 这里和设备的添加过程呼应
34 */
35 if (drv->bus->p->drivers_autoprobe) {
36 /*
37 驱动添加时匹配设备
38 */
39 error = driver_attach(drv);
40 if (error)
41 goto out_unregister;
42 }
43 module_add_driver(drv->owner, drv);
44
45 error = driver_create_file(drv, &driver_attr_uevent);
46 if (error) {
47 printk(KERN_ERR "%s: uevent attr (%s) failed\n",
48 __func__, drv->name);
49 }
50 error = driver_add_groups(drv, bus->drv_groups);
51 if (error) {
52 /* How the hell do we get out of this pickle? Give up */
53 printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
54 __func__, drv->name);
55 }
56 /*
57 如果驱动支持从文件接口配置驱动则创建sys下的文件
58 */
59 if (!drv->suppress_bind_attrs) {
60 error = add_bind_files(drv);
61 if (error) {
62 /* Ditto */
63 printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
64 __func__, drv->name);
65 }
66 }
67
68 return 0;
69
70 out_unregister:
71 kobject_put(&priv->kobj);
72 /* drv->p is freed in driver_release() */
73 drv->p = NULL;
74 out_put_bus:
75 bus_put(bus);
76 return error;
77 }

执行过程:增加当前总线的kobject的引用次数,分配驱动私有数据的内存这是驱动核心用来管理驱动的私有数据主要是涉及到kobject的内容。然后就是判断驱动所属的总线是否支持自动探测设备这里一般都是默认支持的,所以继续到 driver_attach然后就是其中的bus_add_driver 接口如下这里注意在bus_add_driver 接口还传入了一个函数接口__driver_attach 如果看过前面设备添加过程,这里对这个和接口函数的作用应该很清楚了。

int driver_attach(struct device_driver *drv)
{
//处理也很简单,遍历总线上的设备挨个匹配
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

bus_add_driver 这个函数和设备添加过程遍历总线上的驱动的处理过程十分相似,就是遍历总线上的 bus->p->klist_devices 依次调用 __driver_attach 接口探测驱动是否支持当前设备。主要区别就是传进来的匹配处理的接口函数的具体实现。

int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0; if (!bus || !bus->p)
return -EINVAL; klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}

所以接下来仔细看__driver_attach  接口,前提是知道上面给这个函数传了什么参数,很明显第一个参数是每个不同的device地址,第二个就是正在添加的驱动。函数接口的实现如下

 1 static int __driver_attach(struct device *dev, void *data)
2 {
3 struct device_driver *drv = data;
4 /*
5 这里的实现和__device_attach 接口有两点区别
6 这个接口恒返回0
7 总线的match接口匹配失败后要锁定设备在继续尝试。这里调用的接口和device相同
8 */
9 if (!driver_match_device(drv, dev))
10 return 0;
11
12 if (dev->parent) /* Needed for USB */
13 device_lock(dev->parent);
14 device_lock(dev);
15 if (!dev->driver)
16 driver_probe_device(drv, dev);
17 device_unlock(dev);
18 if (dev->parent)
19 device_unlock(dev->parent);
20
21 return 0;
22 }

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); pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev); return ret;
}

检查了设备是否注册确认是注册设备后才执行.....继续到really_probe 。突然豁然开朗这不是就是前面设备注册过程最后调用的那个探测接口吗;这里仅仅是传入的参数顺序不同。前面是不停的遍历驱动传进来,设备固定执行这个接口匹配;现在相反的传入不同的设备而驱动固定进行匹配。所以这里就不继续再继续探究了有兴趣上面的链接去设备的添加过程看看,这里我就偷个懒了。然后就是后续的处理操作主要都是对应的文件在/sys目录下的创建。

驱动卸载流程

这个过程就是上面过程的逆向操作,所以不在详细看了,简单的走读记录一下调用栈,方便日后用到的时候查,基本也都是基于device_driver的高层封装。

void bus_remove_driver(struct device_driver *drv)
{
if (!drv->bus)
return; if (!drv->suppress_bind_attrs)
remove_bind_files(drv);
driver_remove_groups(drv, drv->bus->drv_groups);
driver_remove_file(drv, &driver_attr_uevent);
klist_remove(&drv->p->knode_bus);
pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);
driver_detach(drv);
module_remove_driver(drv);
kobject_put(&drv->p->kobj);
bus_put(drv->bus);
}

调用栈

1、driver_unregister
1、driver_remove_groups 删除对应目录下的文件
2、bus_remove_driver
1、删除文件
2、klist的维护
3、driver_detach 这个是关建,就是将之前匹配的驱动和设备解绑。
循环
1、 list_entry(drv->p->klist_devices.k_list.prev,。。。 找到驱动上的设备
2、 __device_release_driver
1、删除sysfs下的文件 因为驱动下会有文件指向设备,设备下也有文件直线驱动所以这里会删两次,
           一次是驱动目录下的第二次是设备下的。
2、设备驱动句柄置空,此时设备还是在的只是没有对应驱动。

drive_detach

void driver_detach(struct device_driver *drv)
{
struct device_private *dev_prv;
struct device *dev; for (;;) {
spin_lock(&drv->p->klist_devices.k_lock);
//判断驱动私有数据中的设备list是否为空,这个list中是与驱动匹配的全部设备
if (list_empty(&drv->p->klist_devices.k_list)) {
//没有设备和驱动绑定了则直接返回
spin_unlock(&drv->p->klist_devices.k_lock);
break;
}
//取出一个设备的私有数据,设备也是靠私有数据来管理所属子设备的
dev_prv = list_entry(drv->p->klist_devices.k_list.prev,
struct device_private,
knode_driver.n_node);
//取出设备
dev = dev_prv->device;
get_device(dev);
spin_unlock(&drv->p->klist_devices.k_lock); if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
//确定这个设备使用的是这个驱动
if (dev->driver == drv)
//驱动设备解绑
__device_release_driver(dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
put_device(dev);
}
}

处理的基本过程在代码中也有对应的注释内容,实际上就是遍历驱动的私有数据中的已匹配的设备list然后挨个执行对应的解绑操作,具体的解绑操作如下:

static void __device_release_driver(struct device *dev)
{
struct device_driver *drv; drv = dev->driver;
if (drv) {
pm_runtime_get_sync(dev);
//删除/sys目录下匹配时创建的文件等
driver_sysfs_remove(dev);
// 通知 卸载驱动,支持则通知对应的总线接口解绑事件
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBIND_DRIVER,
dev); pm_runtime_put_sync(dev);
//执行总线或驱动自己的卸载接口
if (dev->bus && dev->bus->remove)
dev->bus->remove(dev);
else if (drv->remove)
drv->remove(dev);
//设备资源的释放,然后就是句柄的清空和对应内核对象的操作
devres_release_all(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
klist_remove(&dev->p->knode_driver);
// 通知 卸载驱动完成,支持则通知对应的总线接口解绑事件
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBOUND_DRIVER,
dev); }
}

总结

驱动的注册过程整体还是比设备添加过程简单一点的主要要明白一下几点

  1. 驱动和总线同时实现probe,remove,shutdown方法内核会屏蔽驱动的方法仅使用用总线的方法。
  2. 不能有同名的驱动注册到系统中。

剩下的驱动和设备的匹配的相关的操作基本就和设备匹配驱动的过程十分相似了。这就是驱动的注册和卸载操作过程,这也是后续等platform相关的设备驱动,I2C等类似总线的设备和驱动注册和注销的相似过程,所以掌握这一部分很重要后续会反复复习下以加深印象。

参考:https://blog.csdn.net/qq_16777851/article/details/81459931

Linux 驱动框架---linux 驱动的更多相关文章

  1. Linux 驱动框架---net驱动框架

    这一篇主要是学习网络设备驱动框架性的东西具体的实例分析可以参考Linux 驱动框架---dm9000分析 .Linux 对于网络设备的驱动的定义分了四层分别是网络接口层对上是IP,ARP等网络协议,因 ...

  2. Linux 驱动框架---linux 设备

    Linux 设备 Linux驱动中的三大主要基础成员主要是设备,总线和驱动.今天先来从设备开始分析先把设备相关的数据结构放到这里方便后面看到来查,其中有些进行了简单的注释. struct device ...

  3. Linux 驱动框架---i2c驱动框架

    i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开 ...

  4. Linux 驱动框架---platform驱动框架

    Linux系统的驱动框架主要就是三个主要部分组成,驱动.总线.设备.现在常见的嵌入式SOC已经不是单纯的CPU的概念了,它们都会在片上集成很多外设电路,这些外设都挂接在SOC内部的总线上,不同与IIC ...

  5. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  6. Linux内核的LED设备驱动框架【转】

    /************************************************************************************ *本文为个人学习记录,如有错 ...

  7. Linux 驱动框架---dm9000分析

    前面学习了下Linux下的网络设备驱动程序的框架inux 驱动框架---net驱动框架,感觉知道了一个机器的大致结构还是不太清楚具体的细节处是怎么处理的,所以今天就来以dm9000这个网上教程最多的驱 ...

  8. spi驱动框架全面分析,从master驱动到设备驱动

    内核版本:linux2.6.32.2  硬件资源:s3c2440 参考:  韦东山SPI视频教程 内容概括:     1.I2C 驱动框架回顾     2.SPI 框架简单介绍     3.maste ...

  9. 【DSP开发】【Linux开发】Linux下PCI设备驱动程序开发

    PCI是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为计算机系统中应用最为广泛,并且最为通用的总线标准.Linux的内核能较好地支持PCI总线,本文以Inte ...

随机推荐

  1. JVM有哪些垃圾回收器

    JVM 的垃圾回收器 目录 JVM 的垃圾回收器 经典垃圾收集器 Serial 收集器 ParNew 收集器 Parallel Scavenge 收集器 Serial Old 收集器 Parallel ...

  2. Android 开发学习进程0.27 kotlin使用 和viewbinding的使用

    kotlin-android-extensions 插件被废弃 笔者曾经尝试写过一部分的kotlin代码 主要是项目中一些代码是kotlin完成的,其中我认为 kotlin的kotlin-androi ...

  3. Spring基于注解开发的注解使用之AOP(部分源代码分析)

    AOP底层实现动态代理 1.导入spring-aop包依赖 <!--aopV1--> <dependency> <groupId>org.springframewo ...

  4. Connection Manager简称connman

    ConnMan    Connection Manager简称connman,connman是使用d-bus做为进程间通信机制来管理Linux网络链接的一种软件.在connman的d-bus接口中,有 ...

  5. win10自带输入法突然变成了繁体

    有可能是在使用Eclipse/MyEclipse的Ctrl + Shift + F进行代码格式化的时候与输入法的热键冲突了

  6. Language Guide (proto3) | proto3 语言指南(十五)生成类

    Generating Your Classes - 生成类 要生成Java.Python.C++.Go.Ruby.ObjuleC或C代码,需要使用.proto文件中定义的消息类型,还需要在.proto ...

  7. Spring框架——JDBC与事务管理

    JDBC JDBCTemplate简介 XML配置JDBCTemplate 简化JDBC模板查询 事务管理 事务简介 Spring中的事务管理器 Spring中的事务管理器的不同实现 用事务通知声明式 ...

  8. sourcetree注册

    http://www.cnblogs.com/xiofee/p/sourcetree_pass_initialization_setup.html

  9. 虚拟局域网(VLAN)__语音VLAN

    1.语音VLAN特性使得访问端口能够携带来自IP电话的IP语音流量.当交换机连接到Cisco IP电话时,IP电话就用第3层IP优先级(precedence)和第2层服务级别(class of ser ...

  10. CentOS 7 部署redis

    1.下载redis: 地址:http://download.redis.io/releases/: 选择需要下载的版本,然后通过ssh工具导入到centos中,这里放到了/usr/local; 解压文 ...