AMD KFD的BO设计分析系列8-7:TLB管理与刷新

1. 概述

1.1 TLB在GPUVM中的作用

TLB(Translation Lookaside Buffer)是GPU虚拟内存系统的性能关键,缓存虚拟地址到物理地址的映射关系。当页表更新后,必须刷新TLB以保证地址转换的正确性。

复制代码
虚拟地址转换流程
┌─────────────────────────────────────────────┐
│ GPU访问VA                                    │
│   ↓                                         │
│ TLB查询 ──命中──> 直接获取物理地址              │
│   ↓                                         │
│ 未命中                                       │
│   ↓                                         │
│ 页表walk(多级查)                            │
│   ↓                                         │
│ 更新TLB entry                                │
│   ↓                                         │
│ 访问物理地址                                  │
└─────────────────────────────────────────────┘

1.2 TLB刷新的必要性

问题场景

复制代码
T1: 页表PTE[0x100000] = 物理地址A
T2: TLB缓存: VA 0x100000 → 物理地址A
T3: 更新PTE[0x100000] = 物理地址B
T4: GPU访问0x100000 → TLB仍返回地址A ❌ (错误!)

解决方案:T3后立即刷新TLB,清除过期映射。

1.3 AMDGPU TLB架构

复制代码
GPU TLB层次结构
┌────────────────────────────────────────────────────┐
│ VMHUB 0 (GFX)          VMHUB 1 (Compute/SDMA)      │
│ ┌──────────┐           ┌──────────┐                │
│ │ L1 TLB   │           │ L1 TLB   │                │
│ │ (每个CU)  │           │ (每个CU) │                │
│ └────┬─────┘           └────┬─────┘                │
│      │                      │                      │
│      v                      v                      │
│ ┌──────────┐           ┌──────────┐                │
│ │ L2 TLB   │           │ L2 TLB   │                │
│ │ (共享)    │           │ (共享)   │                │
│ └──────────┘           └──────────┘                │
└────────────────────────────────────────────────────┘

关键特性

  • 多级TLB:L1(小而快,每CU独立)+ L2(大而共享)
  • 多VMHUB:每个hub独立管理TLB
  • VMID关联:TLB entry绑定VMID

2. TLB刷新时机

2.1 四大触发场景

2.1.1 页表更新后
c 复制代码
/* 场景1:BO映射变化 */
amdgpu_vm_bo_update(adev, bo_va, clear)
  → amdgpu_vm_update_range()
    → 写入新PTE
    → if (flush_tlb)
        amdgpu_vm_tlb_flush()  // 标记需要刷新

/* 场景2:BO迁移 */
amdgpu_vm_handle_moved()
  → amdgpu_vm_bo_update()
    → 更新PTE指向新物理地址
    → 标记vm->tlb_seq++

判断逻辑

c 复制代码
if (vm->last_update_op != current_op)
    flush_tlb = true;
2.1.2 VMID切换时
c 复制代码
/* 不同VM使用同一VMID */
amdgpu_vmid_grab()
  → 分配VMID给新VM
  → job->vm_needs_flush = true;  // 强制刷新

原因:VMID重用时,TLB中可能残留旧VM的映射

2.1.3 GPU复位后
c 复制代码
if (amdgpu_vmid_had_gpu_reset(adev, id)) {
    vm_flush_needed = true;
    pasid_mapping_needed = true;
}

原因:GPU复位导致所有硬件状态丢失

2.1.4 显式flush请求
c 复制代码
/* Compute VM强制flush */
amdgpu_vm_flush_compute_tlb(adev, vm, flush_type, xcc_mask)

2.2 刷新触发流程图

复制代码
触发源                    检测机制                    执行时机
┌────────────┐          ┌─────────────┐           ┌────────────┐
│ 页表更新    │ ────>    │ tlb_seq++   │  ────>    │ CS提交时     │
└────────────┘          └─────────────┘           │ flush执行   │
                                                  └────────────┘
