2. DRM框架基础:核心数据结构与驱动模型

DRM(Direct Rendering Manager)是Linux内核中负责图形显示与GPU管理的核心子系统。理解DRM框架,首先需要掌握其两大基石:核心数据结构(描述设备与用户会话)和驱动模型(组件注册与生命周期管理)。本章将深入剖析drm_device与drm_file的结构设计,并梳理驱动注册的完整流程。

核心要点: DRM通过分层抽象将GPU硬件差异隔离在驱动层,上层用户态通过drm_file会话访问drm_device管理的显示与渲染资源。

2.1 drm_device:设备级核心结构

drm_device是DRM框架中代表一个显示控制器或GPU设备的顶层结构体。每个物理或虚拟的DRM设备在内核中对应一个drm_device实例,它聚合了所有子组件(CRTC、Encoder、Connector、Plane、FB等)的指针、锁、内存管理对象以及驱动私有数据。

字段 类型 说明
dev struct device * 指向内核通用设备结构,用于电源管理、DMA API等
driver struct drm_driver * 指向驱动实现的操作函数表(file_operations、ioctl等)
mode_config struct drm_mode_config 显示模式配置管理,包含CRTC/Encoder/Connector/Plane链表
primary struct drm_minor * 主设备号对应的minor,用于用户态打开/dev/dri/card0
render struct drm_minor * 渲染设备minor,用于GPU计算上下文(/dev/dri/renderD128)
filelist_mutex struct mutex 保护已打开文件句柄链表的互斥锁
filelist struct list_head 当前打开此设备的所有drm_file实例链表
vblank struct drm_vblank_crtc * Vblank事件管理数组,每个CRTC一个
event_lock spinlock_t 保护事件队列的自旋锁
设计意图: drm_device采用“组合模式”,将显示管线中的各个硬件抽象对象(CRTC、Encoder等)作为子组件挂载到mode_config下。驱动初始化时只需注册这些子组件,DRM核心自动完成拓扑关系构建。

2.2 drm_file:用户会话上下文

每当用户态进程通过open("/dev/dri/card0")打开DRM设备时,内核会创建一个drm_file实例。它代表一个用户会话,隔离不同进程的GPU资源访问权限,并记录该会话的认证状态、已分配的对象句柄、事件订阅等信息。

字段 类型 说明
authenticated bool 是否通过DRM认证(Master权限)
master struct drm_master * 指向Master权限对象,控制模式设置权限
pid struct pid * 打开设备的进程PID
object_idr struct idr 句柄到GEM对象的映射表(用户态可见的整数句柄)
table_lock struct mutex 保护object_idr和事件队列的锁
event_list struct list_head 待传递给用户态的事件链表(Vblank、Page Flip等)
prime struct drm_prime_file_private dma-buf导入/导出句柄管理
权限隔离: 只有拥有Master权限的drm_file才能执行模式设置(如DRM_IOCTL_MODE_SETCRTC)。普通渲染进程只能通过Render节点(/dev/dri/renderD128)提交GPU命令,无法修改显示配置。

2.3 驱动模型与组件注册流程

DRM驱动采用组件化注册模型。驱动初始化时,需要依次完成:设备探测 → 分配drm_device → 注册子组件(CRTC/Encoder/Connector/Plane) → 调用drm_dev_register()暴露给用户态。下图展示了完整的注册流程与数据结构关系:

DRM驱动注册流程与核心数据结构关系图 DRM驱动注册流程与核心数据结构关系 1. 驱动探测 2. 分配 drm_device 3. 注册子组件 子组件注册项 • drm_crtc_init() → CRTC • drm_encoder_init() → Encoder • drm_connector_init() → Connector • drm_universal_plane_init() → Plane • drm_simple_display_pipe_init() 4. drm_dev_register() 5. 用户态 open() 核心数据结构关系 drm_device (全局唯一) drm_file (每进程) drm_mode_config

2.4 组件注册关键API与顺序

