Linux Kernel 如何使用 PCIe ACS

Linux Kernel 如何使用 PCIe ACS 本文基于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 ControlIOMMU 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 能力判断和 workaroundIOMMU 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 的关键路径可以简化为IOMMU 被检测到后调用pci_request_acs()要求 PCI Core 尽量启用 ACS。PCI 设备初始化时调用pci_acs_init()发现 ACS Capability 并配置 ACS Control。IOMMU 建组时调用pci_device_group()用pci_acs_path_enabled()判断 P2P 隔离边界。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_SVSource ValidationPCI_ACS_RRP2P Request RedirectPCI_ACS_CRP2P Completion RedirectPCI_ACS_UFUpstream ForwardingPCI_ACS_TBTranslation Blocking仅在 ATS disabled、external facing、untrusted 设备上启用这里的目标是优先保证 DMA 隔离P2P Request/Completion 被 redirect 到上游路径IOMMU 才有机会参与地址翻译和权限检查。2.3 Quirk 处理drivers/pci/quirks.c中有两类 ACS quirkpci_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参数参数作用风险pcidisable_acs_redirpci_dev[;...]强制关闭匹配设备上的RR/CR/EC允许 P2P traffic 不被强制上游转发会降低设备间隔离可能让更多设备进入同一个 IOMMU Grouppciconfig_acsflagspci_dev[;...]按 bit 配置 ACS flags0表示关闭1表示开启x表示保持不变管理员需要理解拓扑和安全影响config_acs的 bit 定义来自Documentation/admin-guide/kernel-parameters.txtbitACS 控制位0Source Validation1Translation Blocking2P2P Request Redirect3P2P Completion Redirect4Upstream Forwarding5P2P Egress Control6Direct 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 CapabilityLinux 仍会调用pci_enable_acs()因为部分 Root Port 可能通过 quirk 实现等价 ACS 行为。更完整的初始化流程如下IOMMU 检测路径调用pci_request_acs()设置全局pci_acs_enable表示后续 PCI 设备初始化时应尽量启用 ACS 默认隔离策略。PCI 设备初始化进入pci_acs_init()先通过pci_find_ext_capability()查找标准 ACS Extended Capability并把 offset 保存到dev-acs_cap。pci_acs_init()继续调用pci_enable_acs()这是 ACS Control 的主要配置入口。pci_enable_acs()先调用pci_dev_specific_enable_acs()让设备特定 quirk 有机会通过厂商寄存器完成等价 ACS 配置。如果 quirk 没有接管且设备存在标准 ACS CapabilityPCI Core 读取PCI_ACS_CAP和当前PCI_ACS_CTRL并保存 firmware 原始配置。默认策略由pci_std_enable_acs()应用通常会尝试设置SV/RR/CR/UF并在条件满足时设置TB。管理员参数随后生效pcidisable_acs_redir可清除RR/CR/ECpciconfig_acs可按 bit 覆盖指定 ACS 控制位。最终配置通过pci_write_config_word()写回PCI_ACS_CTRL后续 IOMMU Group 和 P2P 判断会读取这个有效 ACS 状态。3. ACS 与 IOMMU3.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 GroupPCI 设备的 group 构建逻辑在drivers/iommu/iommu.c::pci_device_group()。其中核心判断是REQ_ACS_FLAGS PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UFpci_device_group()从设备向上遍历 PCI 层级先处理 DMA alias。如果设备存在上游 DMA alias需要使用 alias 所在的最小 IOMMU 粒度。从该位置继续向上查找直到遇到满足REQ_ACS_FLAGS的 ACS 隔离路径。如果路径上缺少 ACS 隔离能力就继续向上合并到桥或上游设备所在的 group。如果没有找到共享 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 Direct4.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()从 provider 和 client 各自向上找公共上游桥。统计路径距离。检查路径上的桥是否设置 ACS redirect。根据结果返回PCI_P2PDMA_MAP_BUS_ADDR、PCI_P2PDMA_MAP_THRU_HOST_BRIDGE或PCI_P2PDMA_MAP_NOT_SUPPORTED。pci_p2pdma_map_type表示 P2PDMA page 最终应该按哪种 DMA 地址路径处理定义在include/linux/pci-p2pdma.henumpci_p2pdma_map_type{PCI_P2PDMA_MAP_UNKNOWN0,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 bridgeDMA 映射应返回错误PCI_P2PDMA_MAP_BUS_ADDRprovider 和 client 可通过 PCI Switch 直接通信不经过 host bridgeDMA engine 使用 PCI bus address是真正的 P2P DirectPCI_P2PDMA_MAP_THRU_HOST_BRIDGEprovider 和 client 可以通信但路径经过 allowlist 中的 host bridge使用普通物理地址或 IOVA 映射不是 Direct P2P在 DMA-IOMMU 路径中PCI_P2PDMA_MAP_BUS_ADDR会把 scatterlist segment 标记成 bus addressiommu_map_sg()跳过该 segmentPCI_P2PDMA_MAP_THRU_HOST_BRIDGE和PCI_P2PDMA_MAP_NONE则继续走普通 IOVA 映射流程。4.3 ACS 影响calc_map_type_and_dist()中的关键判断是pci_bridge_has_acs_redir()。如果路径上任何桥打开了 ACS redirectLinux 就认为 provider 和 client 之间的 traffic 会被强制走 host bridge而不是 Direct P2P。因此没有 ACS redirect可以返回PCI_P2PDMA_MAP_BUS_ADDRDMA engine 使用 PCI bus address形成 Direct P2P。有 ACS redirect返回PCI_P2PDMA_MAP_THRU_HOST_BRIDGE或PCI_P2PDMA_MAP_NOT_SUPPORTED取决于 host bridge 是否可用或在 allowlist 中。verbose 模式下kernel 会打印提示建议使用pcidisable_acs_redirpath关闭该路径上的 redirect。驱动不能直接调用一个通用的pci_disable_acs_redir()来关闭 ACS redirect。Linux 中没有导出的此类 PCI Driver APIdisable_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 路径或失败并由管理员通过pcidisable_acs_redirpath这类参数承担关闭隔离的决策。P2PDMA 调用流程如下4.4 Direct 条件P2P Direct 成立时通常需要满足provider 和 client 都能归约到 PCI 设备。两者在同一可路由层级内或 host bridge 支持对应路径。路径上没有会强制 redirect 的 ACS 控制位。provider 的 P2P resource 已注册并可用于 DMA。这也是为什么 ACS 和 P2P Direct 存在天然冲突安全隔离希望打开RR/CRDirect P2P 性能路径希望关闭 redirect。4.5 回退路径DMA-IOMMU 映射时会调用pci_p2pdma_state()PCI_P2PDMA_MAP_BUS_ADDR把 segment 标记为 bus addressiommu_map_sg()跳过该 segmentDMA 地址来自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 后不能作为 Direct bus address 路径使用。在 verbose 模式打印pcidisable_acs_redir...建议。由管理员决定是否牺牲隔离来换取 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_acs或disable_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 Codedrivers/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 workarounddrivers/iommu/iommu.cpci_device_group()、REQ_ACS_FLAGS、IOMMU default domain 和 DMA ownershipdrivers/pci/p2pdma.cpci_p2pdma_distance_many()、calc_map_type_and_dist()、pci_bridge_has_acs_redir()drivers/iommu/dma-iommu.cP2PDMA page 在dma_map_sg()路径中的处理Documentation/admin-guide/kernel-parameters.txtpcidisable_acs_redir、pciconfig_acsDocumentation/driver-api/pci/p2pdma.rstLinux P2PDMA provider/client/orchestrator 模型