在嵌入式Linux系统中, 我们经常会遇到大量片上系统(SoC) 外设, 这些外设在硬件上通常直接集成在芯片内部, 在软件上却没有像PCI、USB那样的标准枚举机制. 想象一下, 如果一个城市没有门牌号系统和邮政系统, 快递员如何准确找到每家每户?Platform驱动机制就是Linux为解决这个问题而设计的"片上设备寻址系统"
Platform驱动的本质: 一种将那些无法被标准总线枚举机制识别的设备(主要是SoC内部外设)纳入Linux设备模型管理框架的抽象机制. 它通过设备树(Device Tree) 或平台数据(Platform Data) 等静态描述方式, 在设备和驱动之间建立连接
Platform驱动的设计遵循两个核心原则:

为了更好地理解Platform机制, 让我们想象一个学校的课程管理系统:
// 核心数据结构定义(简化版)struct platform_device { const char *name; // 设备名称, 用于匹配 int id; // 设备ID, -1表示唯一设备struct device dev; // 继承自通用设备结构 u32 num_resources; // 资源数量struct resource *resource; // 资源数组指针 conststruct platform_device_id *id_entry; // 设备ID表 /* ... 其他成员 ... */};struct platform_driver { int (*probe)(struct platform_device *); // 探测函数 int (*remove)(struct platform_device *); // 移除函数struct device_driver driver; // 继承自通用驱动 conststruct platform_device_id *id_table; // 支持的设备ID表 /* ... 其他成员 ... */};struct resource { resource_size_t start; // 资源起始地址/中断号 resource_size_t end; // 资源结束地址 const char *name; // 资源名称 unsigned long flags; // 资源类型标志 /* ... 其他成员 ... */};
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 // 总线编号// 资源获取APIstruct 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);Platform机制支持多种匹配方式, 按照优先级从高到低:
of_match_table中的compatible字段 | ||
id_table中的名称匹配 | ||

Probe函数是Platform驱动的"出生证明", 它的执行流程值得深入分析:
// 典型的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>; };};
// 常用设备树APIstruct 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驱动的实际应用:
// 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"; };};// 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 4struct 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);staticstruct 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 conststruct of_device_id vtemp_of_match[] = { { .compatible = "demo,virtual-temp", }, { },};MODULE_DEVICE_TABLE(of, vtemp_of_match);// Platform驱动结构staticstruct 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");# 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.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 |
// 在驱动中添加动态调试#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);// 添加调试属性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);// 创建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);/sys/bus/platform/devices2. 检查设备树compatible属性3. 检查驱动id_table | ||
cat /proc/iomem3. 检查资源冲突 | ||
/proc/interrupts2. 验证中断控制器配置3. 检查中断flags | ||
// 支持多个相同设备的驱动设计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;}// 电源管理回调#ifdef CONFIG_PMstatic 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 conststruct dev_pm_ops vtemp_pm_ops = { .suspend = vtemp_suspend, .resume = vtemp_resume, .poweroff = vtemp_suspend, .restore = vtemp_resume,};#endif// 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驱动机制的核心要点总结如下:
