1. 1. DMAR 与 ADMA2 架构深度分析报告
    1. 1.1. 1. 测试验证结果
      1. 1.1.1. 1.1 DataBuffer vs SrbExtension 地址测试
      2. 1.1.2. 1.2 测试结果分析
      3. 1.1.3. 1.3 关键发现
    2. 1.2. 2. StorPortBuildScatterGatherList 分析
      1. 1.2.1. 2.1 用户问题
      2. 1.2.2. 2.2 StorAHCI 使用的 API
      3. 1.2.3. 2.3 两个 API 的区别
      4. 1.2.4. 2.4 StorPortBuildScatterGatherList 的限制
      5. 1.2.5. 2.5 结论
    3. 1.3. 3. ADMA2 架构深度分析
      1. 1.3.1. 3.1 用户的理解
      2. 1.3.2. 3.2 用户理解 vs 实际 SDHCI 规范
      3. 1.3.3. 3.3 代码证据
      4. 1.3.4. 3.4 ADMA2 实际工作流程
      5. 1.3.5. 3.5 为什么 SDHCI 这样设计?
        1. 1.3.5.1. 如果按用户理解的方式设计
        2. 1.3.5.2. 实际 SDHCI 设计
    4. 1.4. 4. DMAR 与 ADMA2 的根本冲突
      1. 1.4.1. 4.1 问题本质
      2. 1.4.2. 4.2 架构图
      3. 1.4.3. 4.3 为什么这不是驱动 Bug
    5. 1.5. 5. 可行解决方案
      1. 1.5.1. 5.1 推荐方案
    6. 1.6. 6. 总结
      1. 1.6.1. 6.1 关键结论
      2. 1.6.2. 6.2 SDHCI 传输模式对比
    7. 1.7. 7. SDMA 在 DMAR 下的可行性分析
      1. 1.7.1. 7.1 SDMA vs ADMA2 架构对比
      2. 1.7.2. 7.2 当前 SDMA 实现问题
      3. 1.7.3. 7.3 SDMA 使用内部缓冲区的原因
      4. 1.7.4. 7.4 SDMA DMAR 兼容方案
        1. 1.7.4.1. 方案:直接使用 Srb->DataBuffer
        2. 1.7.4.2. 方案可行性评估
      5. 1.7.5. 7.5 SDMA vs PIO 性能对比
      6. 1.7.6. 7.6 SDMA DMAR 兼容性结论
    8. 1.8. 8. SDMA DMAR 兼容方案实现
      1. 1.8.1. 8.1 实现概述
      2. 1.8.2. 8.2 修改的文件
      3. 1.8.3. 8.3 关键代码修改
        1. 1.8.3.1. tqsdma.c - 直接 IOVA 路径选择
        2. 1.8.3.2. tagqueue.c - 跳过 CPU copy
        3. 1.8.3.3. transhandler.c - 边界处理更新
      4. 1.8.4. 8.4 诊断日志
      5. 1.8.5. 8.5 INF 配置
      6. 1.8.6. 8.6 测试要点
    9. 1.9. 附录: 参考资料

DMAR 与 ADMA2 架构深度分析报告

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
// entrypts.c:1534
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, // 需要 MDL
PVOID CurrentVa,
ULONG Length,
PPOST_SCATTER_GATHER_EXECUTE ExecutionRoutine, // 异步回调
...
);

限制

  1. 需要 MDL - 驱动需要自己为内存创建 MDL
  2. 异步操作 - 结果通过回调返回,复杂度高
  3. 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
// tqadma2.c:318-321
// 3.cfg system addr - ADMA2 DESCRIPTOR TABLE ADDRESS (written to register 0x58)
data->data_mng.sys_addr = node->phy_node_buffer.head.pa; // Descriptor Table 的地址!

// cmdhandler.c:1146-1148
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 硬件架构的根本冲突

  1. SDHCI 规范要求:Descriptor Table 必须能被硬件 DMA 访问
  2. Windows 安全设计:External 设备只能 DMA 访问 Storport 管理的 DataBuffer
  3. 矛盾:Descriptor Table 不是 DataBuffer,无法获得 IOVA

5. 可行解决方案

方案 可行性 性能 说明
PIO 模式 ✅ 已验证 完全绕过 DMA,无需 Descriptor
OEM BIOS/SSDT 需配合 将设备标记为 Internal
禁用 Kernel DMA Protection 不推荐 降低系统安全性

