第一章:车规级驱动模型总览
各位同学,咱们今天聊聊Linux内核里的设备驱动模型。说实话,我刚入行那会儿,也被kobject、kset这些东西搞得晕头转向。后来做了几个车规项目,才慢慢摸到门道。
你想想看,一个车载系统里,可能有几十个外设——摄像头、雷达、CAN控制器、以太网交换机……每个设备都得有个驱动。如果没有一套统一的管理机制,那代码得乱成什么样?
内核的驱动模型,说白了就是干这个的。它用一套抽象框架,把设备、驱动、总线这三者串起来。我习惯把它理解成「相亲平台」——设备是姑娘,驱动是小伙,总线就是那个媒人。
1.1 kobject / kset / sysfs:内核里的对象管理
先说说kobject。这是内核里最基础的对象结构。你看代码:
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
每个kobject都对应sysfs里的一个目录。我做过一个项目,调试时发现某个设备节点没出现,查了半天,原来是kobject的parent没设对。嗯,这种坑踩过一次就记住了。
kset呢?它是一组kobject的集合。你可以把它理解成一个「群组」。比如所有块设备驱动,可以放在同一个kset下。sysfs里看到的/sys/bus/、/sys/class/,背后都是kset在支撑。
sysfs是内核暴露给用户空间的窗口。你cat /sys/class/xxx/uevent,就能看到设备的热插拔事件。我个人习惯在调试驱动时,先看看sysfs下有没有对应的目录——如果目录都没生成,那probe肯定没走通。
核心要点:kobject是内核对象的最小单元,kset管理一组kobject,sysfs是它们的用户空间映射。三者配合,构成了内核的设备管理骨架。
1.2 驱动与设备的匹配机制
设备来了,驱动怎么找到它?内核有一套匹配机制。最常见的是基于设备树(Device Tree)的匹配。
设备树里描述硬件:
i2c@ff160000 {
compatible = "snps,designware-i2c";
reg = <0xff160000 0x1000>;
interrupts = <0 20 4>;
clocks = <&i2c_clk>;
#address-cells = <1>;
#size-cells = <0>;
temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
驱动里声明:
static const struct of_device_id tmp102_of_match[] = {
{ .compatible = "ti,tmp102" },
{ }
};
MODULE_DEVICE_TABLE(of, tmp102_of_match);
匹配时,内核会遍历设备树节点,拿compatible属性和驱动里的of_device_id表比对。一旦匹配上,就调用驱动的probe函数。
除了设备树,还有ACPI匹配、I2C设备ID表匹配、SPI设备ID表匹配等等。车规场景下,设备树用得最多。我记得有一次,客户说某个传感器驱动加载不上,我一看设备树,compatible写成了"ti,tmp102a"——少了个字母。这种低级错误,排查起来最费时间。
避坑指南:我曾经在调试时发现驱动匹配不上,查了两天才发现是设备树里reg属性写错了。I2C地址是7位还是8位?设备树里用的是7位地址,左移一位后的值。这个细节,新手特别容易搞混。
1.3 probe流程详解
匹配成功后,内核会调用驱动的probe函数。这是驱动初始化的核心入口。咱们看看典型的probe流程:
static int tmp102_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tmp102 *tmp102;
int ret;
// 1. 分配私有数据结构
tmp102 = devm_kzalloc(&client->dev, sizeof(*tmp102), GFP_KERNEL);
if (!tmp102)
return -ENOMEM;
// 2. 初始化硬件
ret = tmp102_init_client(client);
if (ret < 0)
return ret;
// 3. 注册中断
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, tmp102_interrupt,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"tmp102", tmp102);
if (ret < 0)
return ret;
// 4. 注册到内核框架(如hwmon、input等)
tmp102->hwmon_dev = devm_hwmon_device_register_with_info(
&client->dev, client->name, tmp102,
&tmp102_chip_info, NULL);
// 5. 保存私有数据
i2c_set_clientdata(client, tmp102);
return 0;
}
probe里一般做这几件事:
- 分配资源——内存、GPIO、中断、时钟等
- 初始化硬件——写寄存器、复位、校准
- 注册子系统——比如hwmon、input、net等
- 保存上下文——把私有数据挂到设备上
我建议你在probe函数开头加一句dev_info,打印设备信息。这样调试时,看内核日志就知道probe有没有走到。我曾经遇到一个情况,probe返回了0,但设备就是不工作——后来发现是中断号没配对,probe虽然成功了,但中断触发不了。
注意:probe函数里不要做耗时操作。车规系统对启动时间有严格要求,有些ECU要求在几百毫秒内完成所有驱动初始化。如果probe里做了msleep(100),那整个启动时序就崩了。我见过一个项目,就因为probe里加了1秒延时,导致系统无法通过启动时间测试。
probe失败怎么办?内核会返回错误码,驱动不会绑定到设备上。常见的失败原因有:
| 错误码 | 含义 | 常见场景 |
|---|---|---|
| -ENODEV | 设备不存在 | I2C地址不对,设备没上电 |
| -ENOMEM | 内存不足 | 分配DMA缓冲区失败 |
| -EINVAL | 参数无效 | 设备树属性解析错误 |
| -EPROBE_DEFER | 延迟probe | 依赖的资源还没准备好 |
说到-EPROBE_DEFER,这个很有意思。车规系统里,设备初始化有先后顺序。比如某个传感器依赖I2C控制器,但I2C控制器驱动还没加载。这时候传感器驱动probe时发现找不到父设备,就返回-EPROBE_DEFER。内核会把该驱动放到延迟队列,等依赖设备就绪后再重新尝试probe。
我做过一个项目,有7个设备互相依赖,probe顺序乱成一团。最后靠-EPROBE_DEFER机制,内核自动排好了顺序。你想想看,要是没有这个机制,我们得手动写一堆初始化顺序表,那得多痛苦。
总结一下:设备驱动模型是内核的基石。kobject/kset/sysfs提供了对象管理框架,匹配机制把设备和驱动牵上线,probe函数则是驱动初始化的主战场。搞懂这三块,车规级驱动开发就算入门了。
下一章,咱们深入聊聊设备树——车规场景下最常用的硬件描述语言。到时候我会分享一些实际项目中的设备树调试技巧,保证让你少走弯路。