Linux Device Link 机制详解
1. 什么是device link
Device link(设备链接)是Linux内核驱动核心中用来表示设备之间依赖关系的机制。它允许驱动作者描述超越单纯父子层次关系的设备依赖,例如兄弟设备之间的依赖,同时能够让驱动核心自动处理这些依赖关系。
Device link 结合了两种依赖类型:
- 排序依赖:保证"供应商(supplier)"设备和"消费者(consumer)"设备之间正确的挂起/恢复和关闭排序列
- 驱动存在依赖:保证供应商设备先绑定驱动,消费者设备才会被探测;供应商解除绑定前,消费者会先解除绑定
根据需求,可以通过标志位配置不同行为:
- DL_FLAG_STATELESS:只需要正确的挂起/恢复和关闭排序,不需要强制驱动存在依赖
- DL_FLAG_PM_RUNTIME:启用运行时PM集成,消费者运行时恢复时自动恢复供应商并保持其活动
2. 为什么需要有device link
默认情况下,Linux驱动核心仅根据设备层次结构中的父子关系处理设备依赖:
- 挂起/关闭时:子设备总是在父设备之前挂起
- 恢复时:父设备总是在子设备之前恢复
但实际场景中存在更多依赖需求:
- 非父子关系的依赖:兄弟设备之间也可能存在依赖,需要保证特定的执行顺序
- 驱动存在依赖:一个设备必须等待另一个设备绑定驱动后,自身才能正确探测或运行
这两种依赖通常同时存在,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. 典型使用场景
- MMU + 总线主设备:MMU为总线主设备提供DMA地址转换,需要在总线主设备活动时保持运行,总线主设备驱动需要等待MMU绑定后才能绑定
- Thunderbolt主机控制器:NHI设备和PCIe热插拔端口是同级设备,恢复时需要NHI先重建PCI隧道,热插拔端口才能恢复,使用无状态设备链接保证顺序
- 混合图形:独立GPU的HDA音频控制器依赖VGA设备,使用device link表示依赖
- ACPI _DEP:ACPI通过_DEP定义设备启动顺序,device link可以正确实现这种依赖
- SoC内部依赖:显示/视频编码IP依赖内存访问IP,使用device link保证探测顺序
6. 与其他方案的对比
| 方案 | 特点 | 对比device link |
|---|---|---|
struct dev_pm_domain |
用于共享开关的设备,不保证挂起/恢复排序,不支持驱动存在依赖 | device link同时支持排序和驱动依赖 |
struct generic_pm_domain |
比device link重,不支持关闭排序和驱动存在依赖,不能在ACPI使用 | device link更轻量,适用场景更广 |