┌────────────┐          ┌──────────────┐                │
│ VMID切换    │ ────>   │vm_needs_flush │ ───────────────┘
└────────────┘          └──────────────┘
                              
┌────────────┐          ┌─────────────┐           ┌────────────┐
│ GPU复位     │ ────>    │强制刷新      │  ──── >   │ 立即执行    │
└────────────┘          └─────────────┘           └────────────┘

3. TLB序列号机制

3.1 核心数据结构

位置drivers/gpu/drm/amd/amdgpu/amdgpu_vm.h

c 复制代码
struct amdgpu_vm {
    atomic64_t tlb_seq;              // TLB序列号(递增)
    struct dma_fence *last_tlb_flush; // 上次flush的fence
    atomic64_t kfd_last_flushed_seq; // KFD/Compute专用
};

struct amdgpu_job {
    uint64_t vm_tlb_seq;             // 快照:job创建时的seq
    bool vm_needs_flush;             // 是否需要flush
};

3.2 序列号工作原理

复制代码
时间线:
T0: vm->tlb_seq = 100
     ↓
T1: 更新页表(amdgpu_vm_update_range)
     ↓
T2: atomic64_inc(&vm->tlb_seq)  → seq = 101
     ↓
T3: 创建CS job
    job->vm_tlb_seq = 100(快照旧值)
     ↓
T4: amdgpu_vm_flush()检查
    if (job->vm_tlb_seq != vm->tlb_seq)  // 100 != 101
        job->vm_needs_flush = true;
     ↓
T5: emit TLB invalidate命令
     ↓
T6: fence signal后,job->vm_tlb_seq更新为101

3.3 序列号递增时机

方式1:通过fence callback

c 复制代码
/* 页表更新完成后递增 */
static void amdgpu_vm_tlb_seq_cb(struct dma_fence *fence, ...) {
    struct amdgpu_vm *vm = ...;
    atomic64_inc(&vm->tlb_seq);  // 版本+1
}

/* 绑定callback */
amdgpu_vm_update_range()
  → vm->update_funcs->commit(&params, fence)
    → dma_fence_add_callback(fence, tlb_seq_cb)

方式2:CPU模式立即递增

c 复制代码
amdgpu_vm_cpu_commit() {
    if (p->needs_flush)
        atomic64_inc(&p->vm->tlb_seq);  // 同步更新
    mb();  // 内存屏障
}

3.4 Compute VM的特殊处理

c 复制代码
int amdgpu_vm_flush_compute_tlb(...) {
    uint64_t tlb_seq = amdgpu_vm_tlb_seq(vm);
    
    // 使用xchg原子操作避免重复flush
    if (atomic64_xchg(&vm->kfd_last_flushed_seq, tlb_seq) == tlb_seq)
        return 0;  // 已刷新过,跳过
    
    // 执行PASID flush
    amdgpu_gmc_flush_gpu_tlb_pasid(adev, vm->pasid, ...);
}

优势:多个KFD队列共享VM时,避免重复flush

4. Graphics模式flush:amdgpu_vm_flush()

4.1 函数概览

位置drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c

调用时机:CS提交时,在IB执行前

c 复制代码
int amdgpu_vm_flush(struct amdgpu_ring *ring,
                    struct amdgpu_job *job,
                    bool need_pipe_sync)
{
    bool vm_flush_needed = job->vm_needs_flush;
    bool pasid_mapping_needed = false;
    
    // 1. 检查GPU reset
    if (amdgpu_vmid_had_gpu_reset(adev, id)) {
        vm_flush_needed = true;
        pasid_mapping_needed = true;
    }
    
    // 2. 检查PASID映射
    if (id->pasid != job->pasid || !id->pasid_mapping)
        pasid_mapping_needed = true;
    
    // 3. 快速退出
    if (!vm_flush_needed && !need_pipe_sync)
        return 0;
    
    // 4. 构造命令
    amdgpu_ring_ib_begin(ring);
    
    if (need_pipe_sync)
        amdgpu_ring_emit_pipeline_sync(ring);
    
    if (vm_flush_needed) {
        trace_amdgpu_vm_flush(ring, job->vmid, job->vm_pd_addr);
        amdgpu_ring_emit_vm_flush(ring, job->vmid, job->vm_pd_addr);
    }
    
    if (pasid_mapping_needed)
        amdgpu_gmc_emit_pasid_mapping(ring, job->vmid, job->pasid);
    
    // 5. 发出fence
    amdgpu_fence_emit(ring, &fence, NULL, 0);
    
    // 6. 更新VMID状态
    id->last_flush = dma_fence_get(fence);
    id->pasid_mapping = dma_fence_get(fence);
    
    amdgpu_ring_ib_end(ring);
}

