4. KMS 驱动核心数据结构:初始化与注册

在 DRM/KMS 驱动中,三个核心数据结构构成了驱动与内核框架之间的桥梁:struct drm_driver(驱动描述符)、struct drm_device(设备实例)和 struct drm_mode_config_funcs(模式配置回调)。理解它们的初始化顺序与注册时机,是从 FBDEV 平滑迁移到 KMS 的关键一步。

迁移要点: FBDEV 驱动通常将硬件初始化、帧缓冲分配、文件操作接口混杂在同一个 probe 函数中。KMS 驱动则严格遵循“设备实例创建 → 模式配置初始化 → 驱动注册”的三段式流程,每个阶段对应不同的数据结构。

4.1 struct drm_driver:驱动描述符的填充

struct drm_driver 是 DRM 框架识别驱动的“身份证”,它定义了驱动名称、支持的 GPU 系列、文件操作接口、以及各种回调函数。在 KMS 驱动中,该结构体通常以 static const 形式定义,并在模块入口处注册。

字段 类型 说明 KMS 驱动典型值
driver_features u32 驱动能力标志位 DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC
name const char * 驱动名称,用于 sysfs 和调试 "mykms"
desc const char * 驱动描述 "My KMS Driver"
date const char * 驱动日期 "20250301"
major/minor/patchlevel int 驱动版本号 1, 0, 0
fops const struct file_operations * 用户空间文件操作接口 &mykms_fops
gem_create_object 回调 GEM 对象创建函数 mykms_gem_create_object
prime_handle_to_fd/prime_fd_to_handle 回调 DMA-BUF 导入导出 drm_gem_prime_* 通用实现
关键标志位说明:
  • DRIVER_MODESET:必须设置,表示驱动使用 KMS 而非 FBDEV。
  • DRIVER_GEM:必须设置,启用 GEM 内存管理。
  • DRIVER_ATOMIC:推荐设置,启用原子模式设置(Atomic Modeset)。
  • DRIVER_RENDER:可选,若支持 GPU 渲染则设置。

以下是一个典型的 drm_driver 定义示例:

static const struct file_operations mykms_fops = {
    .owner          = THIS_MODULE,
    .open           = drm_open,
    .release        = drm_release,
    .unlocked_ioctl = drm_ioctl,
    .mmap           = drm_gem_mmap,
    .poll           = drm_poll,
    .read           = drm_read,
};

static const struct drm_driver mykms_driver = {
    .driver_features    = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
    .name               = "mykms",
    .desc               = "My KMS Driver",
    .date               = "20250301",
    .major              = 1,
    .minor              = 0,
    .patchlevel         = 0,
    .fops               = &mykms_fops,
    .gem_create_object  = mykms_gem_create_object,
    .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
    .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
    .gem_prime_import   = drm_gem_prime_import,
    .gem_prime_export   = drm_gem_prime_export,
    .dumb_create        = mykms_dumb_create,
    .dumb_map_offset    = mykms_dumb_map_offset,
    .dumb_destroy       = drm_gem_dumb_destroy,
};

4.2 struct drm_device:设备实例的创建与初始化

struct drm_device 是 DRM 框架中代表一个显示设备的实例。它通过 drm_dev_alloc() 或 drm_dev_init() 创建,并在 probe 函数中完成初始化。KMS 驱动通常使用 devm_drm_dev_alloc() 进行资源管理,简化错误处理。

初始化流程包含以下关键步骤:

  1. 分配设备实例:调用 devm_drm_dev_alloc(),传入父设备、驱动描述符和私有数据结构体大小。
  2. 设置 DRM 设备私有数据:通过 container_of 或直接访问返回的指针,初始化硬件相关字段。
  3. 模式配置初始化:调用 drm_mode_config_init(),并填充 drm_mode_config_funcs。
  4. 注册 DRM 设备:调用 drm_dev_register(),使设备对用户空间可见。
struct mykms_private {
    struct drm_device    drm;
    void __iomem        *regs;
    int                  irq;
    struct clk          *clk;
};

static int mykms_probe(struct platform_device *pdev)
{
    struct mykms_private *priv;
    struct drm_device *drm;
    int ret;

    // 1. 分配设备实例,包含私有数据
    priv = devm_drm_dev_alloc(&pdev->dev, &mykms_driver,
                              struct mykms_private, drm);
    if (IS_ERR(priv))
        return PTR_ERR(priv);

    drm = &priv->drm;

    // 2. 初始化硬件资源(寄存器、时钟、中断等)
    priv->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->regs))
        return PTR_ERR(priv->regs);

    // 3. 模式配置初始化
    drm_mode_config_init(drm);
    drm->mode_config.funcs = &mykms_mode_config_funcs;
    drm->mode_config.min_width = 640;
    drm->mode_config.max_width = 1920;
    drm->mode_config.min_height = 480;
    drm->mode_config.max_height = 1080;

    // 4. 注册 DRM 设备
    ret = drm_dev_register(drm, 0);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, drm);
    return 0;
}
常见错误: 在调用 drm_dev_register() 之前,必须确保所有模式配置参数(如 min_width、max_width)和回调函数已经设置完毕。否则,用户空间在设备注册后立即查询能力时可能读到未初始化的值。

