RFC-0263:将驱动程序通信迁移到服务 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 将驱动程序和客户端从连接到 devfs 迁移到使用服务。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2024-08-25 |
审核日期(年-月-日) | 2024-10-28 |
问题陈述
目前,非驱动程序几乎所有对驱动程序的访问都通过设备文件系统 (devfs) 进行中介。遗憾的是,devfs 存在以下几个缺点:
- 很难将 devfs 客户端限制为使用特定协议。
- 该 API 让驱动程序难以公开多个协议。
- 拓扑路径和硬编码类路径会向客户端公开内部实现细节,并可能导致意外中断。
- 由于组件管理器不知道 devfs 连接,因此无法将其用于依赖项跟踪。此外,虽然目前任何驱动程序和客户端都可以从 devfs 迁移到汇总服务,但驱动程序作者在执行此类迁移时目前面临着重大挑战。
摘要
我们提议通过将此类访问迁移到使用由组件管理器中介的汇总服务,来改变非驱动程序访问驱动程序的方式。此迁移将同时涵盖拓扑路径 (/dev/sys/) 和类路径 (/dev/class/)。在此过程中,使用 fuchsia.device/Controller 协议的客户端对驱动程序的所有剩余访问权限也将被移除。此 RFC 将涵盖在迁移到聚合服务时面临的挑战,并提出解决方案以确保驱动程序作者不会受到过度影响。我们还希望为驱动程序访问权限提供更标准化的解决方案,以改善代码健康状况并降低编写驱动程序和客户端的难度。
利益相关方
教员:
审核者:
- cja@
- suraj@
- csuter@
- jfsulliv@
- 组件框架团队
咨询了:
- 驾驶团队 / 驾驶员作者
社交化:
此 RFC 已通过驱动程序框架团队的设计审核。
背景
直到最近,Fuchsia 中的驱动程序还不是组件,非驱动程序与驱动程序通信的唯一方式是通过 devfs。驱动程序管理器维护 devfs,并填充以下两个相关的分支:/dev/sys/ 和 /dev/class。
- /dev/sys/ 从板级驱动程序作为根目录开始填充,并表示驱动程序管理器中的父子绑定关系的层次结构。此分支中的路径称为“拓扑路径”。具有对“dev-topological”目录功能的访问权限的组件可以打开拓扑路径。
- /dev/class/ 由在添加子设备时指定类名称的驱动程序填充。然后,驱动程序管理器会向 /dev/class/
/ 添加一个与驱动程序对应的条目。这样,具有类似输出的驱动程序便可归为一组,例如,所有摄像头都可能在 /dev/class/camera 中有一个条目。此分支中的路径称为“类路径”。具有“dev-class”目录功能访问权限的组件可以打开类路径,此功能通常会进一步限制为仅可访问特定类文件夹。
这两个分支中的驱动程序条目允许对驱动程序进行两种类型的访问:device_controller 和 device。
- device_controller 提供了 fuchsia.device/Controller 接口,可让组件查询、绑定和重新绑定驱动程序。
- device 会向驱动程序提供特定于驱动程序的接口,该接口实际上与驱动程序在添加子设备时指定的类名称相关联。例如,当前添加类名称为“camera”的设备的所有驱动程序都提供 fuchsia.hardware.camera 接口,而连接到 /dev/class/camera/000(如果存在)会实例化与该驱动程序的 fuchsia.hardware.camera 接口的连接。
过去几年,我们一直在努力隔离和移除 fuchsia.device/Controller 接口的用法,因此移除这些用法将被视为超出本 RFC 的范围。此处提及此接口是为了完整起见,并提醒您,集合服务不会提供任何类似接口。
设计初衷
此次迁移的主要目标是移除用于访问驱动程序的 devfs 系统,并将其替换为更加标准化的基于服务的连接模型。这种方法有许多优点,其中许多与 devfs 的缺点有关:
- 提高了依赖项和关联的可见性。通过让客户端通过服务 capability 进行连接,组件管理器可以跟踪连接并维护准确的依赖项图。使用 devfs 时是不可能的,因为其结构对组件管理器而言基本不透明。
- 服务提供了一种更完善的文档和更灵活的方式来从驱动程序导出功能。使用 devfs 时,每个设备实例只能提供一个协议,并且从不明确定义协议类型。借助汇总服务,您可以提供多个协议,并且每个协议都会在服务定义中明确列出。
- 更明确且受严格控制的访问权限。组件必须指定它正在请求的服务类型,这与 devfs 中的拓扑路径(允许组件访问任何驱动程序)相反。
- 移除了对拓扑路径的依赖项。拓扑路径可让您了解驱动程序管理器如何挂载每个驱动程序。因此,对拓扑路径进行硬编码表示对驱动程序框架内部运作的依赖,而我们希望避免这种情况。
此外,devfs 访问可以通过各种方式完成,包括同步和异步,以及通过类路径和拓扑路径。我们希望提供一种方法来连接驱动程序和非驱动程序提供的服务,这应该有助于改善代码运行状况并降低编写驱动程序和客户端的难度。
要求
迁移必须为 devfs 的所有当前用户提供解决方案,以免丢失任何功能。 此外,驱动程序和客户端必须能够执行软迁移,添加服务并连接到服务,而无需提供服务的所有客户端和驱动程序同时迁移。
设计
驱动程序框架工作
客户端或驱动程序从 devfs 迁移的每次迁移都必须单独处理。不过,驱动程序框架需要进行一些工作,以协助解决上述问题。
类名称 <-> 协议 / 服务映射
为便于转换,系统会在 c++ 头文件中汇总类名称、协议名称和服务名称之间的映射。目前不存在任何显式映射,客户端和驱动程序之间只是存在一个隐式协定,即每个类都对应于特定的 fidl 协议。使此映射显式化可以启用以下步骤:
- 确保
/dev/class/<classname>
的客户端和服务器就一组协议达成一致 - 允许客户端从连接到
/dev/class/<classname>
迁移到fuchsia.hardware.<protocolname>
。
此映射将需要为每个类名称创建一个服务(目前大约有 200 个可能的类名称)。您可能还需要为当前仅可通过拓扑路径访问的服务创建此映射中的其他条目。此映射仅在迁移期间持续有效。迁移每个类后,可以移除其在映射中的条目。
公交信息的传播
移除拓扑路径后,驱动程序管理器将负责将总线信息从总线驱动程序传播到所有派生驱动程序,以用于识别服务实例。驱动程序库可让您更轻松地存储和检索此类信息。
启用软迁移:
当驱动程序和客户端从 devfs 迁移到服务时,我们必须确保一方能够处理或提供这两种连接类型,以确保连接的连续性。此外,我们必须确保另一端不提供这两种连接类型,以防止出现重复的条目。例如,如果驱动程序 A 和 B 都为客户端 C 和 D 提供 Foo 连接,而驱动程序 A 只能自然切换到服务,那么客户端必须在 devfs 和服务之间进行选择,然后只能看到一个产品。 解决方案是多步骤迁移。实现这种软迁移的方法有多种,其中需要在两者中任一一方上执行一些额外的工作。无论采用哪种方案,迁移阶段都可以按类进行推进,以便更轻松地进行部署,这也让不同类别可以自定义迁移策略。
建议的方案:通过 devfs 自动宣传服务
修改了 devfs,以便为每个 devfs 条目自动通告服务。服务类型将由类名称表示,使用之前所述的类名称 -> 服务映射。在同一更改中,devfs 还会将与映射的类名称对应的所有服务功能公开给引导组件。为类名称启用服务后,客户端可以改用服务,然后驱动程序会改用广告服务。驱动程序迁移只需移除 DevfsAddArgs,改为调用 AddService。最大的缺点是,在类类型转换的所有客户端上,驱动程序转换都会被阻止。
迁移顺序如下: 1) 更改 devfs 以通告服务 2) 客户端转换为服务 3) 驱动程序通告服务并移除 devfs
方案 1:创建客户端工具:DeviceAndServiceWatcher
创建一个工具,在给定类名称 / 服务名称的情况下,该工具会连接到汇总服务和 /dev/class/
迁移顺序为: 1) 添加 DeviceAndServiceWatcher 2) 客户端迁移以使用 DeviceAndServiceWatcher 3) 驱动程序通告服务并移除 devfs 4) 完成所有迁移后,从 DeviceAndService Watcher 中移除 devfs 逻辑
替代方案 2:延迟移除 devfs
让驱动程序在通告服务后继续提供 devfs 条目。 客户端转换后,驱动程序可以移除 devfs 代码。这样做的好处是不会阻止驱动程序转化,但后续仍需要对驱动程序进行更改。此外,这还存在一些缺点:新的 AddService 代码不会立即执行,因此除了驱动程序必须进行的更改(以移除 devfs 代码)之外,当客户端最终改用服务时,可能还需要对驱动程序进行其他更改。
迁移顺序如下: 1) 驱动程序添加服务 2) 客户端转换为服务 3) 驱动程序移除 devfs
实现
对于与特定类名称交互的每组驱动程序和客户端,可以逐步执行此迁移。需要提供一些工具和库,驱动程序框架将执行几次迁移,以确保库满足迁移驱动程序和客户端的需求。确保迁移过程足够符合人体工学要求后,我们会将类名称迁移添加为开放任务,供更广泛的 Fuchsia 社区参与。特定类/接口将被标识为需要更多关注,并且将需要更协调的方法。这包括仍在使用 fuchsia.device/Controller 接口的所有客户端,这些客户端需要先解决此问题,然后才能进行迁移。当所有驱动程序和客户端都迁移到服务后,就可以移除 devfs 的内部实现。
迁移机制
下面介绍了转换特定类驱动程序的步骤,以便进一步了解迁移方式:
1) 添加类名称 <-> 协议 / 服务映射
- 必须根据当前用法推断类与 fidl 协议的映射。
- 然后,需要创建一个服务来封装该协议,并将类与服务的映射记录在相应的标头中。这样,devfs 就可以开始宣传该服务了。
- 然后,您需要更新 CML 文件,以将服务路由到客户端。
2) 客户端迁移
客户端迁移的复杂程度会有所不同,因为客户端可以使用拓扑路径或类路径。客户端迁移可分为两个阶段:确定所需的服务提供方,以及连接到服务。第一步可能已经由客户端使用类路径完成。
标识所需的服务提供商
执行以下任一操作的客户端都必须实现此迁移步骤:
- 使用拓扑路径
- 使用硬编码的类路径(例如/dev/class/camera/000)
- 连接到给定 devfs 条目的
device_controller
,然后调用GetTopologicalPath()
。
在上述每种情况下,客户端都会从服务/Fidl 接口提供商之前(或似乎已同步)迁移到聚合服务,在聚合服务中以异步方式添加和移除实例,且实例名称不能用于识别。
此类客户可以选择以下几种方案:
- 连接到第一个可用的服务实例。对于预计只有一个服务提供程序(例如电源控制器)的更简单的工具和测试,这通常就足够了。这还允许客户端保持连接是同步的假设。这不是通常首选的解决方案,因为它会忽略系统的异步特性。
- 从每个服务实例查询信息,以确定它是否为正确的实例。您可以通过以下两种方式实现此目的:
- 连接到每个服务实例的 fidl 接口。许多 fidl 协议已指定与设备相关的信息(例如,
fuchsia.camera/Control
协议提供了GetInfo
调用。如果当前提供的协议不包含标识信息,则可以修改该协议以添加此类特定于设备的信息。 - 向服务添加额外的
GetInfo
协议,该协议可以包含设备标识信息。将识别接口与主通信接口分离有以下几项优势:- 识别接口主要由驱动程序库处理,因为它提供一组静态信息。
- 最好让识别接口处理多个同时连接,以便于查询,而大多数驱动程序无法处理与其其他接口的多个同时连接。
- 分离识别逻辑有助于在日后的工作中更轻松地将识别步骤集成到框架中。
GetInfo
接口的主要缺点是需要从驱动程序中填充更多信息,因此 devfs 更难以自动提供这些信息。在这种情况下,驱动程序需要修改其 DevfsAddArgs 以添加相关数据,而 devfs 将负责处理该接口。这必须由驾驶员完成,然后客户端才能转换为服务。
- 连接到每个服务实例的 fidl 接口。许多 fidl 协议已指定与设备相关的信息(例如,
- 未来可能存在的一个选项是,向组件添加标记以指明特定设备的相关信息。然后,客户端可以查询与每个服务实例关联的标记。
设备专用信息和总线信息:
有许多信息可帮助客户在连接到汇总服务时识别正确的驱动程序。这些信息可能包括设备 ID (DID)、产品 ID (PID)、供应商 ID (VID),以及传递给驱动程序的任何数量的元数据。不过,从拓扑路径迁移时,客户端因不使用拓扑路径而可能丢失的信息只有三项:驱动程序名称(驱动程序知道)、总线类型和总线 ID/地址。为了弥补总线信息的丢失,驱动程序框架将添加从总线驱动程序向所有后代传播总线类型和地址的功能。目前,此信息以元数据的形式提供给多种类型的总线驱动程序(例如 i2c),因此实现只需将总线类型和地址标记为特定元数据条目,并确保其始终传播到子项。然后,驾驶员可以使用上述任一选项向客户提供公交车信息。
连接到服务提供商
客户端确定要连接到的服务实例后,连接本身与连接到类路径非常相似。
- 客户端必须确保自己有权访问该 capability,它可以将“dev-class”capability 替换为预期的服务 capability。这可能需要在多个文件中完成,具体取决于客户端的拓扑位置。另请注意:某些分片会传播“dev-class”capability,因此 capability 的来源可能并不总是很明显。
- 对于 devfs 和服务,理想的连接方法涉及 DeviceWatcher。此库会异步通知客户端新的服务提供程序实例。最大的变化应该只是被监控的位置。
3) 驱动程序迁移
如上所述,如果在服务中将 GetInfo
接口用作驱动程序进行广告宣传,则驱动程序需要修改其 DevfsAddArgs 以添加相关数据,而 devfs 将负责处理该接口。在客户端转换为服务之前,必须由驱动程序执行此操作。
驱动程序迁移涉及调用 AddService
,还可能会将提供的协议封装在服务中。如果使用 GetInfo 接口,驱动程序将提供一个包含要传送的静态信息的驱动程序库,而不是将其传入 DevfsAddArgs。在添加 AddService
调用的同时,驱动程序会从 NodeAddArgs 条目中移除 devfs_args
。这将停用与驱动程序关联的所有 devfs 条目。
性能
此次迁移对广告效果的影响微乎其微。在组件的生命周期中,通常只会建立一次与组件的连接,并且连接类型的差异很小。
工效学设计
在设计此次迁移时,我们非常注重人体工学问题。主要问题涉及通过拓扑路径移除访问权限:
- 在不使用拓扑路径的情况下区分某些驱动程序将更加困难,因为客户端必须连接到每个服务才能确定它是否是所需的服务。通过提供客户端库,可让客户端提供条件函数,从而帮助选择正确的设备,而无需添加样板代码,从而克服此缺点。
- 驱动程序可能需要添加包含识别信息的接口或函数。还可以提供客户端库,以协助驱动程序提供任何
GetInfo
协议。 - 您将无法再使用 shell 通过 /dev/sys 目录在设备树中导航。我们希望其他 ffx 工具能够满足这些需求。此外,还需要额外的路由才能将每个服务功能路由到客户端。
总体而言,我们认为此次迁移带来的积极效益远远大于额外的复杂性。
向后兼容性
我们会为使用 devfs 的驱动程序和客户端提供软迁移路径,但在某个类转换后,我们将停止为该类提供 devfs 支持。我们的目标是移除所有旧版接口的实例,因此我们不打算在初始迁移期过后提供任何向后兼容性支持。
所有驱动程序和客户端都迁移完毕后,devfs 将被移除,这意味着任何其他客户端都无法通过 devfs 访问驱动程序。
安全注意事项
这项变更将通过以下几种方式增强安全性:
- 客户端将无法再使用拓扑路径,以提供对子树中的任何驱动程序的无限制访问权限。
- 客户端在有权访问驱动程序时将无法再使用 fuchsia.device/Controller 接口
- 系统将更明确地跟踪每个客户端可以访问哪些服务和协议,并且系统和组件关闭将变得更加透明,因为可以明确跟踪依赖项。
- 现在,系统会与司机分享公交车信息,而之前不会。 此信息将在枚举中编码,以限制释放个人身份信息的可能性。虽然驾驶员无需知道其父级巴士的信息,但我们认为与驾驶员分享此类信息不会构成安全风险。
隐私注意事项
不适用
测试
此迁移的测试主要包括端到端测试和冒烟测试以及任何相关的主机测试,因为如果有任何错误,客户端将无法连接到其服务提供商。由于此更改会影响许多主机测试,因此我们建议迁移者使用所有受影响的主机测试进行手动测试。为了减少对拓扑路径的依赖,我们将借此机会改进设备枚举测试。设备枚举测试将改为检查是否正在加载所有驱动程序软件包,而不是检查特定路径。
使用 DriverTestRealm 的测试可能会受到迁移的影响,并且在迁移过程中应与其他客户端一样处理。需要添加路由,以适应使用 DriverTestRealm 的任何测试。
文档
我们计划在迁移过程中详细说明如何迁移一类驱动程序和客户端。今后,我们还将提供有关如何异步连接到服务的示例和最佳实践。随着 devfs 的弃用,我们将清理文档以移除对它的引用,并确保所有驱动程序文档都介绍了广告和连接到服务。由于驱动程序作者需要越来越多地处理 capability 路由,因此我们计划在当前文档中添加有关如何确保 capability 路由到正确组件以及如何调试常见 capability 路由问题的内容。
缺点、替代方案和未知问题
缺点
从 devfs 迁移到服务的主要缺点是,服务连接现在将是强类型的,并且 capability 路由现在必须指定要路由的服务。这两项更改都使得系统的设置要求更为严格,但也提供了更多检查来确保系统的正确使用。
迁移到服务的另一个缺点是组件连接的稳定性可能会降低。汇总服务相对较新,在迁移过程中可能会出现意外的极端情况。此外,此迁移涉及将驱动程序从由驱动程序框架团队管理的系统迁移到由其自己的团队支持的组件框架。团队之间始终存在沟通不畅的风险,这可能会导致所需功能失去支持。
替代方案
实现部分介绍了几种备选迁移方案。此迁移的主要替代方案是不执行此操作。虽然新驱动程序和客户端仍会首选使用服务,但可以保留 devfs 以供旧版驱动程序使用。这样做会使添加较新组件变得更加困难,并会进一步复杂化关闭流程,从而增加 Flake 数量。保留 devfs 还会继续阻止我们清理驱动程序生命周期,并通常会减慢团队的进度,因为我们将被迫绕过旧版系统。