4.2 关键步骤详解

4.2.1 条件判断
c 复制代码
/* 需要flush的情况 */
bool vm_flush_needed = 
    job->vm_needs_flush &&           // tlb_seq变化
    ring->funcs->emit_vm_flush &&    // 硬件支持
    job->vm_pd_addr != INVALID;      // 有效PD地址

/* 需要PASID映射的情况 */
bool pasid_mapping_needed = 
    (id->pasid != job->pasid) ||     // PASID变化
    !id->pasid_mapping ||            // 首次映射
    !dma_fence_is_signaled(...);     // 上次映射未完成
4.2.2 Pipeline Sync
c 复制代码
if (need_pipe_sync)
    amdgpu_ring_emit_pipeline_sync(ring);

作用:等待所有之前的命令执行完成,确保TLB flush在正确时机生效

4.2.3 TLB Invalidate命令
c 复制代码
amdgpu_ring_emit_vm_flush(ring, job->vmid, job->vm_pd_addr);

硬件实现(例如GFX9):

c 复制代码
/* PM4命令包 */
PACKET3(PACKET3_INVALIDATE_TLBS, 0)
    VMID(job->vmid)              // 指定VMID
    FLUSH_TYPE(0)                // 0=全部, 2=PDE/PTE
4.2.4 PASID映射更新
c 复制代码
amdgpu_gmc_emit_pasid_mapping(ring, job->vmid, job->pasid);

作用:更新VMID→PASID的硬件映射表,用于ATS/IOMMU

4.3 Fence同步机制

c 复制代码
/* 发出fence */
amdgpu_fence_emit(ring, &fence, NULL, 0);

/* 更新VMID状态 */
mutex_lock(&id_mgr->lock);
dma_fence_put(id->last_flush);
id->last_flush = dma_fence_get(fence);  // 记录最新flush fence
mutex_unlock(&id_mgr->lock);

用途

  • 其他job可以等待此fence,确保TLB已刷新
  • 避免并发访问过期TLB entry

5. Compute模式flush:amdgpu_vm_flush_compute_tlb()

5.1 与Graphics模式的区别

特性 Graphics模式 Compute模式
触发方式 CS提交时隐式 KFD显式调用
使用标识 VMID PASID
flush范围 单个VMHUB 可选all_hub
去重机制 kfd_last_flushed_seq

5.2 核心实现

位置drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c

c 复制代码
int amdgpu_vm_flush_compute_tlb(struct amdgpu_device *adev,
                                struct amdgpu_vm *vm,
                                uint32_t flush_type,
                                uint32_t xcc_mask)
{
    uint64_t tlb_seq = amdgpu_vm_tlb_seq(vm);
    
    // 1. 去重检查
    if (atomic64_xchg(&vm->kfd_last_flushed_seq, tlb_seq) == tlb_seq)
        return 0;  // 已flush过此版本
    
    // 2. 特定架构flush所有hub
    bool all_hub = false;
    if (adev->family == AMDGPU_FAMILY_AI ||
        adev->family == AMDGPU_FAMILY_RV)
        all_hub = true;
    
    // 3. 遍历XCC执行flush
    for_each_inst(xcc, xcc_mask) {
        r = amdgpu_gmc_flush_gpu_tlb_pasid(adev, vm->pasid,
                                           flush_type, all_hub, xcc);
        if (r) break;
    }
}

