本文详细分析 drm_pagemap 的设计目的、原理和实现。


本文技术建立在前面的migrate_vma_*和ZONE_DEVICE技术上。如需理解这两个技术,请移步:

一、设计目的:为什么设计出来drm_pagemap?

1.1 问题背景

GPU 要实现统一虚拟内存(SVM),需要在系统内存和设备内存(VRAM)之间透明迁移数据。Linux 内核提供了 migrate_vma_* API 和 ZONE_DEVICEMEMORY_DEVICE_PRIVATE)来支持这种迁移,但这些原始 API 非常底层且复杂。下面是直接使用内核原始 API 实现迁移数据的过程:

  1. devm_memremap_pages() → 注册 ZONE_DEVICE,需要正确设置 dev_pagemap_ops
  2. migrate_vma_setup() → 收集源页面,准备迁移数据结构
  3. 手动分配目标页面 + 填充 PFN
  4. 手动 DMA map 源/目标页面
  5. 调用驱动的 DMA 拷贝引擎
  6. migrate_vma_pages() → 原子交换页表
  7. migrate_vma_finalize() → 清理
  8. 处理反方向回迁(CPU fault 时自动触发)

每个 GPU 驱动(Intel Xe、AMD、NVIDIA Nouveau 等)如果各自实现这套逻辑,会有大量重复代码,且容易出错。

1.2 drm_pagemap 的定位

drm_pagemap 是 DRM 子系统的 设备内存迁移抽象层,位于 Linux mm 层和 GPU 驱动之间:

┌──────────────────────────────────────────────────────────┐
│  GPU 驱动 (Xe / AMDGPU / Nouveau)                         │
│  - 分配 VRAM (BO)                                         │
│  - 提供 VRAM PFN                                          │
│  - 使用 DMA 引擎拷贝数据                                    │
├──────────────────────────────────────────────────────────┤
│  drm_pagemap (DRM 子系统)                 ← 本文分析对象    │
│  - 统一的 ZONE_DEVICE ops                                 │
│  - 封装 migrate_vma_* 四步迁移协议                          │
│  - 管理 zone_device_data (zdd)                            │
│  - 处理 DMA 映射/解映射                                     │
│  - 处理 CPU fault 回迁                                     │
│  - timeslice 防抖机制                                      │
├──────────────────────────────────────────────────────────┤
│  Linux mm (migrate_vma_*, HMM, ZONE_DEVICE)              │
│  - 页表操作                                               │
│  - 页面生命周期管理                                         │
│  - MMU notifier                                           │
└──────────────────────────────────────────────────────────┘

一句话总结drm_pagemap 让 GPU 驱动只需要关心"分配 VRAM"和"DMA 拷贝",其余的迁移协议、页面生命周期、zdd 管理、CPU 回迁全部由框架处理。这个就是drm_pagemap的职责和功能。从其名称上理解,就是设备页面映射。把设备内存映射为page,然后用来支持迁移。


接下来从具体的数据结构和API来理解该框架的实现。

二、核心概念与数据结构

2.1 三层对象模型

drm_pagemap 定义了三个层次的对象:

┌─────────────────────────────────────────────────────────────┐
│ 第1层: drm_pagemap (设备级,每个 VRAM 区域一个)                 │
│   - 对应一块可迁移的设备内存区域                                 │
│   - 包含 dev_pagemap (ZONE_DEVICE 注册) + drm_pagemap_ops    │
│   - 生命周期 = 设备生命周期                                    │
├─────────────────────────────────────────────────────────────┤
│ 第2层: drm_pagemap_devmem (每次迁移一个)                       │
│   - 代表一次从 RAM 到 VRAM 的迁移动作                           │
│   - 关联一个 VRAM allocation (如 BO)                          │
│   - 包含 drm_pagemap_devmem_ops (拷贝回调)                    │
│   - 生命周期 = 设备页面在 mm 中的生命周期                        │
├─────────────────────────────────────────────────────────────┤
│ 第3层: drm_pagemap_zdd (每个设备页面一个)                      │
│   - zone_device_data,挂在 page->zone_device_data            │
│   - 引用计数,引用 devmem_allocation                          │
│   - 负责异步释放(page_free 回调)                             │
│   - 记录 device_private_page_owner                          │
└─────────────────────────────────────────────────────────────┘

