一、什么是platform_driver
platform_driver 是 Linux 内核中用来描述一个 Platform 驱动的结构体。
它的作用是:
- 告诉内核"我能驱动哪些设备"(通过匹配表)
- 实现设备的初始化和清理逻辑
- 提供
probe()和remove()等回调函数
二、platform_driver结构体详解
platform_driver 结构体定义在 TaishanPi-3-Linux/kernel-6.1/include/linux/platform_device.h 中:
c
struct platform_driver {
int (*probe)(struct platform_device *); // 设备探测函数,设备和驱动匹配成功时调用
int (*remove)(struct platform_device *); // 设备移除函数(旧版本)
void (*remove_new)(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; // 设备 ID 匹配表(用于非设备树匹配)
bool prevent_deferred_probe; // 阻止延迟探测
bool driver_managed_dma; // 驱动自行管理 DMA (true/false)
};1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
核心参数:
probe:设备匹配成功后的初始化函数remove/remove_new:设备移除时的清理函数driver. name:驱动名称driver.of_match_table:设备树匹配表
1、初始化函数
当设备和驱动匹配成功后,内核会自动调用 probe() 函数。
函数原型:
c
int (*probe)(struct platform_device *pdev);1
参数:
pdev:指向匹配成功的platform_device结构体
返回值:
0:初始化成功- 负数:初始化失败(错误码)
作用:
- 获取设备资源(寄存器地址、中断号等)
- 申请字符设备号
- 注册字符设备
- 创建设备节点
- 初始化硬件
2、清理函数
当设备移除或驱动卸载时,内核会自动调用 remove() 或 remove_new() 函数。
函数原型:
c
// 旧版本(返回 int)
int (*remove)(struct platform_device *pdev);
// 新版本(返回 void)
void (*remove_new)(struct platform_device *pdev);1
2
3
4
5
2
3
4
5
两个版本的区别:
remove():旧版本,返回int(但内核通常忽略返回值)remove_new():新版本(内核 6.x),返回void,更符合实际使用场景- 新驱动建议使用
remove_new()
作用:
- 释放
probe()中申请的资源 - 注销字符设备
- 删除设备节点
- 释放中断
- 取消地址映射
3、系统关闭函数
当系统关闭或重启时调用 shutdown() ,用于安全关闭设备。
使用场景:
- 关闭设备电源
- 停止 DMA 传输
- 保存设备状态
4、电源管理函数
suspend / resume 用于系统休眠和唤醒时的设备电源管理。
使用场景:
- 笔记本电脑休眠/唤醒
- 嵌入式设备低功耗模式
5、设备树匹配表
driver.of_match_table 这是最重要的成员,用于指定驱动能匹配哪些设备树节点。
匹配表结构:
c
struct of_device_id {
char name[32]; // 设备名称
char type[32]; // 设备类型
char compatible[128]; // 匹配的 compatible 字符串
const void *data; // 可选的私有数据
};1
2
3
4
5
6
2
3
4
5
6
例子:
c
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" }, // 匹配 compatible = "lckfb,mychardev" 的节点
{ /* 结束符 */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);1
2
3
4
5
2
3
4
5
6、ID匹配表
id_table 用于不使用设备树的旧式匹配方式。
使用场景:
- x86 平台(部分)
- 旧内核(不支持设备树)
7、阻止延迟探测
prevent_deferred_probe 当设为 true 时,如果 probe() 返回 -EPROBE_DEFER,内核不会延迟重试。
使用场景:
- 驱动不希望被延迟探测
- 通常保持默认值
false
8、DMA管理标志
driver_managed_dma 指示驱动是否自行管理 DMA。
使用场景:
- 设备需要进行 DMA 传输
- 简单的字符设备通常不需要关心此字段
三、编写platform_driver步骤
我们创建以下的设备树节点
c
/ {
mychardev@0 {
compatible = "lckfb,mychardev";
reg = <0x12340000 0x1000>; // 寄存器地址
interrupts = <56>; // 中断号
dev-id = <0xFFA8>; // 自定义属性
status = "okay";
};
};1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
1、定义匹配表
c
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" }, // 必须和设备树中的 compatible 一致
{ /* 结束符 */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);1
2
3
4
5
2
3
4
5
2、实现probe函数
c
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
int irq;
u32 dev_id;
// 1. 获取寄存器地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (! res) {
return -ENODEV;
}
// 2. 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
return irq;
}
// 3. 读取自定义属性
if (of_property_read_u32(pdev->dev.of_node, "dev-id", &dev_id)) {
return -EINVAL;
}
// 4. 其他的代码:
// - 申请字符设备号
// - 注册 cdev
// - 创建设备节点
// - ioremap 映射寄存器
// - 注册中断处理函数
return 0;
}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
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
3、实现remove函数
c
static int my_remove(struct platform_device *pdev)
{
// 释放资源:
// 1. 注销字符设备
// 2. 删除设备节点
// 3. 释放中断
// 4. iounmap 取消地址映射
return 0;
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
4、定义platform_driver
c
static struct platform_driver my_driver = {
.probe = my_probe, // 匹配成功后调用
.remove = my_remove, // 移除时调用
.driver = {
.name = "mychardev", // 驱动名称
.of_match_table = my_of_match, // 设备树匹配表
},
};1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
5、注册驱动
使用 module_platform_driver() 宏自动实现 module_init() 和 module_exit():
c
module_platform_driver(my_driver);1
使用此宏会自动被展开为以下的代码:
c
static int __init my_driver_init(void)
{
return platform_driver_register(&my_driver);
}
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
所以我们使用此宏之后,无需其他的操作。
四、驱动与设备的匹配过程
1、匹配流程
2、compatible匹配规则
设备树:
c
mychardev@0 {
compatible = "lckfb,mychardev";
};1
2
3
2
3
驱动匹配表:
c
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" },
{ }
};1
2
3
4
2
3
4
INFO
compatible字符串必须完全一致- 设备树可以有多个
compatible值,驱动只需匹配其中一个即可 - 匹配成功后,内核会调用
probe()函数
3、多个compatible的情况
设备树节点:
c
mychardev@0 {
compatible = "lckfb,mychardev-v2", "lckfb,mychardev";
};1
2
3
2
3
驱动匹配表:
c
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev-v2" }, // 优先匹配 v2
{ .compatible = "lckfb,mychardev" }, // 兼容
{ }
};1
2
3
4
5
2
3
4
5
内核会依次尝试匹配,只要有一个匹配成功即可。
五、完整的驱动框架
c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
// 1. 定义匹配表
static const struct of_device_id my_of_match[] = {
{ . compatible = "lckfb,mychardev" },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
// 2. 实现 probe 函数
static int my_probe(struct platform_device *pdev)
{
printk(KERN_INFO "mychardev: probe!\n");
// 获取资源、初始化设备...
return 0;
}
// 3. 实现 remove 函数
static int my_remove(struct platform_device *pdev)
{
printk(KERN_INFO "mychardev: remove!\n");
// 释放资源...
return 0;
}
// 4. 定义 platform_driver
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
. driver = {
.name = "mychardev",
.of_match_table = my_of_match,
},
};
// 5. 注册驱动
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LCKFB");
MODULE_DESCRIPTION("Platform Driver Frame");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
44
45
46
47
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
44
45
46
47