1

我已经收集了一些组件(包括软件和硬件)的知识,这些组件涉及基于 ARM 的板中的一般 DMA 事务,但我不明白它是如何完美集成的,我没有找到完整的连贯性对此的描述。

我会写下我已经拥有的高水平知识,我希望有人能在我错的地方纠正我并完成缺失的部分,这样整个画面就会清楚。我的描述从用户空间软件开始,然后深入到硬件组件。被误解的部分采用斜体粗体格式。

  • 用户模式应用程序请求从某个设备读取/写入,即进行 I/O 操作。
  • 操作系统接收请求并将其交给适当的驱动程序(每个操作系统都有自己的机制来执行此操作,我不需要在这里进一步深入研究,但如果您想在这里分享见解,欢迎您)
  • 负责处理 I/O 请求的驱动程序必须知道设备映射到的地址(因为我对基于 ARM 的板感兴趣,afaik 只有内存映射 I/O 而没有端口输入/输出)。在大多数情况下(如果我们考虑类似智能手机的板子),有一个 linux 内核从引导时从引导加载程序给出的设备树中解析设备地址(现代方法),或者 linux 是预编译的对于特定型号系列和电路板,其中包含设备地址(在其源代码中硬编码)(在较旧和过时的方法中)。在某些情况下(在智能手机中经常发生),部分驱动程序是预编译的,只是打包到内核中,即它们的源是封闭的,因此与设备对应的地址是未知的。这是对的吗?
  • 鉴于驱动程序知道它要与之通信的设备的相关寄存器的地址,它会分配一个缓冲区(通常在内核空间中),设备将向其写入数据(在 DMA 的帮助下)。驱动程序需要通知设备该缓冲区的位置,但是设备使用(操作内存)的地址与驱动程序(cpu)使用的地址不同,因此,驱动程序需要通知设备关于它刚刚分配的缓冲区的“总线地址”。驱动程序如何通知设备该地址?使用 IOMMU 有多受欢迎?使用 IOMMU 时,是否有一个硬件组件管理寻址或每个设备一个?
  • 然后驱动程序命令设备完成其工作(通过操作其寄存器),设备将输出数据直接传输到内存中分配的缓冲区。在这里,我对 device-driver:bus:bus-controller:actual-device 的关系有点困惑。以一些假想的设备为例,它知道以 I2C 协议进行通信;SoC 指定了一个 I2C 总线接口——这实际上是什么?I2C 总线是否有某种总线控制器?cpu是与I2C总线接口通信还是直接与设备通信?(即I2C 总线接口是无缝的)。我想对设备驱动程序有一定经验的人可以很容易地回答这个问题。
  • 该设备填充一个 DMA 通道。由于设备没有直接连接到内存,而是通过一些总线连接到 DMA 控制器(控制总线),它与 DMA 交互以将所需的数据传输到内存中分配的缓冲区。当电路板供应商使用 ARM IP 内核和总线规范时,此步骤涉及通过 AMBA 规范(即 AHB/multi-AHB/AXI)的总线上的事务,以及设备和其上的 DMAC 之间的某些协议。我想了解更多关于这一步的信息,实际发生了什么?ARM的DMA控制器有很多规格,哪一种比较流行?哪个过时了?
  • 当设备完成时,它发送一个中断,该中断通过中断控制器传送到操作系统,操作系统的中断处理程序将其引导到适当的驱动程序,该驱动程序现在知道 DMA 传输已完成。
4

1 回答 1

4

您在这里稍微混淆了两件事 - 有一些设备(例如 UART、MMC 控制器、音频控制器,通常是低带宽设备)依赖于外部 DMA 控制器(Linux 术语中的“DMA 引擎”),但许多设备是只需以自己的方式进行总线主控并直接执行自己的 DMA(例如 GPU、USB 主机控制器,当然还有 DMA 控制器本身)。前者涉及到 CPU 对 DMA 控制器进行编程的额外复杂性,所以我将忽略它,只考虑简单的总线主控 DMA。

在一个典型的 ARM SoC 中,CPU 集群和其他主外设,以及内存控制器和其他从外设,都通过各种 AMBA 互连连接在一起,形成一个单一的“总线”(一般都映射到 Linux 中的“平台总线”) ),主机根据互连的地址映射对从机寻址。您可以放心地假设设备驱动程序知道(无论是通过设备树还是硬编码)设备出现在 CPU 的物理地址映射中的位置,否则它们将毫无用处。