一句话总结:drm_pagemap = 给 VRAM 穿上 struct page 的衣服 + 在 RAM 和 VRAM 之间搬运数据 + 管理这些设备页面的生死。

2.2 关键数据结构

drm_pagemap — 设备内存管理器
struct drm_pagemap {
    const struct drm_pagemap_ops *ops;  // 驱动回调
    struct device *dev;                 // 所属设备
};

驱动需要实现的操作:

struct drm_pagemap_ops {
    // 将设备页面映射为可被 GPU 访问的地址
    // 当 HMM 返回 device_private 页面时调用
    struct drm_pagemap_addr (*device_map)(dpagemap, dev, page, order, dir);

    // 解除 device_map 的映射
    void (*device_unmap)(dpagemap, dev, addr);

    // 将 mm 的虚拟地址范围迁移到设备内存
    // 框架负责获取 mmap_lock,驱动负责分配 VRAM + 调用框架迁移函数
    int (*populate_mm)(dpagemap, start, end, mm, timeslice_ms);
};
drm_pagemap_devmem — 一次迁移的上下文
struct drm_pagemap_devmem {
    struct device *dev;                          // 所属设备
    struct mm_struct *mm;                        // 目标地址空间
    struct completion detached;                  // 所有设备页面已脱离
    const struct drm_pagemap_devmem_ops *ops;    // 拷贝回调
    struct drm_pagemap *dpagemap;                // 所属 drm_pagemap
    size_t size;                                 // 迁移大小
    u64 timeslice_expiration;                    // 最早允许回迁的时间
};

驱动需要实现的操作:

struct drm_pagemap_devmem_ops {
    // 释放 VRAM allocation(所有设备页面不再被使用时调用)
    void (*devmem_release)(devmem_allocation);

    // 填充 VRAM PFN 数组(从 VRAM offset → struct page PFN)
    int (*populate_devmem_pfn)(devmem_allocation, npages, pfn[]);

    // DMA 拷贝: 系统 RAM → VRAM
    int (*copy_to_devmem)(pages[], pagemap_addr[], npages);

    // DMA 拷贝: VRAM → 系统 RAM
    int (*copy_to_ram)(pages[], pagemap_addr[], npages);
};
drm_pagemap_zdd — 设备页面的元数据
struct drm_pagemap_zdd {
    struct kref refcount;                        // 引用计数
    struct drm_pagemap_devmem *devmem_allocation; // 关联的迁移上下文
    void *device_private_page_owner;             // 页面所有者标识
};

安装在每个设备页面的 page->zone_device_data 中,当所有引用释放时:

  1. 通过 devmem_allocation->ops->devmem_release() 释放 VRAM
  2. 通过 complete_all(&devmem->detached) 通知等待者
drm_pagemap_addr — 地址编码
struct drm_pagemap_addr {
    dma_addr_t addr;   // DMA 地址 或 驱动自定义地址
    u64 proto : 54;    // 互连协议 (SYSTEM / DRIVER / 自定义)
    u64 order : 8;     // 页面 order (2^order 个页面)
    u64 dir : 2;       // DMA 方向
};

