2、Platform驱动框架概述:Platform总线、设备与驱动的匹配机制,为什么需要Platform框架

2.1 从一次“硬编码”的教训说起

我记得刚入行那会儿,接手一个老项目。

板子上挂了个GPIO按键,驱动里直接写死了中断号——IRQ 42。当时觉得挺正常,嵌入式嘛,硬件固定,写死就写死了。结果产品迭代,换了颗主控,中断号变成了56。好家伙,整个驱动得重新编译,还得改好几处代码。

你想想看,这还只是一个按键。要是换成I2C控制器、SPI设备、网卡……每个都这么搞,那代码得乱成什么样?

说白了,传统驱动开发方式就是“硬件信息硬编码”。驱动和设备紧紧耦合在一起。换一块板子,驱动就得改。这显然不是个可持续的方案。

核心痛点:驱动代码里混杂了硬件地址、中断号、DMA通道等平台相关参数。导致驱动无法跨平台复用,维护成本极高。

2.2 Platform框架的诞生:解耦的艺术

Linux内核社区也意识到了这个问题。他们想:能不能把“设备信息”和“驱动逻辑”分开?

设备信息归设备信息,比如基地址0x1000、中断号56。驱动逻辑归驱动逻辑,比如怎么初始化、怎么收发数据。两者通过一个“中间人”来牵线搭桥。

这个中间人,就是Platform总线。

Platform总线是Linux内核中一种虚拟总线。它不像PCI、USB那样有真实的物理总线,而是用来管理那些“挂载在CPU总线上、但无法被自动枚举”的设备。比如:

  • 片上外设(UART、I2C、SPI控制器)
  • GPIO控制器
  • 中断控制器
  • 一些简单的内存映射设备

我习惯把Platform总线理解成一个“婚姻介绍所”。

设备端说:“我叫uart1,我在地址0x1000,中断号56。”
驱动端说:“我能驱动uart,我的名字叫uart_driver。”
Platform总线负责匹配,匹配上了,就调用驱动的probe函数,把设备信息传进去。

这样一来,驱动里不再有硬编码的地址和中断号。所有硬件参数都从设备信息里拿。换板子?改设备信息就行,驱动代码不用动。

2.3 匹配机制:三种“相亲”方式

Platform总线怎么匹配设备和驱动?主要有三种方式。我在项目中三种都遇到过,各有各的适用场景。

匹配方式 匹配依据 典型场景
设备树匹配 compatible 属性 现代ARM/ RISC-V平台,主流方式
ACPI匹配 ACPI表项 x86平台,服务器领域
Legacy ID匹配 name 字段 老旧平台或非设备树系统

设备树匹配是目前最常用的方式。设备树里写:

uart1: serial@1000 {
    compatible = "vendor,uart-v1";
    reg = <0x1000 0x100>;
    interrupts = <56>;
};

驱动里声明:

static const struct of_device_id uart_of_match[] = {
    { .compatible = "vendor,uart-v1" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);

Platform总线看到compatible字符串一致,就“牵线成功”。

Legacy ID匹配是早期的方式。设备注册时给个名字,驱动也声明个名字,名字对上就行。简单粗暴,但容易冲突。我曾经在一个老项目里看到两个驱动用了同一个名字,结果加载顺序乱了,系统启动时串口死活不工作。排查了半天,最后发现是名字冲突。

避坑指南:我曾经在设备树里写错过compatible字符串,少了个逗号。驱动死活不probe。查了两天才发现是字符串不匹配。所以,设备树里的compatible一定要和驱动里的完全一致,包括大小写和标点。

2.4 为什么需要Platform框架?三个核心价值

你可能会问:搞这么复杂,就为了解耦?

嗯,解耦只是表面。更深层的原因有三个。

  1. 代码复用:同一份驱动代码,可以跑在不同硬件平台上。只需要更换设备树文件。我在项目中把一套UART驱动从ARM Cortex-A7移植到了RISC-V平台,驱动代码一行没改,只改了设备树。这就是Platform框架的威力。
  2. 热插拔友好:虽然Platform设备大多是片上外设,不支持物理热插拔。但框架本身支持动态添加/移除设备。比如通过sysfs接口,可以在运行时注册一个新的Platform设备,驱动会自动匹配并probe。这在某些虚拟化场景下很有用。
  3. 统一驱动模型:Linux内核有统一的设备驱动模型(Device Driver Model)。Platform框架是其中的一部分。它让所有驱动都遵循相同的生命周期管理:probe、remove、suspend、resume。内核可以统一管理电源、休眠、唤醒等操作。没有这个框架,每个驱动都得自己实现一套,那可就乱套了。

一句话总结:Platform框架让驱动开发从“面向硬件”变成了“面向框架”。你只需要关心驱动逻辑,硬件信息交给设备树或ACPI去描述。

2.5 一张图看懂Platform框架

下面这张SVG图,是我自己画的Platform框架核心逻辑。你看一遍就能明白整个流程。

Platform驱动框架核心逻辑 Platform 设备 设备树节点 compatible = "vendor,uart-v1" reg = <0x1000 0x100> interrupts = <56> Platform 驱动 of_device_id 表 .compatible = "vendor,uart-v1" .probe 函数 .remove 函数 Platform 总线 匹配机制:compatible / name / ACPI 注册 注册 匹配成功 驱动 probe 执行 获取资源 → 初始化硬件 → 注册子系统 设备与驱动绑定成功

流程很简单:设备注册到总线,驱动注册到总线。总线拿着compatible字符串去匹配。匹配上了,调用驱动的probe。probe里,驱动从设备信息里拿地址、中断号,然后初始化硬件。整个过程,驱动不关心硬件具体在哪,只关心怎么操作。

2.6 小结:Platform框架不是什么黑魔法

说白了,Platform框架就是一层“中间件”。它把硬件描述和驱动逻辑拆开,让两者可以独立演进。

你不需要记住所有细节。只要记住三个核心点:

  • 设备:描述“有什么硬件”
  • 驱动:描述“怎么操作硬件”
  • 总线:负责“谁和谁配对”

我在后续章节里,会带你手写一个完整的Platform驱动。从设备树节点怎么写,到驱动probe里怎么拿资源,一步步来。到时候你就知道,这东西其实没那么神秘。

个人习惯:我每次写新驱动,都会先在设备树里把节点搭好,确认硬件地址和中断号无误。然后再写驱动代码。顺序搞反了,容易出问题。你试试看,这个习惯能帮你省不少调试时间。


公众号:蓝海资料掘金营,微信deep3321