Linux Kernel 如何使用 PCIe ACS
2026/7/5 2:59:31 网站建设 项目流程

本文基于Linux kernel v6.18 源码,重点说明 Linux 如何配置 PCIe ACS、ACS 和 IOMMU Group,以及 P2P Direct 场景下 kernel 如何处理 ACS redirect。


1. 概述


1.1 问题背景

PCIe ACS 解决的是“设备间 P2P TLP 是否必须回到上游路径”的问题。IOMMU 解决的是“DMA 地址如何翻译和保护”的问题。两者配合时,ACS 决定设备之间是否存在绕过 IOMMU 的 P2P 路径,IOMMU 据此决定哪些设备能被拆成独立的 IOMMU Group。

PCI Core 在枚举 PCIe Topology 时发现并配置 ACS Control;IOMMU Core 不直接配置 ACS,而是读取 ACS path 的隔离结果来决定 IOMMU Group 边界;P2P Direct 路径也会读取 ACS redirect 状态,如果 redirect 打开,就不能按直接 bus address 路径处理。图中的 Policy input 表示内核默认策略和pci=参数会共同影响 ACS Control 的最终值。

在 Linux 中,ACS 不是由 IOMMU driver 直接写寄存器完成的,而是由 PCI Core 在 PCI 设备初始化时发现 ACS Extended Capability 并配置PCI_ACS_CTRL。IOMMU Core 后续通过pci_acs_path_enabled()查询路径隔离能力。

1.2 代码范围

主要代码路径如下:

模块文件作用
PCI Coredrivers/pci/pci.cACS 能力发现、默认启用、内核参数配置、路径检查
PCI Quirkdrivers/pci/quirks.c设备特定 ACS 能力判断和 workaround
IOMMU Coredrivers/iommu/iommu.cPCI 设备 IOMMU Group 构建
P2PDMAdrivers/pci/p2pdma.cP2P 距离计算、ACS redirect 检查
DMA-IOMMUdrivers/iommu/dma-iommu.cP2P page 在 IOMMU DMA API 中的映射处理

1.3 关键路径

Linux ACS 的关键路径可以简化为:

  1. IOMMU 被检测到后调用pci_request_acs(),要求 PCI Core 尽量启用 ACS。
  2. PCI 设备初始化时调用pci_acs_init(),发现 ACS Capability 并配置 ACS Control。
  3. IOMMU 建组时调用pci_device_group(),用pci_acs_path_enabled()判断 P2P 隔离边界。
  4. P2PDMA 选择 Direct 路径时调用pci_p2pdma_distance_many(),检查路径上是否存在 ACS redirect。

2. ACS 初始化


Linux 中 ACS 的最终配置由三类输入共同决定:IOMMU 是否请求 ACS、设备或平台 quirk、管理员通过pci=指定的内核参数。

2.1 使能来源

ACS 使能的入口不是某个单独的 IOMMU driver 写 ACS 寄存器,而是 IOMMU 检测路径先调用pci_request_acs(),设置 PCI Core 的全局变量pci_acs_enable。之后每个 PCI 设备初始化时,pci_enable_acs()根据这个全局请求决定是否应用 kernel 默认 ACS 策略。需要注意的是,pci_acs_enable只是“请求启用 ACS”的软件开关;真正能不能启用,还取决于设备是否有标准 ACS Capability,或者是否存在设备特定 quirk。

2.2 默认策略

ACS 默认策略由pci_std_enable_acs()决定。Linux 在标准 ACS Capability 存在且系统请求启用 ACS 时,会在PCI_ACS_CTRL中尝试打开:

  • PCI_ACS_SV:Source Validation
  • PCI_ACS_RR:P2P Request Redirect
  • PCI_ACS_CR:P2P Completion Redirect
  • PCI_ACS_UF:Upstream Forwarding
  • PCI_ACS_TB:Translation Blocking,仅在 ATS disabled、external facing、untrusted 设备上启用

这里的目标是优先保证 DMA 隔离:P2P Request/Completion 被 redirect 到上游路径,IOMMU 才有机会参与地址翻译和权限检查。

2.3 Quirk 处理

drivers/pci/quirks.c中有两类 ACS quirk:

  • pci_dev_specific_acs_enabled():设备没有标准 ACS Capability,或者标准能力不足时,用设备特定知识判断它是否具备等价隔离能力。
  • pci_dev_specific_enable_acs():对部分平台写厂商寄存器,启用等价 ACS 行为。

典型例子是 Intel PCH Root Port。部分设备通过 LPC RCBA、UPDCR、MPC 等寄存器禁用 peer decode 或启用 requester ID 检查,从而提供SV/RR/CR/UF等价能力。

因此,quirk 有两个作用:一是让没有标准 ACS Capability 的硬件表达“等价隔离能力”;二是在初始化阶段通过厂商寄存器补上标准 ACS Control 无法覆盖的控制。

