1. 课程导入
在嵌入式Linux开发中,驱动工程师面对的第一个“拦路虎”往往不是寄存器操作,而是Linux那套抽象而庞大的设备驱动模型。很多初学者能写出裸机驱动,却在把代码接入内核时迷失方向:为什么要有bus、device、driver三者的分离?为什么probe函数迟迟不被调用?Platform驱动又在这套体系中扮演什么角色?本章作为整个课程的开篇,先建立一张全局地图,让后续每一行代码都能找到自己的坐标。
一、Linux设备驱动模型的核心思想
Linux设备驱动模型(Linux Device Driver Model,简称LDDM)是内核2.6之后逐步完善的一套统一框架,其根本目标是解决三个问题:设备与驱动的解耦、电源管理的统一、以及sysfs用户态视图的建立。它的核心抽象只有三个对象:总线(bus)、设备(device)、驱动(driver)。
总线是匹配规则的持有者,它维护两条链表:一条挂设备,一条挂驱动。每当有新设备或新驱动注册进来,总线就会尝试用匹配函数(match)把二者配对,配对成功后调用驱动的probe函数。这种“注册即匹配”的机制,使得驱动不必关心设备何时出现,设备也不必知道谁来驱动它,双方只依赖总线这一中间层。
在sysfs中,这套关系被完整地映射为目录树:/sys/bus/下每个总线都有devices/和drivers/两个子目录,通过软链接可以互相跳转。理解这张目录树,是调试驱动加载问题的第一把钥匙。
二、Platform驱动在模型中的定位
并非所有设备都能像USB、PCI那样被总线自动枚举。SoC内部集成了大量控制器——UART、I2C、SPI、GPIO、看门狗、DMA——它们没有可探测的ID,地址和中断号是硬件设计时固定的。为了把这类“不可枚举”的设备也纳入统一模型,内核引入了Platform总线,它是一种虚拟总线,用软件方式描述硬件资源。
Platform驱动因此处于一个特殊位置:它不是独立于设备驱动模型之外的东西,而是模型的一个具体实例。它复用bus/device/driver的匹配机制,只是匹配依据从设备ID变成了设备树compatible字符串或平台设备名。可以说,掌握了Platform驱动,就掌握了Linux驱动模型的通用套路,再去看I2C、SPI子系统,会发现结构惊人地相似。
三、从注册到probe的完整流程
下面用一段最小化的Platform驱动骨架,串起整个流程。注意观察注册顺序与匹配时机。
#include <linux/platform_device.h>
#include <linux/of.h>
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev_info(&pdev->dev, "probe ok, mem start=%pa\n", &res->start);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "remove\n");
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,demo-dev" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "demo-drv",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
对应的设备树节点只需声明compatible,内核启动时会把该节点展开为platform_device,随后总线匹配到驱动并调用probe:
demo@0x12000000 {
compatible = "vendor,demo-dev";
reg = <0x12000000 0x1000>;
interrupts = <0 45 4>;
clocks = <&clk_demo>;
};
四、典型误区与复盘
误区一:把Platform驱动当成“另一种驱动写法”。它本质仍是设备驱动模型,只是总线是虚拟的。若忽略bus/device/driver三者关系,遇到probe不执行时就会无从下手。
误区二:compatible字符串写错或漏写。这是probe不触发最常见的原因。设备树中的compatible必须与of_match_table中的字符串逐字符一致,包括厂商前缀。
误区三:在probe中直接使用硬编码地址。正确做法是通过platform_get_resource或devm_ioremap_resource获取资源,否则设备树就失去了意义。
五、学习目标与课程框架
本课程围绕“能独立完成一个真实SoC外设的Platform驱动”这一目标展开,整体分为五个阶段:
| 阶段 | 核心内容 | 产出 |
|---|---|---|
| 基础篇 | 驱动模型、模块机制、字符设备、sysfs | 可加载的最小驱动模块 |
| Platform篇 | platform_device/driver、设备树、资源获取 | 能被probe的Platform驱动 |
| 进阶篇 | 并发控制、中断、DMA、电源管理 | 稳定可用的完整驱动 |
| 调试篇 | printk、ftrace、sysfs调试、常见故障定位 | 独立排障能力 |
| 实战篇 | 综合项目:从设备树到用户态接口全链路 | 可交付的驱动项目 |
学习建议:每学一个概念,都回到/sys/bus/platform/下用ls -l观察真实的内核对象关系。看得见的目录树,比任何文字描述都更能建立直觉。
当你能够清晰地回答“设备从哪来、驱动怎么匹配、probe何时执行、资源如何获取”这四个问题,Linux驱动模型的大门就已经为你打开。接下来的章节,我们将从最基础的模块加载开始,一步步把这张地图走成脚下的路。