4.3 struct drm_mode_config_funcs:模式配置回调的实现

struct drm_mode_config_funcs 是 KMS 驱动的“模式设置引擎”,它定义了帧缓冲创建、原子提交、输出属性验证等核心操作。在支持原子模式设置的驱动中,atomic_check 和 atomic_commit 是最重要的两个回调。

回调函数 触发时机 典型实现
fb_create 用户空间调用 ADDFB2 ioctl 分配 GEM 对象并创建 drm_framebuffer
output_poll_changed 热插拔事件触发 调用 drm_kms_helper_hotplug_event()
atomic_check 原子提交前验证 检查时钟、带宽、扫描线限制
atomic_commit 原子提交执行 编程硬件寄存器,触发 VBLANK
atomic_state_alloc/free 原子状态管理 分配/释放驱动私有状态结构体

以下是一个简化的 drm_mode_config_funcs 实现:

static int mykms_fb_create(struct drm_device *drm,
                           struct drm_framebuffer *fb,
                           struct drm_file *file_priv,
                           const struct drm_mode_fb_cmd2 *mode_cmd)
{
    // 验证像素格式、宽度、高度
    // 分配 GEM 对象作为 backing storage
    // 调用 drm_gem_fb_create() 或自定义实现
    return drm_gem_fb_create(drm, fb, file_priv, mode_cmd);
}

static int mykms_atomic_check(struct drm_device *drm,
                              struct drm_atomic_state *state)
{
    // 遍历所有 CRTC、plane、connector 的状态
    // 检查时钟频率是否在硬件支持范围内
    // 检查扫描线带宽是否超过限制
    // 返回 0 表示验证通过,负值表示拒绝
    return 0;
}

static int mykms_atomic_commit(struct drm_device *drm,
                               struct drm_atomic_state *state,
                               bool nonblock)
{
    // 非阻塞提交:将状态复制到硬件 shadow 寄存器
    // 阻塞提交:等待 VBLANK 后切换寄存器
    // 调用 drm_atomic_helper_commit() 或自定义实现
    return drm_atomic_helper_commit(drm, state, nonblock);
}

static const struct drm_mode_config_funcs mykms_mode_config_funcs = {
    .fb_create          = mykms_fb_create,
    .output_poll_changed = mykms_output_poll_changed,
    .atomic_check       = mykms_atomic_check,
    .atomic_commit      = mykms_atomic_commit,
    .atomic_state_alloc = mykms_atomic_state_alloc,
    .atomic_state_free  = mykms_atomic_state_free,
};

4.4 数据结构关系与初始化流程

下图展示了三个核心数据结构在驱动初始化过程中的关系与调用顺序:

KMS 核心数据结构初始化流程 KMS 核心数据结构初始化流程 module_init() platform_driver_register() mykms_probe() devm_drm_dev_alloc() drm_mode_config_init() 设置 mode_config.funcs drm_dev_register() 数据结构对应关系: • struct drm_driver → 静态定义,在 module_init 时注册 • struct drm_device → 在 probe 中通过 devm_drm_dev_alloc 分配 • struct drm_mode_config_funcs → 在 drm_mode_config_init 后赋值 • 注册顺序:driver → device → mode_config → register ⚠ 常见陷阱: • 在 drm_dev_register 之前必须完成所有 mode_config 设置 • atomic_check 必须验证所有 CRTC/plane/connector 的组合

4.5 注册与注销的对称性

KMS 驱动必须保证初始化与注销的对称性。在 remove 回调中,需要按相反顺序释放资源:

static int mykms_remove(struct platform_device *pdev)
{
    struct drm_device *drm = platform_get_drvdata(pdev);

    drm_dev_unregister(drm);   // 先注销设备
    drm_atomic_helper_shutdown(drm); // 关闭所有 CRTC
    // devm 管理的资源自动释放

    return 0;
}
最佳实践:
  • 优先使用 devm_drm_dev_alloc() 而非 drm_dev_alloc(),前者在设备移除时自动释放。
  • 在 remove 中显式调用 drm_atomic_helper_shutdown(),确保所有显示管道被正确关闭。
  • 使用 drm_dev_unregister() 而非 drm_dev_put(),前者会同步移除 sysfs 节点并等待所有打开的文件句柄关闭。

4.6 从 FBDEV 迁移的注意事项

FBDEV 做法 KMS 做法 迁移要点
register_framebuffer() drm_dev_register() KMS 不再直接注册 fb_info,而是通过 DRM 框架管理
fb_ops->fb_setcoloreg drm_mode_config_funcs->atomic_commit 颜色查找表通过 CRTC 的 gamma_lut 属性设置
fb_ops->fb_pan_display drm_plane_state->src_x/src_y 平移操作通过 plane 的源坐标属性实现
struct fb_var_screeninfo struct drm_display_mode 显示模式通过 connector 的 modes 列表管理
直接操作寄存器设置模式 drm_crtc_helper_funcs->mode_set 硬件编程逻辑封装在 CRTC 回调中

通过以上三个核心数据结构的正确初始化与注册,KMS 驱动能够与 DRM 框架无缝协作,为用户空间提供统一的显示控制接口。下一阶段将深入每个子对象的实现细节,包括 CRTC、Encoder、Connector 和 Plane 的创建与配置。