在嵌入式 Linux 系统中,我们经常会遇到大量片上系统(SoC)外设。这类外设在硬件上通常直接集成在芯片内部,但在软件上却没有像 PCI、USB 那样的标准枚举机制。想象一下:如果一个城市没有门牌号系统和邮政系统,快递员如何准确找到每家每户?Platform 驱动机制就是 Linux 为解决这个问题而设计的“片上设备寻址系统”。
Platform 驱动的本质:一种将那些无法被标准总线枚举机制识别的设备(主要是 SoC 内部外设)纳入 Linux 设备模型管理框架的抽象机制。它通过设备树(Device Tree)或平台数据(Platform Data)等静态描述方式,在设备与驱动之间建立连接。若你想系统补齐 Linux 内核相关知识脉络,可在 网络/系统 板块延伸阅读。
一、设计哲学:分离与抽象
1.1 核心设计思想
Platform 驱动的设计遵循两个核心原则:
-
设备与驱动分离原则
- 设备描述硬件资源(寄存器地址、中断号等)
- 驱动描述操作方法(如何初始化、如何读写等)
- 两者通过名称或兼容性字符串进行匹配
-
资源抽象原则
- 将硬件资源(内存、中断、时钟、GPIO 等)抽象为统一的数据结构
- 提供标准 API 访问这些资源,使驱动与具体硬件细节解耦

1.2 现实世界类比:学校课程管理系统
为了更好地理解 Platform 机制,让我们想象一个学校的课程管理系统:
| 系统组件 |
Platform机制对应 |
学校系统类比 |
| Platform Device |
课程安排表 |
每门课程的时间、教室、学生名单等资源描述 |
| Platform Driver |
教师教学能力 |
教师的教学方法、教案、授课技巧等 |
| 匹配过程 |
课程分配 |
教务处根据课程需求匹配适合的教师 |
| 设备树 |
全校课程总表 |
描述所有课程及其资源需求的中央数据库 |
| Probe函数 |
教师备课 |
教师拿到课程表后开始准备教学材料 |
| 资源管理 |
教学资源分配 |
分配教室、投影仪、实验设备等 |
二、核心数据结构解剖
2.1 三大支柱结构
// 核心数据结构定义(简化版)
struct platform_device {
const char *name; // 设备名称, 用于匹配
int id; // 设备ID, -1表示唯一设备
struct device dev; // 继承自通用设备结构
u32 num_resources; // 资源数量
struct resource *resource; // 资源数组指针
const struct platform_device_id *id_entry; // 设备ID表
/* ... 其他成员 ... */
};
struct platform_driver {
int (*probe)(struct platform_device *); // 探测函数
int (*remove)(struct platform_device *); // 移除函数
struct device_driver driver; // 继承自通用驱动
const struct platform_device_id *id_table; // 支持的设备ID表
/* ... 其他成员 ... */
};
struct resource {
resource_size_t start; // 资源起始地址/中断号
resource_size_t end; // 资源结束地址
const char *name; // 资源名称
unsigned long flags; // 资源类型标志
/* ... 其他成员 ... */
};
2.2 数据结构关系图解

2.3 关键数据结构详解
2.3.1 struct resource:资源描述符
资源是 Platform 机制的核心抽象,它统一描述了各种硬件资源:
// 资源类型标志(部分)
#define IORESOURCE_IO 0x00000100 // IO端口资源
#define IORESOURCE_MEM 0x00000200 // 内存映射资源
#define IORESOURCE_IRQ 0x00000400 // 中断资源
#define IORESOURCE_DMA 0x00000800 // DMA通道
#define IORESOURCE_BUS 0x00001000 // 总线编号
// 资源获取API
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type,
unsigned int num);
int platform_get_irq(struct platform_device *dev, unsigned int num);
struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,
const char *name);
2.3.2 匹配机制:设备与驱动的“红娘系统”
Platform 机制支持多种匹配方式,按优先级从高到低:
| 匹配方式 |
描述 |
使用场景 |
| 设备树兼容性匹配 |
通过 of_match_table 中的 compatible 字段 |
现代嵌入式系统,设备树描述 |
| ACPI ID匹配 |
通过 ACPI 表中的设备 ID |
x86 体系,支持 ACPI 的系统 |
| 平台设备ID匹配 |
通过 id_table 中的名称匹配 |
传统平台数据方式 |
| 名称直接匹配 |
直接比较 device.name 和 driver.name |
简单设备,兼容旧代码 |
三、工作流程深度解析
3.1 完整生命周期流程

