DMAR 与 ADMA2 架构深度分析报告
1. 测试验证结果 1.1 DataBuffer vs SrbExtension 地址测试 通过诊断代码验证了 IOMMU 映射行为:
1 2 3 DMAR_DIAG: Address comparison test: DataBuffer: PA(NULL)=0x0000000000000000 PA(Srb)=0x0000000000200760 DIFF(IOVA) SrbExtension: PA(NULL)=0x0000000072EF04A0 PA(Srb)=0x0000000072EF04A0 SAME
1.2 测试结果分析
内存类型
PA(NULL)
PA(Srb)
结论
DataBuffer
0x00000000 (失败)
0x00200760
DIFF → 有 IOMMU 映射 ✅
SrbExtension
0x72EF04A0
0x72EF04A0
SAME → 无 IOMMU 映射 ❌
1.3 关键发现
DataBuffer (0x00200760): 低地址,在 IOMMU IOVA 空间内,硬件可访问
SrbExtension (0x72EF04A0): 高地址,是物理地址 (PA),未被 IOMMU 映射
这解释了为什么 ADMA2 失败:Descriptor Table 在 SrbExtension 中,使用 PA 而非 IOVA
2. StorPortBuildScatterGatherList 分析 2.1 用户问题
StorPortBuildScatterGatherList 能否为自定义内存(如 ADMA Descriptor Table)获取 IOVA?
2.2 StorAHCI 使用的 API StorAHCI 调用的是 StorPortGetScatterGatherList,而非 StorPortBuildScatterGatherList:
1 2 srbExtension->Sgl = (PLOCAL_SCATTER_GATHER_LIST)StorPortGetScatterGatherList(adapterExtension, Srb);
这个 API 获取的是 Srb->DataBuffer 的 SGL ,不是为自定义内存创建映射。
2.3 两个 API 的区别
API
用途
输入
IOVA 支持
StorPortGetScatterGatherList(Srb)
获取 Srb->DataBuffer 的 SGL
Srb
✅ 返回 IOVA
StorPortBuildScatterGatherList(MDL, VA, ...)
为任意内存 创建 SGL
MDL + VA
⚠️ 理论上应该支持
2.4 StorPortBuildScatterGatherList 的限制 根据 Microsoft 文档 :
1 2 3 4 5 6 7 8 ULONG StorPortBuildScatterGatherList ( PVOID HwDeviceExtension, PVOID Mdl, PVOID CurrentVa, ULONG Length, PPOST_SCATTER_GATHER_EXECUTE ExecutionRoutine, ... ) ;
限制 :
需要 MDL - 驱动需要自己为内存创建 MDL
异步操作 - 结果通过回调返回,复杂度高
External 设备未知 - 文档没说明是否为 External 设备创建 IOMMU 映射
2.5 结论 StorPortBuildScatterGatherList 理论上 可以为任意内存创建 SGL,但:
需要自己创建 MDL
是异步操作
文档没有保证 对 External 设备返回 IOVA
3. ADMA2 架构深度分析 3.1 用户的理解
软件驱动负责管理 system memory 的 Descriptor 数据;软件可以使用 DMA 或者非 DMA 方式访问 Descriptor 数据,获取到 DataBuffer 的物理地址,再配置进硬件的 ADMA2 Engine register;之后软件启动 ADMA2 hardware engine,hardware 自动搬运数据。
3.2 用户理解 vs 实际 SDHCI 规范
项目
用户理解
实际 SDHCI 规范
写入 0x58 寄存器的内容
DataBuffer 地址
Descriptor Table 地址
谁解析 Descriptor
软件 (CPU)
硬件 (ADMA2 Engine)
Descriptor 访问方式
软件 PIO 读取
硬件 DMA 读取
3.3 代码证据 从 Bayhub 驱动代码可以看到:
1 2 3 4 5 6 7 8 data->data_mng.sys_addr = node->phy_node_buffer.head.pa; sdhci_writel(host, SDHCI_ADMA_ADDRESS, os_get_phy_addr32l(sys_addr)); if (host->bit64_enable) sdhci_writel(host, SDHCI_ADMA_ADDRESSH, os_get_phy_addr32h(sys_addr));
写入 0x58 寄存器的是 Descriptor Table 的地址 ,不是 DataBuffer 的地址!
3.4 ADMA2 实际工作流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 1: 软件在内存中创建 Descriptor Table │ │ │ │ System Memory: │ │ ┌─────────────────────────────────────────────┐ │ │ │ Descriptor Table @ 0x72EF0000 (物理地址) │ │ │ │ ┌────────┬────────┬──────┬──────┐ │ │ │ │ │Addr │Length │Type │Attr │ │ │ │ │ │0x200760│4096 │Tran │Val │ -> Page1 │ │ │ │ │0x201760│4096 │Tran │Val │ -> Page2 │ │ │ │ │0x202760│512 │Tran │End │ -> Page3 │ │ │ │ └────────┴────────┴──────┴──────┘ │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ │ 软件 PIO 写入 v ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 2: 软件写 Descriptor Table 地址到 0x58 寄存器 │ │ │ │ sdhci_writel(host, 0x58, 0x72EF0000); // Descriptor地址! │ │ │ │ 注意: 写入的是 0x72EF0000 (Descriptor Table 地址) │ │ 不是 0x200760 (DataBuffer 地址) │ └─────────────────────────────────────────────────────────────────┘ │ │ 软件发送 SD 命令 v ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 3: 硬件自动通过 DMA 读取 Descriptor Table │ │ │ │ SD Host Controller: │ │ ┌─────────────────────────────────────────────┐ │ │ │ ADMA2 Engine (硬件状态机): │ │ │ │ │ │ │ │ 1. 从 0x58 寄存器读取 Descriptor 地址 │ │ │ │ 2. 通过 PCI 总线 DMA 读取 0x72EF0000 的内容 │ <-- 这里! │ │ │ 3. 解析得到 DataBuffer 地址: 0x200760 │ │ │ │ 4. 通过 PCI 总线 DMA 读写 DataBuffer │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘
3.5 为什么 SDHCI 这样设计? 如果按用户理解的方式设计 1 2 3 4 5 6 7 8 for (每个 Descriptor) { 软件读取 Descriptor 提取 DataBuffer 地址 写入硬件寄存器 等待硬件完成 处理中断 }
问题 :
一次 1MB 传输可能有 256+ 个 Descriptor (每个 4KB)
每个 Descriptor 需要一次 CPU 中断和寄存器操作
效率极低
实际 SDHCI 设计 1 2 3 4 5 6 7 8 9 10 软件: 创建 Descriptor Table (一次性) 软件: 写入 Descriptor Table 地址 (1 次寄存器操作) 软件: 发送 SD 命令 硬件: while (not End) { DMA 读取下一个 Descriptor DMA 传输数据 } 产生 1 次完成中断
优势 :高效,CPU 只需要一次设置操作。
4. DMAR 与 ADMA2 的根本冲突 4.1 问题本质 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ┌─────────────────────────────────────────────────────────────────┐ │ IOMMU (DMA Remapping) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ SD Host Controller System Memory │ │ (External Device) │ │ │ │ │ │ DMA 请求: 读取 0x72EF0000 │ │ │ (Descriptor Table PA) │ │ v │ │ ┌──────────┐ "0x72EF0000 不在 │ │ │ IOMMU │ ───> 该设备的映射表中" │ │ └──────────┘ → 拒绝访问! │ │ → ST_FDS 错误 │ │ │ └─────────────────────────────────────────────────────────────────┘
4.2 架构图 1 2 3 4 5 6 7 8 9 10 11 12 13 SD Host Controller System Memory ┌─────────────────┐ ┌───────────────────────────────┐ │ ADMA System Addr│────>│ Descriptor Table (需要 IOVA) │ │ (硬件寄存器) │ │ [Addr|Len|Tran|Attr] │ └─────────────────┘ │ [Addr|Len|Tran|End&Val] │ └───────────┬───────────────────┘ │ v ┌───────────────────────────────┐ │ Data Pages = Srb->DataBuffer │ │ (有 IOVA 映射) ✓ │ │ Page #1, #2, #3, #4... │ └───────────────────────────────┘
内存区域
角色
分配者
IOMMU 映射
Descriptor Table
硬件控制结构
Miniport 驱动
❌ 无 (PA)
Data Pages
实际 I/O 数据
Storport/上层
✅ 有 (IOVA)
4.3 为什么这不是驱动 Bug 这是 Windows Kernel DMA Protection 设计与 ADMA 硬件架构的根本冲突 :
SDHCI 规范要求 :Descriptor Table 必须能被硬件 DMA 访问
Windows 安全设计 :External 设备只能 DMA 访问 Storport 管理的 DataBuffer
矛盾 :Descriptor Table 不是 DataBuffer,无法获得 IOVA
5. 可行解决方案
方案
可行性
性能
说明
PIO 模式
✅ 已验证
低
完全绕过 DMA,无需 Descriptor
OEM BIOS/SSDT
需配合
高
将设备标记为 Internal
禁用 Kernel DMA Protection
不推荐
高
降低系统安全性
5.1 推荐方案 短期 (驱动可实现) :
长期 (需 OEM 配合) :
请求 OEM 提供 SSDT Patch ,将 Bayhub SD Controller 标记为 internal 设备
这将使 IOMMU 使用 identity mapping,允许 DMA 模式正常工作
6. 总结 6.1 关键结论
测试验证 :DataBuffer 有 IOVA (0x200760),SrbExtension 没有 (0x72EF04A0 是 PA)
StorPortBuildScatterGatherList :理论上可以为任意内存创建 SGL,但需要 MDL,是异步操作,且对 External 设备的 IOVA 支持未知
ADMA2 架构 :
0x58 寄存器存储的是 Descriptor Table 地址 ,不是 DataBuffer 地址
硬件 自动通过 DMA 读取 Descriptor Table,软件无法干预
这是 SDHCI 规范定义的硬件行为
根本问题 :Windows Kernel DMA Protection 设计与 ADMA 硬件架构的冲突,不是驱动 bug
6.2 SDHCI 传输模式对比
模式
Descriptor
Data Access
地址要求
DMAR 兼容
PIO
无
CPU 逐字读写
无需 IOVA
✅ 兼容
SDMA
无
硬件 DMA
需要 IOVA
⚠️ 有条件兼容
ADMA2
有
硬件 DMA
Descriptor + Data 都需要 IOVA
❌ 不兼容
ADMA3
有
硬件 DMA
Descriptor + Data 都需要 IOVA
❌ 不兼容
7. SDMA 在 DMAR 下的可行性分析 7.1 SDMA vs ADMA2 架构对比 1 2 3 4 5 6 7 8 9 10 11 12 13 ADMA2: ┌─────────────────┐ ┌───────────────────┐ ┌──────────────┐ │ sys_addr (0x58) │────>│ Descriptor Table │────>│ Data Buffer │ │ │ │ (需要 IOVA) │ │ (需要 IOVA) │ └─────────────────┘ └───────────────────┘ └──────────────┘ 写入 Desc 地址 硬件 DMA 读取 硬件 DMA 读写 SDMA: ┌─────────────────┐ ┌──────────────────────────────────────────┐ │ sys_addr (0x00) │────>│ Data Buffer (直接指向数据,无 Descriptor) │ │ │ │ (需要 IOVA) │ └─────────────────┘ └──────────────────────────────────────────┘ 写入 Data 地址 硬件 DMA 直接读写
关键区别 :SDMA 没有 Descriptor Table,sys_addr 直接指向数据缓冲区!
7.2 当前 SDMA 实现问题 1 2 3 4 5 6 7 dma = node->data_tbl; data->data_mng.sys_addr = dma.pa;
问题 :内部缓冲区 (node->data_tbl) 的 PA 无 IOVA,硬件无法访问。
7.3 SDMA 使用内部缓冲区的原因
原因
说明
SDMA Boundary
SDMA 有 4KB/8KB/… 边界限制,跨边界需中断重新编程
物理连续性
SDMA 不支持 Scatter-Gather,需要连续物理内存
对齐要求
缓冲区需要 boundary 对齐
7.4 SDMA DMAR 兼容方案 方案:直接使用 Srb->DataBuffer 条件 :如果 Srb->DataBuffer 满足以下条件,可以直接使用(跳过内部缓冲区):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 bool tq_sdma_build_io_dmar_compatible (void * p, node_t * node) { srb_ext_t * pext = node_2_srb_ext(node); request_t * req = &pext->req; if (req->srb_sg_len == 1 ) { phy_addr_t iova; ULONG len; iova = StorPortGetPhysicalAddress(pdx, pext->psrb, req->srb_buff, &len); if (len >= req->tag_req_t .sec_cnt * 512 ) { data->data_mng.sys_addr = iova; data->data_mng.driver_buff = req->srb_buff; return TRUE; } } return FALSE; }
方案可行性评估
条件
检查方法
可能性
srb_sg_len == 1
检查 SG 条目数
小块传输时可能为 1
DataBuffer 有 IOVA
StorPortGetPhysicalAddress(Srb, DataBuffer)
✅ 已验证有 IOVA
Boundary 对齐
检查地址是否对齐
取决于具体地址
7.5 SDMA vs PIO 性能对比
模式
优点
缺点
SDMA (DMAR 兼容)
无 CPU copy,DMA 传输
需要满足条件,复杂度高
PIO
简单可靠,100% DMAR 兼容
CPU 占用高,性能低
7.6 SDMA DMAR 兼容性结论
场景
SDMA 可行性
srb_sg_len == 1 (连续)
✅ 可行 - 直接使用 DataBuffer IOVA
srb_sg_len > 1 (分散)
❌ 不可行 - SDMA 不支持 Scatter-Gather
任意场景 (PIO fallback)
✅ 可行 - 使用 PIO 模式
8. SDMA DMAR 兼容方案实现 8.1 实现概述 已实现 SDMA DMAR 兼容路径,当条件满足时直接使用 Srb->DataBuffer 的 IOVA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 当前条件: - srb_sg_len == 1 (DataBuffer 物理连续) - 满足 boundary 对齐或传输大小 <= boundary 路径选择: ┌─────────────────────────────────────────────────────────────────┐ │ tq_sdma_build_io: │ │ │ │ if (srb_sg_len == 1 && boundary条件满足) { │ │ // ✅ 直接路径:使用 DataBuffer IOVA │ │ sys_addr = StorPortGetPhysicalAddress(Srb, DataBuffer); │ │ node->sdma_direct_iova = 1; // 标记直接路径 │ │ // 无需 CPU copy! │ │ } else { │ │ // ❌ Fallback:使用内部缓冲区 (PA) │ │ // 警告:DMAR 下可能失败 │ │ } │ └─────────────────────────────────────────────────────────────────┘
8.2 修改的文件
文件
修改内容
include/transh.h
node_t 添加 sdma_direct_iova 标志
include/card.h
data_dma_mng_t 添加 sdma_direct_iova 标志
tagqueue/tqsdma.c
tq_sdma_build_io 添加直接 IOVA 路径选择逻辑
tagqueue/tagqueue.c
完成回调跳过 CPU copy (直接路径)
host/transhandler.c
cmd_sdma_trans_done 和边界处理跳过 CPU copy
bhtsddr.inf
DMA 模式改为 SDMA (0x80000000)
8.3 关键代码修改 tqsdma.c - 直接 IOVA 路径选择 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if (pext->psrb != NULL ) { sg_list_t temp_sg[MAX_SGL_RANGE]; u32 sg_len = os_get_sg_list(pdx, pext->psrb, temp_sg); if (sg_len == 1 ) { u64 data_iova = temp_sg[0 ].Address; if ((data_iova & (sdma_bd_len - 1 )) == 0 || mgr->total_bytess <= sdma_bd_len) { dma.pa.QuadPart = data_iova; dma.va = req->srb_buff; data->data_mng.sys_addr = dma.pa; data->data_mng.sdma_direct_iova = 1 ; node->sdma_direct_iova = 1 ; DbgErr("DMAR_DIAG: SDMA DIRECT IOVA path! sys_addr=0x%08X%08X\n" , ...); } } }
tagqueue.c - 跳过 CPU copy 1 2 3 4 5 6 7 8 9 if (pnode->sdma_like) { if (pnode->sdma_direct_iova) { DbgErr("DMAR_DIAG: SDMA completion - direct IOVA path, skip CPU copy\n" ); } else if (DATA_DIR_IN == psrb_ext->req.data_dir) { os_memcpy(psrb_ext->req.srb_buff, pnode->data_tbl.va, ...); } }
transhandler.c - 边界处理更新 1 2 3 4 5 6 7 8 9 if (mgr->sdma_direct_iova) { mgr->offset += min_size; mgr->sys_addr.QuadPart += min_size; DbgErr("DMAR_DIAG: SDMA boundary updated sys_addr for next segment\n" ); } else { os_memcpy(...); }
8.4 诊断日志 测试时观察以下日志:
1 2 3 4 5 6 7 8 9 10 // ✅ 直接 IOVA 路径成功 DMAR_DIAG: SDMA DIRECT IOVA path! sys_addr=0x0000000000200760 len=4096 (no CPU copy) DMAR_DIAG: SDMA completion - direct IOVA path, skip CPU copy // ⚠️ Fallback 到内部缓冲区 DMAR_DIAG: SDMA srb_sg_len=3 (need 1 for direct IOVA), using internal buffer DMAR_DIAG: WARNING - Internal buffer uses PA, may fail under DMAR for external devices! // ⚠️ DataBuffer 未对齐 DMAR_DIAG: SDMA DataBuffer not aligned for direct path (addr=0x00200123, boundary=4096)
8.5 INF 配置 1 2 3 HKR,"Parameters\GG8", "test_dma_mode_setting",0x00010001, 0x80000000
8.6 测试要点
测试项
预期结果
小块读 (< boundary)
直接 IOVA 路径成功
小块写 (< boundary)
直接 IOVA 路径成功
大块读 (> boundary)
直接 IOVA 路径 + boundary 处理
分散内存
Fallback 到内部缓冲区 (可能失败)
附录: 参考资料
Microsoft Docs: StorPortGetPhysicalAddress
Microsoft Docs: StorPortBuildScatterGatherList
Microsoft Docs: Enabling DMA Remapping for Device Drivers
SD Host Controller Simplified Specification (SDHCI)
Windows Driver Samples: StorAHCI