假设CPU想要从PCI Express设备进行DMA读传输.与PCI Express设备的通信由事务层分组(TLP)提供.从理论上讲,TLP的最大有效载荷大小为1024双字.那么,当CPU向4兆字节大小的PCI Express设备发出DMA读取命令时,DMA控制器如何动作?
Pae*_*els 17
在PCIe枚举阶段,确定最大允许的有效载荷大小(它可以低于设备的最大有效载荷大小:例如,中间PCIe交换机具有较低的最大有效载荷大小).
大多数PCIe设备都是DMA主设备,因此驱动程序将命令传输到设备.设备将发送几个写入数据包,以xx最大大小的TLP块传输4 MiB.
基于PCI的总线在芯片组中没有芯片或子电路形式的"DMA控制器".总线上的每个设备都可以成为总线主设备.主存储器始终是从器件.
假设您已经构建了自己的PCIe设备卡,它可以充当PCI主设备,并且您的程序(在CPU上运行)希望将数据从该卡发送到主存储器(4 MiB).
设备驱动程序从操作系统知道该特定内存区域的内存映射(一些关键字:内存映射I/O,PCI总线枚举,PCI BAR).
驱动程序将命令(写入),源地址,目标地址和长度传输到设备.这可以通过将字节发送到预定义BAR内的特殊地址或写入PCI配置空间来完成.卡上的DMA主机检查这些特殊区域的新任务(分散 - 收集列表).如果是这样,这些任务就会被排队.
现在,DMA主控器知道发送的位置,数据量.他将从本地存储器中读取数据并将其包装为最大有效载荷大小的512字节TLP(路径设备<--->主存储器上的最大有效载荷大小从枚举中得知)并将其发送到目标地址.基于PCI地址的路由机制将这些TLP引导到主存储器.
frr*_*frr 17
@Paebbels 已经解释了大部分内容。在 PCI/PCI-e 中,“DMA”是通过总线主控来实现的,而掌握总线主控能力的是具有总线主控能力的外围设备。外围设备可以处理内存读/写事务,这取决于外围设备将使用什么粒度和写入(或读取)顺序。即,精确的实现细节是特定于外围设备的硬件,并且在主机CPU上运行的相应软件驱动程序必须知道如何操作特定外围设备,以在其中激发所需的DMA流量。
关于“内存管理方面”,请允许我向尊敬的读者推荐Jon Corbet 所著的一本简洁的书的两 章,该书正是关于 Linux 中的这个主题。与 DMA 接壤的内存管理位于操作系统内核的底层。Linux 及其源代码和文档通常是开始寻找“事物在幕后如何工作”的好地方(开源)。我会试着总结一下主题。
首先,请注意,DMA 访问主机 RAM(从外围 PCI 设备)与 PCI MMIO 是不同的事情 = 外围设备拥有自己的专用 RAM 组,希望将其提供给主机通过 MMIO BAR 的主机系统。这与 DMA 不同,是一种不同的机制(尽管不完全是),或者可能是“相反的观点”,如果您愿意......假设 PCI/PCI-e 上的主机和外围设备之间的差异不是很大,主桥/根联合体仅在树形拓扑、总线初始化等方面具有某种特殊的作用:-) 我希望我已经让您感到困惑了。
包含 PCI(-e) 总线树和现代主机 CPU 的计算机系统实际上与多个“地址空间”一起工作。您可能听说过 CPU 的物理地址空间(在 CPU 内核、RAM 控制器和 PCI 根桥之间的“前端总线”处表示)与由操作系统在帮助下管理的“虚拟地址空间” CPU 上对单个用户空间进程的某些硬件支持(包括内核本身的虚拟空间,与物理地址空间不同)。这两个地址空间(物理地址空间和虚拟地址空间)的出现与 PCI(-e) 总线无关。而且,猜猜看:PCI(-e) 总线有自己的地址空间,称为“总线空间”。请注意,还有所谓的“PCI 配置空间”= 另一个并行地址空间。现在让我们从 PCI 配置空间中抽象出来,因为无论如何访问它都是间接且复杂的 = 不会“妨碍”我们的主题。
因此,我们拥有三种不同的地址空间(或类别):物理地址空间、虚拟空间和 PCI(-e) 总线空间。这些需要相互“映射”。地址需要翻译。内核中的虚拟内存管理子系统使用其页表和一些 x86 硬件魔法(关键字:MMU)来完成其工作:从虚拟地址转换为物理地址。当涉及 PCI(-e) 设备时,或者更确切地说是它们的“内存映射 IO”,或者当使用 DMA 时,需要在 CPU 物理地址空间和 PCI(-e) 总线空间之间转换地址。在硬件中,在总线事务中,PCI(-e)根复合体的工作是处理有效负载流量,包括地址转换。在软件方面,内核向驱动程序提供函数(作为其内部 API 的一部分),以便能够在需要时转换地址。由于软件只关心其各自的虚拟地址空间,因此在与 PCI(-e) 外围设备通信时,它需要使用“总线空间”中的地址对 DMA 的“基地址寄存器”进行编程,因为这就是PCI(-e) 外设已上线。外设不会主动与我们玩“多地址转换游戏”...这取决于软件,或者特别是操作系统,使 PCI(-e) 总线空间分配成为主机 CPU 物理地址的一部分空间,并使主机物理空间可供 PCI 设备访问。(虽然不是典型场景,但主机甚至可以有多个 PCI(-e) 根联合体,托管 PCI(-e) 总线的多个树。它们的地址空间分配在主机 CPU 物理地址空间中不得重叠。)
有一个捷径,尽管不完全是:在 x86 PC 中,PCI(-e) 地址空间和主机 CPU 物理地址空间是一体的。不确定这是否是硬件中的硬连线(根复合体没有任何特定的映射/转换功能),或者这是否是 BIOS/UEFI 和 Linux 中“事情发生的方式”。可以说,情况确实如此。但与此同时,这并没有让 Linux 驱动程序编写者的生活变得更轻松。Linux 可以在各种硬件平台上工作,它确实有一个用于转换地址的 API,并且在跨越地址空间时必须使用该 API。
也许有趣的是,PCI(-e) 驱动程序和 DMA 上下文中的 API 简写是“bus_to_virt()”和“virt_to_bus()”。因为,对于软件来说,重要的是其各自的虚拟地址 - 那么为什么要强迫驱动程序作者转换(并跟踪)虚拟、物理和总线地址空间,从而使事情变得复杂,对吧?还有分配内存供 DMA 使用的简写:pci_alloc_concient() 和 pci_map_single() - 以及它们的释放对应项,以及几个同伴 - 如果感兴趣,您确实应该参考 Jon Corbet 的书和更多文档(以及内核源代码)。
因此,作为驱动程序作者,您分配一块 RAM 供 DMA 使用,获得各自“虚拟”风格(某些内核空间)的指针,然后将该指针转换为 PCI“总线”空间,您可以然后引用您的 PCI(-e) 外围设备 =“这是您可以上传输入数据的地方”。
然后,您可以指示外设在分配的内存窗口中执行 DMA 事务。RAM 中的 DMA 窗口可能比“最大 PCI-e 事务大小”更大(通常是这样),这意味着外围设备需要发出多个连续事务才能完成整个分配窗口的传输(可能或可能不需要,具体取决于您的应用程序)。碎片传输的具体组织方式取决于您的 PCI 外围硬件和软件驱动程序。外设可以仅使用连续偏移的已知整数计数。或者它可以使用链接列表。该列表可以动态增长。您可以通过某些 BAR 向外围设备提供该列表,也可以使用第二个 DMA 窗口(或单个窗口的一部分)在 RAM 中构建链接列表,外围 PCI 设备将沿着该链运行。这就是分散-聚集 DMA 在当代 PCI-e 实际设备中的工作原理。
外围设备可以使用 IRQ 发回完成信号或其他一些事件。一般来说,涉及 DMA 的外围设备的操作将是对 BAR 的直接轮询访问、DMA 传输和 IRQ 信号的混合。
正如您可能已经推断出的那样,在执行 DMA 时,外围设备不一定需要拥有板载专用缓冲区,该缓冲区与主机 RAM 中的 DMA 窗口分配一样大。恰恰相反 - 如果应用适合,外设可以轻松地将数据从一个字长 (32b/64b) 的内部寄存器或一个相当于单个“PCI-e 有效负载大小”的缓冲区“流”出(或流向)为了那个安排。或者一个微小的双缓冲区或类似的东西。或者外设确实可以有一个巨大的私有 RAM 来启动 DMA,并且如果不需要/不希望从总线直接进行 MMIO 访问,则不需要将这样的私有 RAM 映射到 BAR (!)。
请注意,一个外设可以轻松地向另一个外设的 MMIO BAR 启动 DMA,就像它可以通过 DMA 向/从主机 RAM 传输数据一样。也就是说,给定一个 PCI 总线,两个外围设备实际上可以直接相互发送数据,而不需要使用主机的“前端总线”上的带宽(或者现在的 PCI 根联合体北部的任何东西:快速路径、环面,等等)它)。
在 PCI 总线初始化期间,BIOS/UEFI 或操作系统将总线地址空间(和物理地址空间)窗口分配给 PCI 总线段和外设 - 以满足 BAR 对地址空间的需求,同时保持分配在系统范围内不重叠。各个 PCI 桥(包括主桥/根联合体)被配置为“解码”它们各自分配的空间,但对于不属于它们自己的地址“保持高阻抗”(静默)。请随意自行谷歌搜索“正解码”与“减法解码”,其中 PCI(-e) 总线上的一个特定路径可以变成“最后手段的地址接收器”,也许只是为了范围遗留 ISA 等
另一个切题的说明可能是:如果您从未在驱动程序中编写过简单的 MMIO,即使用 PCI 设备提供的 BAR,请知道相关关键字(API 调用)是 ioremap()(及其对应的 iounmap,在驱动程序卸载时)。这就是您如何在您的驱动程序中以内存方式访问您的 BAR。
并且:您可以通过调用 mmap() 使映射的 MMIO 条或 DMA 窗口直接可供用户空间进程使用。因此,您的用户空间进程可以直接访问该内存窗口,而无需通过 ioctl() 昂贵且间接的兔子洞。
嗯。Modulo PCI 总线延迟和带宽、可缓存属性等。
我觉得这就是我在引擎盖下陷得太深并且失去动力的地方......欢迎更正。
| 归档时间: | 
 | 
| 查看次数: | 17712 次 | 
| 最近记录: |