3.2 Probe 函数的详细执行过程
Probe 函数是 Platform 驱动的“出生证明”。它到底做了哪些关键动作?下面按典型顺序拆解(分配私有数据、拿资源、映射寄存器、申请 IRQ、初始化硬件、注册子系统、保存 drvdata):
// 典型的probe函数结构
static int sample_probe(struct platform_device *pdev)
{
struct sample_private *priv;
struct resource *mem_res;
int irq_num;
int ret;
// 1. 分配私有数据结构
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
// 2. 获取内存资源
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {
dev_err(&pdev->dev, "No memory resource\n");
return -ENODEV;
}
// 3. 映射寄存器区域
priv->regs = devm_ioremap_resource(&pdev->dev, mem_res);
if (IS_ERR(priv->regs))
return PTR_ERR(priv->regs);
// 4. 获取中断号
irq_num = platform_get_irq(pdev, 0);
if (irq_num < 0)
return irq_num;
// 5. 申请中断
ret = devm_request_irq(&pdev->dev, irq_num, sample_interrupt,
0, "sample-device", priv);
if (ret) {
dev_err(&pdev->dev, "Cannot request IRQ\n");
return ret;
}
// 6. 初始化设备硬件
sample_hw_init(priv);
// 7. 注册到相应子系统
priv->chardev = sample_create_chardev(priv);
if (IS_ERR(priv->chardev))
return PTR_ERR(priv->chardev);
// 8. 保存私有数据指针
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "Sample device probed successfully\n");
return 0;
}
四、设备树集成机制
设备树是现代嵌入式 Linux 系统的“硬件蓝图”,用文本形式描述硬件配置:
// 设备树节点示例
sample_device: sample@10000000 {
compatible = "vendor,sample-device"; // 匹配字符串
reg = <0x10000000 0x1000>; // 寄存器地址和大小
interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>; // 中断描述
clocks = <&sample_clk>; // 时钟源
clock-names = "sample"; // 时钟名称
resets = <&sample_rst>; // 复位控制器
vendor,specific-prop = <1>; // 厂商自定义属性
// 子节点示例
dma-channel {
compatible = "vendor,sample-dma";
#dma-cells = <1>;
};
};
4.2 设备树与驱动的绑定过程