5.3 PASID flush实现

位置drivers/gpu/drm/amd/amdgpu/amdgpu_gmc.c

c 复制代码
int amdgpu_gmc_flush_gpu_tlb_pasid(adev, pasid, flush_type, all_hub, inst)
{
    struct amdgpu_ring *ring = &adev->gfx.kiq[inst].ring;
    
    // 方式1:使用KIQ(内核接口队列)
    if (adev->gmc.flush_pasid_uses_kiq && ring->sched.ready) {
        // 构造KIQ命令包
        amdgpu_ring_alloc(ring, ndw);
        kiq->pmf->kiq_invalidate_tlbs(ring, pasid, flush_type, all_hub);
        amdgpu_ring_commit(ring);
        
        // 等待完成
        r = amdgpu_fence_wait_polling(ring, seq, timeout);
    }
    // 方式2:直接寄存器访问
    else {
        adev->gmc.gmc_funcs->flush_gpu_tlb_pasid(adev, pasid,
                                                 flush_type, all_hub, inst);
    }
}

KIQ优势

  • 异步执行,不阻塞CPU
  • 多XCC并行flush

6. 硬件flush实现

6.1 VMID flush(Graphics)

位置drivers/gpu/drm/amd/amdgpu/gmc_v9_0.c(示例)

c 复制代码
static void gmc_v9_0_flush_gpu_tlb(adev, vmid, vmhub, flush_type) {
    // 1. 选择寄存器地址
    uint32_t reg = hub->vm_inv_eng0_req + hub->eng_distance * eng;
    
    // 2. 写入flush请求
    WREG32_NO_KIQ(reg,
        INVALIDATE_L2_PTES(1) |
        INVALIDATE_L2_PDE0(1) |
        INVALIDATE_L1_PTES(1) |
        CLEAR_PROTECTION_FAULT_STATUS_ADDR(0) |
        VMID(vmid) |
        FLUSH_TYPE(flush_type));
    
    // 3. 轮询ACK位
    for (i = 0; i < timeout; i++) {
        if (RREG32_NO_KIQ(ack_reg) & (1 << vmid))
            break;
        udelay(1);
    }
}

关键参数

  • INVALIDATE_L2_PTES:清除L2 TLB中的PTE
  • INVALIDATE_L2_PDE0:清除L2 TLB中的PDE
  • INVALIDATE_L1_PTES:清除L1 TLB
  • FLUSH_TYPE:0=全部, 2=仅PDE/PTE

6.2 PASID flush(Compute)

c 复制代码
static void gmc_v9_0_flush_gpu_tlb_pasid(adev, pasid, flush_type, all_hub, inst) {
    uint32_t seq;
    uint16_t queried_pasid;
    
    // 1. 写入PASID和flush类型
    WREG32_SOC15(GC, 0, mmVM_INVALIDATE_ENG17_REQ,
        PASID(pasid) |
        FLUSH_TYPE(flush_type) |
        INVALIDATE_L2_PTES(1) |
        INVALIDATE_L2_PDE0(1) |
        INVALIDATE_L1_PTES(1));
    
    // 2. 读取ACK
    for (i = 0; i < timeout; i++) {
        seq = RREG32_SOC15(GC, 0, mmVM_INVALIDATE_ENG17_ACK);
        if (seq & 0x80000000)  // 检查busy位
            break;
        udelay(1);
    }
}

6.3 Flush类型说明

c 复制代码
/* flush_type定义 */
#define AMDGPU_TLB_FLUSH_LEGACY  0  // 全部flush(包括L1/L2)
#define AMDGPU_TLB_FLUSH_LIGHT   2  // 仅PDE/PTE(不清L1)

/* 使用场景 */
- Legacy: VMID切换、GPU reset
- Light: 页表更新(频繁场景)

7. 延迟flush优化

7.1 批量flush策略

复制代码
传统方式(每次更新立即flush):
┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐
│ Map │──>│Flush│   │ Map │──>│Flush│   ... (N次flush)
└─────┘   └─────┘   └─────┘   └─────┘

