驱动模型总览:Linux设备驱动模型核心概念
大家好,我是你们的老朋友。今天咱们聊聊Linux设备驱动模型。说实话,我刚入行那会儿,也被这套东西搞得晕头转向。什么总线、设备、驱动,感觉像一团乱麻。但后来我发现,搞懂这个模型,就像拿到了嵌入式开发的万能钥匙。
你想想看,一个嵌入式系统里,CPU、内存、外设,它们怎么协同工作?驱动模型就是答案。它把硬件抽象成一个个对象,让内核能统一管理。说白了,就是给硬件世界立规矩。
总线、设备、驱动:铁三角关系
这三者的关系,我习惯用一个比喻:总线是高速公路,设备是收费站,驱动是收费员。没有高速公路,收费站和收费员就没法工作。没有收费站,收费员就没事干。没有收费员,收费站就是个摆设。
在Linux内核里,它们是这样配合的:
- 总线:负责匹配设备和驱动。它维护着两个链表,一个挂设备,一个挂驱动。
- 设备:代表一个物理或虚拟硬件。它告诉内核“我在这儿,我有这些资源”。
- 驱动:知道怎么操作设备。它告诉内核“我能管这类设备,给我资源我就干活”。
核心要点:设备和驱动通过总线“相亲”。总线负责牵线搭桥,一旦匹配成功,驱动就绑定到设备上,开始干活。
我在项目中遇到过一个问题:一个I2C触摸屏死活不工作。查了半天,发现是设备树里设备名写错了,驱动匹配不上。嗯,从那以后,我每次写设备树都反复核对名字。
platform总线:嵌入式系统的灵魂
说到platform总线,我得先问一句:为什么需要它?
你看,PCI设备、USB设备,它们都有标准的总线协议。但嵌入式系统里,很多设备是直接挂在CPU总线上的,比如GPIO、SPI、I2C控制器。这些设备没有标准总线,怎么办?
Linux内核的解决方案就是——platform总线。它是个虚拟总线,专门为那些“没有总线”的设备服务。我个人习惯叫它“万能插座”,什么设备都能往上插。
platform总线的工作原理
platform总线的工作流程,说白了就三步:
- 注册设备:调用
platform_device_register(),把设备挂到总线上。 - 注册驱动:调用
platform_driver_register(),把驱动挂到总线上。 - 匹配绑定:总线遍历设备和驱动,找到匹配项,调用驱动的probe函数。
匹配规则很简单:设备名和驱动名相同。或者通过设备树里的compatible属性匹配。
我的小技巧:写platform驱动时,我习惯在驱动名字后面加个版本号,比如“my_drv_v1”。这样调试时一眼就能看出加载的是哪个版本。
代码示例:一个简单的platform驱动
来看个实际例子。这是我以前写的一个LED驱动骨架:
// 驱动结构体
static struct platform_driver led_driver = {
.driver = {
.name = "my_led",
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};
// probe函数
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
// 获取设备资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resource\n");
return -ENODEV;
}
// 映射寄存器地址
led_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!led_base) {
return -ENOMEM;
}
dev_info(&pdev->dev, "LED driver probed successfully\n");
return 0;
}
// 模块入口
static int __init led_init(void)
{
return platform_driver_register(&led_driver);
}
module_init(led_init);
这段代码里,platform_get_resource 是个关键函数。它从设备树里提取寄存器地址、中断号等信息。我曾经踩过一个坑:设备树里reg属性写错了,结果ioremap返回NULL,驱动直接崩溃。所以,设备树一定要仔细检查。
设备树与platform总线的配合
现在嵌入式开发,基本都离不开设备树。设备树就是硬件的“身份证”,告诉内核“我是什么、我在哪、我需要什么”。
一个典型的设备树节点长这样:
my_led: led@ff200000 {
compatible = "my,led";
reg = <0xff200000 0x1000>;
interrupts = <0 42 4>;
status = "okay";
};
内核解析设备树时,会为每个节点创建一个 platform_device。然后platform总线拿着这个设备,去匹配驱动。匹配的关键就是 compatible 属性。
注意:设备树里的compatible属性,一定要和驱动里 of_match_table 中的字符串完全一致。大小写、标点符号都不能错。我见过有人把逗号写成句号,结果驱动死活加载不上。
驱动模型的层次结构
Linux设备驱动模型,其实是个树形结构。顶层是 bus_type,下面挂着 device 和 device_driver。每个设备又可能挂在子总线上,形成嵌套。
举个例子:
- 一个I2C控制器,挂在platform总线上。
- 这个I2C控制器上,又挂着一个触摸屏设备。
- 触摸屏设备,由对应的I2C驱动来操作。
这种层次结构,让内核能灵活管理各种设备。我做过一个项目,系统里有3个I2C控制器,每个控制器上挂了5-6个设备。用驱动模型管理起来,井井有条。
避坑指南
最后,分享几个我踩过的坑:
- 资源冲突:两个驱动同时操作同一个寄存器,系统就崩了。解决办法是使用
devm_*系列函数,自动管理资源。 - probe失败不清理:probe函数里,如果中途出错,一定要释放已申请的资源。否则内存泄漏,系统迟早挂掉。
- 设备树和驱动不匹配:这是最常见的问题。我建议在驱动里加打印,看看probe有没有被调用。没有调用,99%是匹配问题。
调试小技巧:在 /sys/bus/platform/devices/ 目录下,可以看到所有platform设备。在 /sys/bus/platform/drivers/ 下,可以看到所有platform驱动。如果设备没出现,说明设备树没解析成功。如果驱动没出现,说明驱动没注册成功。
好了,这一章就到这里。驱动模型是嵌入式开发的基石,搞懂了它,后面的路就好走了。下一章,咱们深入讲讲设备树的解析过程,到时候见。