Intel Discrete GPU - Memory - Migrate
说到 Migrate,我们可能最开始想到 Kernel 中内存子系统中 MM Compact 时所涉及到的迁移,Compact 的迁移是为了能够得到更多连续的空闲物理页,迁移时没有指定的源/目的地址。而这里提到的 Migrate 更像是一个 DMA 操作,有地址目标、大小等,而且不仅仅发生在系统内存中。
Xe 驱动中的 Migrate 通过 Intel GPU 硬件中模块实现,它一般被称之为Copy Engine或Blit Engine(Block Image Transfer),它能够完成的内存迁移操作可以在系统内存内部、系统内存与独立显存之间、独立显存内部;
Xe Migrate 的全局初始化通过xe_migrate_init完成,当一个 APP 的显存涉及到迁移时,Migrate 将介入其中,每个 APP 在 GPU 侧均有对应的 VM 来管理地址空间,Migrate 迁移显存时将会切换至其对应的指令 Queue 和地址空间 VM,而这些前置准备工作均由 init 来完成,
其中的核心流程即大致如下,
struct xe_migrate *xe_migrate_init(struct xe_tile *tile)
{
...
vm = xe_vm_create(xe, XE_VM_FLAG_MIGRATION |
XE_VM_FLAG_SET_TILE_ID(tile));
...
xe_migrate_prepare_vm(tile, m, vm);
...
xe_exec_queue_create_class(xe, primary_gt, vm,
XE_ENGINE_CLASS_COPY,
EXEC_QUEUE_FLAG_KERNEL |
EXEC_QUEUE_FLAG_PERMANENT, 0);
...
}
其中最为关键的操作是通过xe_migrate_prepare_vm搭建上述 VM 所需要的页表结构,
Migrate 的范围包含系统内存和独立显存,所以这个页表结构会稍微复杂些,对应的 PTE 要指向这些显存位置;
xe_migrate_prepare_vm的整体实现过程比较晦涩,乍一开始看它的实现时说实施是抵触的,里面涉及个多个 PDE/PTE Entry 的构建与对应 Offset 的写入,阅读这块代码时,建议一边看一边将页表的结构记录下来,便于理解;
我来简单概述下这里相关的几个要点,
-
在每个 VM 地址空间的创建之初,会默认分配一个 Root 页表作为 PGD,它在代码中叫做 pt_root,当然 Migrate VM 也不例外;
-
Migrate 采用
NUM_PT_SLOTS(默认 32) 个页表作为其操作显存迁移时的页表结构,其中有普通的显存拷贝映射时使用的用途,也有作为基础页表使用时的用途;以 DG1 GPU 为例的话,页表映射最大支持三级,还有VRAM_IDENTITY_MAP_COUNT(默认 2) 个页表用来作为独立显存的 IDENTITY 映射,那么将有五个页表作为基础页表的使用,它们是页面 27-31; -
IDENTITY 页表由 30、31 组成,页面 31 用来做
compressed pat场景使用,所以一般情况只下用到 30 一个页面,通过xe_migrate_program_identity完成映射,注意这里的是以 1G PTE 的粒度来映射大部分独立显存,最高 1G 地址空间的独立显存由于某些使用场景的特殊性,是采用 2M PTE 粒度来映射的。另外 IDENTITY 的映射 Offset 为 256;
基于以上构建过程,现在 Migrate 所需的页表结构已建立完成,大致如下所示,