2.4 内核参数

Linux 主线支持两个直接影响 ACS 控制位的pci=参数:

参数作用风险
pci=disable_acs_redir=<pci_dev>[;...]强制关闭匹配设备上的RR/CR/EC,允许 P2P traffic 不被强制上游转发会降低设备间隔离,可能让更多设备进入同一个 IOMMU Group
pci=config_acs=<flags>@<pci_dev>[;...]按 bit 配置 ACS flags,0表示关闭,1表示开启,x表示保持不变管理员需要理解拓扑和安全影响

config_acs的 bit 定义来自Documentation/admin-guide/kernel-parameters.txt

bitACS 控制位
0Source Validation
1Translation Blocking
2P2P Request Redirect
3P2P Completion Redirect
4Upstream Forwarding
5P2P Egress Control
6Direct Translated P2P

这两个参数是在pci_setup()中解析的,但真正应用发生在pci_enable_acs()里。disable_acs_redir会先清除RR/CR/EC,然后config_acs再按管理员指定 bit 覆盖 ACS Control,所以最终结果以最后写入PCI_ACS_CTRL的值为准。

2.5 初始化流程

PCI Core 的 ACS 初始化入口是pci_acs_init()pci_acs_init()会把 ACS Extended Capability 的 offset 保存在dev->acs_cap。即使设备没有标准 ACS Capability,Linux 仍会调用pci_enable_acs(),因为部分 Root Port 可能通过 quirk 实现等价 ACS 行为。更完整的初始化流程如下:

  1. IOMMU 检测路径调用pci_request_acs(),设置全局pci_acs_enable,表示后续 PCI 设备初始化时应尽量启用 ACS 默认隔离策略。
  2. PCI 设备初始化进入pci_acs_init(),先通过pci_find_ext_capability()查找标准 ACS Extended Capability,并把 offset 保存到dev->acs_cap
  3. pci_acs_init()继续调用pci_enable_acs(),这是 ACS Control 的主要配置入口。
  4. pci_enable_acs()先调用pci_dev_specific_enable_acs(),让设备特定 quirk 有机会通过厂商寄存器完成等价 ACS 配置。
  5. 如果 quirk 没有接管,且设备存在标准 ACS Capability,PCI Core 读取PCI_ACS_CAP和当前PCI_ACS_CTRL,并保存 firmware 原始配置。
  6. 默认策略由pci_std_enable_acs()应用,通常会尝试设置SV/RR/CR/UF,并在条件满足时设置TB
  7. 管理员参数随后生效:pci=disable_acs_redir可清除RR/CR/ECpci=config_acs可按 bit 覆盖指定 ACS 控制位。
  8. 最终配置通过pci_write_config_word()写回PCI_ACS_CTRL,后续 IOMMU Group 和 P2P 判断会读取这个有效 ACS 状态。


3. ACS 与 IOMMU


3.1 隔离边界

IOMMU 只能保护经过 IOMMU 的 DMA。如果两个 PCIe 设备可以通过 Switch 直接 P2P,不经过 Root Complex/IOMMU,那么 IOMMU 页表无法阻止这种访问。ACS 的意义就是把这种 P2P 路径 redirect 到上游,或者报告 violation,从而让 IOMMU Group 的隔离假设成立。在 Linux 中,IOMMU Group 是隔离粒度。能互相绕过 IOMMU 的设备必须在同一个 group 中,不能单独分配给不同安全域。

3.2 IOMMU Group

PCI 设备的 group 构建逻辑在drivers/iommu/iommu.c::pci_device_group()。其中核心判断是:

REQ_ACS_FLAGS = PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF

pci_device_group()从设备向上遍历 PCI 层级:

  1. 先处理 DMA alias。如果设备存在上游 DMA alias,需要使用 alias 所在的最小 IOMMU 粒度。
  2. 从该位置继续向上查找,直到遇到满足REQ_ACS_FLAGS的 ACS 隔离路径。
  3. 如果路径上缺少 ACS 隔离能力,就继续向上合并到桥或上游设备所在的 group。
  4. 如果没有找到共享 group,才新建一个 IOMMU Group。

3.3 DMA 重映射

IOMMU Group 建好后,IOMMU Core 为 group 分配 default domain。普通 DMA API 最终会使用这个 domain 完成 IOVA 分配和 IOMMU 映射。

关键点是:ACS 不做地址翻译,IOMMU 也不做 PCIe 路由控制。ACS 负责保证不可信 P2P 不绕开上游路径,IOMMU 负责对经过自己的 DMA 请求做地址翻译和权限控制。

3.4 VFIO 直通

VFIO 依赖 IOMMU Group 作为用户态直通的安全边界。vfio获取设备 group 后,会通过 IOMMU API 管理 DMA ownership 和 domain attach。

