Linux Device Link机制

Linux Device Link 机制详解

Device link(设备链接)是Linux内核驱动核心中用来表示设备之间依赖关系的机制。它允许驱动作者描述超越单纯父子层次关系的设备依赖,例如兄弟设备之间的依赖,同时能够让驱动核心自动处理这些依赖关系。

Device link 结合了两种依赖类型:

  1. 排序依赖:保证"供应商(supplier)"设备和"消费者(consumer)"设备之间正确的挂起/恢复和关闭排序列
  2. 驱动存在依赖:保证供应商设备先绑定驱动,消费者设备才会被探测;供应商解除绑定前,消费者会先解除绑定

根据需求,可以通过标志位配置不同行为:

  1. DL_FLAG_STATELESS:只需要正确的挂起/恢复和关闭排序,不需要强制驱动存在依赖
  2. DL_FLAG_PM_RUNTIME:启用运行时PM集成,消费者运行时恢复时自动恢复供应商并保持其活动

默认情况下,Linux驱动核心仅根据设备层次结构中的父子关系处理设备依赖:

  1. 挂起/关闭时:子设备总是在父设备之前挂起
  2. 恢复时:父设备总是在子设备之前恢复

但实际场景中存在更多依赖需求:

  1. 非父子关系的依赖:兄弟设备之间也可能存在依赖,需要保证特定的执行顺序
  2. 驱动存在依赖:一个设备必须等待另一个设备绑定驱动后,自身才能正确探测或运行
    这两种依赖通常同时存在,device link正好满足了这个需求,允许在驱动核心中明确表示这种依赖关系,由内核自动处理。

3. 怎么使用device link机制

3.1 添加时机

可以添加设备链接的最早时间点:对供应商调用 device_add() 并对消费者调用 device_initialize() 之后。

添加时需要注意系统一致性:

不能在挂起/恢复转换过程中添加,需要使用 lock_system_sleep() 防止并发,或者从不会与挂起/恢复并行执行的回调(如 probe 回调、启动时PCI quirk)中添加

如果从消费者 probe 回调添加依赖于供应商的链接,消费者需要在添加后检查供应商存在性,如果不存在则需要延迟探测

3.2 删除时机

如果在 probe 回调中添加无状态(DL_FLAG_STATELESS)设备链接,通常需要在 remove 回调中删除,保持对称

驱动核心管理的设备链接由内核自动删除

删除操作同样需要避免与挂起/恢复转换并行

3.3 常用标志说明

标志

作用

DL_FLAG_STATELESS

不需要驱动存在依赖,仅需排序保证

DL_FLAG_PM_RUNTIME

需要运行时PM集成

DL_FLAG_RPM_ACTIVE

运行时恢复供应商,防止其在消费者运行时挂起

DL_FLAG_AUTOREMOVE_CONSUMER

消费者探测失败或解除绑定时自动清除链接

DL_FLAG_AUTOREMOVE_SUPPLIER

供应商探测失败或解除绑定时自动清除链接

注意:DL_FLAG_AUTOREMOVE_CONSUMER / DL_FLAG_AUTOREMOVE_SUPPLIER /

DL_FLAG_AUTOPROBE_CONSUMER 不能与 DL_FLAG_STATELESS 组合使用。

3.4 限制

托管设备链接(未设置 DL_FLAG_STATELESS)可能导致消费者探测无限期延迟,如果供应商驱动缺失,消费者永远不会被探测

托管设备链接不能直接删除,由内核根据自动删除标志处理;无状态设备链接需要调用者通过 device_link_del() 或 device_link_remove() 主动删除

DL_FLAG_RPM_ACTIVE + DL_FLAG_STATELESS 连续调用可能导致供应商运行时使用计数器泄漏,需要特殊处理

4. device link的实现机制

4.1 数据结构

设备链接将原来的树状设备层次结构转变为有向无环图,每个设备会维护与供应商、消费者的链接关系。

设备链接有明确的状态机定义:

c 复制代码
enum device_link_state {
    DL_STATE_NONE = -1,
    DL_STATE_DORMANT = 0,       // supplier和consumer的driver都未绑定
    DL_STATE_AVAILABLE,          // supplier已绑定,consumer还未绑定
    DL_STATE_CONSUMER_PROBE,     // consumer正在probe,且supplier driver已就位
    DL_STATE_ACTIVE,             // consumer和supplier都已绑定
    DL_STATE_SUPPLIER_UNBIND,    // supplier driver正在解绑
};

4.2 状态转换

