4、StreamID与SubstreamID:StreamID的分配策略,SubstreamID在虚拟化中的应用
好,咱们进入第四章。StreamID 和 SubstreamID,这两个概念是 SMMU 寻址的基石。说白了,SMMU 怎么知道当前访问内存的设备是谁?就是靠 StreamID。而 SubstreamID 呢?它是虚拟化场景下,让同一个设备的不同虚拟机或进程能共享 SMMU 页表的关键。
我个人习惯把 StreamID 比作设备的“身份证号”,SubstreamID 则是这个设备上不同“工作上下文”的编号。搞不清楚这两个 ID 的分配,后面 TLB 性能调优根本无从谈起。我在项目中遇到过好几次,因为 StreamID 分配冲突,导致设备 DMA 访问直接挂死,查了两天才定位到问题。
4.1 StreamID:设备的硬件身份证
StreamID 通常由硬件总线接口(比如 PCIe Requester ID、AMBA Stream ID)直接生成,或者由固件在设备初始化时静态配置。它必须全局唯一,不能重复。
4.1.1 StreamID 的分配策略
分配策略主要分两种:静态分配和动态分配。我建议在大多数嵌入式系统中使用静态分配,简单可靠。
| 策略 | 描述 | 适用场景 | 风险点 |
|---|---|---|---|
| 静态分配 | 固件在启动时,为每个设备分配固定的 StreamID。通常写在设备树或 ACPI 表中。 | 设备数量固定、拓扑简单的系统(如手机 SoC、嵌入式设备)。 | 灵活性差,硬件变更需要修改固件。 |
| 动态分配 | 系统运行时,由 SMMU 驱动或 hypervisor 动态分配 StreamID。设备热插拔场景常用。 | PCIe 热插拔、虚拟化环境(VF 分配)。 | 分配冲突、资源耗尽、需要复杂的 ID 管理算法。 |
嗯,这里要注意。静态分配虽然简单,但一定要预留足够的 StreamID 空间。我曾经在一个项目里,因为只给每个 PCIe 端点分配了 8 位 StreamID,结果后来扩展了 3 个多功能设备,直接导致 ID 溢出。最后不得不改硬件设计,教训深刻。
4.1.2 StreamID 与 SMMU 配置的绑定
StreamID 一旦确定,就需要在 SMMU 的 Stream Table Entry (STE) 中配置对应的映射关系。每个 STE 包含了该 StreamID 的转换规则、页表基地址、权限控制等信息。
// 伪代码:配置 StreamID 对应的 STE
// 假设 StreamID = 0x5
smmu_ste_t *ste = get_ste_base() + 0x5;
ste->valid = 1;
ste->config = SMMU_CONFIG_BYPASS; // 或者 SMMU_CONFIG_TRANSLATE
ste->ttb0 = page_table_base_address;
ste->asid = 0x100; // 关联的 ASID
4.2 SubstreamID:虚拟化下的上下文标识
SubstreamID 是 ARM SMMUv3 引入的概念。它允许一个物理设备(一个 StreamID)内部,区分来自不同虚拟机或不同进程的请求。你想想看,一个支持 SR-IOV 的网卡,它有多个虚拟功能(VF),每个 VF 属于不同的虚拟机。如果没有 SubstreamID,SMMU 怎么知道这个 DMA 请求是来自 VM1 还是 VM2?
SubstreamID 就是干这个的。它通常由设备在 DMA 描述符中携带,或者由系统软件在 PASID(Process Address Space ID)中编码。
4.2.1 SubstreamID 在虚拟化中的角色
在虚拟化场景下,SubstreamID 的核心作用是实现 共享虚拟内存(SVM) 和 设备直通(Device Passthrough)。
- 设备直通: 将物理设备(或 VF)直接分配给虚拟机。此时,StreamID 标识物理设备,SubstreamID 标识虚拟机内部的进程或上下文。
- SVM: 设备可以直接访问进程的虚拟地址空间。SubstreamID 就是进程的 PASID。SMMU 根据 SubstreamID 查找对应的进程页表。
4.2.2 SubstreamID 的分配与管理
SubstreamID 的分配通常由 hypervisor 或设备驱动完成。我个人建议的分配策略是:
- 按虚拟机分配: 每个虚拟机分配一个唯一的 SubstreamID 范围。例如,VM1 使用 0x100-0x1FF,VM2 使用 0x200-0x2FF。
- 按进程分配: 在 SVM 场景下,每个进程分配一个 PASID(即 SubstreamID)。这个 PASID 由操作系统内核管理。
- 复用与回收: 当虚拟机或进程销毁时,对应的 SubstreamID 必须回收。否则会导致 ID 泄漏,最终耗尽资源。
我记得有一次调试一个虚拟化环境,发现某个 VF 的 DMA 请求总是被 SMMU 拒绝。查了半天,发现是 hypervisor 在分配 SubstreamID 时,给两个不同的 VF 分配了相同的 ID。嗯,这种低级错误,往往是最难查的。
4.3 StreamID 与 SubstreamID 的协同工作
在实际的 SMMU 查找过程中,StreamID 和 SubstreamID 是组合使用的。SMMU 先根据 StreamID 找到对应的 STE,然后根据 SubstreamID(如果存在)找到对应的 Substream Table Entry (SSTE) 或直接使用 STE 中的配置。
下面这张图展示了它们的关系:
4.4 实际项目中的分配建议
基于我多年的经验,我总结了几条 StreamID 和 SubstreamID 的分配原则,希望能帮你少走弯路:
- 预留足够的 ID 空间: 设计阶段就要考虑未来扩展。StreamID 至少预留 16 位,SubstreamID 至少预留 20 位。
- 使用连续的 ID 范围: 方便 SMMU 的 Stream Table 实现,也便于调试时快速定位。
- 避免 ID 复用: 特别是在热插拔场景下,设备移除后必须释放 ID,新设备不能立即使用同一个 ID,要有冷却期。
- 记录分配日志: 在固件中记录每次 StreamID/SubstreamID 的分配情况。我曾经靠这个日志,在 5 分钟内定位了一个 ID 冲突问题。