Linux Platform驱动深度剖析: 从设计思想到实战解析
引言: 为什么需要Platform驱动?
在嵌入式Linux系统中, 我们经常会遇到大量片上系统(SoC) 外设, 这些外设在硬件上通常直接集成在芯片内部, 在软件上却没有像PCI、USB那样的标准枚举机制. 想象一下, 如果一个城市没有门牌号系统和邮政系统, 快递员如何准确找到每家每户?Platform驱动机制就是Linux为解决这个问题而设计的"片上设备寻址系统"
Platform驱动的本质 : 一种将那些无法被标准总线枚举机制识别的设备(主要是SoC内部外设)纳入Linux设备模型管理框架的抽象机制. 它通过设备树(Device Tree) 或平台数据(Platform Data) 等静态描述方式, 在设备和驱动之间建立连接
一、设计哲学: 分离与抽象
1.1 核心设计思想
Platform驱动的设计遵循两个核心原则:
-
设备与驱动分离原则
- 设备描述硬件资源(寄存器地址、中断号等)
- 驱动描述操作方法(如何初始化、如何读写等)
- 两者通过名称或兼容性字符串进行匹配
-
资源抽象原则
- 将硬件资源(内存、中断、时钟、GPIO等)抽象为统一的数据结构
- 提供标准API访问这些资源, 使驱动与具体硬件细节解耦
用户空间
驱动层
内核核心层
抽象层
硬件层
具体硬件电路
设备树描述
平台数据
Platform机制
设备模型
Platform Device
Platform Driver
字符设备
工业IO
输入子系统
用户应用1
用户应用2
sys文件系统
1.2 现实世界类比: 学校课程管理系统
为了更好地理解Platform机制, 让我们想象一个学校的课程管理系统:
| 系统组件 | Platform机制对应 | 学校系统类比 |
|---|---|---|
| Platform Device | 课程安排表 | 每门课程的时间、教室、学生名单等资源描述 |
| Platform Driver | 教师教学能力 | 教师的教学方法、教案、授课技巧等 |
| 匹配过程 | 课程分配 | 教务处根据课程需求匹配适合的教师 |
| 设备树 | 全校课程总表 | 描述所有课程及其资源需求的中央数据库 |
| Probe函数 | 教师备课 | 教师拿到课程表后开始准备教学材料 |
| 资源管理 | 教学资源分配 | 分配教室、投影仪、实验设备等 |
二、核心数据结构解剖
2.1 三大支柱结构
c
// 核心数据结构定义(简化版)
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 数据结构关系图解
包含
继承
继承
包含
包含
包含
指向
platform_device
+const char* name
+int id
+struct device dev
+u32 num_resources
+struct resource* resource
+platform_device_id* id_entry
platform_driver
+struct device_driver driver
+platform_device_id* id_table
+int (probe)(platform_device)
+int (remove)(platform_device)
device_driver
+const char* name
+struct module* owner
+const struct of_device_id* of_match_table
+int (probe)(device)
device
+struct device* parent
+void* platform_data
+struct device_node* of_node
+const char* init_name
resource
+resource_size_t start
+resource_size_t end
+const char* name
+unsigned long flags
platform_device_id
+char name[PLATFORM_NAME_SIZE]
+kernel_ulong_t driver_data
of_device_id
+char name[32]
+char type[32]
+char compatible[128]
+void* data
device_node
2.3 关键数据结构详解
2.3.1 struct resource - 资源描述符
资源是Platform机制的核心抽象, 它统一描述了各种硬件资源:
c
// 资源类型标志(部分)
#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 完整生命周期流程
系统其他部分 Platform Device Platform Driver Platform Core BSP/设备树 系统其他部分 Platform Device Platform Driver Platform Core BSP/设备树 系统启动阶段 驱动加载阶段 设备进入工作状态 驱动加载失败 alt [找到匹配设备] [未找到匹配] 运行阶段 loop [正常运行] 卸载/关机阶段 注册Platform Device (platform_device_register) 添加到全局设备链表 注册Platform Driver (platform_driver_register) 遍历设备链表查找匹配 调用probe函数 获取资源 (platform_get_resource等) 初始化硬件 注册子设备 注册字符设备/其他接口 返回-ENODEV 通过file_operations交互 读写寄存器/处理中断 卸载驱动/关机请求 调用remove函数 释放资源 注销设备接口 驱动卸载完成
3.2 Probe函数的详细执行过程
Probe函数是Platform驱动的"出生证明", 它的执行流程值得深入分析:
c
// 典型的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;
}
四、设备树集成机制
4.1 设备树中的Platform设备描述
设备树是现代嵌入式Linux系统的"硬件蓝图", 它用文本形式描述硬件配置:
dts
// 设备树节点示例
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 设备树与驱动的绑定过程
运行时
启动阶段
编译阶段
.dts源文件
dtc编译器
.dtb二进制文件
Bootloader
Linux内核
Open Firmware API
Platform Driver
of_match_table
probe函数
4.3 从设备树获取数据的API
c
// 常用设备树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);
}
五、完整实例: 虚拟温度传感器驱动
让我们通过一个完整的虚拟温度传感器驱动实例, 理解Platform驱动的实际应用:
5.1 设备树描述
dts
// 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 驱动实现
c
// 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配置
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 |
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 |
ofdump |
设备树查看工具 | ofdump /proc/device-tree |
6.2 调试技巧和实践
- 动态调试输出
c
// 在驱动中添加动态调试
#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调试接口
c
// 添加调试属性
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接口
c
// 创建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 多设备支持策略
c
// 支持多个相同设备的驱动设计
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 电源管理集成
c
// 电源管理回调
#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支持
c
// 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 核心架构回顾
硬件设备
设备描述
设备树/平台数据
Platform核心
驱动代码
Platform Driver
匹配成功
调用probe
获取资源
初始化硬件
注册到子系统
设备可用
8.2 关键概念对照表
| 概念 | 描述 | 类比 |
|---|---|---|
| Platform Device | 硬件设备的软件表示 | 课程安排表 |
| Platform Driver | 设备操作的实现 | 教师教学能力 |
| 设备树 | 硬件配置描述语言 | 建筑蓝图 |
| Probe函数 | 设备初始化入口 | 教师备课过程 |
| 资源(Resource) | 硬件资源的抽象 | 教学资源分配 |
| 匹配表 | 设备与驱动的关联规则 | 课程分配规则 |
8.3 Platform驱动开发的黄金法则
- 分离原则: 保持设备描述与驱动逻辑分离
- 资源管理: 正确获取和释放所有硬件资源
- 错误处理: 所有可能失败的操作都要检查返回值
- 电源管理: 实现完整的电源状态转换
- 并发安全: 考虑多线程访问的安全性
- 文档完整: 为每个函数和数据结构添加注释
8.4 历史演进
| 时期 | 技术 | 特点 | 局限性 |
|---|---|---|---|
| 2.4内核 | 硬编码设备信息 | 在驱动中直接写死硬件参数 | 可移植性差, 代码冗余 |
| 2.6早期 | Platform Data | 通过板级文件传递设备信息 | 仍需代码修改, 不够灵活 |
| 2.6后期 | 设备树(PowerPC) | 硬件描述与驱动分离 | 需要Bootloader支持 |
| 3.x以后 | 设备树(ARM) | 成为ARM Linux标准 | 学习曲线较陡 |
| 现代内核 | ACPI/设备树共存 | 支持多种硬件描述方式 | 复杂性增加 |