LV10-10-platform-01-platform驱动基础

本文主要是platform——平台总线驱动基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
Linux开发板 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2
u-boot 2013.01
点击查看本文参考资料
点击查看相关文件下载
文件下载链接
------

一、platform简介

在我们之前学习的字符设备驱动模型中,只要应用程序调用open()打开了相应的设备文件,就可以使用ioctl通过驱动程序来控制我们的硬件,这种模型很直观,但是从软件设计的角度看,却是一种十分糟糕的方式,它有一个致命的问题,就是设备信息和驱动代码冗余在一起,一旦硬件信息发生改变甚至设备已经不在了,就必须要修改驱动源码,非常的麻烦。

为了解决这种驱动代码和设备信息耦合的问题,Linux提出了platform bus(平台总线)的概念,即使用虚拟总线将设备信息和驱动程序进行分离,设备树的提出就是进一步深化这种思想,将设备信息进行更好的整理。

Linux2.6以后的设备驱动模型,Linux内核中的总线主要负责管理挂接在该总线下的设备与驱动,平台总线会维护两条链表,分别管理设备和驱动,将设备信息与驱动程序分类管理,可以提高驱动程序的可移植性。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相同地,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。 相对于USBPCII2CSPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。 这种虚拟的总线仅用来管理设备和驱动(最核心的作用之一就是完成设备和驱动的匹配)

设备主要提供硬件资源,驱动主要气功驱动代码,而总线则是完成设备和驱动匹配的媒介。

二、platform总线理解

上边仅仅是做了一个简介,我感觉并不能很好的帮助我理解platform平台总线模型。这一部分笔记是来自于正点原子的驱动开发手册,详细的可以查看手册原文,这里做一个笔记,加深自己的理解。

1. 驱动的分隔与分离

对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了 Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。

假如现在有三个平台 ABC,这三个平台(这里的平台说的是 SOC)上都有MPU6050 这个I2C接口的六轴传感器,按照我们写裸机I2C驱动的时候的思路,每个平台都有一个MPU6050的驱动,因此编写出来的最简单的驱动框架如下图:

image-20220902122116722

可以看出,每种平台下都有一个主机驱动和设备驱动,主机驱动肯定是必须要的,毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个,因为不管对于那个 SOC 来说, MPU6050 都是一样,通过 I2C 接口读写数据就行了,只需要一个MPU6050 的驱动程序即可。

如果再来几个 I2C 设备,比如 AT24C02FT5206(电容触摸屏)等,若都按照上图的写法,那么设备端的驱动将会重复的编写好几次。显然在 Linux驱动程序中这种写法是不推荐的,最好的做法就是每个平台的 I2C 控制器都提供一个统一的接口(也叫做主机驱动),每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统一的 I2C接口驱动来访问,这样就可以大大简化驱动文件,比如ABC三种平台下的 MPU6050 驱动框架就可以简化为下图的形式:

image-20220902122621641

实际的 I2C 驱动设备肯定有很多种,不止 MPU6050 这一个,那么实际的驱动架构就如下图所示:

image-20220902123155397

这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来,比如I2CSPI 等等都会采用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中,一般I2C主机控制器驱动已经由半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,我们只需要提供设备信息即可,比如I2C设备的话提供设备连接到了哪个 I2C接口上, I2C 的速度是多少等等。相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。

2. 设备、驱动和总线

驱动进行分隔和分离后,在linux中就形成了总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线搭桥。所以linux中的设备、驱动和总线的模式如下图:

image-20220902123555631

当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。 Linux 内核中大量的驱动程序都采用总线、驱动和设备模式。

3. 驱动的分层

Linux 下的驱动往往也是分层的,分层的目的也是为了在不同的层处理不同的内容。以常常使用到的input(输入子系统,后边会学习到)为例,简单介绍一下驱动的分层。

input 子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给 input 核心层。 input 核心层会处理各种 IO 模型,并且提供 file_operations 操作集合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用关心。

可以看出借助分层模型可以极大的简化我们的驱动编写,对于驱动编写来说非常的友好。

4. platform平台驱动模型

我们上边学习了设备驱动的分离,并且引出了总线(bus)、驱动(driver)和设备(device)模型,比如 I2CSPIUSB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总线、驱动和设备模型该怎么办呢?

为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driverplatform_device

