Linux驱动——Platform设备驱动

一、Platform 驱动简介

在嵌入式 Linux 驱动开发中,面对 SoC 内部集成的硬件(如 GPIO、UART、I2C 控制器等),直接操作寄存器的驱动往往存在可移植性差的问题——硬件引脚或地址变动时,需修改大量驱动代码。

为此,在 Linux 2.6 以后的设备驱动模型中,需关心总线、设备和驱动这三个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

一个现实的 Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于 PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,在 SoC 系统中集成的独立外设控制器、挂接在 SoC 内存空间的外设等却不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为 platform 总线,相应的设备称为 platform_device,而驱动成为 platform_driver。

显然,这样做的好处是,实现了此类设备和驱动的分离,增强设备驱动的可移植性。平台总线模型也叫 platform 总线模型。是 Linux 内核虚拟出来的一条总线,不是真实的导线。平台总线模型就是把原来的驱动 C 文件给分成了俩个 C 文件,一个是 device.c,一个是 driver.c。把稳定不变的放在 driver.c 里面,需要变得就放在了 device.c 里面。

Platform 机制的核心是将驱动程序拆分为纯硬件信息和纯软件逻辑两部分:

  • 纯硬件信息:如寄存器物理地址、GPIO 编号、中断号等,由platform_device结构体描述;
  • 纯软件逻辑:如硬件初始化、数据读写等操作,由platform_driver结构体描述。

驱动开发中,仅需维护 platform_device 中的硬件信息,platform_driver 的软件代码一旦写好,无需修改即可适配不同硬件。

下面分别来看这两个部分的内容。

二、Platform device

platform_device用于描述硬件设备的具体信息(如地址、GPIO 等),其核心结构体定义如下:

1
2
3
4
5
6
7
struct platform_device {
const char *name; // 硬件节点名称(用于匹配驱动)
int id; // 设备编号(单设备填-1,多设备用0、1...区分)
struct device dev; // 设备基础信息,含platform_data字段
u32 num_resources; // resource数组中硬件信息的个数
struct resource *resource; // 内核标准硬件信息数组(地址、中断等)
};

platform_device支持两种方式描述硬件信息,可单独或同时使用:

  • 方式 1:自定义结构体(灵活适配)

驱动工程师自主定义硬件信息结构体,通过dev.platform_data挂载到platform_device。

步骤示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 自定义硬件信息结构体(按需声明参数)
struct led_resource {
unsigned long phy_baseaddr; // 寄存器物理地址
unsigned long size; // 寄存器大小
unsigned long gpio; // GPIO编号
};

// 2. 初始化硬件信息(填写实际参数)
struct led_resource led_hw = {
.phy_baseaddr = 0xC001C000,
.size = 0x24,
.gpio = 12
};

// 3. 关联到platform_device
struct platform_device led_dev = {
.name = "tarena_led", // 匹配驱动的关键(需与driver.name一致)
.id = -1,
.dev = {
.platform_data = &led_hw // 挂载自定义硬件信息
}
};
  • 方式 2:内核标准 resource 结构体(规范统一)

复用内核struct resource结构体,按类型(地址、GPIO 等)描述硬件信息,通过resource数组挂载。

struct resource定义:

1
2
3
4
5
6
7
8
9
10
11
struct resource {
resource_size_t start; //表示资源的起始值,
resource_size_t end; //表示资源的最后一个字节的地址, 如果是中断,end和satrt相同
const char *name; // 可不写
unsigned long flags; //资源的类型
struct resource *parent, *sibling, *child;
};

// [flags] 类型,通常将该硬件使用的物理地址、中断号视为资源:
#define IORESOURCE_MEM 0x00000200 //内存: 物理地址资源
#define IORESOURCE_IRQ 0x00000400 //中断: 中断号资源

步骤示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 用resource数组描述硬件信息(地址+GPIO)
struct resource led_res[] = {
// 地址类资源(IORESOURCE_MEM)
{
.start = 0xC001C000,
.end = 0xC001C000 + 0x24 - 1, // 结束地址=起始+大小-1
.flags = IORESOURCE_MEM
},
// GPIO类资源(IORESOURCE_IRQ)
{
.start = 12,
.end = 12, // 单一GPIO,start=end
.flags = IORESOURCE_IRQ
}
};

// 2. 关联到platform_device
struct platform_device led_dev = {
.name = "tarena_led",
.id = -1,
.resource = led_res, // 挂载resource数组
.num_resources = ARRAY_SIZE(led_res) // 必须指定资源个数
};

注册设备 platform_driver_register:

1
2
3
4
// #define platform_driver_register(drv) \
// __platform_driver_register(drv, THIS_MODULE)

platform_device_register(struct platform_device *);

三、Platform driver

platform_driver用于定义软件操作逻辑(如硬件初始化、用户接口),其核心结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct platform_driver
{
//当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中
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 *);

//内核维护的所有的驱动必须包含该成员,通常driver->name用于和设备进行匹配
struct device_driver driver;

//往往一个驱动可能能同时支持多个硬件,这些硬件的名字都放在该结构体数组中
const struct platform_device_id *id_table;

bool prevent_deferred_probe;
}
  • driver.name:需与platform_device.name一致,作为匹配依据;
  • probe 函数:硬件与驱动匹配成功后,内核自动调用,负责初始化硬件、注册设备接口等;
  • remove 函数:驱动或设备卸载时调用,负责释放资源(与 probe 操作相反)。

