Linux驱动编程

[TOC]

一、Linux驱动模型

​ Linux中的驱动模型遵循一个基本的逻辑:设备和驱动分离。其中设备是一个结构体,用于描述设备的硬件信息,例如LED的设备应当描述LED的引脚。驱动是另一个结构体,用于描述操作,例如LED的驱动应该包含LED的开或关的控制。只有当两个结构体绑定在一起时。驱动就会根据设备描述的信息操作设备。例如有两个LED灯需要控制,那么就应该有两个LED设备和一个LED驱动。当LED1绑定驱动时就会控制LED1的引脚,当LED2绑定驱动时就会控制LED2的引脚。

​ 硬件设备和驱动的描述方式可能不同,但是在Linux的驱动模型中最终都会转化为设备结构体和驱动结构体。并通过匹配机制将设备和驱动绑定在一起。

​ 在Linux设备模型出现后,Linux中只有一种驱动模型(设备-总线-驱动模型)。初学者可能以为设备树等和基本的总线式驱动模型不同,是不同的模型。其实不然,后面进行详细介绍。

1. 设备-总线-驱动模型

​ 该模型是在Linux系统中有一个结构体,结构体两端分别挂设备和驱动。当有新挂载的驱动时将新的驱动与已经挂载的设备相比较,如果合适则将驱动和设备绑定。这个在设备和驱动之间的结构体就是总线。

​ 这个总线和实际硬件中总线没有什么关系。只是将有公共操作的驱动和设备放在同一个总线上,由总线实现这一部分公共操作,就不用在每个驱动中都实现这一部分公共操作。因此在Linux系统中的i2c总线只是总线模型在i2c接口上的实现。在i2c总线中会实现i2c的基本操作,例如i2c读写。挂载在i2c总线上的驱动就不需要再实现i2c的操作。与实际硬件无关的总线:platform总线,该总线与任何硬件设备无关,完全是虚拟的。

​ 在这个模型中设备和驱动在注册到系统之前都需要描述对应的总线。设备和驱动都是结构体,填充数据后通过注册方法挂载到总线上。

​ 设备和驱动是否合适需要通过总线驱动提供的match函数进行比对。因此总线可以决定总线上的设备和驱动的匹配方式。

2. 设备树

​ 设备树是一个文件,文件中描述了所有的硬件信息,包括处理器本身的信息和开发板的硬件信息。因此设备树在不同的开发板、不同的芯片上都是不同的。下面是uart的描述,关于设备树的细节参考设备树语法。设备树中包括了硬件的基本信息,包括设备寄存器的起始地址,使用的中断等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uart0: uart@11040000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x11040000 0x1000>;
interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clock SS928V100_UART0_CLK>;
clock-names = "apb_pclk";
/* dmas = <&edmacv310_0 20 20>, <&edmacv310_0 21 21>; */
/* dma-names = "rx","tx"; */
status = "disabled";
};

uart1: uart@11041000 {
compatible = "arm,pl011", "arm,primecell";
reg = <0x11041000 0x1000>;
interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clock SS928V100_UART1_CLK>;
clock-names = "apb_pclk";
/* dmas = <&edmacv310_0 22 22>, <&edmacv310_0 23 23>; */
/* dma-names = "rx","tx"; */
status = "disabled";
};

​ 设备树并不是区别与总线模型的另一个模型,而是总线模型的补充。在原来的总线模型中只能通过C源文件中的设备结构体描述设备。这种方式导致每次修改设备信息都需要重新编译。设备树出现后通过设备树描述硬件设备,在系统启动时分析设备树并创建设备结构体。此时结构体中并没有设备的全部信息,但是of_node属性已经被填充,of_node代表当前设备对应的设备树节点。随后系统根据设备树节点的compatible属性与驱动的of_match_table中的compatible属性相匹配,匹配成功则将两者绑定并调用驱动的probe函数,将匹配到的设备传递给probe函数。probe函数通过of_node节点分析当前设备的其他属性并填充设备结构体,以保证后续的操作。

3. id_table

​ id_table模型通过id_table表进行驱动和设备的匹配,一般通过匹配id_table中的name属性确定是否匹配,id_table允许一个data属性,当前驱动可以同时驱动不同类型的设备时data属性可以用来描述不同设备间的不同。

​ 任何设备和驱动都有其所在的总线,当设备或驱动挂载成功后调用对应总线的match函数进行匹配,而设备树匹配和id_table匹配则是在match函数中实现的。因此具体的匹配顺序需要根据总线的match函数进行分析。

