驱动程序通过读取和写入设备内存中的位和字节与系统中的设备进行通信。在 Fuchsia 中,驱动程序执行以下操作来设置对设备内存的访问权限:
- 获取一个对象,该对象表示系统中的设备内存区域。
- 将该对象映射到驱动程序主机(即驱动程序所在的进程)中的 地址空间。
图 1. 进程将其虚拟内存对象 (VMO) 映射到自己的虚拟内存地址区域 (VMAR),以访问 VMO 表示的内存区域。
从驱动程序访问设备的内存
在 Fuchsia 中,驱动程序通过获取一个虚拟内存对象 (VMO)(该对象表示系统中的设备内存区域)来获得在设备内存中读取和写入数据的能力。
与通常由 DRAM 支持的进程内存不同,设备内存区域由寄存器支持。因此,设备的 VMO 表示指向这些寄存器的内存映射 I/O (MMIO) 区域。当驱动程序从此区域读取或写入数据时,其效果与直接从设备的寄存器读取或写入数据相同。
根总线驱动程序(例如平台、PCI 或 ACPI 总线驱动程序)知道连接到总线的所有设备,并且具有为系统中的设备创建 VMO 的特殊能力。创建后,这些 VMO 可以传递给其他驱动程序。然后,驱动程序使用 VMO 访问其目标设备的内存映射 I/O 区域。
以下序列介绍了使驱动程序能够访问设备内存的事件:
- 驱动程序管理器发现设备并 将其绑定到驱动程序。
- 驱动程序请求为设备生成 VMO。
- 根总线驱动程序调用 syscall 以创建设备的 VMO。
- Zircon 内核会创建一个表示设备内存区域的 VMO。
- 根总线驱动程序接收 VMO 并将其传递给驱动程序。
- VMAR
- 驱动程序在驱动程序主机的映射地址空间中读取和写入数据,以与设备进行通信。
为设备生成 VMO
Fuchsia 提供了以下(但不限于)方法来检索表示设备内存的 VMO:
- PCI FIDL 协议提供了
GetBar(),用于从设备检索基址 寄存器 (BAR)。 - ACPI FIDL 协议提供了
GetMmio(),用于检索设备的 内存区域。 - 平台设备 FIDL 协议提供了
GetMmio(),用于 检索设备的内存区域。
驱动程序获取设备的 VMO 后,会将 VMO 映射到驱动程序主机中的虚拟地址空间区域。驱动程序必须进行此设置,才能在设备内存中 读取和写入数据 (请参阅在驱动程序中将 VMO 映射到 VMAR)。
虽然图 1 似乎显示每个设备都有一个 VMO,但设备在物理内存中可以有多个内存区域。在这种情况下,设备可能需要生成多个 VMO 来分别表示这些内存区域。
物理内存中的设备 VMO
一般来说,VMO 表示虚拟内存的连续区域。但对于设备,VMO 的创建方式与其他常规 VMO 不同。设备的 VMO 由物理页面支持,这些物理页面涵盖系统物理内存中的连续区域。
如需创建此类 VMO,具有特殊功能的总线驱动程序会调用
zx_vmo_create_physical syscall,并向 Zircon 内核提供设备内存区域的物理地址和大小。
然后,内核会返回一个 VMO,该 VMO 由系统内存中的连续物理页面集群支持。
为设备分配地址空间
在 Fuchsia 系统中,设备的地址空间区域通过以下方式之一确定:
但是,USB 设备永远不会收到地址空间区域。换句话说,当 USB 设备热插拔到 Fuchsia 系统时,永远不会为 USB 设备生成 VMO。相反,USB 控制器驱动程序会获取所有 USB 设备的内存映射资源。USB 设备的驱动程序使用 FIDL 协议与其 USB 控制器驱动程序进行通信,然后 USB 控制器驱动程序会将这些交互转换为其自己的内存映射地址空间中的读取和写入操作。
对于也允许热插拔的 PCI 设备,系统会为所有可能热插拔的 PCI 设备预分配特定的地址空间区域。此地址空间的子部分会分配给绑定到热插拔 PCI 设备的驱动程序。PCI 控制器驱动程序负责向驱动程序提供此地址空间的正确区域。
在驱动程序中将 VMO 映射到 VMAR
VMAR(虚拟内存地址区域)表示进程中 虚拟地址空间的连续区域。在 Fuchsia 中,进程通常会将 VMO(表示系统中虚拟内存的连续区域)映射到 VMAR,以方便在 VMO 表示的内存区域中读取和写入数据。
如果不映射到 VMAR,进程只能使用一组
syscall(例如 zx_vmo_read 和 zx_vmo_write)与 VMO 进行交互。
这是因为所有内存都映射到内核的地址空间中,只有 syscall 才能切换到内核模式以直接从内存读取或写入内存。
驱动程序不得使用这些 syscall 与设备内存 VMO 进行交互,并且必须 将表示设备内存区域的 VMO 映射到 驱动程序主机(即驱动程序所在的进程)中的 VMAR。 驱动程序不得使用 syscall 与设备的内存进行交互,原因如下:
- 与寄存器映射方式相关的问题(请参阅 缓存和未缓存的寄存器)。
- 可安全用于与寄存器交互的指令类型存在限制。
如果单个驱动程序主机中有多个驱动程序,则同一驱动程序主机中的所有驱动程序共享同一个 根 VMAR。
用于与设备内存交互的帮助程序库
Fuchsia 提供了一个帮助程序库(请参阅 lib/mmio),该库抽象了使用 VMO 和 VMAR 的
详细信息。此帮助程序库提供的抽象可确保对设备上的寄存器进行读取和写入操作是安全的。
例如,由于并非与系统中内存交互的每条指令都被认为是安全的,因此帮助程序库包含内联汇编指令,以确保在寄存器中读取和写入数据时的正确性。
缓存和未缓存的寄存器
在 Fuchsia 中,最好以缓存一致的方式映射寄存器。在 x64 上,外围内存始终是缓存一致的,因此可以映射为缓存。但是,在 ARM 上,外围内存不是缓存一致的,因此通常映射为未缓存。
Fuchsia 允许使用替代方法映射寄存器并手动管理这些寄存器的缓存一致性。但是,只有在使用未缓存的映射时遇到性能问题的高级开发者才应考虑此方法。