默认情况下,驱动核心仅强制执行设备之间的依赖关系,这些依赖关系源自设备层次结构中的父/子关系:在挂起、恢复或关闭系统时,设备的顺序是基于这种关系的,即子设备总是在其父设备之前挂起,父设备总是在其子设备之前恢复。
有时需要表示超出纯粹的父/子关系的设备依赖关系,例如兄弟之间的关系,并且让驱动核心自动处理这些关系。
其次,默认情况下,驱动核心不强制执行任何驱动程序存在的依赖关系,即一个设备必须在另一个设备探测或正常运行之前绑定到驱动程序。
通常这两种依赖类型会一起出现,因此一个设备在驱动程序存在和挂起/恢复以及关闭顺序方面都依赖于另一个设备。
设备链接允许在驱动核心中表示这种依赖关系。
在其标准或受控形式中,设备链接结合了这两种依赖类型:它保证了"供应商"设备和其"消费者"设备之间的正确挂起/恢复和关闭顺序,并且保证了供应商的驱动程序存在。在供应商绑定到驱动程序之前,不会探测消费者设备,并且在供应商解绑之前,消费者设备会被解绑。
当供应商的驱动程序存在对消费者的挂起/恢复和关闭顺序没有影响时,设备链接可能只需使用DL_FLAG_STATELESS标志简单设置即可。换句话说,对供应商的驱动程序存在的强制是可选的。
另一个可选功能是运行时PM集成:通过在添加设备链接时设置DL_FLAG_PM_RUNTIME标志,PM核心被指示在消费者运行时恢复时保持供应商设备处于活动状态,直到消费者运行时恢复为止。
用法
设备链接可以在对供应商调用device_add()和对消费者调用device_initialize()之后的最早时间点添加。
稍后添加它们是合法的,但必须注意系统保持一致的状态:例如,不能在挂起/恢复转换过程中添加设备链接,因此需要使用lock_system_sleep()来阻止这样的转换的开始,或者需要从已保证不会与挂起/恢复转换并行运行的函数中添加设备链接,例如从设备->probe回调或引导时的PCI quirk。
另一个不一致状态的例子是,一个设备链接代表了一个驱动程序存在的依赖关系,但是在消费者的->probe回调中添加了该链接,而供应商尚未开始探测:如果驱动核心早些时候已知道设备链接,它就不会首先探测消费者。因此,责任在于消费者在添加链接后检查供应商的存在,并在不存在时推迟探测。[请注意,在供应商仍在探测时从消费者的->probe回调创建链接是有效的,但是消费者必须知道在创建链接时供应商已经可用(例如,如果消费者刚刚获取了一些资源,如果供应商在那时不可用,这些资源将不可用)。]
如果在供应商或消费者驱动程序的->probe回调中添加了具有DL_FLAG_STATELESS设置(即无状态设备链接)的设备链接,通常会在其->remove回调中删除它以保持对称。这样,如果驱动程序被编译为模块,设备链接将在模块加载时添加,并在卸载时有序地删除。适用于设备核心管理的设备链接将由其自动删除。
在设备链接添加时可以指定多个标志,其中已经提到了两个:DL_FLAG_STATELESS用于表示不需要驱动程序存在的依赖关系(但仅需要正确的挂起/恢复和关闭顺序),DL_FLAG_PM_RUNTIME用于表示需要运行时PM集成。
另外两个标志专门针对设备链接从消费者的->probe回调中添加的用例:DL_FLAG_RPM_ACTIVE可以指定在消费者运行时恢复时运行时恢复供应商并防止在消费者运行时挂起之前挂起供应商。DL_FLAG_AUTOREMOVE_CONSUMER导致在消费者无法探测或稍后解绑时自动清除设备链接。
类似地,当设备链接从供应商的->probe回调中添加时,DL_FLAG_AUTOREMOVE_SUPPLIER导致在供应商无法探测或稍后解绑时自动清除设备链接。
如果既未设置DL_FLAG_AUTOREMOVE_CONSUMER也未设置DL_FLAG_AUTOREMOVE_SUPPLIER,则可以使用DL_FLAG_AUTOPROBE_CONSUMER请求驱动核心在链接后自动为消费者驱动程序探测驱动程序。
但是,请注意,DL_FLAG_AUTOREMOVE_CONSUMER、DL_FLAG_AUTOREMOVE_SUPPLIER或DL_FLAG_AUTOPROBE_CONSUMER与DL_FLAG_STATELESS的任何组合都是无效的,不能使用。
限制
驱动程序作者应该意识到,对于受控设备链接的驱动程序存在依赖(即在链接添加时未指定DL_FLAG_STATELESS)可能导致消费者的探测被无限期地推迟。如果要求在达到某个initcall级别之前必须探测消费者,这可能会成为一个问题。更糟糕的是,如果供应商驱动程序被列入黑名单或缺失,消费者将永远不会被探测。
此外,受控设备链接不能直接删除。它们将根据DL_FLAG_AUTOREMOVE_CONSUMER和DL_FLAG_AUTOREMOVE_SUPPLIER标志在不再需要时由驱动核心自动删除。然而,无状态设备链接(即具有DL_FLAG_STATELESS设置的设备链接)预计将由调用device_link_add()添加它们的调用者通过device_link_del()或device_link_remove()来删除。
在调用device_link_add()时传递DL_FLAG_RPM_ACTIVE和DL_FLAG_STATELESS可能会导致在随后调用device_link_del()或device_link_remove()删除返回的设备链接时,供应商设备的PM运行时使用计数保持非零。如果在这些调用之间连续两次为相同的消费者-供应商对调用device_link_add(),则允许供应商的PM运行时使用计数在尝试删除链接时下降,这可能会导致在消费者仍处于PM运行时活动状态时挂起供应商,这必须避免。[为了解决这个限制,只需让消费者至少运行时挂起一次,或者在具有PM运行时禁用的情况下为其调用pm_runtime_set_suspended(),在device_link_add()和device_link_del()或device_link_remove()之间。]
有时驱动程序依赖于可选资源。当这些资源不存在时,它们可以以降级模式(功能集或性能降低)运行。一个例子是SPI控制器可以使用DMA引擎或以PIO模式工作。控制器可以在探测时确定可选资源的存在,但是在不存在时无法知道它们将在不久的将来(由于供应商驱动程序的探测)可用还是永远不可用。因此无法确定是否推迟探测。在探测后,通知驱动程序可选资源何时可用是可能的,但对于驱动程序来说,基于运行时的操作模式切换到这些资源的可用性的机制比基于探测推迟的机制要复杂得多。无论如何,可选资源都超出了设备链接的范围。
示例
- 一个MMU设备与一个总线主设备并存,两者在同一个电源域中。MMU实现了总线主设备的DMA地址转换,并且在总线主设备活动时应该运行时恢复并保持活动。在MMU设备(供应商)和总线主设备(消费者)之间添加具有运行时PM集成的设备链接,可以实现这一效果。在运行时PM方面的效果与MMU是主设备的效果相同。
- 事实上,这两个设备共享同一个电源域,通常会建议使用struct dev_pm_domain或struct generic_pm_domain,但是这些不是独立的设备,它们碰巧共享一个电源开关,而是MMU设备为总线主设备提供服务,没有总线主设备,MMU设备就没有用处。设备链接在这些设备之间创建了一个合成的层次关系,因此更合适。
- 一个雷电主机控制器包括多个PCIe热插拔端口和一个NHI设备来管理PCIe开关。从系统休眠恢复时,NHI设备需要在热插拔端口恢复之前重新建立PCI隧道到连接的设备。如果热插拔端口是NHI的子设备,这种恢复顺序将自动由PM核心强制执行,但不幸的是它们是姑母。解决方案是从热插拔端口(消费者)到NHI设备(供应商)添加设备链接。对于这种用例,不需要驱动程序存在的依赖关系。
- 混合图形笔记本中的离散GPU通常具有用于HDMI/DP音频的HDA控制器。在设备层次结构中,HDA控制器是VGA设备的兄弟,但两者共享同一个电源域,只有在VGA设备连接了HDMI/DP显示器时才需要HDA控制器。从HDA控制器(消费者)到VGA设备(供应商)的设备链接恰当地表示了这种关系。
- ACPI允许通过_DEP对象定义设备的启动顺序。一个经典的例子是,当一个设备上的ACPI电源管理方法是基于I2C访问实现的,并且需要特定的I2C控制器存在和正常工作,才能使该设备的电源管理工作。
- 在一些SoC中,显示、视频编解码器和视频处理IP核对透明存储器访问IP核存在功能依赖关系,后者处理突发访问和压缩/解压缩。
替代方案
- 可以使用struct dev_pm_domain来覆盖总线、类别或设备类型的回调。它适用于共享单个开关的设备,但它不保证特定的挂起/恢复顺序,这需要单独实现。它本身也不能跟踪所涉及设备的运行时PM状态,并且只有当它们都运行时挂起时才关闭电源开关。此外,它不能用于强制特定的关闭顺序或驱动程序存在的依赖关系。
- struct generic_pm_domain比设备链接更加复杂,并且不允许关闭顺序或驱动程序存在的依赖关系。它也不能在ACPI系统上使用。
实施
一旦添加了设备链接,设备层次结构(正如其名称所示)将成为一个有向无环图。
在挂起/恢复期间,这些设备的顺序由dpm_list确定。在关闭期间,它由devices_kset确定。如果没有设备链接存在,这两个列表将是设备树的扁平化、一维表示,使得设备被放置在其所有祖先之后。这是通过自上而下遍历ACPI命名空间或OpenFirmware设备树,并在发现它们时将设备附加到列表中来实现的。
一旦添加了设备链接,列表需要满足额外的约束,即设备被递归地放置在其所有供应商之后。为了确保这一点,在添加设备链接时,消费者和整个子图(消费者的所有子设备和消费者)都会被移动到列表的末尾。(从device_link_add()调用device_reorder_to_tail()。)
为了防止将依赖关系循环引入图中,设备链接添加时会验证供应商不依赖于消费者或消费者的任何子设备或消费者。(从device_link_add()调用device_is_dependent()。)如果违反了该约束,device_link_add()将返回NULL,并记录一个警告。
值得注意的是,这也防止了从父设备到子设备的设备链接的添加。但是相反是允许的,即从子设备到父设备的设备链接。由于驱动核心已经保证了父子之间的正确挂起/恢复和关闭顺序,因此只有在需要驱动程序存在的依赖关系时,这样的设备链接才有意义。在这种情况下,驱动程序作者应该仔细权衡设备链接是否是合适的工具。一个更合适的方法可能是简单地使用延迟探测或添加一个设备标志,导致父驱动在子驱动之前被探测。
状态机
https://www.kernel.org/doc/html/v6.6/driver-api/device_link.html#c.device_link_state
API
https://www.kernel.org/doc/html/v6.6/driver-api/device_link.html#api