4.3 从设备树获取数据的 API
日常写驱动时,你最常掉坑的地方往往不是匹配,而是“属性读取失败却没兜底”。下面这些 API 几乎天天会用到:
// 常用设备树API
struct device_node *np = pdev->dev.of_node;
// 1. 读取属性值
u32 reg_val;
of_property_read_u32(np, "reg", ®_val);
// 2. 读取字符串属性
const char *str;
of_property_read_string(np, "clock-names", &str);
// 3. 读取数组
u32 array[10];
int len = of_property_read_variable_u32_array(np, "array-prop",
array, 0, 10);
// 4. 解析子节点
struct device_node *child;
for_each_child_of_node(np, child) {
// 处理每个子节点
}
// 5. 获取GPIO描述符
int gpio = of_get_named_gpio(np, "enable-gpio", 0);
if (gpio_is_valid(gpio)) {
devm_gpio_request(&pdev->dev, gpio, "enable");
gpio_direction_output(gpio, 1);
}
如果你还需要对设备树语法、binding 规范、调试方式做系统查阅,建议配合 技术文档 板块的相关资料一起梳理。
五、完整实例:虚拟温度传感器驱动
只看概念不写代码,很容易“懂了但不会用”。下面通过一个完整的虚拟温度传感器驱动实例,把 Platform 驱动的关键路径串起来。
5.1 设备树描述
// arch/arm/boot/dts/myboard.dts 片段
/ {
// 其他节点...
vtemp: virtual-temperature {
compatible = "demo,virtual-temp";
reg = <0x20000000 0x100>;
interrupts = <GIC_SPI 100 IRQ_TYPE_EDGE_RISING>;
clocks = <&clk50m>;
demo,temp-base = <250>; // 基础温度值
demo,temp-range = <50>; // 温度范围
status = "okay";
};
};
5.2 驱动实现
// drivers/hwmon/vtemp.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/device.h>
#define DRIVER_NAME "virtual-temp"
#define VTEMP_MAX_CHANNELS 4
struct vtemp_data {
struct device *hwmon_dev;
void __iomem *regs;
int irq;
struct mutex lock;
u32 temp_base;
u32 temp_range;
u32 current_temp;
};
// 温度读取函数
static ssize_t temp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct vtemp_data *data = dev_get_drvdata(dev);
u32 temp;
mutex_lock(&data->lock);
// 模拟温度读取: 基础温度 + 随机波动
temp = data->temp_base + (prandom_u32() % data->temp_range);
data->current_temp = temp;
mutex_unlock(&data->lock);
return sprintf(buf, "%d\n", temp);
}
// 中断处理函数
static irqreturn_t vtemp_interrupt(int irq, void *dev_id)
{
struct vtemp_data *data = dev_id;
// 模拟温度超限中断
dev_info(data->hwmon_dev->parent,
"Temperature threshold exceeded: %d°C\n",
data->current_temp);
// 这里可以添加真正的硬件操作
// ioread32(data->regs + INTR_STATUS_REG);
// iowrite32(0, data->regs + INTR_CLEAR_REG);
return IRQ_HANDLED;
}
// 属性定义
static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
static struct attribute *vtemp_attrs[] = {
&sensor_dev_attr_temp1_input.dev_attr.attr,
NULL
};
ATTRIBUTE_GROUPS(vtemp);
// Probe函数
static int vtemp_probe(struct platform_device *pdev)
{
struct vtemp_data *data;
struct resource *res;
int ret;
// 1. 分配数据结构
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
// 2. 获取内存资源并映射
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
// 3. 获取中断
data->irq = platform_get_irq(pdev, 0);
if (data->irq < 0)
return data->irq;
// 4. 从设备树获取自定义属性
if (pdev->dev.of_node) {
of_property_read_u32(pdev->dev.of_node, "demo,temp-base",
&data->temp_base);
of_property_read_u32(pdev->dev.of_node, "demo,temp-range",
&data->temp_range);
} else {
// 回退到默认值
data->temp_base = 250;
data->temp_range = 50;
}
// 5. 申请中断
ret = devm_request_irq(&pdev->dev, data->irq, vtemp_interrupt,
0, DRIVER_NAME, data);
if (ret) {
dev_err(&pdev->dev, "无法申请IRQ %d\n", data->irq);
return ret;
}
// 6. 初始化互斥锁
mutex_init(&data->lock);
// 7. 注册hwmon设备
data->hwmon_dev = devm_hwmon_device_register_with_groups(
&pdev->dev, DRIVER_NAME, data, vtemp_groups);
if (IS_ERR(data->hwmon_dev))
return PTR_ERR(data->hwmon_dev);
// 8. 保存私有数据
platform_set_drvdata(pdev, data);
dev_info(&pdev->dev,
"虚拟温度传感器初始化成功, 基础温度: %d, 范围: ±%d\n",
data->temp_base, data->temp_range/2);
return 0;
}
// Remove函数
static int vtemp_remove(struct platform_device *pdev)
{
struct vtemp_data *data = platform_get_drvdata(pdev);
// 清理资源
mutex_destroy(&data->lock);
dev_info(&pdev->dev, "虚拟温度传感器驱动已卸载\n");
return 0;
}
// 匹配表
static const struct of_device_id vtemp_of_match[] = {
{ .compatible = "demo,virtual-temp", },
{ },
};
MODULE_DEVICE_TABLE(of, vtemp_of_match);
// Platform驱动结构
static struct platform_driver vtemp_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = vtemp_of_match,
.owner = THIS_MODULE,
},
.probe = vtemp_probe,
.remove = vtemp_remove,
};
module_platform_driver(vtemp_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("虚拟温度传感器驱动");
MODULE_VERSION("1.0");
5.3 Makefile 配置
# Kbuild文件
obj-$(CONFIG_VIRTUAL_TEMP) += vtemp.o
# Makefile片段
# 在drivers/hwmon/Makefile中添加
obj-$(CONFIG_VIRTUAL_TEMP) += vtemp.o
# Kconfig配置
# 在drivers/hwmon/Kconfig中添加
config VIRTUAL_TEMP
tristate "Virtual temperature sensor support"
depends on HAS_IOMEM
help
This driver supports a virtual temperature sensor for demonstration
of Linux platform driver mechanism.
If unsure, say N.
六、调试与诊断技术
6.1 调试工具和命令
| 工具/命令 |
用途 |
示例 |
dmesg |
查看内核日志 |
dmesg \| grep -i platform |
ls /sys/bus/platform/ |
查看平台总线设备 |
ls -la /sys/bus/platform/devices/ |
udevadm |
设备管理工具 |
udevadm info -a -p /sys/bus/platform/devices/* |
devmem |
直接内存访问 |
devmem 0x10000000 |
cat /proc/interrupts |
查看中断统计 |
cat /proc/interrupts \| grep temp |
ofdump |
设备树查看工具 |
ofdump /proc/device-tree |
6.2 调试技巧和实践
- 动态调试输出
// 在驱动中添加动态调试
#define DEBUG
#ifdef DEBUG
#define vtemp_dbg(dev, fmt, ...) \
dev_dbg(dev, "%s:%d " fmt, __func__, __LINE__, ##__VA_ARGS__)
#else
#define vtemp_dbg(dev, fmt, ...)
#endif
// 使用方式
vtemp_dbg(&pdev->dev, "温度值: %d\n", current_temp);
- sysfs 调试接口
// 添加调试属性
static ssize_t debug_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct vtemp_data *data = dev_get_drvdata(dev);
return sprintf(buf, "regs: %p\nirq: %d\nbase_temp: %d\n",
data->regs, data->irq, data->temp_base);
}
static DEVICE_ATTR_RO(debug);
- procfs 接口
// 创建proc文件
static int vtemp_proc_show(struct seq_file *m, void *v)
{
struct vtemp_data *data = m->private;
seq_printf(m, "Virtual Temperature Sensor Status\n");
seq_printf(m, "===============================\n");
seq_printf(m, "Registers: 0x%p\n", data->regs);
seq_printf(m, "IRQ: %d\n", data->irq);
seq_printf(m, "Current temp: %d\n", data->current_temp);
return 0;
}
// 在probe函数中创建
proc_create_single_data("driver/vtemp", 0, NULL,
vtemp_proc_show, data);
6.3 常见问题排查
| 问题现象 |
可能原因 |
排查方法 |
| probe 函数未调用 |
设备未注册或匹配失败 |
1. 检查 /sys/bus/platform/devices 2. 检查设备树 compatible 属性 3. 检查驱动 id_table |
| 资源获取失败 |
资源定义错误或冲突 |
1. 检查设备树 reg 属性 2. 查看 cat /proc/iomem 3. 检查资源冲突 |
| 中断不触发 |
中断配置错误 |
1. 检查 /proc/interrupts 2. 验证中断控制器配置 3. 检查中断 flags |
| 内存映射失败 |
地址错误或权限问题 |
1. 检查物理地址有效性 2. 验证 ioremap 权限 3. 检查 MMU 配置 |
七、高级主题与最佳实践
7.1 多设备支持策略
当同类外设挂多个实例时,你的驱动还能不能保持结构清晰?一个常见做法是维护全局设备列表与 class,按实例创建节点:
// 支持多个相同设备的驱动设计
struct vtemp_driver_data {
struct list_head devices;
struct mutex lock;
struct class *class;
};
static int vtemp_probe(struct platform_device *pdev)
{
struct vtemp_device *dev;
struct vtemp_driver_data *drv_data;
// 获取或创建驱动全局数据
drv_data = vtemp_get_driver_data();
// 创建设备实例
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
// 初始化设备
// ...
// 添加到全局列表
mutex_lock(&drv_data->lock);
list_add_tail(&dev->list, &drv_data->devices);
mutex_unlock(&drv_data->lock);
// 创建设备节点
dev->dev = device_create(drv_data->class, &pdev->dev,
MKDEV(vtemp_major, dev->id),
dev,
"vtemp%d", dev->id);
return 0;
}
7.2 电源管理集成
// 电源管理回调
#ifdef CONFIG_PM
static int vtemp_suspend(struct device *dev)
{
struct vtemp_data *data = dev_get_drvdata(dev);
// 保存状态
data->saved_reg = ioread32(data->regs + CONFIG_REG);
// 关闭设备电源
iowrite32(0, data->regs + POWER_REG);
return 0;
}
static int vtemp_resume(struct device *dev)
{
struct vtemp_data *data = dev_get_drvdata(dev);
// 恢复电源
iowrite32(1, data->regs + POWER_REG);
// 恢复状态
iowrite32(data->saved_reg, data->regs + CONFIG_REG);
return 0;
}
static const struct dev_pm_ops vtemp_pm_ops = {
.suspend = vtemp_suspend,
.resume = vtemp_resume,
.poweroff = vtemp_suspend,
.restore = vtemp_resume,
};
#endif
7.3 DMA 支持
// DMA配置示例
static int vtemp_configure_dma(struct vtemp_data *data,
struct platform_device *pdev)
{
struct dma_slave_config config;
int ret;
// 获取DMA通道
data->dma_chan = dma_request_chan(&pdev->dev, "rx");
if (IS_ERR(data->dma_chan)) {
ret = PTR_ERR(data->dma_chan);
data->dma_chan = NULL;
return ret;
}
// 配置DMA参数
memset(&config, 0, sizeof(config));
config.direction = DMA_DEV_TO_MEM;
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.src_addr = data->regs_phys + DATA_REG;
config.src_maxburst = 4;
ret = dmaengine_slave_config(data->dma_chan, &config);
if (ret) {
dev_err(&pdev->dev, "DMA配置失败: %d\n", ret);
dma_release_channel(data->dma_chan);
data->dma_chan = NULL;
return ret;
}
return 0;
}
八、总结
通过本文的深入拆解,可以把 Platform 驱动机制的关键点归纳成一句话:用统一的设备模型承接“不可枚举设备”,用资源抽象降低硬件差异,用匹配机制把设备与驱动绑定起来。
8.1 核心架构回顾

8.2 关键概念对照表
| 概念 |
描述 |
类比 |
| Platform Device |
硬件设备的软件表示 |
课程安排表 |
| Platform Driver |
设备操作的实现 |
教师教学能力 |
| 设备树 |
硬件配置描述语言 |
建筑蓝图 |
| Probe函数 |
设备初始化入口 |
教师备课过程 |
| 资源(Resource) |
硬件资源的抽象 |
教学资源分配 |
| 匹配表 |
设备与驱动的关联规则 |
课程分配规则 |
- 分离原则:保持设备描述与驱动逻辑分离
- 资源管理:正确获取和释放所有硬件资源
- 错误处理:所有可能失败的操作都要检查返回值
- 电源管理:实现完整的电源状态转换
- 并发安全:考虑多线程访问的安全性
- 文档完整:为每个函数和数据结构添加注释
8.4 历史演进
| 时期 |
技术 |
特点 |
局限性 |
| 2.4内核 |
硬编码设备信息 |
在驱动中直接写死硬件参数 |
可移植性差,代码冗余 |
| 2.6早期 |
Platform Data |
通过板级文件传递设备信息 |
仍需代码修改,不够灵活 |
| 2.6后期 |
设备树(PowerPC) |
硬件描述与驱动分离 |
需要 Bootloader 支持 |
| 3.x以后 |
设备树(ARM) |
成为 ARM Linux 标准 |
学习曲线较陡 |
| 现代内核 |
ACPI/设备树共存 |
支持多种硬件描述方式 |
复杂性增加 |
推荐在 云栈社区 持续关注内核与驱动相关的实战案例与问题讨论。