找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2128

积分

0

好友

271

主题
发表于 昨天 19:00 | 查看: 9| 回复: 0

引言:为什么需要 Platform 驱动?

在嵌入式 Linux 系统中,我们经常会遇到大量片上系统(SoC)外设。这类外设在硬件上通常直接集成在芯片内部,但在软件上却没有像 PCI、USB 那样的标准枚举机制。想象一下:如果一个城市没有门牌号系统和邮政系统,快递员如何准确找到每家每户?Platform 驱动机制就是 Linux 为解决这个问题而设计的“片上设备寻址系统”。

Platform 驱动的本质:一种将那些无法被标准总线枚举机制识别的设备(主要是 SoC 内部外设)纳入 Linux 设备模型管理框架的抽象机制。它通过设备树(Device Tree)平台数据(Platform Data)等静态描述方式,在设备与驱动之间建立连接。若你想系统补齐 Linux 内核相关知识脉络,可在 网络/系统 板块延伸阅读。

一、设计哲学:分离与抽象

1.1 核心设计思想

Platform 驱动的设计遵循两个核心原则:

  1. 设备与驱动分离原则

    • 设备描述硬件资源(寄存器地址、中断号等)
    • 驱动描述操作方法(如何初始化、如何读写等)
    • 两者通过名称或兼容性字符串进行匹配
  2. 资源抽象原则

    • 将硬件资源(内存、中断、时钟、GPIO 等)抽象为统一的数据结构
    • 提供标准 API 访问这些资源,使驱动与具体硬件细节解耦

Platform设计思想示意图

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 数据结构关系图解

Platform数据结构关系

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 完整生命周期流程

Platform驱动生命周期

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;
}

四、设备树集成机制

4.1 设备树中的 Platform 设备描述

设备树是现代嵌入式 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 调试技巧和实践

  1. 动态调试输出
// 在驱动中添加动态调试
#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);
  1. 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);
  1. 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 核心架构回顾

Platform核心架构回顾

8.2 关键概念对照表

概念 描述 类比
Platform Device 硬件设备的软件表示 课程安排表
Platform Driver 设备操作的实现 教师教学能力
设备树 硬件配置描述语言 建筑蓝图
Probe函数 设备初始化入口 教师备课过程
资源(Resource) 硬件资源的抽象 教学资源分配
匹配表 设备与驱动的关联规则 课程分配规则

8.3 Platform 驱动开发的黄金法则

  1. 分离原则:保持设备描述与驱动逻辑分离
  2. 资源管理:正确获取和释放所有硬件资源
  3. 错误处理:所有可能失败的操作都要检查返回值
  4. 电源管理:实现完整的电源状态转换
  5. 并发安全:考虑多线程访问的安全性
  6. 文档完整:为每个函数和数据结构添加注释

8.4 历史演进

时期 技术 特点 局限性
2.4内核 硬编码设备信息 在驱动中直接写死硬件参数 可移植性差,代码冗余
2.6早期 Platform Data 通过板级文件传递设备信息 仍需代码修改,不够灵活
2.6后期 设备树(PowerPC) 硬件描述与驱动分离 需要 Bootloader 支持
3.x以后 设备树(ARM) 成为 ARM Linux 标准 学习曲线较陡
现代内核 ACPI/设备树共存 支持多种硬件描述方式 复杂性增加

推荐在 云栈社区 持续关注内核与驱动相关的实战案例与问题讨论。




上一篇:推理环境感知训练:TensorRT/INT8部署精度对齐
下一篇:Ralph Loop实践:Stop Hook让AI Agent持续迭代
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 15:54 , Processed in 0.217472 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表