优化方式(批量flush):
┌─────┐   ┌─────┐   ┌─────┐   ┌───────┐
│ Map │   │ Map │   │ Map │──>│ Flush │   (1次flush)
└─────┘   └─────┘   └─────┘   └───────┘
       tlb_seq++                  CS时执行

7.2 序列号机制优势

c 复制代码
/* 多次更新只flush一次 */
T1: amdgpu_vm_bo_update() → tlb_seq: 100 → 101
T2: amdgpu_vm_bo_update() → tlb_seq: 101 → 102
T3: amdgpu_vm_bo_update() → tlb_seq: 102 → 103

T4: CS提交
    job->vm_tlb_seq = 100(创建时快照)
    current tlb_seq = 103
    → 只需flush一次,覆盖所有更新

7.3 Selective flush

c 复制代码
/* 仅flush变化的VMID */
if (job->vm_pd_addr != id->last_pd_addr)
    vm_flush_needed = true;  // PD变化才flush
else
    vm_flush_needed = false; // PD未变,TLB仍有效

8. 典型场景分析

8.1 场景1:普通BO映射

复制代码
用户态:ioctl(MAP_BO)
  ↓
内核态:amdgpu_vm_bo_map()
  ↓
创建mapping,加入invalids
  ↓
CS提交:amdgpu_cs_vm_handling()
  ↓
amdgpu_vm_handle_moved()
  └─ amdgpu_vm_bo_update()
      └─ amdgpu_vm_update_range()
          ├─ 写入PTE
          └─ tlb_seq: 100 → 101
  ↓
amdgpu_vm_flush()
  ├─ 检测: job->vm_tlb_seq(100) != vm->tlb_seq(101)
  ├─ emit_vm_flush(vmid, pd_addr)
  └─ id->last_flush = fence
  ↓
GPU执行IB前flush TLB

8.2 场景2:BO驱逐恢复

复制代码
BO被驱逐(VRAM→GTT)
  ↓
amdgpu_vm_bo_evicted() → 加入evicted链表
  ↓
下次CS提交:
  ├─ amdgpu_vm_validate()
  │   └─ 恢复BO,转moved状态
  ├─ amdgpu_vm_handle_moved()
  │   └─ 更新PTE指向GTT地址
  │       └─ tlb_seq++
  └─ amdgpu_vm_flush()
      └─ 清除旧VRAM地址的TLB entry
  ↓
GPU访问新GTT地址

8.3 场景3:Compute队列提交

复制代码
KFD用户队列提交
  ↓
amdgpu_amdkfd_gpuvm_restore_process_bos()
  ↓
检查VM状态
  ↓
if (页表已更新)
    amdgpu_vm_flush_compute_tlb(vm, LEGACY, xcc_mask)
      ├─ 检查kfd_last_flushed_seq
      ├─ amdgpu_gmc_flush_gpu_tlb_pasid(pasid)
      │   └─ KIQ发送invalidate命令
      └─ 更新kfd_last_flushed_seq
  ↓
队列开始执行,TLB已刷新

9. 性能优化技巧

9.1 减少flush频率

c 复制代码
/* 技巧1:延迟更新 */
多次map操作 → 一次flush

/* 技巧2:合并更新 */
amdgpu_vm_update_range(start=0, last=1000)  // 一次更新1000页
  → 一次flush覆盖所有

/* 技巧3:避免不必要的flush */
if (job->vm_pd_addr == id->last_pd_addr &&
    job->vm_tlb_seq == vm->tlb_seq)
    skip_flush();  // PD和TLB版本均未变

9.2 使用Light flush

c 复制代码
/* 频繁场景使用轻量flush */
if (adev->gmc.flush_tlb_needs_extra_type_2)
    flush_type = AMDGPU_TLB_FLUSH_LIGHT;  // type=2
else
    flush_type = AMDGPU_TLB_FLUSH_LEGACY; // type=0