三、platform总线机制

1. 在系统中的体现

platform总线在根文件系统下的目录为/sys/bus/platform,该目录下有两个子目录和相关的platform属性文件:

  • devices目录下存放的是platform总线下的所有设备。

  • drivers目录下存放的是platform总线下的所有驱动程序。

1
2
3
4
5
6
7
[root@farsight]#ls /sys/bus/platform/ -lh
total 0
drwxr-xr-x 2 0 0 0 Jan 1 04:06 devices
drwxr-xr-x 37 0 0 0 Jan 1 04:06 drivers
-rw-r--r-- 1 0 0 4.0K Jan 1 04:06 drivers_autoprobe
--w------- 1 0 0 4.0K Jan 1 04:06 drivers_probe
--w------- 1 0 0 4.0K Jan 1 04:06 ueven

2. 管理与匹配机制

platform总线的驱动与设备的管理与匹配机制如下图所示:

image-20220902135400211

四、基本数据类型

上边描述platform总线的管理与匹配机制的时候出现了几种数据结构,这一部分主要是介绍一下这些重要的数据结构。

1. struct bus_type

Linux系统内核使用bus_type结构体表示总线,这个结构体定义在linux内核源码的下边这个文件中:

1
include/linux/device.h

我们打开这个文件,找到struct bus_type结构体如下:

点击查看源码中对该结构体的注释说明
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* struct bus_type - The bus type of the device
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like ("foo%u", dev->id).
* @dev_root: Default device to use as the parent.
* @dev_attrs: Default attributes of the devices on the bus.
* @bus_groups: Default attributes of the bus.
* @dev_groups: Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a nonzero value if the
* given device can be handled by the given driver.
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
*
* @online: Called to put the device back online (after offlining it).
* @offline: Called to put the device offline for hot-removal. May fail.
*
* @suspend: Called when a device on this bus wants to go to sleep mode.
* @resume: Called to bring a device on this bus out of sleep mode.
* @pm: Power management operations of this bus, callback the specific
* device driver's pm-ops.
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
* @p: The private data of the driver core, only the driver core can
* touch this.
* @lock_key: Lock class key for use by the lock validator
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, "platform" bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus' methods, PM operations, and the driver core's
* private data.
*/
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
27
28
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);
int (*offline)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

struct iommu_ops *iommu_ops;

struct subsys_private *p;
struct lock_class_key lock_key;
};

成员中match指向的函数很重要,此函数指针指向的函数就是完成设备和驱动之间匹配的,总线就是使用match函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数match 函数有两个参数: devdrv,这两个参数分别为 devicedevice_driver类型,也就是设备和驱动。

2. platform总线相关结构体

2.1 总线定义

platform 总线是 bus_type 的一个具体实例,定义在linux内核源码中的这个文件中:

1
drivers/base/platform.c

我们打开这个文件,可以看到 platform 总线定义如下:

1
2
3
4
5
6
7
8
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

其中,platform_match就是匹配函数。

2.2 匹配函数

platform匹配函数定义在linux内核源码的下边这个文件中:

1
drivers/base/platform.c

我们打开这文件可以看到这个函数定义如下:

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
27
28
29
30
31
32
33
34
/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}

从函数视实现中,我们可以看到,驱动和设备的匹配有四种方法 :

  • OF 类型的匹配,也就是设备树采用的匹配方式

of_driver_match_device函数定义在linux内核源码的下边这个文件中:

1
include/linux/of_device.h

device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的compatible属性会和of_match_table表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。

  • ACPI 匹配方式

Advanced Configuration and Power Management Interface 高级配置和电源管理接口,是PC机平台采用的一种硬件配置接口没怎么使用过,后续使用了再补充笔记。

  • id_table 匹配

每个 platform_driver 结构体有一个id_table成员变量,它里边保存了很多id信息。这些id信息存放着这个 platform驱动所支持的驱动类型。

  • 名称匹配

这是第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,毕竟名称匹配是最简单的。

【匹配优先级】

从匹配函数中可以看出,不考虑第二种ACPI匹配方式的情况下,匹配的优先级为:

设备树匹配 > ID匹配 > 名称匹配

3. platform驱动相关结构体

3.1 struct platform_driver

platform_driver结构体表示platform驱动,这个结构体定义在linux内核源码的这个文件中:

1
include/linux/platform_device.h 

我们打开这个文件,可以看到这个结构体及其成员定义如下:

1
2
3
4
5
6
7
8
9
10
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};

【成员说明】

  • probe:函数指针,指向一个probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么probe 就需要自行实现。
  • remove:函数指针,指向一个remove函数,设备卸载了的时候会调用该函数。
  • driver struct device_driver类型(后边会介绍该结构体),Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。内核里所有的驱动必须包含该结构体
  • id_tablestruct platform_device_id *类型(后便会介绍该结构体),指向id_table 表,platform 总线匹配驱动和设备的时候采用的第三种方法, id_table 是个表(也就是数组),每个元素的类型为 platform_device_id

【定义实例】

1
2
3
4
5
6
7
8
/** 定义platform平台驱动 */
struct platform_driver hello_driver = {
.driver = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
},
.probe = hello_driver_probe, /* 设备和驱动匹配成功之后的调用函数 */
.remove = hello_driver_remove, /* 卸载设备 */
};

3.2 struct device_driver

device_driver结构体定义在linux内核源码的这个文件中:

1
include/linux/device.h

我们打开这个文件,可以看到这个结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct device_driver {
const char *name;
struct bus_type *bus;

struct module *owner;
const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;

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;
};

【成员说明】

  • namechar *类型,驱动名称,使用名称匹配的时候匹配device用,最后一个成员是必须要初始化的。
  • busstruct bus_type *类型,总线类型。
  • ownerstruct module *类型,一般填THIS_MODULE
  • of_match_tablestruct of_device_id *类型(后边会介绍),用于设备树匹配 of_match_ptr(某struct of_device_id对象地址) 。of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型。

3.3 struct platform_device_id

在使用ID匹配的时候,这个结构体类型在设备和驱动中都会用到。在驱动中,需要用该结构体对象定义一个结构体数组,而在驱动中,只需要定义一个变量即可。这个结构体定义在linux内核源码的下边这个文件中:

1
include/linux/mod_devicetable.h

我们打开这个文件,会看到这个结构体定义如下:

1
2
3
4
5
6
7
8
#define PLATFORM_NAME_SIZE      20
#define PLATFORM_MODULE_PREFIX "platform:"

struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};

【成员说明】

  • namechar类型,表示匹配用的名称(按我自己的理解这个名称就是匹配的时候用的ID)。
  • driver_datakernel_ulong_t类型,需要向驱动传输的其它数据。

【注意事项】一般是定义在驱动中,定义的是一个结构体数组,一般不指定大小,初始化时最后加{}表示数组结束,在设备程序中只需要定义一个变量即可。

【定义实例】

1
2
3
4
5
6
7
8
9
10
11
/** 
* 定义匹配ID数组
* 与数组中含有的名称相同的设备都可以匹配到该驱动
* 结构体变量名赋值给 struct platform_driver 结构体的 .id_table 成员
*/
struct platform_device_id hello_driver_ids[] =
{
[0] = {.name = "hello_dev"},
[1] = {.name = "a"},
[2] = {} /* means ending */
};

3.4 struct of_device_id

上边提到of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为of_device_id结构体类型,此结构体定义在linux内核源码的这个文件中:

1
include/linux/mod_devicetable.h

我们打开这个文件,可以看到这结构体定义如下:

1
2
3
4
5
6
7
8
9
10
/*
* Struct used for matching a device
*/
struct of_device_id
{
char name[32];
char type[32];
char compatible[128];
const void *data;
};

【成员说明】

  • namechar类型数组,设备名。
  • typechar类型数组,设备类型。
  • compatiblechar类型数组,对于设备树而言,通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

【注意事项】一般是定义在驱动中,定义的是一个结构体数组,一般不指定大小,初始化时最后加{}表示数组结束。

【定义实例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** 
* 定义匹配设备树数组
* 与数组中含有的名称相同的设备都可以匹配到该驱动
* 结构体变量名赋值给 struct platform_driver 结构体的 .driver.of_match_table 成员
*/
struct of_device_id hello_driver_dt_ids[] = {
[0] = {.compatible = "fs4412,hello-dt"}, /* 设备树中 compatible 的名称 */
[1] = {.compatible = "origen4412,hello-dt"},
[2] = {} /* means ending */
};

