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 匹配优先级(从高到低)

  1. 设备树 compatible 匹配:驱动通过 of_match_table 声明支持的 compatible 字符串,与设备树节点的 compatible 属性比对。
  2. ACPI 匹配:在支持 ACPI 的平台上,使用 ACPI 表进行匹配。
  3. 名称匹配:驱动名称与设备名称(或设备 ID 表)直接比对。
  4. 平台设备 ID 表匹配:驱动通过 id_table 指定支持的设备 ID。
  5. 默认匹配:如果以上均未命中,部分总线(如 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 并挂载到对应总线上。

设备树 → 设备模型 映射关系 设备树(DTB) / { soc { can@2090000 { compatible = "fsl,flexcan"; reg = <0x02090000 0x4000>; interrupts = <0 142 4>; clocks = <&clks 2>; }; }; }; 内核解析 Linux 设备模型 platform_bus (struct bus_type) struct device init_name: "2090000.can" of_node: &can_node struct device_driver name: "flexcan" of_match_table: flexcan_of_match 匹配 驱动 probe() 获取资源 platform_get_resource(pdev, IORESOURCE_MEM, 0) → reg platform_get_irq(pdev, 0) → irq

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 字符串与驱动 of_match_table 不匹配(注意大小写、厂商前缀)。
• 驱动未注册到正确的总线(CAN 控制器通常挂载在 platform_bus,而非 SPI/I2C 总线)。
• 设备树节点 status = "disabled" 导致内核跳过该设备。
• 缺少必要的时钟或中断资源,probe 函数提前返回 -ENODEV。

2.5 总线、设备、驱动关系总结

设备模型的三元组关系可以用以下逻辑闭环概括:

  • 设备树提供“是什么”:描述硬件存在的位置、资源、配置。
  • 总线提供“怎么连”:定义设备与驱动的通信协议和匹配规则。
  • 驱动提供“怎么用”:实现硬件操作逻辑,向内核注册标准化接口。

在网关驱动加载流程中,设备树解析完成后,内核自动完成设备注册;当 CAN 控制器驱动模块加载或内核内置时,总线匹配机制触发 probe,驱动开始接管硬件初始化。这一模型保证了代码的可移植性和硬件描述的独立性。