前文:TTM ttm_tt 技术分析系列2:概述与核心原理分析TTM框架下的ttm_tt实现,本文聚焦于AMD的具体实现,看下AMD是如何应用ttm_tt的。
1. AMD 驱动中的 ttm_tt 架构
1.1 amdgpu_ttm_tt 结构
struct amdgpu_ttm_tt {
struct ttm_dma_tt ttm; // 基础 DMA 扩展
struct drm_gem_object *gobj; // 关联的 GEM 对象
u64 offset; // GTT 偏移地址
uint64_t userptr; // 用户空间虚拟地址
struct task_struct *usertask; // 所属进程
uint32_t userflags; // 用户标志
struct hmm_range *range; // HMM 范围跟踪
};
正如前文所言,AMD在ttm_tt框架的基础上,扩展了System Memory的应用–userptr。在后续的函数实现中,你能看到if/else分支来处理GTT路径和uertpr路径。
1.2 后端操作函数表
static struct ttm_backend_func amdgpu_backend_func = {
.bind = &amdgpu_ttm_backend_bind,
.unbind = &amdgpu_ttm_backend_unbind,
.destroy = &amdgpu_ttm_backend_destroy,
};
下面的章节按照ttm_tt类型的bo的创建、页面填充(populate)、绑定(bind)、解绑(unbind)、释放页面(unpopulated)、销毁(destroy)几个生命周期的动作实现进行分析,讲述了system memory里的bo从出生到消亡的传奇一生。
2. 创建与初始化
2.1 ttm_tt 对象创建
static struct ttm_tt *amdgpu_ttm_tt_create(struct ttm_buffer_object *bo,
uint32_t page_flags)
{
struct amdgpu_ttm_tt *gtt;
// 分配 AMD 扩展结构
gtt = kzalloc(sizeof(struct amdgpu_ttm_tt), GFP_KERNEL);
if (gtt == NULL)
return NULL;
// 设置后端操作函数
gtt->ttm.ttm.func = &amdgpu_backend_func;
// 关联 GEM 对象
gtt->gobj = &bo->base;
// 初始化 scatter-gather 支持的 DMA ttm
if (ttm_sg_tt_init(>t->ttm, bo, page_flags)) {
kfree(gtt);
return NULL;
}
return >t->ttm.ttm;
}
关键点:
- 分配扩展的
amdgpu_ttm_tt而非基础ttm_tt - 使用
ttm_sg_tt_init()支持 scatter-gather DMA - 保存 GEM 对象引用用于后续 dma-buf 操作
2.2 初始化类型选择
TTM 提供三种初始化接口:
// 基础初始化 - 普通页面数组
int ttm_tt_init(struct ttm_tt *ttm,
struct ttm_buffer_object *bo,
uint32_t page_flags);
// DMA 初始化 - 添加 DMA 地址映射
int ttm_dma_tt_init(struct ttm_dma_tt *ttm_dma,
struct ttm_buffer_object *bo,
uint32_t page_flags);
// SG 初始化 - 支持 scatter-gather 表
int ttm_sg_tt_init(struct ttm_dma_tt *ttm_dma,
struct ttm_buffer_object *bo,
uint32_t page_flags);
AMD 选择 ttm_sg_tt_init 以支持:
- DMA-BUF 导入的外部 buffer
- Scatter-gather DMA 映射
- 与其他设备的内存共享
3. 页面填充(Populate)
3.1 填充入口
static int amdgpu_ttm_tt_populate(struct ttm_tt *ttm,
struct ttm_operation_ctx *ctx)
{
struct amdgpu_device *adev = amdgpu_ttm_adev(ttm->bdev);
struct amdgpu_ttm_tt *gtt = (void *)ttm;
// 场景1: 用户指针 (userptr)
if (gtt && gtt->userptr) {
ttm->sg = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
if (!ttm->sg)
return -ENOMEM;
ttm->page_flags |= TTM_PAGE_FLAG_SG;
ttm->state = tt_unbound;
return 0;
}
// 场景2: DMA-BUF 导入
if (ttm->page_flags & TTM_PAGE_FLAG_SG) {
if (!ttm->sg) {
struct dma_buf_attachment *attach;
struct sg_table *sgt;
attach = gtt->gobj->import_attach;
sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
if (IS_ERR(sgt))
return PTR_ERR(sgt);
ttm->sg = sgt;
}
// 从 SG 表提取页面和 DMA 地址
drm_prime_sg_to_page_addr_arrays(ttm->sg, ttm->pages,
gtt->ttm.dma_address,
ttm->num_pages);
ttm->state = tt_unbound;
return 0;
}
// 场景3: SWIOTLB 支持
#ifdef CONFIG_SWIOTLB
if (adev->need_swiotlb && swiotlb_nr_tbl()) {
return ttm_dma_populate(>t->ttm, adev->dev, ctx);
}
#endif
// 场景4: 普通内存分配
return ttm_populate_and_map_pages(adev->dev, >t->ttm, ctx);
}
3.2 三种填充场景详解
(1) Userptr 场景
用途: 允许用户空间直接传递指针给 GPU,无需数据拷贝
流程:
用户空间地址
↓ amdgpu_ttm_tt_set_userptr()
记录 userptr + usertask
↓ populate 时分配 sg_table
延迟到 bind 时处理
↓ amdgpu_ttm_tt_pin_userptr()
└─> hmm_range_fault() 获取物理页面
└─> 建立 GPU 映射
这种情形,参见专文:AMD KFD userptr内存分配技术分析。
(2) DMA-BUF 场景
用途: 在不同设备间零拷贝共享 buffer
流程:
外部设备 Buffer
↓ dma_buf_export()
创建 dma_buf 对象
↓ amdgpu_gem_prime_import()
创建 amdgpu_bo 并关联 attachment
↓ populate 时
dma_buf_map_attachment()
↓
获取 sg_table
↓ drm_prime_sg_to_page_addr_arrays()
提取页面指针和 DMA 地址
优势:
- 多设备共享同一块物理内存
- 避免 CPU 拷贝开销
- 支持跨驱动内存共享(如摄像头→GPU)
(3) 普通分配场景
默认路径: ttm_populate_and_map_pages()
int ttm_populate_and_map_pages(struct device *dev, struct ttm_dma_tt *tt,
struct ttm_operation_ctx *ctx)
{
unsigned i, j;
for (i = 0; i < tt->ttm.num_pages; i++) {
// 分配单个页面
tt->ttm.pages[i] = alloc_page(gfp_flags);
// 建立 DMA 映射
tt->dma_address[i] = dma_map_page(dev, tt->ttm.pages[i],
0, PAGE_SIZE,
DMA_BIDIRECTIONAL);
}
return 0;
}
特点:
- 逐页分配,灵活但可能产生碎片
- 立即建立 DMA 映射
- 支持 DMA32 限制(32位地址)
4. GART 绑定(Bind)
4.1 绑定流程
static int amdgpu_ttm_backend_bind(struct ttm_tt *ttm,
struct ttm_mem_reg *bo_mem)
{
struct amdgpu_device *adev = amdgpu_ttm_adev(ttm->bdev);
struct amdgpu_ttm_tt *gtt = (void*)ttm;
uint64_t flags;
int r = 0;
// 1. 处理 userptr
if (gtt->userptr) {
r = amdgpu_ttm_tt_pin_userptr(ttm);
if (r) {
DRM_ERROR("failed to pin userptr\n");
return r;
}
}
// 2. 检查特殊内存类型
if (bo_mem->mem_type == AMDGPU_PL_GDS ||
bo_mem->mem_type == AMDGPU_PL_GWS ||
bo_mem->mem_type == AMDGPU_PL_OA)
return -EINVAL;
// 3. 检查是否有 GART 地址
if (!amdgpu_gtt_mgr_has_gart_addr(bo_mem)) {
gtt->offset = AMDGPU_BO_INVALID_OFFSET;
return 0;
}
// 4. 计算 PTE 标志
flags = amdgpu_ttm_tt_pte_flags(adev, ttm, bo_mem);
// 5. 绑定到 GART
gtt->offset = (u64)bo_mem->start << PAGE_SHIFT;
r = amdgpu_gart_bind(adev, gtt->offset, ttm->num_pages,
ttm->pages, gtt->ttm.dma_address, flags);
if (r)
DRM_ERROR("failed to bind %lu pages at 0x%08llX\n",
ttm->num_pages, gtt->offset);
return r;
}
4.2 GART 地址空间
GART (Graphics Address Remapping Table) 是 GPU 的页表机制:
物理内存: [Page0][Page1][Page2]...
↓ GART Mapping
GPU 地址: 0x100000 → Page0
0x101000 → Page1
0x102000 → Page2
关键函数:
int amdgpu_gart_bind(struct amdgpu_device *adev, uint64_t offset,
int pages, struct page **pagelist,
dma_addr_t *dma_addr, uint64_t flags)
{
// 1. 计算 GART 页表项起始位置
uint64_t *pte = adev->gart.ptr + (offset >> PAGE_SHIFT);
// 2. 逐页填充页表
for (i = 0; i < pages; i++) {
pte[i] = amdgpu_gart_get_pte_flags(adev, flags) |
dma_addr[i];
}
// 3. 刷新 TLB
amdgpu_gart_flush_gpu_tlb(adev, 0);
return 0;
}
4.3 PTE 标志计算
uint64_t amdgpu_ttm_tt_pte_flags(struct amdgpu_device *adev,
struct ttm_tt *ttm,
struct ttm_mem_reg *mem)
{
uint64_t flags = 0;
// 可读标志
flags |= AMDGPU_PTE_READABLE;
// 可写标志
if (ttm->page_flags & TTM_PAGE_FLAG_WRITE)
flags |= AMDGPU_PTE_WRITEABLE;
// 缓存策略
switch (ttm->caching_state) {
case tt_cached:
flags |= AMDGPU_PTE_SNOOPED;
break;
case tt_wc:
flags |= AMDGPU_PTE_WC;
break;
case tt_uncached:
// 无额外标志
break;
}
// 系统内存标志
flags |= AMDGPU_PTE_SYSTEM;
return flags;
}
PTE 标志位含义:
AMDGPU_PTE_READABLE: GPU 可读AMDGPU_PTE_WRITEABLE: GPU 可写AMDGPU_PTE_SNOOPED: 缓存一致性(cache coherent)AMDGPU_PTE_WC: Write-Combine 模式AMDGPU_PTE_SYSTEM: 系统内存标记
5. 解绑(Unbind)
static int amdgpu_ttm_backend_unbind(struct ttm_tt *ttm)
{
struct amdgpu_device *adev = amdgpu_ttm_adev(ttm->bdev);
struct amdgpu_ttm_tt *gtt = (void *)ttm;
int r;
// 1. 解除 userptr 固定
if (gtt->userptr)
amdgpu_ttm_tt_unpin_userptr(ttm);
// 2. 检查是否已绑定
if (gtt->offset == AMDGPU_BO_INVALID_OFFSET)
return 0;
// 3. 解除 GART 绑定
r = amdgpu_gart_unbind(adev, gtt->offset, ttm->num_pages);
if (r)
DRM_ERROR("failed to unbind %lu pages at 0x%08llX\n",
gtt->ttm.ttm.num_pages, gtt->offset);
return r;
}
解绑操作:
- 清除 GART 页表项
- 刷新 GPU TLB
- 释放 userptr 页面引用
- 不释放物理页面(由 unpopulate 负责)
6. 页面释放(Unpopulate)
static void amdgpu_ttm_tt_unpopulate(struct ttm_tt *ttm)
{
struct amdgpu_ttm_tt *gtt = (void *)ttm;
struct amdgpu_device *adev;
// 1. Userptr 场景
if (gtt && gtt->userptr) {
amdgpu_ttm_tt_set_user_pages(ttm, NULL);
kfree(ttm->sg);
ttm->page_flags &= ~TTM_PAGE_FLAG_SG;
return;
}
// 2. DMA-BUF 场景
if (ttm->sg && gtt->gobj->import_attach) {
struct dma_buf_attachment *attach = gtt->gobj->import_attach;
dma_buf_unmap_attachment(attach, ttm->sg, DMA_BIDIRECTIONAL);
ttm->sg = NULL;
return;
}
// 3. 普通分配场景
adev = amdgpu_ttm_adev(ttm->bdev);
#ifdef CONFIG_SWIOTLB
if (adev->need_swiotlb && swiotlb_nr_tbl()) {
ttm_dma_unpopulate(>t->ttm, adev->dev);
return;
}
#endif
// 默认路径: 解除映射并释放页面
ttm_unmap_and_unpopulate_pages(adev->dev, >t->ttm);
}
释放策略:
- Userptr: 仅解除引用,不释放(用户空间所有)
- DMA-BUF: 解除映射,由导出者管理生命周期
- 普通分配: 解除 DMA 映射并释放页面
7. 销毁(Destroy)
static void amdgpu_ttm_backend_destroy(struct ttm_tt *ttm)
{
struct amdgpu_ttm_tt *gtt = (void *)ttm;
// 释放用户进程引用
if (gtt->usertask)
put_task_struct(gtt->usertask);
// 释放基础 DMA ttm 资源
ttm_dma_tt_fini(>t->ttm);
// 释放扩展结构
kfree(gtt);
}
清理顺序:
- 释放进程引用(userptr)
- 调用
ttm_dma_tt_fini()释放页面数组 - 释放
amdgpu_ttm_tt结构本身
8. 完整生命周期示例
场景:普通 GTT Buffer 创建到销毁
// 1. 创建 Buffer Object
amdgpu_bo_create(adev, ¶ms, &bo);
└─> ttm_bo_init()
└─> ttm_tt_create()
└─> amdgpu_ttm_tt_create() [state: unpopulated]
// 2. 首次访问触发绑定
amdgpu_bo_pin(bo, AMDGPU_GEM_DOMAIN_GTT);
└─> ttm_bo_validate()
└─> ttm_bo_handle_move_mem()
├─> ttm_tt_populate()
│ └─> amdgpu_ttm_tt_populate() [state: unbound]
│ └─> 分配页面 + DMA 映射
└─> ttm_tt_bind()
└─> amdgpu_ttm_backend_bind() [state: bound]
└─> 更新 GART 页表
// 3. GPU 使用
GPU 通过 GART 地址访问系统内存
// 4. 迁移(可选)
ttm_bo_move_ttm(bo, &new_mem);
├─> ttm_tt_unbind()
│ └─> amdgpu_ttm_backend_unbind() [state: unbound]
└─> ttm_tt_bind() 到新位置 [state: bound]
// 5. 销毁
amdgpu_bo_unref(&bo);
└─> ttm_bo_release()
└─> ttm_tt_destroy()
├─> ttm_tt_unbind() [state: unbound]
├─> ttm_tt_unpopulate() [state: unpopulated]
└─> amdgpu_ttm_backend_destroy() [释放]
9. 小结
AMD 驱动对 ttm_tt 的实现特点列表如下:
9.1 AMD 驱动实现特点总览
| 特性类别 | 实现方式 | 核心函数 | 关键优势 |
|---|---|---|---|
| 多场景支持 | 统一 populate 接口分发 | amdgpu_ttm_tt_populate() | 一套代码支持多种内存源 |
| HMM 集成 | Userptr + HMM Range | amdgpu_ttm_tt_pin_userptr() | 零拷贝访问用户内存 |
| 优化策略 | 延迟分配 + 批量操作 | amdgpu_ttm_backend_bind() | 降低内存占用,提升性能 |
| 生命周期管理 | 状态机 + 引用计数 | amdgpu_ttm_backend_destroy() | 防止资源泄漏 |
9.2 三种内存场景对比
| 场景 | 内存来源 | Populate 行为 | Bind 特殊处理 | Unpopulate 行为 | 典型用途 |
|---|---|---|---|---|---|
| 普通分配 | alloc_page() | 分配页面 + DMA 映射 | 标准 GART 绑定 | 释放页面和映射 | 渲染缓冲、Staging |
| Userptr | 用户空间内存 | 仅分配 sg_table | HMM 固定 + GART 绑定 | 解除引用,不释放 | Compute 零拷贝输入 |
| DMA-BUF | 外部设备 buffer | 映射 attachment 提取 sg | 标准 GART 绑定 | 解除映射,不释放 | 跨设备共享(摄像头/显示) |
9.3 关键操作函数映射
| 生命周期阶段 | TTM 核心接口 | AMD 驱动实现 | 主要工作 |
|---|---|---|---|
| 创建 | ttm_tt_create() | amdgpu_ttm_tt_create() | 分配 amdgpu_ttm_tt 结构 |
| 填充 | ttm_tt_populate() | amdgpu_ttm_tt_populate() | 场景分发:普通/userptr/dma-buf |
| 绑定 | ttm_tt_bind() | amdgpu_ttm_backend_bind() | 更新 GART 页表 + TLB 刷新 |
| 解绑 | ttm_tt_unbind() | amdgpu_ttm_backend_unbind() | 清除 GART 映射 + 释放 userptr 引用 |
| 释放 | ttm_tt_unpopulate() | amdgpu_ttm_tt_unpopulate() | 场景分发:释放/解除映射/解除引用 |
| 销毁 | ttm_tt_destroy() | amdgpu_ttm_backend_destroy() | 释放 task 引用 + 结构体 |
9.4 HMM 集成优势
| 传统方式 (get_user_pages) | HMM 方式 (hmm_range_fault) | 改进效果 |
|---|---|---|
| 需要手动 pin 页面 | 自动跟踪页表变化 | 减少内核干预 |
| 不支持页面迁移 | 透明支持 CPU/GPU 迁移 | 统一内存架构基础 |
| 换出时需要显式处理 | 自动处理换出/换入事件 | 简化驱动逻辑 |
| 不支持透明大页 | 原生支持 THP | 性能提升 10-30% |
| 需要 mmu_notifier 同步 | 内置同步机制 | 降低复杂度 |
9.5 性能优化关键点
| 优化点 | 实现方式 | 性能提升 | 适用场景 |
|---|---|---|---|
| 延迟分配 | unpopulated 状态延迟到首次使用 | 减少 50-70% 初始内存占用 | 大量 BO 创建 |
| 批量 DMA 映射 | 使用 dma_map_sg() 而非逐页映射 | 减少 IOMMU 页表更新次数 | IOMMU 开启时 |
| SG 表支持 | ttm_sg_tt_init() 统一处理 | 零拷贝跨设备共享 | DMA-BUF 场景 |
| SWIOTLB 支持 | 检测并使用专用路径 | 兼容受限 DMA 设备 | 老旧硬件/虚拟化 |
| TLB 批量刷新 | 绑定后统一刷新而非逐页 | 减少 GPU 停顿时间 | 大 BO 绑定 |
9.6 错误处理机制
| 错误场景 | 检测方式 | 处理策略 | 防护措施 |
|---|---|---|---|
| 分配失败 | alloc_page() 返回 NULL | 返回 -ENOMEM 触发 OOM | NO_RETRY 标志快速失败 |
| DMA 映射失败 | dma_map_page() 失败 | 回滚已映射页面 | 事务性操作 |
| GART 空间耗尽 | amdgpu_gart_bind() 失败 | 触发 BO 迁移或驱逐 | 预留紧急空间 |
| Userptr 非法 | hmm_range_fault() 失败 | 返回错误,不建立映射 | 验证地址范围 |
| 进程已退出 | gtt->usertask->mm == NULL | 跳过 userptr 处理 | 检查 mm 有效性 |
9.7 与通用 TTM 的差异
| 方面 | 通用 TTM 实现 | AMD 驱动扩展 | 扩展原因 |
|---|---|---|---|
| 数据结构 | struct ttm_tt | struct amdgpu_ttm_tt | 添加 userptr/HMM 支持 |
| 初始化 | ttm_tt_init() | ttm_sg_tt_init() | 支持 scatter-gather DMA |
| 页面固定 | 不支持 userptr | amdgpu_ttm_tt_pin_userptr() | HMM 集成 |
| GART 管理 | 通用 aperture | AMDGPU 特定页表格式 | 硬件差异 |
| PTE 标志 | 基础标志 | 扩展缓存/一致性标志 | 硬件特性支持 |
9.8 关键设计决策
| 决策点 | 选择方案 | 替代方案 | 选择理由 |
|---|---|---|---|
| 初始化方式 | SG 表初始化 | 基础 DMA 初始化 | 支持 DMA-BUF 共享 |
| Userptr 实现 | HMM 框架 | get_user_pages + notifier | 更好的页面迁移支持 |
| 页面分配 | 延迟分配 | 立即分配 | 减少内存峰值占用 |
| DMA 映射时机 | Populate 时 | Bind 时 | 提前发现映射错误 |
| 状态管理 | 三态状态机 | 布尔标志组合 | 清晰的生命周期 |
下一篇: 《TTM ttm_tt 技术分析系列4:应用场景与最佳实践》将介绍实际使用场景和性能优化技巧。在编写中…
536

被折叠的 条评论
为什么被折叠?