/** 定义platform平台驱动 */
struct platform_driver hello_driver = {
.driver = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
.of_match_table = hello_driver_dt_ids,
},
.probe = hello_driver_probe, /* 设备和驱动匹配成功之后的调用函数 */
.remove = hello_driver_remove, /* 卸载设备 */
// .id_table = hello_driver_ids, /* 可匹配的设备ID数组 */
};

4. platform设备相关结构体

4.1 struct platform_device

platform_device 这个结构体表示 platform 设备,要注意,如果内核支持设备树的话就不要再使用platform_device来描述设备了,因为改用设备树去描述了。当然了,如果一定要用platform_device来描述设备信息的话也是可以的。这个结构体定义在linux内核源码的这个文件中:

1
include/linux/platform_device.h

我们打开这个文件,可以看到结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;

const struct platform_device_id *id_entry;

/* MFD cell pointer */
struct mfd_cell *mfd_cell;

/* arch specific additions */
struct pdev_archdata archdata;
};

【成员说明】

  • namechar *类型, 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为xxx-gpio,那么此 name字段也要设置为xxx-gpio必须要进行初始化

  • idint类型,设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1

  • devstruct device类型(后边会说明),设备模块必须包含该结构体。

  • num_resourcesu32类型,表示资源数量,一般为resource成员(一般是一个数组)资源的个数 。

  • resourcestruct resource *类型(后边会说明),指向资源数组,表示资源,也就是设备信息,比如外设寄存器等。

【定义实例】

1
2
3
4
5
6
7
/** 定义platform平台设备 */
struct platform_device hello_device = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
.dev.release = hello_device_release, /* 删除设备 */
.resource = hello_device_res, /* 资源数组 */
.num_resources = ARRAY_SIZE(hello_device_res), /* 资源数组元素个数 */
};

4.2 struct device

device结构体定义在linux内核源码的这个文件中:

1
include/linux/device.h

我们打开这个文件,可以看到这个结构体定义如下(成员很多,这里只写了几个可能用到的):

1
2
3
4
5
6
7
8
9
struct device {
//... ...
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
struct device_node *of_node; /* associated device tree node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
void (*release)(struct device *dev);
// ... ...
};

【成员说明】

  • busstruct bus_type *类型,总线类型。
  • devtdev_t类型,表示设备号。
  • driverstruct device_driver *类型,设备驱动。
  • of_nodestruct device_node *类型,驱动和设备匹配成功后,这里将指向匹配成功的设备在设备树中的节点省去获取节点的过程。
  • release:函数指针,指向一个删除设备函数。

【使用实例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int hello_driver_probe(struct platform_device *p_pltdev)
{
/* 0.相关变量定义 */
struct device_node *pnode = NULL; /* 存储设备树节点 */
const char *str = NULL;
/* 1.获取设备树节点 */
pnode = p_pltdev->dev.of_node;
/* 2.获取设备树reg数据 */
printk("=====================================================\n");
if(of_property_read_string(pnode, "my-name", &str) < 0)
printk("of_property_read_string failed!\n");
else
printk("name=%s\n", str);
printk("=====================================================\n");
/* 打印提示信息 */
printk("[INFO]hello_driver_probe is called!\n");
return 0;
}

4.3 struct resource

Linux 内核使用 resource结构体表示资源 ,该结构体定义在linux内核源码的这个文件中:

1
include/linux/ioport.h

我们打开这个文件,可以看到结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};

【成员说明】

  • startresource_size_t类型, 表示资源的起始信息,对于内存类的资源,就表示内存起始地址。
  • endresource_size_t类型,表示资源的终止信息,对于内存类的资源,就表示内存终止地址。
  • name char *类型,表示资源名字。
  • flagsunsigned long类型,表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h 里面。我们常用的是 IORESOURCE_MEMIORESOURCE_IRQ 这两种。start 和 end 的含义会随着 flags而变更。

flagsIORESOURCE_MEM时,startend 分别表示该platform_device占据的内存的开始地址和结束值;注意不同MEM的地址值不能重叠,重叠的话可能会报下边的错误:

1
failed to claim resource 2/3/4/5

flagsIORESOURCE_IRQ 时,startend分别表示该platform_device使用的中断号的开始地址和结束值。

【定义实例】