使用场景
实际上 Migrate 不仅仅用于普通显存的迁移使用,在 Xe 驱动的实现中,有些 GPU 页表异步更新动作也通过它来完成,下面我们分别来介绍下这两种使用场景。
显存迁移
显存迁移的核心在xe_migrate_copy中实现,上面我们提到过,显存迁移时的源或目的空间皆可以是系统内存或独立显存,我们需要知道的是,如何用刚刚搭建好的页表结构来完成显存迁移。
我们以一块显存从独立显存 Migrate 到系统内存的场景为例来看一下需要经过哪些流程,
-
确认每次迁移的大小,在独立显存中通常为默认
MAX_PREEMPTDISABLE_TRANSFER(8M) 大小,再根据每个最后一级页表的映射空间LEVEL0_PAGE_TABLE_ENCODE_SIZE(2M) ,确定到单次所需要使用到的页面数avail_pts; -
源/目的地址分别通过
pte_update_size来计算好源/目的地址的虚拟地址 VA,虚拟地址的设定将涉及两种情况,-
地址空间为系统内存,通过
xe_migrate_vm_addr计算出 VA,static u64 xe_migrate_vm_addr(u64 slot, u32 level) { /* First slot is reserved for mapping of PT bo and bb, start from 1 */ return (slot + 1ULL) << xe_pt_shift(level + 1); }- 这里 slot 通过
pt_ofs参数指定,计算方式值得思考一下,其实是 Migrate 页表结构的一个运用,slot + 1 再外加 shift 意味着 28 号页面的第 1 个 entry 开始设定 VA
- 这里 slot 通过
-
地址空间为独立显存,通过
xe_migrate_vram_ofs计算出 VAstatic u64 xe_migrate_vram_ofs(struct xe_device *xe, u64 addr, bool is_comp_pte) { u64 identity_offset = IDENTITY_OFFSET; ... addr -= xe->mem.vram.dpa_base; return addr + (identity_offset << xe_pt_shift(2)); }- 这里比系统内存的 VA 更容易理解,拿到
IDENTITY_OFFSET(256) 外加 shift 后,即为独立显存的 IDENTITY 映射空间,VA 与独立显存 PA 一致
- 这里比系统内存的 VA 更容易理解,拿到
-
-
通过
xe_bb_new创建迁移指令队列; -
通过
emit_pte做迁移地址在页表结构中的 PTE 填充,填充动作是通过 GPU 的MI_STORE_DATA_IMM指令实现,物理地址为独立显存 PA 或经过 IOMMU 映射过系统内存后取得的 IPA; -
向 GPU 的迁移命令队列中通过
emit_copy插入最终的迁移指令,对应的指令为XY_FAST_COPY_BLT_CMD,源/目的地址和迁移大小均在指令后追加;
页表更新
页表更新动作体现在xe_migrate_update_pgtables中,其中包含借助 CPU 更新和借助 GPU 更新,CPU 更新时可以立即将页表写入内存,但前提是页表更新时 GPU 没有访问对应 APP 地址空间的依赖,否则将通过 GPU 来更新。
Migrate 的使用场景对应于 GPU 更新页表,主要实现函数为__xe_migrate_update_pgtables,页表所需更新的内容已在显存 MAP 时存储至pt_update_ops结构中(可以看下 MAP 篇文章回忆下),pt_update_ops包含多个pt_op,其中num_entries成员代表我们需要更新的页表数,pt_bo成员代表需要更新的页表目标;
随后需要再根据我们的 GPU 硬件是否为独立显卡决定页表的写入空间,分别看下两种情况,
- 集成显卡的情况
首先需要通过最开始搭建好的 Migrate 全局页表结构衔接上需要更新的页表页面pt_bo,所需使用的页表结构从NUM_KERNEL_PDE这个偏移开始;
- 独立显卡的情况
独立显卡的显存空间因为是 IDENTITY 映射,所以pt_bo映射搭建这一块可以无需考虑
映射好目标pt_bo,最后一步就是将页表项写入至pt_bo,无论是集成显卡或独立显存,均通过write_pgtable来完成目标项的写入,其实这里与显存迁移时的使用方法极其类似,由于独立显存的 IDENTITY 映射存在偏移,所以针对独立显卡的情况先借助xe_migrate_vram_ofs取得偏移。最后通过 GPU 的MI_STORE_DATA_IMM指令完成页表项的写入。