☰
Linux 6.6内核 PCIe 深度解析(十二):ACS(Access Control Services)— 直通安全的守门人
2026/10/3 7:13:55 网站建设 项目流程

〇、全景: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 直接走 switch
绕过 IOMMU,隔离失效

② 有 ACS RR:P2P 被重定向
回 root complex 经 IOMMU

Root Complex(IOMMU 在这里拦 DMA)

Root Port

Switch

Downstream Port 1

Downstream 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_CAP(0x04):能力位,硬件声明"我支持哪些 ACS 功能"(只读)。
  • PCI_ACS_CTRL(0x06):控制位,软件写这里来使能对应的 ACS 功能(读写)。

1.2 7 个位各管什么

位缩写作用
SVSource Validation校验请求的 Requester ID,防止设备伪造别人的 ID 发请求
TBTranslation Blocking阻止未翻译的请求通过(和 ATS 冲突,见第四节)
RRP2P Request Redirect把下游的 P2P 请求重定向回 root complex(经 IOMMU)
CRP2P Completion Redirect把 P2P 完成也重定向回 root complex
UFUpstream Forwarding强制请求只能向上游转发(不能 P2P 横着走)
ECP2P Egress Control控制 P2P egress 端口(按端口粒度阻止 P2P)
DTDirect 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;}

两个关键点:

  1. acs_flags &= (cap | PCI_ACS_EC):只检查"能力位里声明了的"位。没声明的位(比如 RR 不在 cap 里)意味着"硬连线使能",不用查 ctrl。EC 特殊——它即使不在 cap 里也要查(因为 EC 是可选的 egress control)。
  2. (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_initpci.c:3690probe 时找 ACS capability + 尝试启用
pci_acs_enabledpci.c:3593判断设备"等效"是否支持 ACS(effective vs actual)
pci_acs_flags_enabledpci.c:3556标准检查:读 cap/ctrl 寄存器比较位
pci_acs_path_enabledpci.c:3666从设备到根逐级检查 ACS
pci_request_acspci.c:881请求启用 ACS(IOMMU 初始化调用)
pci_enable_acspci.c:986启用 ACS 的主入口
pci_std_enable_acspci.c:950标准启用(逐个位使能,TB 有条件)
pci_dev_specific_acs_enabledquirks.c:5131quirk 判断等效支持(-ENOTTY = 无 quirk)
pci_acs_ctrl_enabledquirks.c:4590quirk 用的"要求位 ⊆ 提供位"比较
pci_quirk_mf_endpoint_acsquirks.c:4908多函数 endpoint 的等效 ACS quirk
pci_quirk_intel_pch_acsquirks.c:4767Intel PCH Root Port ACS quirk
pci_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_acs)
drivers/pci/quirks.cACS quirk 表(上百条 vendor/device 映射)
drivers/iommu/iommu.cIOMMU group 划分(消费pci_acs_path_enabled)

记住三点

  1. ACS 堵的是 P2P 绕过 IOMMU 的洞:设备直通后,如果它能和别的设备 P2P,就能绕过 IOMMU 的隔离。ACS 的 RR/CR/UF 把 P2P 重定向回 root complex(经 IOMMU)或直接阻止。IOMMU 要的最小集是REQ_ACS_FLAGS = SV|RR|CR|UF。

  2. "等效支持"是核心思想:单函数 endpoint 没有 P2P 机会,即使没有 ACS capability 也等效支持(pci_acs_enabled返回 true)。pci_acs_path_enabled从设备到根逐级检查,任何一级缺 ACS 都算"不安全",设备就得和上游 bridge 捆进同一个 IOMMU group。

  3. quirk 是 ACS 的可信层:大量硬件缺 capability 但有等效隔离(多函数网卡 vendor 验证不做 P2P),或相反有 bug(Intel SPT PCH 用 dword 而非 word)。内核靠pci_dev_acs_enabled[]/pci_dev_acs_ops[]两张表兜底,-ENOTTY表示"交回标准检查"。TB 和 ATS 冲突——启用了 ATS 就不能开 TB。

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

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

立即咨询