5.1 推荐方案

短期 (驱动可实现):

  • 使用 PIO 模式 - 已验证可工作

长期 (需 OEM 配合):

  • 请求 OEM 提供 SSDT Patch,将 Bayhub SD Controller 标记为 internal 设备
  • 这将使 IOMMU 使用 identity mapping,允许 DMA 模式正常工作

6. 总结

6.1 关键结论

  1. 测试验证:DataBuffer 有 IOVA (0x200760),SrbExtension 没有 (0x72EF04A0 是 PA)

  2. StorPortBuildScatterGatherList:理论上可以为任意内存创建 SGL,但需要 MDL,是异步操作,且对 External 设备的 IOVA 支持未知

  3. ADMA2 架构

    • 0x58 寄存器存储的是 Descriptor Table 地址,不是 DataBuffer 地址
    • 硬件自动通过 DMA 读取 Descriptor Table,软件无法干预
    • 这是 SDHCI 规范定义的硬件行为
  4. 根本问题: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
// tqsdma.c:157-165 当前实现
dma = node->data_tbl; // 从 UncachedExtension 分配的内部缓冲区
data->data_mng.sys_addr = dma.pa; // PA,不是 IOVA!

// 流程:
// 写: Srb->DataBuffer --CPU copy--> node->data_tbl --DMA--> SD卡
// 读: SD卡 --DMA--> node->data_tbl --CPU copy--> Srb->DataBuffer

问题:内部缓冲区 (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
// 伪代码:SDMA DMAR 兼容实现
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;

// 条件 1:检查 SG List 是否只有一个条目(物理连续)
if (req->srb_sg_len == 1) {
// 条件 2:获取 DataBuffer 的 IOVA
phy_addr_t iova;
ULONG len;
iova = StorPortGetPhysicalAddress(pdx, pext->psrb, req->srb_buff, &len);

// 条件 3:检查 boundary 对齐(可选,取决于传输大小)
if (len >= req->tag_req_t.sec_cnt * 512) {
// ✅ 可以直接使用 DataBuffer!
data->data_mng.sys_addr = iova; // IOVA!
data->data_mng.driver_buff = req->srb_buff;
// 不需要 CPU copy!
return TRUE;
}
}

// ❌ Fallback 到 PIO 模式或报错
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
// 检查是否可以使用直接 IOVA 路径
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) {
// DataBuffer 物理连续 - 可以直接使用 IOVA!
u64 data_iova = temp_sg[0].Address;

// 检查 boundary 条件
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) {
// 直接路径 - 数据已直接 DMA 到 DataBuffer
DbgErr("DMAR_DIAG: SDMA completion - direct IOVA path, skip CPU copy\n");
} else if (DATA_DIR_IN == psrb_ext->req.data_dir) {
// 内部缓冲区路径 - 需要 CPU copy
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) {
// 直接路径 - 跳过 CPU copy,更新 sys_addr 到下一个边界
mgr->offset += min_size;
mgr->sys_addr.QuadPart += min_size; // 更新到下一个 DataBuffer 段
DbgErr("DMAR_DIAG: SDMA boundary updated sys_addr for next segment\n");
} else {
// 内部缓冲区路径 - 执行 CPU copy
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
; 启用 SDMA 模式测试
;Mode values: 0=SDMA, 1=ADMA2, 2=ADMA2+INF, 4=ADMA3, 5=ADMA3+INF, 6=ADMA_MIX, 0xF=PIO
HKR,"Parameters\GG8", "test_dma_mode_setting",0x00010001, 0x80000000

8.6 测试要点

测试项 预期结果
小块读 (< boundary) 直接 IOVA 路径成功
小块写 (< boundary) 直接 IOVA 路径成功
大块读 (> boundary) 直接 IOVA 路径 + boundary 处理
分散内存 Fallback 到内部缓冲区 (可能失败)

附录: 参考资料

  1. Microsoft Docs: StorPortGetPhysicalAddress
  2. Microsoft Docs: StorPortBuildScatterGatherList
  3. Microsoft Docs: Enabling DMA Remapping for Device Drivers
  4. SD Host Controller Simplified Specification (SDHCI)
  5. Windows Driver Samples: StorAHCI