2. Linux 设备模型基础:总线、设备、驱动模型及匹配机制
在嵌入式 Linux 系统中,设备模型是连接硬件与软件的核心骨架。对于网关驱动加载流程而言,理解 总线(Bus)、设备(Device)、驱动(Driver) 三者之间的关系,以及它们如何通过匹配机制协同工作,是解析设备树并初始化 CAN 控制器的前提。
2.1 核心概念:总线、设备与驱动
Linux 设备模型将硬件资源抽象为三个核心角色,它们通过内核注册机制形成统一的设备树管理视图。
| 角色 | 内核数据结构 | 职责描述 | 网关场景示例 |
|---|---|---|---|
| 总线(Bus) | struct bus_type |
管理设备与驱动的注册、匹配、枚举;提供标准操作接口(如 probe、remove)。 | platform_bus(虚拟总线,挂载 SoC 内部外设)、spi、i2c、can(网络子系统) |
| 设备(Device) | struct device |
描述一个具体的硬件实体,包含资源信息(寄存器地址、中断号、时钟等)。 | CAN 控制器设备节点(如 flexcan@2090000)、GPIO 控制器、SPI 从设备 |
| 驱动(Driver) | struct device_driver |
实现硬件控制逻辑,提供 probe/remove 回调;负责初始化硬件、注册中断、创建接口。 | flexcan_driver、m_can_driver、cc770_driver |
关键理解: 总线是“媒人”,设备是“资源描述”,驱动是“操作代码”。驱动不直接操作硬件地址,而是通过设备结构体获取标准化资源。
2.2 匹配机制:驱动如何找到设备
当系统启动或模块加载时,内核会遍历总线上的设备列表,为每个设备寻找合适的驱动。匹配过程遵循严格的优先级顺序,确保设备树节点与驱动 probe 函数正确绑定。
2.2.1 匹配优先级(从高到低)
- 设备树 compatible 匹配:驱动通过
of_match_table声明支持的 compatible 字符串,与设备树节点的compatible属性比对。 - ACPI 匹配:在支持 ACPI 的平台上,使用 ACPI 表进行匹配。
- 名称匹配:驱动名称与设备名称(或设备 ID 表)直接比对。
- 平台设备 ID 表匹配:驱动通过
id_table指定支持的设备 ID。 - 默认匹配:如果以上均未命中,部分总线(如 platform)允许驱动通过名称模糊匹配。
网关开发重点: CAN 控制器驱动几乎全部依赖 设备树 compatible 匹配。设备树节点中的
compatible = "fsl,imx6q-flexcan", "fsl,imx6ul-flexcan"; 必须与驱动中 of_match_table 的字符串完全一致,否则驱动不会 probe。
2.2.2 匹配流程伪代码
// 内核总线核心匹配逻辑(简化)
int bus_match(struct device *dev, struct device_driver *drv) {
// 1. 优先检查设备树匹配
if (drv->of_match_table) {
if (of_match_node(drv->of_match_table, dev->of_node))
return 1; // 匹配成功
}
// 2. 检查 ACPI 匹配
if (acpi_driver_match_device(dev, drv))
return 1;
// 3. 检查 ID 表匹配
if (drv->id_table) {
if (platform_match_id(drv->id_table, dev))
return 1;
}
// 4. 名称回退匹配
if (strcmp(dev_name(dev), drv->name) == 0)
return 1;
return 0; // 匹配失败
}
2.3 设备模型与设备树的关联
设备树(Device Tree)是设备模型在嵌入式平台上的“实例化蓝图”。内核在启动阶段解析设备树二进制(DTB),为每个节点创建 struct device 并挂载到对应总线上。
2.4 驱动加载与 probe 触发流程
当驱动与设备匹配成功后,内核自动调用驱动的 probe 函数。对于 CAN 控制器驱动,probe 函数是初始化硬件、注册网络设备的入口。
2.4.1 标准加载步骤
| 步骤 | 动作 | 关键函数/操作 |
|---|---|---|
| 1 | 驱动注册 | platform_driver_register(&flexcan_driver) |
| 2 | 总线遍历设备 | 内核遍历 platform_bus 设备链表,调用 bus_match() |
| 3 | 匹配成功 | 通过 of_match_table 命中设备树节点 |
| 4 | 调用 probe | flexcan_probe(struct platform_device *pdev) |
| 5 | 资源获取 | platform_get_resource() 获取寄存器基址、中断号 |
| 6 | 硬件初始化 | 时钟使能、复位 CAN 控制器、配置波特率、设置过滤器 |
| 7 | 网络设备注册 | register_candev() 将 CAN 设备注册到网络子系统 |
2.4.2 probe 函数典型结构
static int flexcan_probe(struct platform_device *pdev) {
struct net_device *ndev;
struct flexcan_priv *priv;
struct resource *mem;
int irq, err;
// 1. 分配网络设备 + 私有数据
ndev = alloc_candev(sizeof(*priv), TX_QUEUES);
priv = netdev_priv(ndev);
// 2. 获取设备树资源
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(&pdev->dev, mem);
irq = platform_get_irq(pdev, 0);
// 3. 获取时钟
priv->clk = devm_clk_get(&pdev->dev, NULL);
clk_prepare_enable(priv->clk);
// 4. 硬件初始化(复位、配置模式、设置波特率)
err = flexcan_chip_init(priv);
// 5. 设置网络设备操作函数
ndev->netdev_ops = &flexcan_netdev_ops;
ndev->flags |= IFF_ECHO;
// 6. 注册 CAN 设备
err = register_candev(ndev);
platform_set_drvdata(pdev, ndev);
return 0;
}
常见匹配失败原因:
• 设备树 compatible 字符串与驱动
• 驱动未注册到正确的总线(CAN 控制器通常挂载在 platform_bus,而非 SPI/I2C 总线)。
• 设备树节点 status = "disabled" 导致内核跳过该设备。
• 缺少必要的时钟或中断资源,probe 函数提前返回 -ENODEV。
• 设备树 compatible 字符串与驱动
of_match_table 不匹配(注意大小写、厂商前缀)。• 驱动未注册到正确的总线(CAN 控制器通常挂载在 platform_bus,而非 SPI/I2C 总线)。
• 设备树节点 status = "disabled" 导致内核跳过该设备。
• 缺少必要的时钟或中断资源,probe 函数提前返回 -ENODEV。
2.5 总线、设备、驱动关系总结
设备模型的三元组关系可以用以下逻辑闭环概括:
- 设备树提供“是什么”:描述硬件存在的位置、资源、配置。
- 总线提供“怎么连”:定义设备与驱动的通信协议和匹配规则。
- 驱动提供“怎么用”:实现硬件操作逻辑,向内核注册标准化接口。
在网关驱动加载流程中,设备树解析完成后,内核自动完成设备注册;当 CAN 控制器驱动模块加载或内核内置时,总线匹配机制触发 probe,驱动开始接管硬件初始化。这一模型保证了代码的可移植性和硬件描述的独立性。