一. 简介
我们在前面几章编写的设备驱动都非常的简单,都是对
IO
进行最简单的读写操作。
像
I2C
、SPI
、
LCD
等这些复杂外设的驱动就不能这么去写了,
Linux
系统要考虑到驱动的可重用性,因
此,提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的
platform
设备驱动,也叫做平台设备驱动。
接下来我们就来学习一下
Linux
下的驱动分离与分层,
以及
platform
框架下的设备驱动该如何编写。
二. Linux驱动分离与分层的简介
对于
Linux
这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话,就会在
Linux 内核中存在大量无意义的重复代码。
尤其是驱动程序,因为驱动程序占用了
Linux
内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久
Linux
内核的文件数量就庞大到无法接受的地步。
假如,现在有三个平台
A
、
B
和
C
,这三个平台
(
这里的平台说的是
SOC,即芯片)
上都有
MPU6050
这
个
I2C
接口的六轴传感器,按照我们写裸机
I2C
驱动的时候的思路,每个平台都有一个
MPU6050
的驱动,因此,编写出来的最简单的驱动框架如下图
所示:
从上图
可以看出,每种平台下都有一个主机驱动和设备驱动,主机驱动肯定是必须
要的,毕竟不同的平台其
I2C
控制器不同。但是右侧的设备驱动就没必要每个平台都写一个,
因为不管对于那个
SOC
来说,
MPU6050
都是一样,通过
I2C
接口读写数据就行了,只需要一
个
MPU6050
的驱动程序即可。
显然在
Linux
驱
动程序中这种写法是不推荐的。
最好的做法就是每个平台的
I2C
控制器都提供一个统一的接口
(
也叫做主机驱动
)
,每个设备也只提供一个驱动程序
(
设备驱动
)
,每个设备通过统一的
I2C
接口驱动来访问,这样就可以大大简化驱动文件,例如,上图
中三种平台下的
MPU6050
驱动
框架就可以简化下图
所示:
1. 驱动的分隔
实际的
I2C
驱动设备肯定有很多种,不止
MPU6050
这一个,那么实际的驱动架构如下图
所示:
这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来,比如
I2C
、
SPI
等等都会采用驱动分隔的方式来简化驱动的开发。
在实际的驱动开发中,一般
I2C
主机控制器驱动已经由半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,我们只需要提供设备信
息即可,例如,
I2C
设备的话,提供设备连接到了哪个
I2C
接口上,
I2C
的速度是多少等等。
相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息
(
比如,从设备树中获
取到设备信息
)
,然后根据获取到的设备信息来初始化设备。
这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。
这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)
模型,也就是常说的驱动分离。
总线就是驱动和设备信息的月老,负责给两者牵线
搭桥,如下图
所示:
当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。
Linux 内核中大量的驱动程序都采用总线、驱动和设备模式。
2. 驱动的分层
大家应该听说过网络的
7
层模型,不同的层负责不同的内容。
同样的,
Linux
下的驱动往往也是分层的,分层的目
的也是为了在不同的层处理不同的内容。
以其他书籍或者资料常常使用到的
input(
输入子系统,
后面会有专门的章节详细的讲解
)
为例,简单介绍一下驱动的分层。
input
子系统负责管理所有
跟输入有关的驱动,包括键盘、鼠标、触摸等。
最底层的就是设备原始驱动,负责获取输入设
备的原始值,获取到的输入事件上报给
input
核心层。
input
核心层会处理各种
IO
模型,并且提
供
file_operations
操作集合。
我们在编写输入设备驱动的时候只需要处理好输入事件的上报即
可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。
可以看出借助分层模
型可以极大的简化我们的驱动编写,对于驱动编写来说非常的友好。