驱动开发者在bind()回调中必须遵循严格的注册顺序,否则会导致drm_dev_register()失败或用户态无法正确枚举显示设备。以下是标准注册步骤:

  1. 分配drm_device:调用drm_dev_alloc(),传入drm_driver操作表。
  2. 初始化模式配置:调用drm_mode_config_init(),设置min_width、max_height等限制。
  3. 注册CRTC:drm_crtc_init_with_planes(),绑定主平面和光标平面。
  4. 注册Encoder:drm_encoder_init(),指定编码器类型(TMDS、LVDS、DP等)。
  5. 注册Connector:drm_connector_init(),设置检测回调get_modes()。
  6. 注册Plane:drm_universal_plane_init(),声明支持的像素格式和缩放能力。
  7. 建立连接关系:drm_connector_attach_encoder(),将Connector绑定到Encoder。
  8. 注册设备:drm_dev_register(),创建设备节点并暴露给用户态。
// 典型DRM驱动 bind() 回调示例(简化)
static int mygpu_bind(struct device *dev) {
    struct drm_device *drm;
    struct mygpu_private *priv;
    int ret;

    // 1. 分配 drm_device
    drm = drm_dev_alloc(&mygpu_driver, dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    priv = drm->dev_private;
    drm_mode_config_init(drm);
    drm->mode_config.min_width = 640;
    drm->mode_config.max_width = 4096;

    // 2. 注册CRTC
    ret = mygpu_crtc_init(drm);
    if (ret) goto err_free;

    // 3. 注册Encoder
    ret = mygpu_encoder_init(drm);
    if (ret) goto err_free;

    // 4. 注册Connector并绑定Encoder
    ret = mygpu_connector_init(drm);
    if (ret) goto err_free;

    // 5. 注册Plane
    ret = mygpu_plane_init(drm);
    if (ret) goto err_free;

    // 6. 最终注册设备
    ret = drm_dev_register(drm, 0);
    if (ret) goto err_free;

    dev_set_drvdata(dev, drm);
    return 0;

err_free:
    drm_dev_put(drm);
    return ret;
}
性能提示: 注册顺序影响用户态枚举效率。建议先注册CRTC和Plane(渲染资源),再注册Encoder和Connector(显示输出),这样用户态在查询drmModeGetResources()时能更快获取完整拓扑。

2.5 用户态打开流程与drm_file创建

当用户态调用open("/dev/dri/card0")时,内核触发以下流程:

  • VFS层根据设备号找到DRM主设备对应的file_operations(即drm_stub_fops)。
  • drm_open()被调用,分配drm_file实例,初始化object_idr、event_list等字段。
  • 调用驱动提供的driver->open()回调,允许驱动分配私有上下文(如GPU命令队列)。
  • 将drm_file加入drm_device->filelist链表。
  • 返回文件描述符给用户态,后续ioctl通过drm_ioctl()分发到具体处理函数。
// drm_open() 核心逻辑(位于 drm_fops.c)
static int drm_open(struct inode *inode, struct file *filp) {
    struct drm_device *drm = drm_minor_acquire(iminor(inode));
    struct drm_file *priv;
    int ret;

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    priv->pid = get_pid(task_pid(current));
    idr_init(&priv->object_idr);
    mutex_init(&priv->table_lock);
    INIT_LIST_HEAD(&priv->event_list);

    filp->private_data = priv;
    priv->minor = drm->primary;

    if (drm->driver->open) {
        ret = drm->driver->open(drm, priv);
        if (ret) goto err_free;
    }

    mutex_lock(&drm->filelist_mutex);
    list_add(&priv->lhead, &drm->filelist);
    mutex_unlock(&drm->filelist_mutex);

    return 0;
err_free:
    kfree(priv);
    return ret;
}
调试技巧: 通过cat /sys/kernel/debug/dri/0/clients可以查看当前打开DRM设备的所有drm_file会话,包括PID、认证状态和内存使用统计。

2.6 数据结构关系总结

下表总结了drm_device与drm_file在生命周期中的角色差异:

维度 drm_device drm_file
生命周期 设备探测到移除(持久) open()到close()(临时)
数量 每个GPU/显示控制器一个 每个打开设备的进程一个
主要职责 管理硬件资源、模式配置、中断 隔离用户会话、句柄映射、事件传递
锁粒度 全局mode_config.mutex、event_lock 每会话table_lock
用户态可见 通过/dev/dri/card0设备节点 通过文件描述符(fd)

理解这两个核心数据结构及其注册流程,是后续深入学习DRM模式设置(Chapter 3)、GEM内存管理(Chapter 4)和GPU调度(Chapter 5)的基础。驱动开发者必须确保组件注册顺序正确、锁使用得当,才能构建稳定高效的图形显示系统。