proto 字段是 drm_pagemap 的核心抽象之一:

  • DRM_INTERCONNECT_SYSTEM:标准 DMA 地址,通过 PCIe 访问系统内存
  • DRM_INTERCONNECT_DRIVER:驱动自定义地址(如 VRAM 物理地址),通过本地总线访问
  • 驱动可以定义更多协议值(如 Xe 的 XE_INTERCONNECT_VRAM

三、迁移协议详解

3.1 RAM → VRAM 迁移(drm_pagemap_migrate_to_devmem)

这是最复杂的路径,完整流程如下:

drm_pagemap_migrate_to_devmem(devmem_allocation, mm, start, end, ...)
│
├── 1. 前置检查
│   ├── VMA 必须存在且完整覆盖 [start, end]
│   ├── VMA 必须是匿名映射 (vma_is_anonymous)
│   └── 分配工作缓冲区 (src[] + dst[] + pagemap_addr[] + pages[])
│
├── 2. 分配 zdd (zone_device_data)
│   └── drm_pagemap_zdd_alloc(pgmap_owner)
│
├── 3. 收集源页面 — migrate_vma_setup()
│   │   flags = MIGRATE_VMA_SELECT_SYSTEM  (只收集系统页面)
│   │
│   │   内核做的事:
│   │   ├── 遍历 [start, end] 的页表
│   │   ├── 对每个有效系统页面: 标记 MIGRATE_PFN_MIGRATE
│   │   ├── 从原页表中 unmap (获得独占控制)
│   │   └── 填充 migrate.src[] 数组
│   │
│   ├── 检查: cpages == npages? (必须全部可迁移)
│   │   └── 如果部分不可迁移 → 返回 -EBUSY
│   │
│   ├── 4. 填充目标 PFN — ops->populate_devmem_pfn()
│   │   │   驱动做的事:
│   │   │   ├── 遍历 VRAM BO 的 TTM resource (buddy blocks)
│   │   │   ├── 将 VRAM 物理偏移转换为 struct page PFN
│   │   │   │   PFN = (vram_offset + pgmap.range.start) >> PAGE_SHIFT
│   │   │   └── 填充 migrate.dst[] 数组
│   │   │
│   │   └── 框架将每个目标页面关联到 zdd:
│   │       page->zone_device_data = zdd  (通过 drm_pagemap_get_devmem_page)
│   │
│   ├── 5. DMA 映射源页面 — drm_pagemap_migrate_map_pages()
│   │   │   对每个源系统页面:
│   │   │   ├── dma_map_page(dev, page, ..., DMA_TO_DEVICE)
│   │   │   └── pagemap_addr[i] = encode(dma_addr, SYSTEM, order, dir)
│   │   │
│   │   └── 目的: GPU 的 DMA 引擎需要通过 DMA 地址读取系统页面
│   │
│   ├── 6. 数据拷贝 — ops->copy_to_devmem(pages[], pagemap_addr[], npages)
│   │   │   驱动做的事:
│   │   │   ├── pages[i] = VRAM 目标页面 (可转换为 VRAM 物理地址)
│   │   │   ├── pagemap_addr[i].addr = 系统源页面的 DMA 地址
│   │   │   └── 使用 GPU SDMA/Copy Engine:
│   │   │       src = pagemap_addr[i].addr (系统 DMA 地址, 通过 GART)
│   │   │       dst = page_to_vram_addr(pages[i])
│   │   │
│   │   └── 关键: 这一步的顺序是 RAM → VRAM
│   │
│   ├── 7. 原子页表交换 — migrate_vma_pages()
│   │   │   内核做的事:
│   │   │   ├── 将 CPU 页表从指向旧系统页面改为指向设备页面
│   │   │   ├── 设备页面 PTE 类型为 device_private swap entry
│   │   │   └── CPU 后续访问会触发 page fault → migrate_to_ram 回调
│   │   │
│   │   └── 同时设置 timeslice 和 zdd 关联:
│   │       devmem_allocation->timeslice_expiration = jiffies + timeslice
│   │       zdd->devmem_allocation = devmem_allocation
│   │
│   └── 8. 完成 — migrate_vma_finalize()
│       │   内核做的事:
│       │   ├── 释放旧系统页面引用
│       │   └── 完成迁移状态清理
│       │
│       └── DMA 解映射源页面
│           drm_pagemap_migrate_unmap_pages(dev, pagemap_addr, npages)
│
└── 清理工作缓冲区, 释放 zdd 引用

3.2 VRAM → RAM 回迁(CPU fault 触发)

当 CPU 访问一个已迁移到 VRAM 的虚拟地址时:

CPU 访问虚拟地址
  │
  ▼
CPU 页表中是 device_private swap entry
  │  → 触发 page fault
  ▼
Linux mm: handle_pte_fault → do_swap_page
  │  → 识别为 device_private 页面
  │  → 调用 dev_pagemap_ops->migrate_to_ram(vmf)
  ▼
drm_pagemap_migrate_to_ram(vmf)  ← 框架提供的统一回调
  │
  ├── timeslice 检查
  │   if (jiffies < zdd->devmem_allocation->timeslice_expiration)
  │       return 0;  // 数据还在 timeslice 内,不迁移
  │
  ├── __drm_pagemap_migrate_to_ram(vas, owner, page, fault_addr, size)
  │   │
  │   ├── migrate_vma_setup(MIGRATE_VMA_SELECT_DEVICE_PRIVATE)
  │   │   收集 [start, end] 中的设备页面到 src[]
  │   │
  │   ├── drm_pagemap_migrate_populate_ram_pfn()
  │   │   分配系统 RAM 页面作为目标,填充 dst[]
  │   │   ├── 使用 vma_alloc_folio() 分配(支持 THP)
  │   │   └── 确保 src 和 dst 页面 order 匹配
  │   │
  │   ├── drm_pagemap_migrate_map_pages(dev, pagemap_addr, dst, DMA_FROM_DEVICE)
  │   │   DMA 映射目标 RAM 页面
  │   │
  │   ├── ops->copy_to_ram(pages[], pagemap_addr[], npages)
  │   │   驱动执行 SDMA 拷贝: VRAM → RAM
  │   │
  │   ├── migrate_vma_pages()    → 原子交换页表
  │   ├── migrate_vma_finalize() → 完成
  │   └── drm_pagemap_migrate_unmap_pages() → DMA 解映射
  │
  └── 返回 0 或 VM_FAULT_SIGBUS

3.3 设备页面释放(page_free 回调)

当最后一个引用设备页面的进程退出或 unmap 时:

CPU 页表中的 device_private swap entry 被清除
  │
  ▼
Linux mm: put_page(device_page)
  │  → 引用计数归零
  │  → 调用 dev_pagemap_ops->page_free(page)
  ▼
drm_pagemap_page_free(page)
  │
  ├── zdd = page->zone_device_data
  ├── drm_pagemap_zdd_put(zdd)
  │     → kref_put → drm_pagemap_zdd_destroy()
  │
  └── drm_pagemap_zdd_destroy(ref)
        ├── devmem = zdd->devmem_allocation
        ├── complete_all(&devmem->detached)    // 通知等待者
        └── devmem->ops->devmem_release(devmem) // 驱动释放 VRAM
              └── 例如: xe_svm_devmem_release()
                    └── xe_bo_put_async(bo)  // 释放 VRAM BO

关键设计page_free 可能在 IRQ 上下文中被调用,但释放 VRAM(释放 BO)通常需要 sleeping lock。因此 zdd 的引用计数 + 异步释放模式非常重要。

3.4 Eviction 路径(drm_pagemap_evict_to_ram)

与 CPU fault 回迁不同,eviction 不需要 mmap lock,使用 migrate_device_* API:

内存压力 / 设备解绑
  │
  ▼
drm_pagemap_evict_to_ram(devmem_allocation)
  │
  ├── ops->populate_devmem_pfn() → 填充源 VRAM PFN
  ├── migrate_device_pfns(src, npages) → 收集设备页面(不需要 mmap lock)
  ├── drm_pagemap_migrate_populate_ram_pfn() → 分配 RAM 目标页面
  ├── drm_pagemap_migrate_map_pages() → DMA 映射 RAM 页面
  ├── ops->copy_to_ram() → SDMA 拷贝
  ├── migrate_device_pages() → 页表交换
  └── migrate_device_finalize() → 完成
      └── 如果 completion 未完成,重试最多 2 次

四、drm_pagemap_zdd 的设计精髓

zdd(zone_device_data)是理解 drm_pagemap 的关键。每个 device_private 页面通过 page->zone_device_data 指向一个 zdd:

       page (device_private)
       ├── zone_device_data ──→ drm_pagemap_zdd
       │                        ├── refcount
       │                        ├── devmem_allocation ──→ drm_pagemap_devmem
       │                        │                         ├── ops (copy/release 回调)
       │                        │                         ├── dpagemap ──→ drm_pagemap
       │                        │                         ├── size
       │                        │                         └── timeslice_expiration
       │                        └── device_private_page_owner
       └── _refcount

4.1 引用计数语义

zdd 引用增加:
  - drm_pagemap_get_devmem_page(page, zdd)  → 迁移时,每个设备页面 +1

zdd 引用减少:
  - drm_pagemap_page_free(page)  → page 的 refcount 归零时 → zdd put

zdd refcount 归零:
  - drm_pagemap_zdd_destroy()
    → complete_all(&devmem->detached)   // 信号
    → ops->devmem_release(devmem)       // 释放 VRAM
    → kfree(zdd)

4.2 一个 devmem_allocation 对应多个 zdd?

不是。一个 devmem_allocation 对应 一个 zdd,但这个 zdd 被同一次迁移中的所有设备页面共享引用:

迁移 N 个页面到 VRAM:
  1. 创建 1 个 zdd
  2. N 个设备页面都指向同一个 zdd → refcount = N
  3. 当所有 N 个页面都被 page_free → refcount 归零 → 释放 VRAM BO

这确保了: VRAM BO 在任何一个设备页面还被使用时都不会被释放

4.3 owner 字段的作用

device_private_page_owner 用于区分不同驱动/设备的设备页面。在 HMM fault 时:

hmm_range.dev_private_owner = ctx->device_private_page_owner;
  • 如果遇到的 device_private 页面 owner 等于 自己 → 视为"本地"设备页面
  • 如果遇到的 device_private 页面 owner 不等于 自己 → 内核自动触发 migrate_to_ram 迁回

这使得多设备场景下,GPU A 不会直接访问 GPU B 的设备页面,而是先迁移回 RAM。


五、timeslice 防抖机制

5.1 问题

如果一个页面频繁在 CPU 和 GPU 之间来回迁移(“ping-pong”),性能会严重下降。

5.2 解决方案

drm_pagemap 引入了 timeslice_expiration

// 迁移到 VRAM 时设置
devmem_allocation->timeslice_expiration = get_jiffies_64() +
    msecs_to_jiffies(timeslice_ms);

// CPU fault 回迁时检查
if (time_before64(get_jiffies_64(), zdd->devmem_allocation->timeslice_expiration))
    return 0;  // 还在 timeslice 内,不允许回迁

这意味着数据迁移到 VRAM 后,在 timeslice_ms 毫秒内即使 CPU 访问也不会回迁,CPU 的 fault handler 直接返回 0(让 CPU 等待)。这给了 GPU 足够的时间完成计算。


六、与 drm_gpusvm 的协作

drm_pagemap 和 drm_gpusvm 是两个互补的框架:

drm_gpusvm                          drm_pagemap
├── 管理 GPU 虚拟地址空间             ├── 管理设备物理内存迁移
├── MMU notifier 管理                ├── ZONE_DEVICE 注册
├── range 区间树                     ├── migrate_vma_* 封装
├── HMM fault + DMA 映射             ├── CPU fault 回迁
└── GPU 页表映射 (通过驱动)           └── 设备页面生命周期

协作流程:

1. drm_gpusvm_range_get_pages() 调用 hmm_range_fault()
   → 如果返回 device_private 页面
   → 调用 drm_pagemap->ops->device_map() 获取 VRAM DMA 地址
   → 存入 range->pages.dma_addr[].proto = DRM_INTERCONNECT_DRIVER

2. 驱动的 GPU PTE 更新代码检查 proto:
   - SYSTEM → 标准 DMA 地址, 通过 PCIe 访问
   - DRIVER → VRAM 地址, 本地总线直接访问

3. drm_pagemap_populate_mm() → 迁移数据到 VRAM
   → 成功后, hmm_range_fault() 返回 device_private 页面
   → 走上面 device_map 路径

七、Xe 驱动参考实现剖析

Xe(Intel)是 drm_pagemap 的第一个用户,展示了完整的使用模式:

7.1 设备注册(xe_devm_add)

int xe_devm_add(struct xe_tile *tile, struct xe_vram_region *vr)
{
    // 1. 在 iomem_resource 中分配一段伪物理地址空间
    res = devm_request_free_mem_region(dev, &iomem_resource, vr->usable_size);

    // 2. 注册 ZONE_DEVICE
    vr->pagemap.type = MEMORY_DEVICE_PRIVATE;
    vr->pagemap.range.start = res->start;
    vr->pagemap.range.end = res->end;
    vr->pagemap.ops = drm_pagemap_pagemap_ops_get();  // ← 框架统一 ops
    vr->pagemap.owner = xe_svm_devm_owner(xe);
    devm_memremap_pages(dev, &vr->pagemap);

    // 3. 初始化 drm_pagemap
    vr->dpagemap.dev = dev;
    vr->dpagemap.ops = &xe_drm_pagemap_ops;

    // 4. 记录 PFN 基址 (用于 VRAM offset → PFN 转换)
    vr->hpa_base = res->start;
}

关键:pagemap.ops 使用的是 drm_pagemap_pagemap_ops_get() 返回的框架统一 ops,而不是驱动自己的 ops。这样 page_freemigrate_to_ram 回调由框架统一处理。

7.2 populate_mm 实现

static int xe_drm_pagemap_populate_mm(struct drm_pagemap *dpagemap,
                                      unsigned long start, unsigned long end,
                                      struct mm_struct *mm, unsigned long timeslice_ms)
{
    // 1. 分配 VRAM BO
    bo = xe_bo_create_locked(xe, ..., end - start, XE_BO_FLAG_VRAM(...));

    // 2. 初始化 devmem_allocation(嵌入在 BO 中)
    drm_pagemap_devmem_init(&bo->devmem_allocation, dev, mm,
                            &dpagemap_devmem_ops, dpagemap, end - start);

    // 3. 将 buddy block 的 private 指向 vram_region(用于 PFN 计算)
    list_for_each_entry(block, blocks, link)
        block->private = vr;

    // 4. 执行迁移
    err = drm_pagemap_migrate_to_devmem(&bo->devmem_allocation, mm,
                                        start, end, timeslice_ms, owner);
    if (err)
        xe_svm_devmem_release(&bo->devmem_allocation);
}

7.3 PFN 填充

static int xe_svm_populate_devmem_pfn(struct drm_pagemap_devmem *devmem,
                                      unsigned long npages, unsigned long *pfn)
{
    struct xe_bo *bo = to_xe_bo(devmem);
    struct list_head *blocks = &to_vram_resource(bo->ttm.resource)->blocks;

    // 遍历 buddy allocator 分配的物理块
    list_for_each_entry(block, blocks, link) {
        struct xe_vram_region *vr = block->private;
        u64 block_pfn = (drm_buddy_block_offset(block) + vr->hpa_base)
                        >> PAGE_SHIFT;
        for (i = 0; i < block_size >> PAGE_SHIFT; ++i)
            pfn[j++] = block_pfn + i;
    }
}

公式:PFN = (VRAM 物理偏移 + pgmap.range.start) >> PAGE_SHIFT

7.4 device_map 实现

static struct drm_pagemap_addr
xe_drm_pagemap_device_map(struct drm_pagemap *dpagemap, struct device *dev,
                          struct page *page, unsigned int order,
                          enum dma_data_direction dir)
{
    if (pgmap_dev == dev) {
        // 同一设备: 将 device_private page 转为 VRAM 物理地址
        addr = xe_vram_region_page_to_dpa(page_to_vr(page), page);
        prot = XE_INTERCONNECT_VRAM;
    } else {
        addr = DMA_MAPPING_ERROR;  // 不同设备: 不支持直接访问
    }
    return drm_pagemap_addr_encode(addr, prot, order, dir);
}

这个 device_mapdrm_gpusvm_range_get_pages() 调用,当 HMM fault 返回的页面是 device_private 时,框架通过此回调获取 GPU 可用的地址。


八、设计模式总结

8.1 策略模式(Strategy Pattern)

drm_pagemap 通过两组 ops 将变化点交给驱动:

框架不变的部分:                        驱动变化的部分:
├── zdd 生命周期管理                   ├── drm_pagemap_ops
├── migrate_vma_* 协议编排             │   ├── device_map (地址转换)
├── DMA 映射/解映射                    │   └── populate_mm (分配+迁移)
├── CPU fault 回迁流程                 │
├── timeslice 检查                     └── drm_pagemap_devmem_ops
└── page_free 异步释放                     ├── devmem_release (释放 VRAM)
                                           ├── populate_devmem_pfn (PFN)
                                           ├── copy_to_devmem (RAM→VRAM)
                                           └── copy_to_ram (VRAM→RAM)

8.2 容器模式

drm_pagemap_devmem 设计为可嵌入驱动结构体:

// Xe 的做法: devmem 嵌入 BO
struct xe_bo {
    struct ttm_buffer_object ttm;
    struct drm_pagemap_devmem devmem_allocation;  // 嵌入
    ...
};

// 通过 container_of 回溯
static struct xe_bo *to_xe_bo(struct drm_pagemap_devmem *devmem)
{
    return container_of(devmem, struct xe_bo, devmem_allocation);
}

8.3 统一 dev_pagemap_ops

框架提供的 drm_pagemap_pagemap_ops_get() 返回统一的 ops:

static const struct dev_pagemap_ops drm_pagemap_pagemap_ops = {
    .page_free = drm_pagemap_page_free,        // 统一的页面释放
    .migrate_to_ram = drm_pagemap_migrate_to_ram, // 统一的回迁处理
};

驱动 不需要 自己实现这两个回调。框架通过 page->zone_device_data (zdd) → devmem_allocation → ops 链条自动路由到正确的驱动回调。


九、完整对象生命周期

驱动初始化:
  xe_devm_add()
    ├── devm_request_free_mem_region()  → 分配伪物理地址空间
    ├── devm_memremap_pages()           → 注册 ZONE_DEVICE, 创建 struct page
    └── 初始化 drm_pagemap              → 设置 ops + dev

迁移 RAM → VRAM:
  populate_mm()
    ├── 创建 BO (VRAM)                  → 分配 VRAM 物理空间
    ├── drm_pagemap_devmem_init()       → 初始化迁移上下文
    └── drm_pagemap_migrate_to_devmem()
          ├── 创建 zdd                  → 关联 owner
          ├── migrate_vma_setup()       → 收集源页面
          ├── populate_devmem_pfn()     → 驱动提供 VRAM PFN
          ├── 设备页面关联 zdd          → page->zone_device_data = zdd
          ├── copy_to_devmem()          → SDMA 拷贝 RAM → VRAM
          ├── migrate_vma_pages()       → 原子交换页表
          └── 设置 timeslice            → 防抖

稳态运行:
  GPU 通过 device_map() 获取 VRAM 地址, 直接访问
  CPU 访问触发 fault → 检查 timeslice → 到期则回迁

页面回收:
  CPU 最后引用释放 → page_free()
    → zdd_put() → refcount 归零
    → devmem_release() → 释放 VRAM BO
    → VRAM 空间返回 buddy allocator

驱动卸载:
  devm_memremap_pages 的 devm 资源自动清理
  所有 struct page 失效

十、关键设计决策与权衡

设计决策 理由
page_free 由框架统一处理 避免每个驱动重复实现 zdd 管理和异步释放
migrate_to_ram 由框架统一处理 回迁逻辑(分配 RAM 页 + DMA map + 拷贝)是通用的
populate_mm 由驱动实现 VRAM 分配策略是驱动特定的(BO 大小、placement 策略)
zdd 引用计数 处理 IRQ 上下文中的 page_free,避免在中断中持有 sleeping lock
timeslice 机制 框架级防抖,避免驱动各自实现
drm_pagemap_addr.proto 字段 统一表示系统 DMA 地址和设备本地地址,GPU PTE 更新可以统一处理
devmem_allocation 可嵌入 避免额外分配,与 BO 生命周期自然绑定
cpages == npages 要求 简化实现,全有或全无迁移,避免部分迁移的复杂状态管理
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