初始状态:device_link_add() 根据供应商和消费者驱动存在情况自动设置,如果探测前创建,进入 DL_STATE_DORMANT

当供应商绑定驱动:链接进入 DL_STATE_AVAILABLE(driver_bound() → device_links_driver_bound())

探测消费者前:检查供应商是否就绪,如果就绪状态更新为 DL_STATE_CONSUMER_PROBE(really_probe() → device_links_check_suppliers())

探测失败:回到 DL_STATE_AVAILABLE

探测成功:进入 DL_STATE_ACTIVE

消费者驱动移除:回到 DL_STATE_AVAILABLE

供应商驱动移除前:将未绑定消费者的链接更新为 DL_STATE_SUPPLIER_UNBIND,触发消费者解绑,完成后回到 DL_STATE_DORMANT

4.3 排序实现

挂起/恢复排序由 dpm_list 确定,关闭排序由 devices_kset 确定

默认情况下,列表是设备树的扁平化表示,设备位于所有祖先之后

添加设备链接后,为满足"消费者必须在所有供应商之后"的约束,会将消费者及其整个子图移动到列表末尾(device_link_add() → device_reorder_to_tail())

添加链接前会验证依赖,防止循环(device_link_add() → device_is_dependent()),如果检测到循环会返回错误

4.4 基于设备树的自动创建

Linux内核会在设备树解析阶段自动处理常见资源依赖,创建对应的fwnode link:

时钟(clocks)、互连(interconnects)、IOMMU、邮箱(mboxes)、DMA、电源域等常用属性会被系统自动处理

在 device_add 过程中,会调用 fw_devlink_link_device 将fwnode link转换为实际的device link

c 复制代码
int device_add(struct device *dev)
{
    // ...
    if (dev->fwnode && !dev->fwnode->dev) {
        dev->fwnode->dev = dev;
        fw_devlink_link_device(dev);
    }
    // ...
}

4.5 探测处理

在真正probe消费者设备之前,内核会检查所有供应商是否就绪:

复制代码
int device_links_check_suppliers(struct device *dev)
{
    list_for_each_entry(link, &dev->links.suppliers, c_node) {
        if (!(link->flags & DL_FLAG_MANAGED))
            continue;

        if (link->status != DL_STATE_AVAILABLE &&
            !(link->flags & DL_FLAG_SYNC_STATE_ONLY)) {
            // 供应商未就绪,返回-EPROBE_DEFER,延迟探测
            return -EPROBE_DEFER;
        }
        WRITE_ONCE(link->status, DL_STATE_CONSUMER_PROBE);
        }
       return 0;
}

如果供应商未就绪,会直接返回 -EPROBE_DEFER,推迟消费者探测,直到供应商就绪。

5. 典型使用场景

  1. MMU + 总线主设备:MMU为总线主设备提供DMA地址转换,需要在总线主设备活动时保持运行,总线主设备驱动需要等待MMU绑定后才能绑定
  2. Thunderbolt主机控制器:NHI设备和PCIe热插拔端口是同级设备,恢复时需要NHI先重建PCI隧道,热插拔端口才能恢复,使用无状态设备链接保证顺序
  3. 混合图形:独立GPU的HDA音频控制器依赖VGA设备,使用device link表示依赖
  4. ACPI _DEP:ACPI通过_DEP定义设备启动顺序,device link可以正确实现这种依赖
  5. SoC内部依赖:显示/视频编码IP依赖内存访问IP,使用device link保证探测顺序

6. 与其他方案的对比

方案 特点 对比device link
struct dev_pm_domain 用于共享开关的设备,不保证挂起/恢复排序,不支持驱动存在依赖 device link同时支持排序和驱动依赖
struct generic_pm_domain 比device link重,不支持关闭排序和驱动存在依赖,不能在ACPI使用 device link更轻量,适用场景更广
相关推荐
从零点2 小时前
ubuntu网络没有WiFi怎么办?网络配置解决步骤
linux·服务器·网络
代码探秘者2 小时前
【算法篇】1.双指针
java·数据结构·人工智能·后端·python·算法
bugu___2 小时前
Linux系统易错点
linux
你这个代码我看不懂2 小时前
Java软引用对象的创建以及对象回收
java·开发语言
开开心心就好2 小时前
免费无广告的礼金记账本,安卓应用
java·前端·ubuntu·edge·pdf·负载均衡·语音识别
Barkamin2 小时前
网络编程套接字
运维·服务器·网络
赵民勇2 小时前
gtkmm之耗时操作不阻塞界面
linux·c++
无籽西瓜a2 小时前
OSI 七层模型详解及面经
java·网络·后端