设备链接¶
默认情况下,驱动核心只强制执行设备层级结构中由父/子关系产生的设备之间的依赖关系:在挂起、恢复或关闭系统时,设备会根据这种关系进行排序,即子设备总是在其父设备之前挂起,而父设备总是在其子设备之前恢复。
有时需要表示超越单纯父/子关系的设备依赖,例如兄弟设备之间的依赖,并让驱动核心自动处理它们。
其次,驱动核心默认不强制执行任何驱动存在依赖,即一个设备必须绑定到驱动程序后,另一个设备才能正确探测或运行。
通常这两种依赖类型同时出现,因此一个设备对另一个设备的依赖既涉及到驱动存在,也涉及到挂起/恢复和关机顺序。
设备链接允许在驱动核心中表示此类依赖关系。
在其标准或受管理的形式中,设备链接结合了两种依赖类型:它保证了“供应商”设备与其“消费者”设备之间正确的挂起/恢复和关机顺序,并保证了供应商上驱动的存在。消费者设备不会在供应商绑定到驱动程序之前被探测,并且会在供应商解绑之前解绑。
当供应商上的驱动存在无关紧要,并且只需要正确的挂起/恢复和关机顺序时,设备链接可以简单地通过设置 DL_FLAG_STATELESS
标志来建立。换句话说,强制供应商上驱动的存在是可选的。
另一个可选功能是运行时电源管理(PM)集成:通过在添加设备链接时设置 DL_FLAG_PM_RUNTIME
标志,PM 核心被指示在消费者运行时恢复时,以及只要消费者处于运行时恢复状态,就运行时恢复供应商并使其保持活跃。
用法¶
添加设备链接的最早时间点是已经为供应商调用了 device_add()
,并为消费者调用了 device_initialize()
之后。
稍后添加它们是合法的,但必须注意系统保持一致状态:例如,不能在挂起/恢复转换过程中添加设备链接,因此要么需要使用 lock_system_sleep()
来阻止此类转换的开始,要么需要从保证不会与挂起/恢复转换并行运行的函数中添加设备链接,例如来自设备 ->probe
回调或启动时 PCI quirk。
另一个不一致状态的例子是,一个设备链接代表了驱动存在依赖,但却是在供应商尚未开始探测时从消费者的 ->probe
回调中添加的:如果驱动核心早些知道这个设备链接,它就不会一开始就探测消费者。因此,消费者有责任在添加链接后检查供应商的存在,并在供应商不存在时延迟探测。[请注意,在供应商仍在探测时,从消费者的 ->probe
回调中创建链接是有效的,但消费者必须知道供应商在链接创建时已经功能正常(例如,如果消费者刚刚获取了一些资源,而如果供应商当时不正常,这些资源就不会可用)。]
如果一个设置了 DL_FLAG_STATELESS
的设备链接(即一个无状态设备链接)是在供应商或消费者驱动的 ->probe
回调中添加的,为了对称性,它通常会在其 ->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()
可能会导致在随后调用 device_link_del()
或 device_link_remove()
以移除其返回的设备链接后,供应商设备的 PM-运行时使用计数器保持非零。这种情况发生的原因是,如果 device_link_add()
对相同的消费者-供应商对连续调用两次,而在这两次调用之间没有移除链接,那么在尝试移除链接时允许供应商的 PM-运行时使用计数器下降可能会导致它在消费者仍处于 PM-运行时活跃状态时被挂起,这是必须避免的。[要解决此限制,只需让消费者至少运行时挂起一次,或者在 device_link_add()
和 device_link_del()
或 device_link_remove()
调用之间,为消费者调用 pm_runtime_set_suspended()
并禁用 PM-运行时即可。]
有时驱动程序依赖于可选资源。当这些资源不存在时,它们能够以降级模式(功能集或性能降低)运行。一个例子是 SPI 控制器,它可以使用 DMA 引擎或在 PIO 模式下工作。控制器可以在探测时确定可选资源的存在,但在资源不存在时无法知道它们是会在不久的将来可用(由于供应商驱动探测)还是永远不可用。因此,无法确定是否延迟探测。在探测后当可选资源可用时通知驱动程序是可能的,但这会给驱动程序带来高昂的成本,因为根据此类资源的可用性在运行时在操作模式之间切换比基于探测延迟的机制要复杂得多。无论如何,可选资源超出了设备链接的范围。
示例¶
MMU 设备与总线主控设备并存,两者位于相同的电源域。MMU 为总线主控设备实现 DMA 地址转换,并且只要总线主控设备处于活跃状态,MMU 就应运行时恢复并保持活跃。总线主控设备的驱动程序不应在 MMU 绑定之前绑定。为了实现这一点,一个具有运行时 PM 集成的设备链接从总线主控设备(消费者)添加到 MMU 设备(供应商)。关于运行时 PM 的效果与 MMU 是主控设备的父设备时相同。
两个设备共享同一电源域通常会建议使用
struct dev_pm_domain
或 struct generic_pm_domain,然而这些并非碰巧共享电源开关的独立设备,而是 MMU 设备为总线主控设备提供服务,没有它就毫无用处。设备链接在设备之间创建了一种合成的层级关系,因此更适合。一个 Thunderbolt 主机控制器包含多个 PCIe 热插拔端口和一个 NHI 设备来管理 PCIe 交换机。从系统睡眠恢复时,NHI 设备需要在热插拔端口恢复之前重新建立到连接设备的 PCI 隧道。如果热插拔端口是 NHI 的子设备,这种恢复顺序将由 PM 核心自动强制执行,但不幸的是它们是旁系设备。解决方案是添加从热插拔端口(消费者)到 NHI 设备(供应商)的设备链接。此用例不需要驱动存在依赖。
混合显卡笔记本中的独立 GPU 通常包含一个用于 HDMI/DP 音频的 HDA 控制器。在设备层级结构中,HDA 控制器是 VGA 设备的兄弟设备,但两者共享相同的电源域,并且 HDA 控制器仅在 HDMI/DP 显示器连接到 VGA 设备时才需要。从 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()
。