设备链接¶
默认情况下,驱动程序核心仅强制执行设备之间在设备层次结构中因父/子关系而产生的依赖关系:当暂停、恢复或关闭系统时,设备根据这种关系排序,即子设备总是在其父设备之前暂停,并且父设备总是在其子设备之前恢复。
有时,需要表示超出简单的父/子关系的设备依赖关系,例如同级设备之间的依赖关系,并让驱动程序核心自动处理它们。
其次,默认情况下,驱动程序核心不强制执行任何驱动程序存在依赖关系,即一个设备必须绑定到驱动程序后,另一个设备才能正确探测或运行。
通常,这两种依赖类型会同时出现,因此一个设备在驱动程序存在方面和在挂起/恢复和关闭顺序方面都依赖于另一个设备。
设备链接允许在驱动程序核心中表示此类依赖关系。
在其标准或托管形式中,设备链接结合了两种依赖类型:它保证了“供应者”设备及其“消费者”设备之间正确的挂起/恢复和关闭顺序,并保证了供应者设备上驱动程序的存在。在供应商绑定到驱动程序之前,不会探测消费者设备,并且在供应商解除绑定之前,它们会被解除绑定。
当供应商上的驱动程序存在无关紧要,只需要正确的挂起/恢复和关闭顺序时,可以使用DL_FLAG_STATELESS
标志简单地设置设备链接。换句话说,强制执行供应商上的驱动程序存在是可选的。
另一个可选功能是运行时 PM 集成:通过在添加设备链接时设置 DL_FLAG_PM_RUNTIME
标志,PM 核心会指示在消费者运行时恢复时,运行时恢复供应商并保持其活动状态。
用法¶
可以添加设备链接的最早时间是在为供应商调用 device_add()
之后,以及为消费者调用 device_initialize()
之后。
稍后添加它们是合法的,但必须注意保持系统处于一致状态:例如,不能在挂起/恢复转换过程中添加设备链接,因此需要使用 lock_system_sleep()
阻止此类转换的开始,或者需要从保证不会与挂起/恢复转换并行运行的函数添加设备链接,例如从设备 ->probe
回调或启动时的 PCI 怪癖。
另一个不一致状态的例子是一个表示驱动程序存在依赖关系的设备链接,但是它是从消费者的 ->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()
帮助删除。
将 DL_FLAG_RPM_ACTIVE
与 DL_FLAG_STATELESS
一起传递给 device_link_add()
可能会导致供应商设备的 PM-runtime 使用计数器在后续调用 device_link_del()
或 device_link_remove()
来删除它返回的设备链接后仍然保持非零。如果对同一个消费者-供应商对连续两次调用 device_link_add()
而没有删除这些调用之间的链接,就会发生这种情况。在这种情况下,允许供应商的 PM-runtime 使用计数器在尝试删除链接时下降可能会导致它在消费者仍然处于 PM-runtime 活动状态时被挂起,这是必须避免的。[为了解决这个限制,在 device_link_add()
和 device_link_del()
或 device_link_remove()
调用之间,让消费者运行时至少挂起一次,或者在禁用 PM-runtime 的情况下为其调用 pm_runtime_set_suspended()
就足够了。]
有时驱动程序依赖于可选资源。当这些资源不存在时,它们能够在降级模式(功能集或性能降低)下运行。一个例子是可以使用 DMA 引擎或在 PIO 模式下工作的 SPI 控制器。控制器可以在探测时确定可选资源的存在,但是如果不存在,则无法知道它们是否会在不久的将来(由于供应商驱动程序探测)可用,或者永远不可用。因此,无法确定是否要延迟探测。可以在探测后通知驱动程序可选资源何时可用,但这对于驱动程序来说代价很高,因为基于此类资源的可用性在运行时在操作模式之间切换会比基于探测延迟的机制复杂得多。在任何情况下,可选资源都超出了设备链接的范围。
示例¶
一个 MMU 设备与一个总线主设备并存,两者都在同一个电源域中。MMU 为总线主设备实现 DMA 地址转换,并且只要总线主设备处于活动状态,就应该恢复运行时并保持活动状态。总线主设备的驱动程序不应在 MMU 绑定之前绑定。为了实现这一点,从总线主设备(消费者)到 MMU 设备(供应商)添加一个带有运行时 PM 集成的设备链接。关于运行时 PM 的效果与 MMU 是主设备的父级相同。
事实上,这两个设备共享同一个电源域通常会建议使用
struct dev_pm_domain
或 struct generic_pm_domain,然而这些不是恰好共享一个电源开关的独立设备,而是 MMU 设备服务于总线主设备,没有它就毫无用处。设备链接在设备之间创建了一种合成的层次关系,因此更合适。Thunderbolt 主机控制器包含多个 PCIe 热插拔端口和一个用于管理 PCIe 交换机的 NHI 设备。在从系统休眠恢复时,NHI 设备需要在热插拔端口恢复之前重新建立到连接设备的 PCI 通道。如果热插拔端口是 NHI 的子端口,则此恢复顺序将由 PM 核心自动强制执行,但不幸的是它们是姑母。解决方案是从热插拔端口(消费者)到 NHI 设备(供应商)添加设备链接。对于此用例,不需要驱动程序存在依赖性。
混合图形笔记本电脑中的独立 GPU 通常具有用于 HDMI/DP 音频的 HDA 控制器。在设备层次结构中,HDA 控制器是 VGA 设备的同级设备,但两者共享同一个电源域,并且仅当 HDMI/DP 显示器连接到 VGA 设备时才需要 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
并记录 WARNING
。
值得注意的是,这也会阻止从父设备到子设备添加设备链接。但是,允许相反的情况,即从子设备到父设备的设备链接。由于驱动程序核心已经保证了父设备和子设备之间正确的挂起/恢复和关闭顺序,因此这样的设备链接只有在需要额外的驱动程序存在依赖性时才有意义。在这种情况下,驱动程序作者应该仔细权衡设备链接是否是此目的的正确工具。更合适的方法可能是简单地使用延迟探测,或者添加一个设备标志,使父驱动程序在子驱动程序之前被探测。
状态机¶
-
enum device_link_state¶
设备链接状态。
常量
DL_STATE_NONE
不跟踪驱动程序的存在。
DL_STATE_DORMANT
供应商/消费者驱动程序都不存在。
DL_STATE_AVAILABLE
供应商驱动程序存在,但消费者驱动程序不存在。
DL_STATE_CONSUMER_PROBE
消费者正在探测(供应商驱动程序存在)。
DL_STATE_ACTIVE
供应商和消费者驱动程序都存在。
DL_STATE_SUPPLIER_UNBIND
供应商驱动程序正在解除绑定。
.=============================.
| |
v |
DORMANT <=> AVAILABLE <=> CONSUMER_PROBE => ACTIVE
^ |
| |
'============ SUPPLIER_UNBIND <============'
设备链接的初始状态由
device_link_add()
根据供应商和消费者上的驱动程序的存在自动确定。如果在任何设备被探测之前创建了链接,则将其设置为DL_STATE_DORMANT
。当供应商设备绑定到驱动程序时,与其消费者的链接将进展到
DL_STATE_AVAILABLE
。(从driver_bound()
调用device_links_driver_bound()
。)在探测消费者设备之前,通过检查消费者设备是否不在 wait_for_suppliers 列表中,并通过检查与供应商的链接是否处于
DL_STATE_AVAILABLE
状态来验证供应商驱动程序的存在。链接的状态更新为DL_STATE_CONSUMER_PROBE
。(从really_probe()
调用device_links_check_suppliers()
。)这可以防止供应商解除绑定。(从device_links_unbind_consumers()
调用wait_for_device_probe()
。)如果探测失败,与供应商的链接将恢复为
DL_STATE_AVAILABLE
。(从really_probe()
调用device_links_no_driver()
。)如果探测成功,到供应商的链接会变为
DL_STATE_ACTIVE
状态。(从driver_bound()
调用device_links_driver_bound()
。)当消费者的驱动稍后被移除时,到供应商的链接会恢复为
DL_STATE_AVAILABLE
状态。(从device_links_driver_cleanup()
调用__device_links_no_driver()
,而device_links_driver_cleanup()
又从__device_release_driver()
调用。)在供应商的驱动被移除之前,到未绑定驱动的消费者的链接会更新为
DL_STATE_SUPPLIER_UNBIND
状态。(从__device_release_driver()
调用device_links_busy()
。)这可以防止消费者进行绑定。(从really_probe()
调用device_links_check_suppliers()
。)已绑定的消费者会从其驱动中释放;正在探测的消费者会等待直到完成。(从__device_release_driver()
调用device_links_unbind_consumers()
。)一旦所有到消费者的链接都处于DL_STATE_SUPPLIER_UNBIND
状态,供应商驱动就会被释放,并且链接会恢复为DL_STATE_DORMANT
状态。(从__device_release_driver()
调用device_links_driver_cleanup()
。)
API¶
请参阅 device_link_add()
, device_link_del()
和 device_link_remove()
。