驱动注册与卸载:

1
2
platform_driver_register(struct platform_driver *);
platform_driver_unregister(struct platform_driver *);

四、核心流程:注册与匹配

(1)注册硬件与驱动

  • 硬件节点注册:platform_device_register(&led_dev),将 led_dev 添加到内核设备链表;
  • 驱动节点注册:platform_driver_register(&led_drv),将 led_drv 添加到内核驱动链表。

(2)内核匹配逻辑

内核遍历设备链表和驱动链表,通过比较 platform_device.name 与 platform_driver.driver.name 实现匹配。

(3)匹配成功后

内核自动调用 platform_driver.probe(pdev),其中 pdev 指向匹配成功的 platform_device,驱动可通过 pdev 获取硬件信息。

五、实例

platform_lab.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// SPDX-License-Identifier: GPL-2.0

#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#define PLATFORM_LAB_NAME "platform_lab"

struct platform_lab {
int id;
};

static int platform_lab_probe(struct platform_device *pdev)
{
struct platform_lab *priv;

priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
return -ENOMEM;
}

priv->id = pdev->id;
platform_set_drvdata(pdev, priv);

dev_info(&pdev->dev, "probed: name=%s id=%d\n", pdev->name, pdev->id);
return 0;
}

static int platform_lab_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "removed: name=%s id=%d\n", pdev->name, pdev->id);
return 0;
}

static const struct of_device_id platform_lab_of_match[] = {
{ .compatible = "edge,platform-lab" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, platform_lab_of_match);

// platform_driver 表示“驱动”;platform_device_register/platform_driver_register
// 会把 device 和 driver 挂到内核的 platform_bus 上,由总线负责 match。
static struct platform_driver platform_lab_driver = {
.probe = platform_lab_probe,
.remove = platform_lab_remove,
.driver = {
.name = PLATFORM_LAB_NAME,
.of_match_table = platform_lab_of_match,
},
};

static int __init platform_lab_driver_init(void)
{
int ret;

// 注册 platform_driver。成功后它会被挂到 platform_bus 的 driver 链表上。
ret = platform_driver_register(&platform_lab_driver);
if (ret) {
pr_err(PLATFORM_LAB_NAME ": failed to register driver: %d\n", ret);
return ret;
}

pr_info(PLATFORM_LAB_NAME ": driver registered\n");
return 0;
}

static void __exit platform_lab_driver_exit(void)
{
platform_driver_unregister(&platform_lab_driver);
pr_info(PLATFORM_LAB_NAME ": driver unregistered\n");
}

module_init(platform_lab_driver_init);
module_exit(platform_lab_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("orange-pi-edge");
MODULE_DESCRIPTION("Minimal platform driver for Linux driver learning");
MODULE_VERSION("1.0");

platform_lab_testdev.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// SPDX-License-Identifier: GPL-2.0

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>

#define PLATFORM_LAB_NAME "platform_lab"

static void platform_lab_device_release(struct device *dev)
{
}

// platform_device 表示“设备”。name/id 是 platform_bus 匹配时的关键字段。
static struct platform_device platform_lab_device = {
.name = PLATFORM_LAB_NAME,
.id = 0,
.dev = {
.release = platform_lab_device_release,
},
};
static int dev_id;
module_param(dev_id, int, 0444);
MODULE_PARM_DESC(dev_id, "platform device id, default 0");

static int __init platform_lab_testdev_init(void)
{
int ret;

platform_lab_device.id = dev_id;

// 注册 platform_device。成功后它会被挂到 platform_bus 的 device 链表上。
ret = platform_device_register(&platform_lab_device);
if (ret) {
pr_err(PLATFORM_LAB_NAME "_testdev: failed to register device: %d\n",
ret);
return ret;
}

pr_info(PLATFORM_LAB_NAME "_testdev: registered %s.%d\n",
PLATFORM_LAB_NAME, dev_id);
return 0;
}

static void __exit platform_lab_testdev_exit(void)
{
platform_device_unregister(&platform_lab_device);
pr_info(PLATFORM_LAB_NAME "_testdev: unregistered\n");
}

module_init(platform_lab_testdev_init);
module_exit(platform_lab_testdev_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test helper: register platform_device for platform_lab");

Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
obj-m += platform_lab.o
obj-m += platform_lab_testdev.o

KDIR ?= /lib/modules/$(shell uname -r)/build

all:
$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean

.PHONY: all clean

编译并观察结果:

1
2
3
4
make
sudo insmod platform_lab_testdev.ko
sudo insmod platform_lab.ko
sudo dmesg | grep platform_lab | tail -n 8

输出:

1
2
3
[121.804980] platform_lab_testdev: registered platform_lab.0
[121.845391] platform_lab platform_lab.0: probed: name=platform_lab id=0
[121.845509] platform_lab: driver registered

查看 driver 下绑定的 device:

1
2
3
4
5
6
> ls /sys/bus/platform/drivers/platform_lab/
bind
module
platform_lab.0
uevent
unbind

其中 platform_lab.0 就是被这个 driver 绑定的设备。

查看 device 目录:

1
ls /sys/bus/platform/devices/platform_lab.0/

实测:

1
2
3
4
5
6
driver
driver_override
modalias
power
subsystem
uevent

Linux驱动——Platform设备驱动
http://example.com/2026/08/05/Linux驱动——Platform设备驱动/
作者
Yu xin
发布于
2026年8月5日
许可协议