1
2
3
4
5
6
7
8
9
10
11
12
/** 
* 定义资源数组(注意不能有重叠区域,否则会报错:failed to claim resource 2/3/4/5)
* 结构体变量名赋值给 struct platform_device 结构体的 .resource 成员
*/
struct resource hello_device_res[] = {
[0] = {.start = 0x1000, .end = 0x1003, .name = "reg1", .flags = IORESOURCE_MEM},
[1] = {.start = 0x2000, .end = 0x2003, .name = "reg2", .flags = IORESOURCE_MEM},
[2] = {.start = 10, .end = 10, .name = "irq1", .flags = IORESOURCE_IRQ},
[3] = {.start = 0x3000, .end = 0x3003, .name = "reg3", .flags = IORESOURCE_MEM},
[4] = {.start = 100, .end = 100, .name = "irq2", .flags = IORESOURCE_IRQ},
[5] = {.start = 62, .end = 62, .name = "irq3", .flags = IORESOURCE_IRQ},
};

4.4 struct platform_device_id

在使用ID匹配的时候,这个结构体类型在设备和驱动中都会用到。在驱动中,需要用该结构体对象定义一个结构体数组,而在驱动中,只需要定义一个变量即可。这个结构体定义在linux内核源码的下边这个文件中:

1
include/linux/mod_devicetable.h

我们打开这个文件,会看到这个结构体定义如下:

1
2
3
4
5
6
7
8
#define PLATFORM_NAME_SIZE      20
#define PLATFORM_MODULE_PREFIX "platform:"

struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};

【成员说明】

  • namechar类型,表示匹配用的名称(按我自己的理解这个名称就是匹配的时候用的ID)。
  • driver_datakernel_ulong_t类型,需要向驱动传输的其它数据。

【注意事项】一般是定义在驱动中,定义的是一个结构体数组,一般不指定大小,初始化时最后加{}表示数组结束,在设备程序中只需要定义一个变量即可。

【定义实例】

1
2
3
4
5
6
7
/** 
* 定义ID匹配数组
* 结构体变量名赋值给 struct platform_device 结构体的 .id_entry 成员
*/
struct platform_device_id hello_id = {
.name = MODULE_NAME, /* 这里要与 struct platform_device 结构体中 name 成员相同 */
};

五、基本函数

1. platform驱动相关函数

1.1 platform_driver_register()

我们使用以下命令查询一下函数所在头文件:

1
grep platform_driver_register -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/platform_device.h>

/* 函数声明 */
int platform_driver_register(struct platform_driver *driver);

【函数说明】该函数向 Linux 内核注册一个 platform 驱动。

【函数参数】

  • driverstruct platform_driver *类型,要注册的 platform 驱动。

【返回值】int类型,成功返回0,失败返回 一个负数,负数的绝对值表示错误码。

【使用格式】none

【注意事项】none

1.2 platform_driver_unregister()

我们使用以下命令查询一下函数所在头文件:

1
grep platform_driver_unregister -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/platform_device.h>

/* 函数声明 */
void platform_driver_unregister(struct platform_driver *drv);

【函数说明】该函数从 Linux 内核卸载一个 platform 驱动。

【函数参数】

  • drvstruct platform_driver *类型,要卸载的 platform 驱动。

【返回值】none

【使用格式】none

【注意事项】none

1.3 platform_get_resource()

我们使用以下命令查询一下函数所在头文件:

1
grep platform_get_resource -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/platform_device.h>

/* 函数声明 */
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);

【函数说明】该函数用于获取设备资源,用在platform驱动中。

【函数参数】

  • devstruct platform_device *类型,资源所在的设备。
  • typeunsigned int类型,获取的资源类型。
  • numunsigned int类型,对应类型资源的序号(如第0MEM、第2IRQ等,不是数组下标,而是同类资源的序号)。

【返回值】struct resource *类型,成功则返回资源结构体首地址,失败则返回NULL

【使用格式】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 设备树节点部分 */
fs4412-adc{
compatible = "fs4412,adc";
reg = <0x126C0000 0x20>;
interrupt-parent = <&combiner>;
interrupts = <10 3>;
};
/* 获取资源 */
struct resource *res1; /* 保存中断信息地址 */
struct resource *res2; /* 保存寄存器基地址信息地址 */