如果 ACS 不足导致多个设备处于同一个 group,那么这些设备必须一起作为安全边界看待。用户态不能只安全地直通其中一个设备,因为同组设备可能通过 P2P 或 DMA alias 互相影响。

3.5 ACS 缺失

当某段 PCIe 路径缺少 Linux 需要的SV/RR/CR/UF时,pci_acs_path_enabled()返回 false。IOMMU Core 会把隔离边界继续向上扩大,表现为更多设备被放入同一个 IOMMU Group。


4. P2P Direct


4.1 P2PDMA 模型

Linux P2PDMA 把参与方分为:

角色含义
Provider提供 P2P memory,例如 PCI BAR 中的一段内存
Client发起 DMA 到 P2P memory 的设备
Orchestrator选择 provider/client 并组织数据路径的驱动

Provider 通过pci_p2pdma_add_resource()注册 P2P memory。Client 仍然使用普通dma_map_sg(),P2PDMA 和 DMA-IOMMU 层会根据 page 类型选择合适映射方式。

4.2 距离计算

P2P Direct 距离计算入口是pci_p2pdma_distance_many()。它对每个 client 调用calc_map_type_and_dist()

  1. 从 provider 和 client 各自向上找公共上游桥。
  2. 统计路径距离。
  3. 检查路径上的桥是否设置 ACS redirect。
  4. 根据结果返回PCI_P2PDMA_MAP_BUS_ADDRPCI_P2PDMA_MAP_THRU_HOST_BRIDGEPCI_P2PDMA_MAP_NOT_SUPPORTED

pci_p2pdma_map_type表示 P2PDMA page 最终应该按哪种 DMA 地址路径处理,定义在include/linux/pci-p2pdma.h

enumpci_p2pdma_map_type{PCI_P2PDMA_MAP_UNKNOWN=0,PCI_P2PDMA_MAP_NONE,PCI_P2PDMA_MAP_NOT_SUPPORTED,PCI_P2PDMA_MAP_BUS_ADDR,PCI_P2PDMA_MAP_THRU_HOST_BRIDGE,};
枚举值含义
PCI_P2PDMA_MAP_UNKNOWN内部初始状态,表示还没有计算 map type,正常 API 不应返回给调用者
PCI_P2PDMA_MAP_NONE当前 page 不是 PCI P2PDMA page,后续按普通 DMA page 处理
PCI_P2PDMA_MAP_NOT_SUPPORTEDP2P 路径不可用,或者必须经过不在 allowlist 中的 host bridge,DMA 映射应返回错误
PCI_P2PDMA_MAP_BUS_ADDRprovider 和 client 可通过 PCI Switch 直接通信,不经过 host bridge;DMA engine 使用 PCI bus address,是真正的 P2P Direct
PCI_P2PDMA_MAP_THRU_HOST_BRIDGEprovider 和 client 可以通信,但路径经过 allowlist 中的 host bridge;使用普通物理地址或 IOVA 映射,不是 Direct P2P

在 DMA-IOMMU 路径中,PCI_P2PDMA_MAP_BUS_ADDR会把 scatterlist segment 标记成 bus address,iommu_map_sg()跳过该 segment;PCI_P2PDMA_MAP_THRU_HOST_BRIDGEPCI_P2PDMA_MAP_NONE则继续走普通 IOVA 映射流程。

4.3 ACS 影响

calc_map_type_and_dist()中的关键判断是pci_bridge_has_acs_redir()。如果路径上任何桥打开了 ACS redirect,Linux 就认为 provider 和 client 之间的 traffic 会被强制走 host bridge,而不是 Direct P2P。

因此:

  • 没有 ACS redirect:可以返回PCI_P2PDMA_MAP_BUS_ADDR,DMA engine 使用 PCI bus address,形成 Direct P2P。
  • 有 ACS redirect:返回PCI_P2PDMA_MAP_THRU_HOST_BRIDGEPCI_P2PDMA_MAP_NOT_SUPPORTED,取决于 host bridge 是否可用或在 allowlist 中。
  • verbose 模式下,kernel 会打印提示,建议使用pci=disable_acs_redir=<path>关闭该路径上的 redirect。

驱动不能直接调用一个通用的pci_disable_acs_redir()来关闭 ACS redirect。Linux 中没有导出的此类 PCI Driver API;disable_acs_redir是 PCI Core 解析的内核命令行参数,最终在pci_enable_acs()中通过__pci_config_acs()清除匹配设备的RR/CR/EC。设备特定的pci_dev_specific_disable_acs_redir()也属于 PCI Core 内部 quirk 路径,不是普通驱动可直接依赖的接口。

因此,驱动侧通常只通过pci_p2pdma_distance_many()pci_p2pdma_map_type()等 P2PDMA 路径判断是否可 Direct P2P;如果 ACS redirect 阻断了 Direct P2P,只能回退到 host bridge 路径或失败,并由管理员通过pci=disable_acs_redir=<path>这类参数承担关闭隔离的决策。

