〇、全景:ACS 决定 IOMMU group 粒度,是直通安全的守门人
把一块 GPU 直通给虚拟机(VFIO passthrough),最怕什么?怕这块 GPU通过 P2P DMA 绕过 IOMMU,直接读写宿主的其他设备或内存。IOMMU 只拦"经过 root complex"的 DMA,如果设备 A 和设备 B 在同一个 switch 下面,A 可以直接点对点(peer-to-peer)把数据怼给 B,根本不经过 IOMMU——隔离就形同虚设。
ACS(Access Control Services)就是堵这个洞的:它是一组 PCIe extended capability(PCI_EXT_CAP_ID_ACS= 0x0D)控制位,让软件能把 P2P 请求重定向回 root complex(从而经过 IOMMU),或者干脆阻止P2P。
一句话主线: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_CAP(0x04):能力位,硬件声明"我支持哪些 ACS 功能"(只读)。
- PCI_ACS_CTRL(0x06):控制位,软件写这里来使能对应的 ACS 功能(读写)。
1.2 7 个位各管什么
| 位 | 缩写 | 作用 |
|---|---|---|
| SV | Source Validation | 校验请求的 Requester ID,防止设备伪造别人的 ID 发请求 |
| TB | Translation Blocking | 阻止未翻译的请求通过(和 ATS 冲突,见第四节) |
| RR | P2P Request Redirect | 把下游的 P2P 请求重定向回 root complex(经 IOMMU) |
| CR | P2P Completion Redirect | 把 P2P 完成也重定向回 root complex |
| UF | Upstream Forwarding | 强制请求只能向上游转发(不能 P2P 横着走) |
| EC | P2P Egress Control | 控制 P2P egress 端口(按端口粒度阻止 P2P) |
| DT | Direct 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 的张力:TB(Translation Blocking)阻止一切未翻译请求,而 ATS(#7 篇)恰恰需要翻译请求通过。所以启用了 ATS 的设备不能用 TB;DT(Direct Translated P2P)则是"允许翻译过的 P2P"——配合 ATS 走一条更高效的路。
1.3 为什么"单函数设备不需要 ACS"
ACS 的注释里反复出现一个关键判断: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_enabled:effective vs actual 的逻辑
pci_acs_enabled(pci.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 行为 */ret=pci_dev_specific_acs_enabled(pdev,acs_flags);if(ret>=0)returnret>0;/* ② 非 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-3591):
Note 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_enabled(pci.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;pos=pdev->acs_cap;if(!pos)returnfalse;/* * 除 EC 外,capability 里没声明的位可以假定为"硬连线使能"。 * 所以先把 acs_flags 过滤成"能力位声明的 + EC"。 */pci_read_config_word(pdev,pos+PCI_ACS_CAP,&cap);acs_flags&=(cap|PCI_ACS_EC);pci_read_config_word(pdev,pos+PCI_ACS_CTRL,&ctrl);return(ctrl&acs_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_enabled(pci.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,*parent=start;do{pdev=parent;if(!pci_acs_enabled(pdev,acs_flags))returnfalse;/* 这一级不支持 → 整条路径不行 */if(pci_is_root_bus(pdev->bus))return(end==NULL);/* 走到 root:end 为 NULL 才算成功 */parent=pdev->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_enable=1;}// 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_acs(pci.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,pos+PCI_ACS_CAP,&cap);pci_read_config_word(dev,pos+PCI_ACS_CTRL,&ctrl);ctrl|=(cap&PCI_ACS_SV);/* Source Validation */ctrl|=(cap&PCI_ACS_RR);/* P2P Request Redirect */ctrl|=(cap&PCI_ACS_CR);/* P2P Completion Redirect */ctrl|=(cap&PCI_ACS_UF);/* Upstream Forwarding *//* Translation Blocking:只在 noats / 外置 / 不受信任时启用 */if(pci_ats_disabled()||dev->external_facing||dev->untrusted)ctrl|=(cap&PCI_ACS_TB);pci_write_config_word(dev,pos+PCI_ACS_CTRL,ctrl);}TB 和 ATS 的冲突在这里体现:pci_ats_disabled()为真(没启用 ATS)才开 TB。因为 TB 阻止"未翻译"的请求,而 ATS 恰恰需要设备发出"地址未翻译、等 IOMMU 翻译"的请求——两者水火不容。所以正常启用了 ATS 的设备,TB 是不开的;只有 noats、外置设备(external_facing,如雷电口)、或标记 untrusted 的设备才开 TB 来硬隔离。
五、quirk:ACS 的灵魂(硬件宣称 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_enabled(quirks.c:5131)遍历这张表,匹配到 (vendor, device) 就调 quirk 函数;返回-ENOTTY表示"没匹配到,我不知道"(交回标准检查)。
5.2 两类典型 quirk
(a)没有 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_enabled(quirks.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_req&acs_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 Port,ACS 寄存器用dword(control 在 offset 8)而非 spec 的word(offset 6)。这是硬件 errata,quirk 必须用 dword 访问、读 offset 8 才能正确操作 ACS。
5.3 启用侧的 quirk:pci_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_acs=pci_quirk_enable_intel_pch_acs,},{PCI_VENDOR_ID_INTEL,PCI_ANY_ID,.enable_acs=pci_quirk_enable_intel_spt_pch_acs,.disable_acs_redir=pci_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_group(iommu.c)用pci_acs_path_enabled决定"设备能否单独一个 group":
// drivers/iommu/iommu.c (v6.6, line 1665-1683) 摘要/* * 从别名导致的最小 IOMMU 粒度点,继续向上游走到"被 PCI ACS 保护 * 免受 P2P DMA"的点。如果沿途有 group,用它。 */for(bus=pdev->bus;!pci_is_root_bus(bus);bus=bus->parent){if(!bus->self)continue;if(pci_acs_path_enabled(bus->self,NULL,REQ_ACS_FLAGS))break;/* 这个 bridge 有 ACS 保护,可以停 */pdev=bus->self;group=iommu_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-passthrough:VFIO 直通要求设备独占一个 iommu_group,否则要一起直通(group 里的设备都得交给同一个 VM)。ACS 决定了这个 group 能划多细——没有 ACS 的桥,group 就只能"粗"到把整个子树捆在一起。
七、关键函数 / 文件索引
7.1 关键函数
| 函数 | 文件:行号 | 作用 |
|---|---|---|
pci_acs_init | pci.c:3690 | probe 时找 ACS capability + 尝试启用 |
pci_acs_enabled | pci.c:3593 | 判断设备"等效"是否支持 ACS(effective vs actual) |
pci_acs_flags_enabled | pci.c:3556 | 标准检查:读 cap/ctrl 寄存器比较位 |
pci_acs_path_enabled | pci.c:3666 | 从设备到根逐级检查 ACS |
pci_request_acs | pci.c:881 | 请求启用 ACS(IOMMU 初始化调用) |
pci_enable_acs | pci.c:986 | 启用 ACS 的主入口 |
pci_std_enable_acs | pci.c:950 | 标准启用(逐个位使能,TB 有条件) |
pci_dev_specific_acs_enabled | quirks.c:5131 | quirk 判断等效支持(-ENOTTY = 无 quirk) |
pci_acs_ctrl_enabled | quirks.c:4590 | quirk 用的"要求位 ⊆ 提供位"比较 |
pci_quirk_mf_endpoint_acs | quirks.c:4908 | 多函数 endpoint 的等效 ACS quirk |
pci_quirk_intel_pch_acs | quirks.c:4767 | Intel PCH Root Port ACS quirk |
pci_dev_specific_enable_acs | quirks.c:5335 | 启用侧的 quirk 分发 |
7.2 关键文件
| 文件 | 内容 |
|---|---|
include/uapi/linux/pci_regs.h | ACS capability 寄存器定义(7 个位) |
drivers/pci/pci.c | ACS 判断/启用逻辑(pci_acs_enabled/pci_enable_acs) |
drivers/pci/quirks.c | ACS quirk 表(上百条 vendor/device 映射) |
drivers/iommu/iommu.c | IOMMU 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),或相反有 bug(Intel SPT PCH 用 dword 而非 word)。内核靠
pci_dev_acs_enabled[]/pci_dev_acs_ops[]两张表兜底,-ENOTTY表示"交回标准检查"。TB 和 ATS 冲突——启用了 ATS 就不能开 TB。