res1 = platform_get_resource(p_pltdev, IORESOURCE_IRQ, 0); /* 中断信息 */
res2 = platform_get_resource(p_pltdev, IORESOURCE_MEM, 0); /* 寄存器基地址 */
printk("[INFO ]res1->start :%d.\n", res1->start);
printk("[INFO ]res2->start=%#x,res2->end - res2->start :%#x.\n",res2->start, res2->end - res2->start);

【注意事项】none

1.4 of_match_ptr()

我们使用以下命令查询一下函数所在头文件:

1
grep of_match_ptr -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 需包含的头文件 */
#include <linux/of.h>

/* 文件结构如下 */
// ... ...
#ifdef CONFIG_OF
// ... ...
#define of_match_ptr(_ptr) (_ptr)
// ... ...
#else /* CONFIG_OF */
// ... ...
#define of_match_ptr(_ptr) NULL
// ... ...
#endif /* CONFIG_OF */

【函数说明】该函数是一个宏,表示当使用设备树时,使用_ptr进行匹配,否则其为空。

点击查看说明

在内核还没有引进设备树的时候,设备和驱动的匹配工作是由driver.name或者id_table来完成的,自从引进设备树之后(就有了CONFIG_OF)这个宏,匹配规则中就多了of_match_table这种通过compatible方式来匹配的方式。同时为了兼容老的匹配方式,定义了宏of_match_ptr

  • 当有设备树的时候(定义了CONFIG_OF),那么of_match_table就有值。
1
.of_match_table = of_match_ptr(adc_driver_dt_ids)

宏替换之后就是

1
.of_match_table = (adc_driver_dt_ids)

这样就会使用设备树的方式进行匹配。

  • 当没有设备树的时候(没有定义CONFIG_OF),那么of_match_table就没值。
1
.of_match_table = of_match_ptr(adc_driver_dt_ids)

宏替换之后就是

1
.of_match_table = NULL

这样就就会使用传统方式匹配。

【函数参数】

  • _ptrstruct of_device_id类型的一个数组,表示设备树匹配的compatible表。

【返回值】none

【使用格式】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** 定义匹配设备树数组
* 与数组中含有的名称相同的设备都可以匹配到该驱动
* 结构体变量名赋值给 struct platform_driver 结构体的 ..driver.of_match_table 成员
*/
struct of_device_id adc_driver_dt_ids[] = {
[0] = {.compatible = "fs4412,adc" },
[1] = {}, /* means ending */
};

/** 定义platform平台驱动 */
struct platform_driver adc_driver = {
.driver = {
.name = MODULE_NAME, /* 必须初始化 name 成员 */
// .of_match_table = adc_driver_dt_ids,
.of_match_table = of_match_ptr(adc_driver_dt_ids), /* 直接使用的话,加载驱动报错,暂未研究 */
},
.probe = adc_driver_probe, /* 设备和驱动匹配成功之后的调用函数 */
.remove = adc_driver_remove, /* 卸载设备 */
};

【注意事项】其实原本没用过这个宏,直接使用数组名就也完成了匹配,但是后便在编写一个驱动的时候,使用platform_get_resource获从设备树获取资源的话,这里不用这个宏的话,在加载模块的时候就会报错,目前还没有仔细研究,所以这里先注意一下,后边搞懂了再补充。

2. platform设备相关函数

2.1 platform_device_register()

我们使用以下命令查询一下函数所在头文件:

1
grep platform_device_register -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/platform_device.h>

/* 函数声明 */
int platform_device_register(struct platform_device *pdev);

【函数说明】该函数设备信息注册到 Linux 内核中,把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe指向的函数。

【函数参数】

  • pdevstruct platform_device *类型,要注册的 platform 设备。

【返回值】int类型,成功返回0,失败返回 一个负数,负数的绝对值表示错误码。

【使用格式】none

【注意事项】none

2.2 platform_device_unregister()

我们使用以下命令查询一下函数所在头文件:

1
grep platform_device_unregister -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/platform_device.h>

/* 函数声明 */
void platform_device_unregister(struct platform_device *pdev);

【函数说明】该函数从 Linux 内核注销一个 platform 设备,如果驱动已匹配则回调驱动方法和设备信息中的release指向的函数 。

【函数参数】

  • pdevstruct platform_device *类型,要注销的 platform 设备。

【返回值】none

【使用格式】none

【注意事项】none