ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Linux 6.6内 IOMMU 深度解析(五):设备挂载 — 从 PCI 枚举到 IOMMU 接管

Linux 6.6内 IOMMU 深度解析(五):设备挂载 — 从 PCI 枚举到 IOMMU 接管 〇、这篇文章在系列中的位置这篇填补中间的空白PCI 设备被发现后IOMMU 怎么接管它——group 怎么分、domain 怎么配、DMA 翻译从何时生效。一、全景时间线pci_scan_single_device()PCI 设备枚举pci_init_capabilities()读 ACS Capabilitydevice_add()触发 BUS_NOTIFY_ADD_DEVICEiommu_bus_notifier()IOMMU 核心介入iommu_probe_device()ops-device_group()创建/确定 iommu_groupACS 决定隔离边界iommu_setup_default_domain()分配 挂载 default domainops-attach_dev()驱动写 DTE/ContextEntryDMA 翻译生效ops-probe_finalize()安装 iommu_dma_ops设备 DMA API 可用关键时序IOMMU 接管发生在 PCI 驱动绑定之前。设备驱动probe()被调用时dma_map_single()等 DMA API 已经走 IOMMU 路径了。二、阶段 1总线通知 — IOMMU 怎么发现新设备IOMMU 核心在iommu_subsys_init()时注册了一个总线通知链// drivers/iommu/iommu.c (v6.6, line 192-229)staticint__initiommu_subsys_init(void){// 注册 bus notifier监听所有 IOMMU-capable bus 的设备增删事件for(i0;iARRAY_SIZE(iommu_buses);i)bus_register_notifier(iommu_buses[i],nb[i]);// iommu_buses[] 包含 pci_bus_typeCONFIG_PCI}当 PCI 设备被pci_device_add()→device_add()添加到设备模型时内核发出BUS_NOTIFY_ADD_DEVICE// drivers/pci/probe.c (v6.6, line 2539-2583)// pci_device_add():// pci_init_capabilities(dev) // ← ACS Cap 在这里读取// device_add(dev-dev) // ← 触发 BUS_NOTIFY_ADD_DEVICE通知回调// drivers/iommu/iommu.c (v6.6, line 1793-1808)staticintiommu_bus_notifier(structnotifier_block*nb,unsignedlongaction,void*data){structdevice*devdata;switch(action){caseBUS_NOTIFY_ADD_DEVICE:returniommu_probe_device(dev);// ★ 新设备caseBUS_NOTIFY_REMOVED_DEVICE:iommu_release_device(dev);// ★ 设备移除}}补充路径 — 重放replay当 IOMMU 驱动注册时iommu_device_register()已存在的设备需要补一刀。bus_iommu_probe()遍历总线上所有已注册设备重新走一遍__iommu_probe_device()。三、阶段 2iommu_group — 隔离的基本单元3.1 group 是什么IOMMU 的硬件隔离以group为单位。一个 group 内的所有设备共享同一个 IOMMU domain同一套页表彼此之间没有硬件隔离。不同 group 之间 IOMMU 保证 DMA 隔离。group 的划分由ops-device_group()回调决定。Intel 和 AMD 都委托给核心的pci_device_group()// drivers/iommu/intel/iommu.c (v6.6, line 4491)// intel_iommu_device_group() → pci_device_group(dev)// drivers/iommu/amd/iommu.c (v6.6, line 1959)// amd_iommu_device_group() → pci_device_group(dev)3.2 ACS决定隔离边界的关键pci_device_group()的核心逻辑依赖ACSAccess Control Services——PCIe 的一项能力控制设备间的 P2Ppeer-to-peerDMA 路由// drivers/iommu/iommu.c (v6.6, line 1532)#defineREQ_ACS_FLAGS(PCI_ACS_SV|PCI_ACS_RR|PCI_ACS_CR|PCI_ACS_UF)// Source Request Completion Upstream// Validation Redirect Redirect Forwarding四项全部启用时设备间的 P2P DMA 被硬件阻断——它们可以分属不同的 group。3.3 group 划分的三条规则是否是否无 ACS有 ACSPCI Device① DMA alias?(PCIe-to-PCI bridge,SR-IOV VF)与 alias 源头同 group② Multi-function 且无 ACS?同 Slot 的兄弟Function 合并到一个 group③ 上游 bridge有 ACS?与上游 bridge同 group独立 groupiommu_group_alloc()规则③的代码// drivers/iommu/iommu.c (v6.6, line 1671-1683)// 向上游遍历 bridge找到第一个 ACS 能阻断 P2P 的点for_each_pci_bridge_above(dev,bus){if(pci_acs_path_enabled(bus-self,NULL,REQ_ACS_FLAGS))break;// ACS 保护在此处划界// 否则 bridge 和下游设备必须在同一个 group}典型场景场景ACS分组结果单功能网卡上游 Root Port 有 ACS有独立 group多功能 NICPFVFfunction 间无 ACS无同一 groupPF 和 VF 无法隔离PCIe-to-PCI bridge 后的传统 PCI 设备—所有下游设备 bridge 同一 groupSR-IOV VFPF 在另一个 group—与 PF 共享 alias group3.4struct iommu_groupiommu.c:50-66structiommu_group{structkobjectkobj;// /sys/kernel/iommu_groups/N/structlist_headdevices;// 该 group 内的 struct group_device 列表structmutexmutex;structiommu_domain*default_domain;// DMA API 使用的 domainstructiommu_domain*blocking_domain;// 阻断 domain用于 VFIO 切换structiommu_domain*domain;// 当前挂载的 domainvoid*iommu_data;char*name;intid;// group 编号};每个 group 在 sysfs 下有对应目录/sys/kernel/iommu_groups/0/ ├── devices/ (属于该 group 的设备 symlink) ├── reserved_regions/ (RMRR/unity map 保留区域) └── type (DMA / DMA-FQ / identity)四、阶段 3default domain — DMA API 的翻译表4.1 domain 类型选择设备 probe 时IOMMU 核心自动分配一个default domain// drivers/iommu/iommu.c (v6.6, line 192-209)// iommu_subsys_init():// 若非 passthrough 且非 strict:// iommu_def_domain_type IOMMU_DOMAIN_DMA_FQ ← 默认 flush-queue 模式// 每个设备可能覆盖// - 不可信设备 (external GPU, Thunderbolt): 强制 IOMMU_DOMAIN_DMA (无 FQ)// - ops-def_domain_type(): Intel/AMD 驱动级覆盖三种默认类型类型行为适用场景IOMMU_DOMAIN_DMA严格映射每次 unmap 立即 invalidate IOTLB不可信设备、调试IOMMU_DOMAIN_DMA_FQFlush-queue 模式批量延迟 invalidation默认吞吐优先IOMMU_DOMAIN_IDENTITY1:1 直通映射iommu.passthrough1性能优先无隔离4.2 分配和挂载// drivers/iommu/iommu.c (v6.6, line 2944-3042)iommu_setup_default_domain(group,target_type)│ ├─iommu_group_alloc_default_domain(group,type)│ └─__iommu_domain_alloc(bus,type)│ └─ ops-domain_alloc(type)// ★ Intel: alloc_domain()│// AMD: protection_domain_alloc()│ └─__iommu_group_set_domain_internal(group,new_domain)└─foreach device in group:└─__iommu_attach_device(domain,dev)└─ domain-ops-attach_dev(domain,dev)// ★ 写硬件4.3 Intel 驱动侧attach_dev做了什么// drivers/iommu/intel/iommu.c (v6.6, line 4107)// intel_iommu_attach_device()// → prepare_domain_attach_device() // 检查 AGAW/页表级别兼容性// → domain_context_mapping() // ★ 写 Context Entry// ① 在该设备对应的 Context Entry 中填入:// - Present 1// - DomainID domain-did// - SLPTPTR page table root (SL)// 或 PASIDDIR PASID table (FL)// ② iommu_flush_write_buffer()// ③ iommu_flush_context() // Invalidate Context Cache4.4 AMD 驱动侧attach_dev做了什么// drivers/iommu/amd/iommu.c (v6.6, line 2200)// amd_iommu_attach_device()// → attach_device(dev, domain)// → 填 DTE:// V1, DomainID, RootPtr, Mode, IR/IW, TV// → iommu_flush_device_table() // Invalidate Device Table Cache// → iommu_completion_wait() // 等 CmdBuf 执行完毕五、阶段 4probe_finalize — DMA API 正式上线attach 完成后设备还需要安装 DMA opsdma_map_single()等 API 才能走 IOMMU 路径// drivers/iommu/iommu.c (v6.6, line 565-577)intiommu_probe_device(structdevice*dev){ret__iommu_probe_device(dev,NULL);// group domain attachif(ret)returnret;ops-probe_finalize(dev);// ★ 安装 DMA ops}Intel 和 AMD 的probe_finalize逻辑相同// 例: drivers/iommu/amd/iommu.c (v6.6, line 1937)staticvoidamd_iommu_probe_finalize(structdevice*dev){set_dma_ops(dev,NULL);// 清掉旧的 DMA ops可能来自 swiotlbiommu_setup_dma_ops(dev,0,U64_MAX);// 安装 iommu_dma_ops}// drivers/iommu/dma-iommu.c (v6.6, line 1630)// iommu_setup_dma_ops():// → iommu_dma_init_domain() // 初始化 IOVA 空间// → dev-dma_ops iommu_dma_ops // 替换 DMA ops从此刻起设备驱动调dma_map_single()→iommu_dma_map_page()→iommu_map()→ 驱动map_pages回调 → 硬件页表更新。六、完整调用链pci_scan_single_device() └─ pci_device_add() // drivers/pci/probe.c:2539 ├─ pci_init_capabilities() // read ACS cap (probe.c:2484) └─ device_add(dev-dev) // probe.c:2583 └─ bus_notify(BUS_NOTIFY_ADD_DEVICE) // drivers/base/core.c:3604 └─ iommu_bus_notifier() // iommu.c:1793 └─ iommu_probe_device() // iommu.c:565 ├─ __iommu_probe_device() │ ├─ iommu_init_device() │ │ ├─ ops-probe_device(dev) // 驱动: 分配 device_domain_info / iommu_dev_data │ │ ├─ iommu_device_link() │ │ └─ ops-device_group(dev) // → pci_device_group() │ │ ├─ pci_acs_path_enabled() // ACS 划界 │ │ ├─ get_pci_function_alias_group() // 多功能合并 │ │ └─ iommu_group_alloc() // 新 group │ │ │ ├─ iommu_setup_default_domain() │ │ ├─ __iommu_domain_alloc() // → ops-domain_alloc() │ │ └─ __iommu_group_set_domain_internal() │ │ └─ __iommu_attach_device() │ │ └─ ops-attach_dev() // ★ 写 ContextEntry / DTE │ │ │ └─ iommu_dma_set_pci_32bit_workaround() │ └─ ops-probe_finalize(dev) └─ iommu_setup_dma_ops(dev) // 安装 iommu_dma_ops pci_bus_add_device() // drivers/pci/bus.c:334 └─ device_attach(dev-dev) // PCI 驱动 probe (此时 iommu 已就绪)七、记住三点① 时序IOMMU 在设备驱动 probe之前接管。驱动probe()调 DMA API 时 IOMMU 已配好。② 隔离group 是 IOMMU 隔离的基本单元ACS 是划分 group 边界的硬件依据。同一 group 内的设备共享页表无硬件隔离。③ 挂载probe_device → device_group → alloc_domain → attach_dev → probe_finalize五步走完DMA 翻译生效。八、关键文件索引文件v6.6关键函数/结构drivers/iommu/iommu.ciommu_bus_notifier,iommu_probe_device,__iommu_probe_device,pci_device_group,iommu_setup_default_domain,struct iommu_groupdrivers/iommu/intel/iommu.cintel_iommu_probe_device,intel_iommu_attach_device,domain_context_mappingdrivers/iommu/amd/iommu.camd_iommu_probe_device,amd_iommu_attach_device,attach_devicedrivers/iommu/dma-iommu.ciommu_setup_dma_ops,iommu_dma_opsdrivers/pci/probe.cpci_device_add,pci_init_capabilitiesdrivers/pci/pci.cpci_acs_enabled,pci_acs_path_enabled,pci_acs_initinclude/linux/iommu.hstruct iommu_ops,struct iommu_domain,struct dev_iommu
返回列表