在更简单的系统上,只有一个地址映射,因此 CPU 用于寻址 RAM 和外围设备的物理地址可以作为 DMA 地址与其他主机自由共享。其他系统更复杂——其中一个比较知名的是Raspberry Pi 的 BCM2835,其中 CPU 和 GPU 具有不同的地址映射;例如,互连是硬连线的,因此 GPU 在“总线地址”0x7e000000 处看到外围设备,而 CPU 在“物理地址”0x20000000 处看到它们。此外,在具有 40 位物理地址的 LPAE 系统中,互连可能需要为不同的主设备提供不同的视图 - 例如,在 TI Keystone 2 SoC 中,从 CPU 的角度来看,所有 DRAM 都在 32 位边界之上,因此如果没有互连,32 位 DMA 主设备将毫无用处向他们展示不同的地址图。对于 Linux,请查看dma-ranges设备树属性以了解如何描述此类 CPU→总线转换。CPU 在告诉主机访问特定 RAM 或外设地址时必须考虑这些转换;Linux 驱动程序应该使用DMA 映射 API,该 API 提供适当转换的 DMA 地址。

IOMMU 提供了比固定互连偏移更大的灵活性——通常,地址可以动态重新映射,并且为了系统完整性,可以防止主机在任何给定时间访问除为 DMA 映射的地址以外的任何地址。此外,在具有超过 4GB RAM 的 LPAE 或 AArch64 系统中,如果 32 位外设需要能够访问 RAM 中任何位置的缓冲区,则需要 IOMMU。您会在当前的许多 64 位系统上看到 IOMMU,用于集成旧版 32 位设备,但它们也越来越受欢迎,用于设备虚拟化。

IOMMU 拓扑取决于系统和正在使用的 IOMMU——我目前正在使用的系统在单独的总线主控外设前面有 7 个独立的 ARM MMU-401/400 设备;另一方面,ARM MMU-500 可以实现为单个系统范围的设备,每个主设备都有单独的 TLB;其他供应商有自己的设计。无论哪种方式,从 Linux 的角度来看,大多数设备驱动程序都应该使用前面提到的 DMA 映射 API 来为 DMA 分配和准备物理缓冲区,如果设备连接到一个,它也会自动设置适当的 IOMMU 映射。这样,单个设备驱动程序就不需要关心 IOMMU 的存在与否。然而,其他驱动程序(通常是 GPU 驱动程序)依赖于 IOMMU 并需要完全控制,因此直接通过IOMMU API管理映射. 本质上,IOMMU 的页表设置为将特定范围的物理地址*映射到 I/O 虚拟地址范围,这些 IOVA 作为 DMA(即总线)地址提供给设备,并且 IOMMU 将 IOVA 转换回物理地址设备访问它们时的地址。一旦 DMA 操作完成,驱动程序通常会删除 IOMMU 映射,以释放 IOVA 空间并让设备不再有权访问 RAM。

请注意,在某些情况下,DMA 传输是循环的,永远不会“完成”。对于显示控制器之类的东西,CPU 可能只是为 DMA 映射一个缓冲区,将该地址传递给控制器​​并触发它启动,然后它将连续执行 DMA 读取以扫描出 CPU 写入该缓冲区的任何内容,直到它是告诉停止。

SoC 互连之外的其他外围总线,如 I 2 C/SPI/USB/等。正如您所怀疑的那样工作 - 有一个总线控制器(它本身就是 AMBA 总线上的一个设备,因此上述任何一个都可能适用于它)具有自己的设备驱动程序。在粗略的概括中,CPU 不直接与外部总线上的设备通信——AMBA 设备的驱动程序说“将 X 写入寄存器 Y”,这只是通过 CPU 执行存储到内存映射地址时发生的; I 2 C 设备驱动程序说“将 X 写入寄存器 Y”,操作系统通常有一些总线抽象层总线控制器驱动程序实现,其中 CPU 使用“将 X 写入设备 Z 上的寄存器 Y”的命令对控制器进行编程,总线控制器硬件将关闭并执行此操作,然后通过外围设备的响应通知操作系统中断或其他方式。

* 从技术上讲,IOMMU 本身,或多或少“只是另一个设备”,可能在互连中具有不同的地址映射,如前所述,但我怀疑任何实际构建这样的系统的人的理智。

于 2015-02-18T01:29:53.303 回答