Linux Platform驱动模型(一)-设备信息
我在Linux字符设备驱动框架一文中简单介绍了Linux字符设备编程模型,在那个模型中,只要应用程序open()了相应的设备文件,就可以使用ioctl通过驱动程序来控制我们的硬件,这种模型直观,但是从软件设计的角度看,却是一种十分糟糕的方式,它有一个致命的问题,就是设备信息和驱动代码冗余在一起,一旦硬件信息发生改变甚至设备已经不在了,就必须要修改驱动源码,非常的麻烦,为了解决这种驱动代码和设备信息耦合的问题,Linux提出了platform bus(平台总线)的概念,即使用虚拟总线将设备信息和驱动程序进行分离,设备树的提出就是进一步深化这种思想,将设备信息进行更好的整理。平台总线会维护两条链表,分别管理设备和驱动,当一个设备被注册到总线上的时候,总线会根据其名字搜索对应的驱动,如果找到就将设备信息导入驱动程序并执行驱动;当一个驱动被注册到平台总线的时候,总线也会搜索设备。总之,平台总线负责将设备信息和驱动代码匹配,这样就可以做到驱动和设备信息的分离。
在设备树出现之前,设备信息只能使用C语言的方式进行编写,在3.0之后,设备信息就开始同时支持两种编写方式——设备树、C语言,如果用设备树,手动将设备信息写到设备树中之后,内核就可以自动从设备树中提取相应的设备信息并将其封装成相应的platform_device对象,i2c_device对象并注册到相应的总线中,如果使用设备树,我们就不需要对设备信息再进行编码。如果使用C语言,显然,我们需要将使用内核提供的结构将设备信息进行手动封装,这种封装又分为两种形式,一种是使用平台文件(静态),将整个板子的所有设备都写在一个文件中并编译进内核。另一种是使用模块(动态),将我们需要的设备信息编译成模块在insmod进内核。对于ARM平台,使用设备树封装设备信息是将来的趋势,但是由于历史原因,当下的内核中这三种方式并存。封装好后再创建相应的xxx_device实例最后注册到总线中。针对平台总线的设备信息,我在Linux设备树语法详解一文中已经讨论了设备树的写法,所以,本文主要讨论4个问题:
- 如何使用C语言封装设备信息?
- 设备树的设备信息和C语言的设备信息如何转换?
- 如何将C语言设备信息封装到platform_device结构中?
- 如何将封装好的platform_device结构注册到平台总线中?
何为资源?
所谓的设备信息,主要分为两种:硬件信息、软件信息,硬件信息主要包括xxx控制器在xxx地址上,xxx设备占用了xxx中断号,即地址资源,中断资源等。内核提供了struct resource来对这些资源进行封装。软件信息的种类就比较多样,比如网卡设备中的MAC地址等等,这些信息需要我们以私有数据的形式封装的设备对象(内核使用面向对象的思想编写,一个设备的设备信息是一个对象,即一个结构体实例,一个设备的驱动方法也是一个对象)中,这部分信息就需要我们自定义结构进行封装。
struct resource那点事
这个结构用来描述一个地址资源或中断资源,除了这个结构,内核还提供了一些宏来帮助我们快速的创建一个resource对象。
//include/linux/ioport.h
18 struct resource {
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 unsigned long desc;
24 struct resource *parent, *sibling, *child;
25 };
struct resource
--19--> start表示资源开始的位置,如果是IO地址资源,就是起始物理地址,如果是中断资源,就是中断号;
--20--> end表示资源结束的位置,如果是IO地址地址,就是映射的最后一个物理地址,如果是中断资源,就不用填;
--21--> name就是这个资源的名字。
--22--> flags表示资源类型,提取函数在寻找资源的时候会对比自己传入的参数和这个成员,理论上只要和可以随便写,但是合格的工程师应该使用内核提供的宏,这些宏也在"ioport.h"中进行了定义,比如IORESOURCE_MEM表示这个资源是地址资源,IORESOURCE_IRQ表示这个资源是中断资源...。
//include/linux/ioport.h
33 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
35 #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
36 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
37 #define IORESOURCE_MEM 0x00000200
38 #define IORESOURCE_REG 0x00000300 /* Register offsets */
39 #define IORESOURCE_IRQ 0x00000400
40 #define IORESOURCE_DMA 0x00000800
41 #define IORESOURCE_BUS 0x00001000
...
147 #define DEFINE_RES_IO(_start, _size)
152 #define DEFINE_RES_MEM(_start, _size)
157 #define DEFINE_RES_IRQ(_irq)
162 #define DEFINE_RES_DMA(_dma)
有了这几个属性,就可以完整的描述一个资源,但如果每个资源都需要单独管理而不是组成某种数据结构,显然是一种非常愚蠢的做法,所以内核的resource结构还提供了三个指针:parent,sibling,child(24),分别用来表示资源的父资源,兄弟资源,子资源,这样内核就可以使用树结构来高效的管理大量的系统资源,linux内核有两种树结构:iomem_resource,ioport_resource,进行板级开发的时候,通常将主板上的ROM资源放入iomem_resource树的一个节点,而将系统固有的I/O资源挂到ioport_resource树上。
下面是一个小例子,分别用两种写法表示了地址资源和中断资源,强烈推荐使用DEFINE_RES_XXX的版本。
//IO地址资源,自己填充resource结构体+flags宏
struct resource res= {
.start = 0x10000000,
.end = 0x20000000-1,
.flags = IORESOURCE_MEM
};
//IO地址资源,使用内核提供的定义宏
struct resource res = DEFINE_RES_MEM(0x20000000, 1024);
//中断资源,自己填充resource结构体+flags宏
struct resource res = {
.start = 10,
.flags = IORESOURCE_IRQ,
};
//中断资源,使用内核提供的定义宏
struct resource res = DEFINE_RES_IRQ(11);
下面是一个资源数组的实例,多个资源的时候就写成数组,这里我同时使用了上面两种写法。
struct resource res[] = {
[0] = {
.start = 0x10000000,
.end = 0x20000000-1,
.flags = IORESOURCE_MEM
},
[1] = DEFINE_RES_MEM(0x20000000, 1024),
[2] = {
.start = 10, //中断号
.flags = IORESOURCE_IRQ|IRQF_TRIGGER_RISING //include/linux/interrupt.h
},
[3] = DEFINE_RES_IRQ(11),
};
resource VS dts
至此,我们已经讨论了使用设备树和resource结构两种方式写设备信息,显然,这两种方式最终是殊途同归的,这里我们简单的讨论一个二者之间的转换问题。下图是我在Linux设备树语法详解一文中用到的dm9000网卡的节点
将它的地址资源写成resource就是这个样子,清晰起见,这里也是两种写法:
struct resource res[] = {
[0] = {
.start = 0x05000000,
.end = 0x05000000+0x2-1,
.flags = IORESOURCE_MEM,
},
[1] = DEFINE_RES_MEM(0x05000004,2),
};
platform_device对象
这个对象就是我们最终要注册到平台总线上的设备信息对象,对设备信息进行编码,其实就是创建一个platform_device对象,可以看出,platform_device和其他设备一样,都是device的子类
//include/linux/platform_device.h
22 struct platform_device {
23 const char *name;
24 int id;
25 bool id_auto;
26 struct device dev;
27 u32 num_resources;
28 struct resource *resource;
29
30 const struct platform_device_id *id_entry;
31
32 /* MFD cell pointer */
33 struct mfd_cell *mfd_cell;
34
35 /* arch specific additions */
36 struct pdev_archdata archdata;
37 };
在这个对象中,我们主要关心以下几个成员
struct platform_device
--23-->name就是设备的名字,注意, 模块名(lsmod)!=设备名(/proc/devices)!=设备文件名(/dev),这个名字就是驱动方法和设备信息匹配的桥梁
--24-->表示这个platform_device对象表征了几个设备,当多个设备有共用资源的时候(MFD),里面填充相应的设备数量,如果只是一个,填-1
--26-->父类对象(include/linux/device.h +722),我们通常关心里面的platform_data和release,前者是用来存储私有设备信息的,后者是供当这个设备的最后引用被删除时被内核回调,注意和rmmod没关系。
--27-->资源的数量,即resource数组中元素的个数,我们用ARRAY_SIZE()宏来确定数组的大小(include/linux/kernel.h +54 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) )
--28-->资源指针,如果是多个资源就是struct resource[]数组名,
下面是一个实例。
static struct platform_device demoobj = {
//2. init obj
.name = "demo0",
.id = -1,
.dev = {
.platform_data = &priv,
.release = dev_release,
},
.num_resources = ARRAY_SIZE(res),
.resource = res,
};
设备对象的注册与注销
准备好了platform_device对象,接下来就可以将其注册进内核,显然内核已经为我们准备好了相关的函数
/**
*注册:把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
*/
int platform_device_register(struct platform_device *);
/**
*注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;
*/
void platform_device_unregister(struct platform_device *);
通常,我们会将platform_device_register写在模块加载的函数中,将platform_device_unregister写在模块卸载函数中。我们可以模仿内核的宏写一个注册注销的快捷方式
#define module_platform_device(xxx) \
static int __init xxx##_init(void) \
{ \
return platform_device_register(&xxx); \
} \
static void __exit xxx##_exit(void) \
{ \
platform_device_unregister(&xxx); \
} \
module_init(xxx##_init); \
module_exit(xxx##_exit);
彩蛋
Linux中几乎所有的"设备"都是"device"的子类,无论是平台设备还是i2c设备还是网络设备,但唯独字符设备不是,从"Linux字符设备驱动框架"一文中我们可以看出cdev并不是继承自device,从"Linux设备管理(二)_从cdev_add说起"一文中我们可以看出注册一个cdev对象到内核其实只是将它放到cdev_map中,直到"Linux设备管理(四)_从sysfs回到ktype"一文中对device_create的分析才知道此时才创建device结构并将kobj挂接到相应的链表,,所以,基于历史原因,当下cdev更合适的一种理解是一种接口(使用mknod时可以当作设备),而不是而一个具体的设备,和platform_device,i2c_device有着本质的区别
Linux Platform驱动模型(一)-设备信息的更多相关文章
- Linux Platform驱动模型(二) _驱动方法
在Linux设备树语法详解和Linux Platform驱动模型(一) _设备信息中我们讨论了设备信息的写法,本文主要讨论平台总线中另外一部分-驱动方法,将试图回答下面几个问题: 如何填充platfo ...
- Linux Platform驱动模型(二) _驱动方法【转】
转自:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/06/6367910.html 在Linux设备树语法详解和Linux Platform ...
- Linux Platform驱动模型(一) _设备信息
我在Linux字符设备驱动框架一文中简单介绍了Linux字符设备编程模型,在那个模型中,只要应用程序open()了相应的设备文件,就可以使用ioctl通过驱动程序来控制我们的硬件,这种模型直观,但是从 ...
- Linux Platform驱动模型(三) _platform+cdev
平台总线是一种实现设备信息与驱动方法相分离的方法,利用这种方法,我们可以写出一个更像样一点的字符设备驱动,即使用cdev作为接口,平台总线作为分离方式: xjkeydrv_init():模块加载函数 ...
- 【linux】驱动-7-平台设备驱动
目录 前言 7. 平台设备驱动 7.1 平台总线 7.1.1 平台总线注册和匹配方式 7.1.2 源码分析 7.2 平台设备 7.2.1 platform_device 7.2.2 设备信息 7.2. ...
- linux内核驱动模型
linux内核驱动模型,以2.6.32内核为例.(一边写一边看的,有点乱.) 1.以内核对象为基础.用kobject表示,相当于其它对象的基类,是构建linux驱动模型的关键.具有相同类型的内核对象构 ...
- Linux 总线、设备、驱动模型 与 设备树
1.总线.设备.驱动模型 本着高内聚.低耦合的原则,Linux 把设备驱动模型分为了总线.设备和驱动三个实体,这三个实体在内核里的职责分别如下: 设备和驱动向总线进行注册,总线负责把设备和对应的驱动绑 ...
- linux RTC 驱动模型分析【转】
转自:http://blog.csdn.net/yaozhenguo2006/article/details/6824970 RTC(real time clock)实时时钟,主要作用是给Linux系 ...
- 【linux】驱动-3-字符设备驱动
目录 前言 3. 字符设备驱动 3.1 Linux设备分类 3.2 设备相关概念 3.2.1 设备号 3.2.2 设备节点 3.2.3 APP open 文件理解 ** 3.3 数据结构 3.3.1 ...
随机推荐
- php解决微信开发中用户昵称中的特殊字符与emoji表情写入mysql错误的问题
解决办法:将3个字节的特殊字符与emoji表情替换掉即可. $nickname = preg_replace('/xE0[x80-x9F][x80-xBF]'.'|xED[xA0-xBF][x80-x ...
- javascript技巧大全套
事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture(); event.srcE ...
- UIButton 关灯小实验
// 写在继承于UIViewController的子类中:创建单视图默认有ViewController类 // 实现:点击任何一颗UIButton,它四周的以及它自身都被变成红色,再点击就会变成原来的 ...
- express4.x中路由中间件和挂载路径的关系
express4.x 中一个路由中间件可以挂载到多个路由上,一个路由也可以绑定多个路由中间件,如: //多个路由匹配一个路由中间件 app.use(['/gre+t', '/hel{2}o'], gr ...
- 前端之Sass/Scss实战笔记
简介 Sass 有两种语法规则(syntaxes),目前新的语法规则(从 Sass 3开始)被称为 “SCSS”( 时髦的css(Sassy CSS)),它是css3语法的的拓展级,就是说每一个语法正 ...
- codis 新版本 CodisLabs 编译安装
codis 3.0 版本编译安装 # 首先安装 go 语言 wget https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz ...
- Mysql的MySqlDataReader对于MysqlConnection是独占式
写Secondlife智能体的知识系统小插件的时候遇到的这个问题. 在把某个NPC的全部知识复制给另一个NPC的时候,对数据库操作,为了提升一点效率希望与数据库建立一次连接,全部添加进表以后再断开连接 ...
- 控制流之continue
continue语句continue语句被用来告诉Python跳过当前循环块中的剩余语句,然后 继续 进行下一轮循环.使用continue语句~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...
- [iOS] 响应式编程开发-ReactiveCocoa(一)
什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...
- CodeForces 629C Famil Door and Brackets
DP. 具体做法:dp[i][j]表示长度为 i 的括号串,前缀和(左括号表示1,右括号表示-1)为 j 的有几种. 状态转移很容易得到:dp[i][j]=dp[i - 1][j + 1]+dp[i ...