P2PDMA 调用流程如下:

4.4 Direct 条件

P2P Direct 成立时,通常需要满足:

  • provider 和 client 都能归约到 PCI 设备。
  • 两者在同一可路由层级内,或 host bridge 支持对应路径。
  • 路径上没有会强制 redirect 的 ACS 控制位。
  • provider 的 P2P resource 已注册并可用于 DMA。

这也是为什么 ACS 和 P2P Direct 存在天然冲突:安全隔离希望打开RR/CR,Direct P2P 性能路径希望关闭 redirect。

4.5 回退路径

DMA-IOMMU 映射时会调用pci_p2pdma_state()

  • PCI_P2PDMA_MAP_BUS_ADDR:把 segment 标记为 bus address,iommu_map_sg()跳过该 segment,DMA 地址来自pci_p2pdma_bus_addr_map()
  • PCI_P2PDMA_MAP_THRU_HOST_BRIDGE:按普通 IOVA 流程映射。
  • PCI_P2PDMA_MAP_NOT_SUPPORTED:返回错误。
  • PCI_P2PDMA_MAP_NONE:不是 P2P page,走普通 DMA 映射。

所以,P2P Direct 不是绕过整个 DMA API,而是在 DMA API 内部根据 P2P map type 选择 bus address 或普通 IOVA。


5. 典型流程


5.1 设备枚举

PCI 枚举过程中,设备 capability 被解析,pci_acs_init()保存dev->acs_cap并尝试配置 ACS。这个阶段决定硬件 ACS Control 的初始状态。

5.2 ACS 启用

如果系统检测到 IOMMU,相关初始化路径会调用pci_request_acs()。之后 PCI Core 按默认策略启用 ACS 隔离位,并叠加设备 quirk 和管理员参数。

5.3 IOMMU 建组

IOMMU driver probe 设备时,通过ops->device_group()获取 group。对 PCI 设备,常见路径会走pci_device_group(),并用pci_acs_path_enabled()判断从设备到上游的隔离边界。

如果 ACS path 满足SV/RR/CR/UF,设备可以在更细粒度上分组;如果不满足,group 会向上合并。

5.4 VFIO 使用

VFIO 只信任 IOMMU Group,而不是单个 PCI Function。用户态直通时,group 内所有设备都属于同一个隔离单元。ACS 缺失或被关闭 redirect 可能导致 group 变大,这是安全边界变化,不只是显示上的变化。

5.5 P2P 判定

P2PDMA 不会为了 Direct 自动关闭 ACS redirect。它会检测路径,发现 redirect 后:

  1. 不能作为 Direct bus address 路径使用。
  2. 在 verbose 模式打印pci=disable_acs_redir=...建议。
  3. 由管理员决定是否牺牲隔离来换取 Direct P2P。

6. 总结


6.1 核心关系

ACS 与 IOMMU 的关系可以概括为:

  • ACS 保证 P2P traffic 不绕过上游路径。
  • IOMMU 对经过自身的 DMA 做地址翻译和权限控制。
  • IOMMU Group 使用 ACS 判断设备之间是否可被安全拆分。

6.2 配置原则

默认配置偏向隔离:Linux 在 IOMMU 存在时会请求启用 ACS,并打开SV/RR/CR/UF等关键位。管理员可以用config_acsdisable_acs_redir覆盖配置,但这会改变安全边界。

6.3 注意点

  • disable_acs_redir有助于 P2P Direct,但会降低隔离。
  • config_acs可以精细控制 ACS bit,但错误配置可能导致 IOMMU Group 变化。
  • Linux 的 P2PDMA 只检测和提示,不自动为 Direct P2P 关闭 redirect。

7. 参考资料


  • Linux kernel V6.18 Source Code
  • drivers/pci/pci.cpci_request_acs()pci_enable_acs()pci_acs_enabled()pci_acs_path_enabled()pci_acs_init()
  • drivers/pci/quirks.cpci_dev_specific_acs_enabled()pci_dev_specific_enable_acs()、Intel PCH ACS workaround
  • drivers/iommu/iommu.cpci_device_group()REQ_ACS_FLAGS、IOMMU default domain 和 DMA ownership
  • drivers/pci/p2pdma.cpci_p2pdma_distance_many()calc_map_type_and_dist()pci_bridge_has_acs_redir()
  • drivers/iommu/dma-iommu.c:P2PDMA page 在dma_map_sg()路径中的处理
  • Documentation/admin-guide/kernel-parameters.txtpci=disable_acs_redir=pci=config_acs=
  • Documentation/driver-api/pci/p2pdma.rst:Linux P2PDMA provider/client/orchestrator 模型

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询