3. GPU驱动接口概览

在Linux显示子系统中,GPU驱动并不是一个孤立的字符设备,而是嵌入在DRM(Direct Rendering Manager)框架中的一套标准化接口集合。理解GPU驱动接口,核心是抓住四条主线:DRM驱动注册解决“驱动如何被内核发现并挂载”,设备初始化解决“硬件资源如何被枚举和配置”,GEM/TTM内存管理解决“显存与系统内存如何被分配、映射和迁移”,命令提交与调度解决“用户态渲染任务如何安全、有序地进入GPU执行”。这四者共同构成GPU驱动与内核、用户态之间的契约。

一、DRM驱动注册:从模块加载到设备节点

现代GPU驱动通常以platform_driver或PCI驱动形式注册,在probe阶段完成DRM设备实例化。以典型DRM驱动为例,注册流程如下:

static int gpu_probe(struct platform_device *pdev)
{
    struct drm_device *drm;
    int ret;

    drm = drm_dev_alloc(&gpu_driver, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    ret = gpu_drm_init(drm);          /* 初始化GEM、IRQ、模式设置 */
    if (ret)
        goto err_free;

    ret = drm_dev_register(drm, 0);   /* 注册到DRM核心,创建/dev/dri/cardX */
    if (ret)
        goto err_cleanup;

    return 0;
}

其中drm_dev_alloc()分配struct drm_device,drm_dev_register()将设备注册到DRM核心并生成/dev/dri/card0与/dev/dri/renderD128节点。前者用于显示控制,后者用于无显示头的渲染计算。驱动还需实现drm_driver结构体中的open、ioctl、gem_create等回调,用户态通过ioctl进入内核。

二、设备初始化:资源枚举与硬件上电

设备初始化阶段,驱动需要完成寄存器映射、时钟与电源域使能、中断注册、固件加载以及GPU核心复位。典型顺序为:

  1. 解析设备树或PCI BAR,映射MMIO寄存器空间;
  2. 使能时钟、电源域和复位控制器;
  3. 加载GPU固件(如微码),写入指令RAM;
  4. 注册中断处理函数,配置中断掩码;
  5. 初始化命令环、显示控制器和内存管理单元。
初始化顺序错误是常见故障源。例如在电源域未使能时访问寄存器,会导致总线挂死或返回全0xFF,表现为驱动probe失败但日志无明确报错。

三、GEM与TTM:显存管理的两层抽象

GEM(Graphics Execution Manager)是DRM的通用缓冲区对象抽象,提供创建、引用、映射和同步机制;TTM(Translation Table Manager)则负责缓冲区在不同内存域之间的迁移与放置。二者关系可理解为:GEM定义“对象是什么”,TTM决定“对象放在哪里”。

GEM对象通过drm_gem_create_object()创建,用户态获得handle后可通过mmap映射到进程地址空间。当GPU需要访问时,驱动调用TTM将对象放置到VRAM或GTT(系统内存通过GPU页表访问)。TTM的ttm_bo结构记录placement、busy状态和fence,迁移时需等待GPU完成对旧位置的访问。

机制职责典型接口关键数据结构
GEM缓冲区对象生命周期、handle、mmapdrm_gem_create_object、drm_gem_mmapdrm_gem_object
TTM内存域放置、迁移、同步ttm_bo_init、ttm_bo_validatettm_buffer_object
Fence异步任务完成通知dma_fence、drm_syncobjdma_fence

四、命令提交与调度基础

用户态通过ioctl提交命令缓冲区(command buffer),驱动将其转换为GPU可执行的指令流。提交路径通常包括:验证缓冲区驻留、构建命令环条目、写入doorbell寄存器、等待fence完成。调度器负责在多个上下文之间分配GPU时间片,常见策略有轮询、优先级和实时抢占。

/* 简化提交路径 */
ret = gpu_validate_buffers(bo_list);
ret = gpu_ring_emit(ring, cmd);
gpu_ring_advance(ring);
writel(ring->doorbell, ring->mmio + DOORBELL_OFFSET);
dma_fence_wait_timeout(&ring->fence, true, HZ);

调度器核心是drm_gpu_scheduler,它维护多个drm_sched_entity,每个entity对应一个用户上下文。作业(job)按优先级进入run queue,由调度线程选出后推入硬件环。若GPU支持硬件调度,驱动可将多个环绑定到同一调度实体,实现更细粒度的并行。

常见误区:认为命令提交是同步阻塞的。实际上现代驱动大量使用异步fence,用户态提交后立即返回,通过syncobj或fence fd等待完成。若驱动在提交路径中直接忙等,会严重降低吞吐并可能触发看门狗复位。

五、案例:一次渲染提交的完整链路

以OpenGL应用绘制一帧为例:用户态创建GEM缓冲区并写入顶点数据,通过ioctl提交命令缓冲区;内核驱动验证缓冲区驻留,将命令写入GPU环,触发doorbell;GPU执行后写回fence;用户态通过drmSyncobjWait等待完成,再交换显示缓冲区。整条链路中,GEM负责缓冲区生命周期,TTM负责放置,调度器负责顺序,fence负责同步,四者缺一不可。

GPU驱动接口四层协作关系 用户态应用 ioctl / mmap / syncobj DRM核心 drm_device / drm_driver GPU驱动 probe / init / irq GEM 缓冲区对象 drm_gem_object handle / mmap / refcount TTM 内存迁移 ttm_buffer_object VRAM / GTT / placement Fence 同步 dma_fence / syncobj 异步完成通知 命令提交与调度 drm_gpu_scheduler → drm_sched_entity → job queue → ring buffer → doorbell 验证驻留 → 写入命令环 → 触发doorbell → fence等待 → 完成回调 完成通知 / fence 返回

六、复盘与排障要点

  • 注册失败:检查drm_dev_register()返回值,确认/dev/dri/节点是否生成,dmesg中是否有“Failed to register”或“no memory”提示。
  • 初始化挂死:优先排查时钟、电源域和复位顺序,使用devm_clk_get和devm_regulator_get确保资源就绪。
  • GEM泄漏:通过/sys/kernel/debug/dri/0/gem查看对象计数,确认用户态是否及时关闭handle。
  • TTM迁移失败:检查VRAM容量和placement标志,确认ttm_bo_validate是否因内存不足返回-ENOMEM。
  • 调度超时:观察drm_sched的job超时日志,确认GPU是否卡死或fence未正确触发。