// Light flush: 仅清PDE/PTE,保留L1 TLB
// 适用于只修改部分映射的场景

9.3 异步flush

c 复制代码
/* KIQ异步flush */
amdgpu_gmc_flush_gpu_tlb_pasid()
  → kiq_invalidate_tlbs()  // 不阻塞CPU
    → 提交命令到KIQ ring
      → 轮询完成(polling模式)

// 优势:CPU可继续其他工作
// 劣势:需要轮询或fence等待

10. 调试与诊断

10.1 Tracepoint

bash 复制代码
# 启用TLB flush tracing
echo 1 > /sys/kernel/debug/tracing/events/amdgpu/amdgpu_vm_flush/enable

# 查看输出
cat /sys/kernel/debug/tracing/trace

# 示例输出:
amdgpu_vm_flush: ring=gfx vmid=3 pd_addr=0x12345000

10.2 常见问题

问题1:TLB flush未生效

  • 检查vm->tlb_seq是否递增
  • 确认job->vm_needs_flush是否为true
  • 验证硬件ACK位是否返回

问题2:过度flush导致性能下降

  • 检查是否频繁触发VMID切换
  • 确认延迟flush机制是否生效
  • 考虑增大VMID池大小

问题3:Compute flush失败

  • 检查KIQ ring是否ready
  • 确认PASID是否正确映射
  • 验证kfd_last_flushed_seq更新

10.3 性能分析

c 复制代码
/* 统计flush次数和耗时 */
ktime_t start = ktime_get();
amdgpu_vm_flush(ring, job, false);
ktime_t end = ktime_get();

pr_info("TLB flush took %lld us, seq=%llu\n",
        ktime_us_delta(end, start),
        atomic64_read(&vm->tlb_seq));

11. 总结

11.1 核心要点

  1. TLB架构

    • 多级TLB(L1/L2)
    • 多VMHUB独立管理
    • VMID/PASID双重标识
  2. 刷新时机

    • 页表更新后(tlb_seq机制)
    • VMID切换时
    • GPU复位后
    • Compute显式请求
  3. 两种模式

    • Graphics:VMID flush,CS时隐式
    • Compute:PASID flush,KFD显式
  4. 性能优化

    • 延迟flush(批量处理)
    • Selective flush(仅变化部分)
    • Light flush(type=2)
    • 异步执行(KIQ)

11.2 关键数据流

复制代码
页表更新 → tlb_seq++ → CS创建job(快照seq) → 
  vm_flush检测差异 → emit invalidate命令 → 
    硬件清除TLB → fence signal → 更新last_flush
相关推荐
DeeplyMind21 天前
AMD KFD的BO设计分析系列7-2:GPU GART 实现深度解析--绑定机制与性能优化
驱动开发·amdgpu·kfd·gart
JiMoKuangXiangQu24 天前
Linux 内存管理:TLB ASID
linux·内存管理·tlb·asid
赖small强1 个月前
【Linux 内存管理】Linux系统中CPU访问内存的完整机制深度解析
linux·缓存·tlb·内存访问·page table
赖small强1 个月前
【Linux 内存管理】Linux系统中CPU内存访问机制与性能优化(32位/64位系统)
linux·内存对齐·tlb·对齐访问·aligned access
DeeplyMind1 个月前
AMD rocr-libhsakmt分析系列3-1: Apertures
linux·amdgpu·rocm·kfd·rocr
赖small强1 个月前
【Linux内存管理】Linux虚拟内存系统详解
linux·虚拟内存·tlb
DeeplyMind1 个月前
AMD rocr-libhsakmt分析系列6-2:共享机制-import
linux·amdgpu·dma-buf·rocm·kfd·rocr
DeeplyMind2 个月前
linux drm子系统专栏介绍
linux·驱动开发·ai·drm·amdgpu·kfd
DeeplyMind3 个月前
AMD rocr-libhsakmt分析系列3-4:svm-reserve模式实现分析
linux·驱动开发·1024程序员节·amdgpu·kfd·rocr