二、具体驱动结构简介

​ Linux驱动模型的核心结构是device结构体和device_driver结构体。device代表着系统中的设备,device_driver代表着系统中的驱动。其它设备或驱动都是由这两个结构体继承(C语言的方式实现面向对象)而来。

​ 以I2C驱动为例:

1
2
3
4
5
struct i2c_driver {
/* ... */
struct device_driver driver;//i2c驱动的结构体中包含driver结构体
/* ... */
};

​ I2C设备的匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;


/* 通过设备树的compatible 属性匹配*/
if (i2c_of_match_device(drv->of_match_table, client))
return 1;

/* ACPI 方式匹配 */
if (acpi_driver_match_device(dev, drv))
return 1;

driver = to_i2c_driver(drv);

/* id_table匹配 */
if (i2c_match_id(driver->id_table, client))
return 1;

return 0;
}

​ 如果以上三种方式都不能匹配到,则匹配失败。首先使用的设备树进行匹配,因此在i2c的设备驱动中,设备树中出现节点的compatible与驱动的compatible属性对应即可匹配成功。也可以使用id_table进行匹配。

附录一:Linux驱动的发展历程

(来自网络查询,未经证实)

在2.5版本内引入include/linux/device.h,其中包括device、device_driver结构体。

在2.6.0版本引入bus_type结构体,同时引入cdev

在2.6.14版本引入设备树

字符设备文件、块设备文件是Linux中描述设备的文件

以下文字是对ChatGPT问答的总结

Linux中最早期使用/dev文件夹管理设备,对应的文件系统是devfs。devfs对每一个设备提供一个设备文件,应用层可以通过读写等方式控制外部设备。后续发展的过程中,Linux开始构建驱动模型,并通过/sys(sysfs文件系统)下的文件描述设备。/sys和/dev的区别是,一个设备在/dev下表现为一个设备文件,在/sys表现为一个目录,目录中包括设备的属性状态等被列为单独的文件。

下面是块设备文件sda在/dev中对应的设备文件

1
brw-rw---- 1 root disk 8, 0 5月   5 08:59 sda

下面是块设备文件sda在/sys中对应的文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-r--r--r-- 1 root root 4096 5月   5 13:41 alignment_offset
lrwxrwxrwx 1 root root 0 5月 5 13:41 bdi -> ../../../../../../../../../virtual/bdi/8:0
-r--r--r-- 1 root root 4096 5月 5 13:41 capability
-r--r--r-- 1 root root 4096 5月 5 08:59 dev
lrwxrwxrwx 1 root root 0 5月 5 09:01 device -> ../../../2:0:0:0
-r--r--r-- 1 root root 4096 5月 5 13:41 discard_alignment
-r--r--r-- 1 root root 4096 5月 5 13:41 events
-r--r--r-- 1 root root 4096 5月 5 13:41 events_async
-rw-r--r-- 1 root root 4096 5月 5 13:41 events_poll_msecs
-r--r--r-- 1 root root 4096 5月 5 13:41 ext_range
-r--r--r-- 1 root root 4096 5月 5 13:41 hidden
drwxr-xr-x 2 root root 0 5月 5 13:41 holders
-r--r--r-- 1 root root 4096 5月 5 13:41 inflight
drwxr-xr-x 2 root root 0 5月 5 13:41 integrity
drwxr-xr-x 3 root root 0 5月 5 13:41 mq
drwxr-xr-x 2 root root 0 5月 5 13:41 power
drwxr-xr-x 3 root root 0 5月 5 08:59 queue
-r--r--r-- 1 root root 4096 5月 5 13:41 range
-r--r--r-- 1 root root 4096 5月 5 08:59 removable
-r--r--r-- 1 root root 4096 5月 5 08:59 ro
-r--r--r-- 1 root root 4096 5月 5 08:59 size
drwxr-xr-x 2 root root 0 5月 5 13:41 slaves
-r--r--r-- 1 root root 4096 5月 5 13:41 stat
lrwxrwxrwx 1 root root 0 5月 5 08:59 subsystem -> ../../../../../../../../../../class/block
drwxr-xr-x 2 root root 0 5月 5 13:41 trace
-rw-r--r-- 1 root root 4096 5月 5 08:59 uevent

参考文献

  1. 【linux】驱动-6-总线-设备-驱动
  2. 关于platform中的id_table
  3. Linux内核驱动:cdev、misc以及device三者之间的联系和区别
  4. Linux设备树解析
0%