总述

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. js模仿京东首页的倒计时功能

    模仿京东首页的倒计时 我们学习了定时器,可以用定时器做一个倒计时,于是我模仿了京东首页倒计时. 先看看京东首页的倒计时. 思路: 如何倒计时? 给一个未来的时间.然后计算未来时间到现在的时间戳. 用定 ...

  2. 二本学生拿到腾讯大厂offer的成长记录

    本人迈莫,是在20年以春招实习生的身份进入鹅厂,经过重重波折,最终成为鹅仔一份子.接下来我会以我亲生经历为例,分享一下普通大学的学生也是可以进去大厂,拭目以待!!! 初入大学 惨遭毒打 时间倒回到17 ...

  3. python的零碎知识

    1.Python代码操作git 安装 pip3 install gitpython 操作git import os from git.repo import Repo # gitpython def ...

  4. day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...

  5. Graceful restart of a server with active WebSocket

    Graceful restart of a server with active WebSocket Simonwep/graceful-ws: ⛓ Graceful WebSocket wrappe ...

  6. The OAuth 2.0 Authorization Framework OAuth2.0的核心角色code 扫码登录

    RFC 6749 - The OAuth 2.0 Authorization Framework https://tools.ietf.org/html/rfc6749 The OAuth 2.0 a ...

  7. .Net 5 C# 反射(Reflection)

    这里有个目录 什么是反射 有什么用?怎么用? 获取类型的类型信息. 获取泛型信息 获取程序集的信息 从已加载的程序集获取 Type 对象 查看类的信息 首尾呼应.重复强调.重要事情说三遍 后记 什么是 ...

  8. 武装你的WEBAPI-OData资源更新Delta

    本文属于OData系列 目录 武装你的WEBAPI-OData入门 武装你的WEBAPI-OData便捷查询 武装你的WEBAPI-OData分页查询 武装你的WEBAPI-OData资源更新Delt ...

  9. 天天写同步,5种SpringMvc异步请求了解下!

    引言 说到异步大家肯定首先会先想到同步.我们先来看看什么是同步? 所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作. 简单来说,同步就是必须一件一件事做,等前一件 ...

  10. 怎样将Sublime Text 设置成中文版(完整教程)

    1.打开Sublime Text,使用快捷键Shift+Ctrl+P,弹出查找栏,如图: 2.在搜索框中输入关键字 install ,出现下拉选项,点击选择其中的:Package Control: I ...