:ACS(Access Control Services)— 直通安全的守门人)
〇、全景ACS 决定 IOMMU group 粒度是直通安全的守门人把一块 GPU 直通给虚拟机VFIO passthrough最怕什么怕这块 GPU通过 P2P DMA 绕过 IOMMU直接读写宿主的其他设备或内存。IOMMU 只拦经过 root complex的 DMA如果设备 A 和设备 B 在同一个 switch 下面A 可以直接点对点peer-to-peer把数据怼给 B根本不经过 IOMMU——隔离就形同虚设。ACSAccess Control Services就是堵这个洞的它是一组 PCIe extended capabilityPCI_EXT_CAP_ID_ACS 0x0D控制位让软件能把 P2P 请求重定向回 root complex从而经过 IOMMU或者干脆阻止P2P。① 无 ACSP2P 直接走 switch绕过 IOMMU隔离失效② 有 ACS RRP2P 被重定向回 root complex 经 IOMMURoot ComplexIOMMU 在这里拦 DMARoot PortSwitchDownstream Port 1Downstream Port 2设备 A直通给 VM设备 B宿主的一句话主线ACS 是一组控制 P2P 的寄存器位内核用它判断设备能否和别的设备安全隔离。隔离能力pci_acs_enabled/pci_acs_path_enabled直接决定 IOMMU group 的粒度——路径上有 ACS 保护设备才能单独一个 group 单独直通。而 ACS 最微妙的地方在于quirk大量硬件没有标准 ACS capability 却有等效的隔离行为或相反内核用一张巨大的 quirk 表来兜底。一、ACS 是什么7 个控制位 一个安全问题1.1 寄存器结构ACS 是 extended capability挂在 PCIe extended capability 链表上——从 offset 0x100 起的另一条链表区别于普通 capability 链表结构很简单// include/uapi/linux/pci_regs.h (v6.6, line 720, 981-991)#definePCI_EXT_CAP_ID_ACS0x0D/* Access Control Services */...#definePCI_ACS_CAP0x04/* ACS Capability Register能力位 */#definePCI_ACS_SV0x0001/* Source Validation */#definePCI_ACS_TB0x0002/* Translation Blocking */#definePCI_ACS_RR0x0004/* P2P Request Redirect */#definePCI_ACS_CR0x0008/* P2P Completion Redirect */#definePCI_ACS_UF0x0010/* Upstream Forwarding */#definePCI_ACS_EC0x0020/* P2P Egress Control */#definePCI_ACS_DT0x0040/* Direct Translated P2P */#definePCI_ACS_EGRESS_BITS0x05/* ACS Egress Control Vector Size */#definePCI_ACS_CTRL0x06/* ACS Control Register控制位写这里使能 */#definePCI_ACS_EGRESS_CTL_V0x08/* ACS Egress Control Vector */PCI_ACS_CAP0x04能力位硬件声明我支持哪些 ACS 功能只读。PCI_ACS_CTRL0x06控制位软件写这里来使能对应的 ACS 功能读写。1.2 7 个位各管什么位缩写作用SVSource Validation校验请求的 Requester ID防止设备伪造别人的 ID 发请求TBTranslation Blocking阻止未翻译的请求通过和 ATS 冲突见第四节RRP2P Request Redirect把下游的 P2P 请求重定向回 root complex经 IOMMUCRP2P Completion Redirect把 P2P 完成也重定向回 root complexUFUpstream Forwarding强制请求只能向上游转发不能 P2P 横着走ECP2P Egress Control控制 P2P egress 端口按端口粒度阻止 P2PDTDirect Translated P2P允许经过翻译的 P2P配合 ATS见下其中RR/CR/UF 是直通隔离的核心——它们一起保证设备发出的 P2P 要么被重定向回 root complex经 IOMMU 检查要么干脆被阻止。这正是 IOMMU group 划分要求的REQ_ACS_FLAGS// drivers/iommu/iommu.c (v6.6, line 1532)#defineREQ_ACS_FLAGS(PCI_ACS_SV|PCI_ACS_RR|PCI_ACS_CR|PCI_ACS_UF)注意TB 和 DT 的张力TBTranslation Blocking阻止一切未翻译请求而 ATS#7 篇恰恰需要翻译请求通过。所以启用了 ATS 的设备不能用 TBDTDirect Translated P2P则是允许翻译过的 P2P——配合 ATS 走一条更高效的路。1.3 为什么单函数设备不需要 ACSACS 的注释里反复出现一个关键判断ACS 只对有机会做 P2P的设备有意义。一个单函数 endpoint 挂在某个 port 下面它没有同一个物理设备里的另一个 function可以做内部 P2P它要 P2P 只能经过 switch/root complex——那里本来就有 ACS/IOMMU 管着。所以规范PCIe 3.0, 6.12.1.3说单函数设备除 downstream port不需要 ACS。这个effective等效vs actual实际的区分是pci_acs_enabled的核心设计见第二节。二、pci_acs_enabledeffective vs actual 的逻辑pci_acs_enabledpci.c:3593回答一个问题“这个设备等效地是否提供了要求的 ACS 隔离能力” 注意它问的是等效不是配置空间里有没有 ACS capability。2.1 判断流程// drivers/pci/pci.c (v6.6, line 3593-3655) 摘要boolpci_acs_enabled(structpci_dev*pdev,u16 acs_flags){intret;/* ① 先问 quirk这个设备有没有特殊的 ACS 行为 */retpci_dev_specific_acs_enabled(pdev,acs_flags);if(ret0)returnret0;/* ② 非 PCIe 设备Conventional PCI/PCI-X永远不支持 ACS * 共享总线任何设备都能收到/snoop 别人的 DMA */if(!pci_is_pcie(pdev))returnfalse;switch(pci_pcie_type(pdev)){/* ③ 这些类型规范明确永不实现 ACS */casePCI_EXP_TYPE_PCIE_BRIDGE:/* PCI/X-to-PCIe bridge */casePCI_EXP_TYPE_PCI_BRIDGE:/* PCIe-to-PCI/X bridge */casePCI_EXP_TYPE_RC_EC:/* RC Event Collector */returnfalse;/* ④ downstream / root port 必须实现 ACS不管单/多函数 */casePCI_EXP_TYPE_DOWNSTREAM:casePCI_EXP_TYPE_ROOT_PORT:returnpci_acs_flags_enabled(pdev,acs_flags);/* ⑤ endpoint/upstream 等只有多函数才需要 ACS */casePCI_EXP_TYPE_ENDPOINT:casePCI_EXP_TYPE_UPSTREAM:casePCI_EXP_TYPE_LEG_END:casePCI_EXP_TYPE_RC_END:if(!pdev-multifunction)break;/* 单函数 → 落到下面 return true */returnpci_acs_flags_enabled(pdev,acs_flags);}/* ⑥ 单函数设备除 downstream port没有 P2P 机会等效支持 */returntrue;}核心是注释里那句话pci.c:3585-3591Note that this interface checks theeffectiveACS capabilities of the device rather than theactualcapabilities. … most single function endpoints are not required to support ACS because they have no opportunity for peer-to-peer access. We therefore return ‘true’ regardless of whether the device exposes an ACS capability.“等效支持”单函数 endpoint 即使配置空间里根本没有 ACS capability也返回 true——因为它压根没有 P2P 的机会等效于隔离得很好。这样 IOMMU 层调用时就不必关心设备类型/拓扑直接问能不能隔离。2.2 标准检查pci_acs_flags_enabled真正有 ACS capability 的设备downstream/root port、多函数设备走pci_acs_flags_enabledpci.c:3556读寄存器// drivers/pci/pci.c (v6.6, line 3556-3574) 摘要staticboolpci_acs_flags_enabled(structpci_dev*pdev,u16 acs_flags){intpos;u16 cap,ctrl;pospdev-acs_cap;if(!pos)returnfalse;/* * 除 EC 外capability 里没声明的位可以假定为硬连线使能。 * 所以先把 acs_flags 过滤成能力位声明的 EC。 */pci_read_config_word(pdev,posPCI_ACS_CAP,cap);acs_flags(cap|PCI_ACS_EC);pci_read_config_word(pdev,posPCI_ACS_CTRL,ctrl);return(ctrlacs_flags)acs_flags;}两个关键点acs_flags (cap | PCI_ACS_EC)只检查能力位里声明了的位。没声明的位比如 RR 不在 cap 里意味着硬连线使能不用查 ctrl。EC 特殊——它即使不在 cap 里也要查因为 EC 是可选的 egress control。(ctrl acs_flags) acs_flags要求的位必须在 ctrl 里都使能了。三、pci_acs_path_enabled从设备到根的路径检查单个设备支持 ACS 还不够——P2P 可能发生在路径上的任何一级。所以真正决定能否隔离的是pci_acs_path_enabledpci.c:3666从设备一路走到 root每一级 bridge 都要支持 ACS。// drivers/pci/pci.c (v6.6, line 3666-3684) 摘要boolpci_acs_path_enabled(structpci_dev*start,structpci_dev*end,u16 acs_flags){structpci_dev*pdev,*parentstart;do{pdevparent;if(!pci_acs_enabled(pdev,acs_flags))returnfalse;/* 这一级不支持 → 整条路径不行 */if(pci_is_root_bus(pdev-bus))return(endNULL);/* 走到 rootend 为 NULL 才算成功 */parentpdev-bus-self;/* 上一级的 bridge */}while(pdev!end);returntrue;}逻辑很直接从 start 逐级往上parent pdev-bus-self每一级都pci_acs_enabled检查任何一级不支持整条路径就不安全。走到 root bus 时如果end NULL调用者要搜到根就成功。四、ACS 启用默认关TB 和 ATS 冲突4.1 默认不启用IOMMU 请求才开ACS 默认不启用pci_acs_enable参数默认 0。只有 IOMMU 驱动初始化时显式请求// drivers/pci/pci.c (v6.6, line 876-884)staticintpci_acs_enable;voidpci_request_acs(void){pci_acs_enable1;}// drivers/iommu/intel/dmar.c (v6.6, line 934) / amd/init.c:3253pci_request_acs();/* IOMMU 初始化时请求启用 ACS */为什么默认关因为 ACS 的 RR/CR重定向会把 P2P 绕道 root complex增加延迟、占用 root complex 带宽。没有 IOMMU不需要隔离时让 P2P 直接走 switch 更快。所以 ACS 是按需启用的。4.2 pci_enable_acs 的启用链// drivers/pci/pci.c (v6.6, line 986-1005) 摘要staticvoidpci_enable_acs(structpci_dev*dev){if(!pci_acs_enable)gotodisable_acs_redir;/* 没被请求 → 不启用 */if(!pci_dev_specific_enable_acs(dev))/* quirk 成功返回 0→ 跳过标准启用 */gotodisable_acs_redir;pci_std_enable_acs(dev);/* 无 quirk-ENOTTY→ 标准启用 */disable_acs_redir:pci_disable_acs_redir(dev);/* 处理 disable_acs_redir 参数 */}标准启用pci_std_enable_acspci.c:950逐个位使能但TB 有条件// drivers/pci/pci.c (v6.6, line 950-979) 摘要staticvoidpci_std_enable_acs(structpci_dev*dev){intpos;u16 cap,ctrl;pci_read_config_word(dev,posPCI_ACS_CAP,cap);pci_read_config_word(dev,posPCI_ACS_CTRL,ctrl);ctrl|(capPCI_ACS_SV);/* Source Validation */ctrl|(capPCI_ACS_RR);/* P2P Request Redirect */ctrl|(capPCI_ACS_CR);/* P2P Completion Redirect */ctrl|(capPCI_ACS_UF);/* Upstream Forwarding *//* Translation Blocking只在 noats / 外置 / 不受信任时启用 */if(pci_ats_disabled()||dev-external_facing||dev-untrusted)ctrl|(capPCI_ACS_TB);pci_write_config_word(dev,posPCI_ACS_CTRL,ctrl);}TB 和 ATS 的冲突在这里体现pci_ats_disabled()为真没启用 ATS才开 TB。因为 TB 阻止未翻译的请求而 ATS 恰恰需要设备发出地址未翻译、等 IOMMU 翻译的请求——两者水火不容。所以正常启用了 ATS 的设备TB 是不开的只有 noats、外置设备external_facing如雷电口、或标记 untrusted 的设备才开 TB 来硬隔离。五、quirkACS 的灵魂硬件宣称 vs 实际行为ACS 最微妙、也是代码里占比最大的部分是quirk——因为硬件的 ACS 声明经常不可信有些设备有标准 ACS capability但实际不工作硬件 bug。有些设备没有标准 ACS capability但硬件设计等效隔离比如多函数设备 vendor 验证过内部不做 P2P、或 SoC 的 Root Port 物理上无法互相 P2P。内核用两张表来兜底5.1pci_dev_acs_enabled[]判断等效是否支持// drivers/pci/quirks.c (v6.6, line 4970-4974)staticconststructpci_dev_acs_enabled{u16 vendor;u16 device;int(*acs_enabled)(structpci_dev*dev,u16 acs_flags);}pci_dev_acs_enabled[]{{PCI_VENDOR_ID_ATI,0x4385,pci_quirk_amd_sb_acs},{PCI_VENDOR_ID_INTEL,PCI_ANY_ID,pci_quirk_rciep_acs},{PCI_VENDOR_ID_INTEL,PCI_ANY_ID,pci_quirk_intel_pch_acs},.../* 上百条 vendor/device → quirk 函数 */{0}};pci_dev_specific_acs_enabledquirks.c:5131遍历这张表匹配到 (vendor, device) 就调 quirk 函数返回-ENOTTY表示没匹配到我不知道交回标准检查。5.2 两类典型 quirka没有 ACS capability 但等效隔离—— 多函数 endpoint// drivers/pci/quirks.c (v6.6, line 4908-4922)staticintpci_quirk_mf_endpoint_acs(structpci_dev*dev,u16 acs_flags){/* * SV、TB、UF 对多函数 endpoint 无关。多函数设备只在支持 P2P 时 * 才需要实现 RR/CR/DT。匹配这个 quirk 的设备已被 vendor 验证 * 不和其他 function 做 P2P所以把这些位当作等效实现。 */returnpci_acs_ctrl_enabled(acs_flags,PCI_ACS_SV|PCI_ACS_TB|PCI_ACS_RR|PCI_ACS_CR|PCI_ACS_UF|PCI_ACS_DT);}这些是 Intel 网卡82575/82576/82580/I350 等——它们是多函数设备一个物理卡多个 function但 vendor 验证过内部不做 P2P所以等效于支持全套 ACS 隔离。pci_acs_ctrl_enabledquirks.c:4590就是要求的位是否都被 quirk 声明的集合覆盖// drivers/pci/quirks.c (v6.6, line 4590-4594)staticintpci_acs_ctrl_enabled(u16 acs_ctrl_req,u16 acs_ctrl_ena){if((acs_ctrl_reqacs_ctrl_ena)acs_ctrl_req)return1;return0;}b硬件有 ACS 但实现有 bug—— Intel SPT PCH// drivers/pci/quirks.c (v6.6, line 4825-4841) 注释摘要/* * Sunrise Point PCH root ports implement ACS, but unfortunately ... * this chipset uses dwords for the ACS capability and control registers * whereas the PCIe spec packs them into words. The bit definitions are * correct, but the control register is at offset 8 instead of 6 ... * This applies to 0xa110-0xa11f, 0xa167-0xa16a. */Intel 100 系列 PCH 的 Root PortACS 寄存器用dwordcontrol 在 offset 8而非 spec 的wordoffset 6。这是硬件 errataquirk 必须用 dword 访问、读 offset 8 才能正确操作 ACS。5.3 启用侧的 quirkpci_dev_acs_ops[]除了判断是否支持还有怎么启用的 quirk有些设备的 ACS 寄存器地址/宽度非标准// drivers/pci/quirks.c (v6.6, line 5320-5333)staticconststructpci_dev_acs_ops{u16 vendor;u16 device;int(*enable_acs)(structpci_dev*dev);int(*disable_acs_redir)(structpci_dev*dev);}pci_dev_acs_ops[]{{PCI_VENDOR_ID_INTEL,PCI_ANY_ID,.enable_acspci_quirk_enable_intel_pch_acs,},{PCI_VENDOR_ID_INTEL,PCI_ANY_ID,.enable_acspci_quirk_enable_intel_spt_pch_acs,.disable_acs_redirpci_quirk_disable_intel_spt_pch_acs_redir,},};小结quirk 是 ACS 的可信层。标准 ACS capability 只对听话的硬件有效真实世界里大量设备要么缺 capability 但有等效隔离用pci_dev_acs_enabled[]声明等效支持要么有 capability 但有 bug用 quirk 特殊处理寄存器访问。pci_dev_specific_acs_enabled返回-ENOTTY是我管不了交回标准检查的降级协议——和 #9 复位篇的-ENOTTY是同一个模式。六、与 IOMMU group 的关系最终目的ACS 的最终消费者是IOMMU group 划分。pci_device_groupiommu.c用pci_acs_path_enabled决定设备能否单独一个 group// drivers/iommu/iommu.c (v6.6, line 1665-1683) 摘要/* * 从别名导致的最小 IOMMU 粒度点继续向上游走到被 PCI ACS 保护 * 免受 P2P DMA的点。如果沿途有 group用它。 */for(buspdev-bus;!pci_is_root_bus(bus);busbus-parent){if(!bus-self)continue;if(pci_acs_path_enabled(bus-self,NULL,REQ_ACS_FLAGS))break;/* 这个 bridge 有 ACS 保护可以停 */pdevbus-self;groupiommu_group_get(pdev-dev);if(group)returngroup;/* 没有 ACS 保护归入上游 bridge 的 group */}逻辑从设备往上走每经过一个 bridge 就问这条路径有没有 ACS 保护pci_acs_path_enabled“。有 ACS 保护 → break设备可以单独一个 group单独直通没有 → 设备必须和这个 bridge 归入同一个 group因为 P2P 可能绕过 IOMMU只能一起管”。这就是 ACS 的实际意义ACS 状态IOMMU group 粒度直通影响路径全程有 ACS每个设备一个 group可以单独直通给 VM路径某级缺 ACS设备和上游 bridge 一个 group必须整个 group 一起直通或不能直通呼应 IOMMU 系列 vfio-passthroughVFIO 直通要求设备独占一个 iommu_group否则要一起直通group 里的设备都得交给同一个 VM。ACS 决定了这个 group 能划多细——没有 ACS 的桥group 就只能粗到把整个子树捆在一起。七、关键函数 / 文件索引7.1 关键函数函数文件:行号作用pci_acs_initpci.c:3690probe 时找 ACS capability 尝试启用pci_acs_enabledpci.c:3593判断设备等效是否支持 ACSeffective vs actualpci_acs_flags_enabledpci.c:3556标准检查读 cap/ctrl 寄存器比较位pci_acs_path_enabledpci.c:3666从设备到根逐级检查 ACSpci_request_acspci.c:881请求启用 ACSIOMMU 初始化调用pci_enable_acspci.c:986启用 ACS 的主入口pci_std_enable_acspci.c:950标准启用逐个位使能TB 有条件pci_dev_specific_acs_enabledquirks.c:5131quirk 判断等效支持-ENOTTY 无 quirkpci_acs_ctrl_enabledquirks.c:4590quirk 用的要求位 ⊆ 提供位比较pci_quirk_mf_endpoint_acsquirks.c:4908多函数 endpoint 的等效 ACS quirkpci_quirk_intel_pch_acsquirks.c:4767Intel PCH Root Port ACS quirkpci_dev_specific_enable_acsquirks.c:5335启用侧的 quirk 分发7.2 关键文件文件内容include/uapi/linux/pci_regs.hACS capability 寄存器定义7 个位drivers/pci/pci.cACS 判断/启用逻辑pci_acs_enabled/pci_enable_acsdrivers/pci/quirks.cACS quirk 表上百条 vendor/device 映射drivers/iommu/iommu.cIOMMU group 划分消费pci_acs_path_enabled记住三点ACS 堵的是 P2P 绕过 IOMMU 的洞设备直通后如果它能和别的设备 P2P就能绕过 IOMMU 的隔离。ACS 的 RR/CR/UF 把 P2P 重定向回 root complex经 IOMMU或直接阻止。IOMMU 要的最小集是REQ_ACS_FLAGS SV|RR|CR|UF。等效支持是核心思想单函数 endpoint 没有 P2P 机会即使没有 ACS capability 也等效支持pci_acs_enabled返回 true。pci_acs_path_enabled从设备到根逐级检查任何一级缺 ACS 都算不安全设备就得和上游 bridge 捆进同一个 IOMMU group。quirk 是 ACS 的可信层大量硬件缺 capability 但有等效隔离多函数网卡 vendor 验证不做 P2P或相反有 bugIntel SPT PCH 用 dword 而非 word。内核靠pci_dev_acs_enabled[]/pci_dev_acs_ops[]两张表兜底-ENOTTY表示交回标准检查。TB 和 ATS 冲突——启用了 ATS 就不能开 TB。