4. DRM驱动初始化:platform driver注册、DRM device创建、mode_config初始化

好,咱们今天聊聊DRM驱动初始化的核心流程。说白了,就是让显示子系统在Linux内核里“活过来”的过程。我做了这么多年显示驱动,每次拿到一个新的SoC平台,第一步要搞定的就是这个初始化流程。

你想想看,一个显示设备要正常工作,得先让内核知道它的存在吧?得给它分配资源吧?得告诉它支持什么显示模式吧?嗯,这就是我们今天要讲的三板斧:platform driver注册DRM device创建mode_config初始化

核心要点:DRM驱动的初始化,本质上是一个“注册-创建-配置”的三步走过程。每一步都有坑,我当年踩过的坑可不少。

4.1 platform driver注册:让内核找到你的设备

先说说platform driver。在嵌入式Linux里,显示控制器通常挂在系统总线上,用platform驱动模型来管理。我个人习惯先把driver定义好,再考虑怎么注册。

来看一个典型的platform driver结构:

static const struct of_device_id mipi_dsi_of_match[] = {
    { .compatible = "vendor,display-controller", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mipi_dsi_of_match);

static struct platform_driver display_driver = {
    .probe  = display_probe,
    .remove = display_remove,
    .driver = {
        .name = "my_display",
        .of_match_table = mipi_dsi_of_match,
        .pm = &display_pm_ops,
    },
};

module_platform_driver(display_driver);

这里有个细节要注意:of_match_table里的compatible字符串,必须和设备树里写的完全一致。我曾经因为少写了一个逗号,折腾了一整天——驱动死活不probe,最后发现是设备树里写的是"vendor,display-controller",而驱动里写的是"vendor,displaycontroller"。嗯,这种低级错误,犯过一次就再也不会忘了。

小技巧:注册platform driver时,建议用module_platform_driver()宏,它帮你处理了module_init和module_exit。省事,也不容易出错。

4.2 DRM device创建:从platform device到DRM世界

platform driver注册成功后,内核会调用probe函数。这时候,我们要在probe里创建DRM device。说白了,就是把一个普通的platform device,包装成一个DRM设备,让它进入DRM的框架里。

我一般这样写probe函数:

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

    // 分配DRM device结构体
    drm = drm_dev_alloc(&display_driver_funcs, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    // 设置driver data,方便后续使用
    platform_set_drvdata(pdev, drm);

    // 注册DRM device
    ret = drm_dev_register(drm, 0);
    if (ret)
        goto err_free;

    return 0;

err_free:
    drm_dev_put(drm);
    return ret;
}

这里有两个关键函数:drm_dev_alloc()drm_dev_register()。前者是分配内存并初始化,后者是让DRM核心知道这个设备的存在。顺序不能乱,先分配再注册。

注意:drm_dev_alloc()分配的内存,要用drm_dev_put()释放。别搞混了,我见过有人用kfree去释放,结果内核直接panic。DRM框架有自己的内存管理方式,别自作聪明。

4.3 mode_config初始化:告诉DRM你的显示能力

DRM device创建好了,接下来要初始化mode_config。这一步是告诉DRM核心:我这个显示设备支持什么分辨率?支持什么显示接口?最大带宽是多少?

mode_config初始化通常在bind函数里做,或者放在probe的后面部分。我个人习惯单独写一个初始化函数:

static int display_mode_config_init(struct drm_device *drm)
{
    // 设置最小和最大分辨率
    drm->mode_config.min_width = 320;
    drm->mode_config.min_height = 240;
    drm->mode_config.max_width = 3840;
    drm->mode_config.max_height = 2160;

    // 设置显示接口类型
    drm->mode_config.output_poll_changed = NULL;
    drm->mode_config.funcs = &display_mode_config_funcs;

    // 设置其他参数
    drm->mode_config.preferred_depth = 32;
    drm->mode_config.prefer_shadow = 0;

    // 别忘了调用这个
    drm_mode_config_init(drm);

    return 0;
}

这里有个容易忽略的点:drm_mode_config_init()必须在设置完参数之后调用。为什么?因为这个函数会初始化一些内部链表和锁,如果参数还没设置好,后面可能会出问题。

我曾经在一个项目里,把drm_mode_config_init()放在了最前面,结果后面设置max_width的时候,发现内部校验通不过——因为初始化时默认值太小了。嗯,这种顺序问题,调试起来特别隐蔽。

4.4 完整的初始化流程

把上面三步串起来,完整的初始化流程是这样的:

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

    // 第一步:分配DRM device
    drm = drm_dev_alloc(&display_driver_funcs, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    platform_set_drvdata(pdev, drm);

    // 第二步:初始化mode_config
    ret = display_mode_config_init(drm);
    if (ret)
        goto err_free;

    // 第三步:注册DRM device
    ret = drm_dev_register(drm, 0);
    if (ret)
        goto err_config;

    return 0;

err_config:
    drm_mode_config_cleanup(drm);
err_free:
    drm_dev_put(drm);
    return ret;
}

你看,逻辑很清晰:先有设备,再配置能力,最后注册上线。这个顺序是DRM框架强制要求的,别想着偷懒。

4.5 常见问题与避坑指南

  • probe不调用? 检查设备树compatible字符串是否匹配,检查驱动是否编译进内核。
  • drm_dev_register失败? 看看是不是mode_config没初始化,或者min/max设置不合理。
  • 内存泄漏? 检查错误路径是否调用了drm_dev_put()和drm_mode_config_cleanup()。

避坑指南:我曾经在一个项目里,probe成功了,但drm_dev_register一直返回-ENOMEM。查了两天,最后发现是drm_dev_alloc时传的driver_funcs里,有个回调函数没实现。DRM框架在register时会调用这些回调,如果没实现就直接报错。所以,driver_funcs里的每个回调都要认真实现,哪怕是个空函数

4.6 知识体系图

下面这张图,把整个初始化流程串起来了。你可以看到,从platform driver注册开始,到DRM device创建,再到mode_config初始化,每一步都有明确的输入和输出。

DRM驱动初始化流程 第一步:platform driver注册 module_platform_driver() 设备树匹配 → probe函数被调用 第二步:DRM device创建 drm_dev_alloc() + drm_dev_register() 分配结构体 → 设置driver data 第三步:mode_config初始化 drm_mode_config_init() 设置分辨率范围、接口类型、深度等 初始化完成 ← 设备树匹配是关键 ← 顺序不能乱 ← 参数设置要合理

这张图里,三个步骤是串行的。你可能会问:为什么不能先注册再配置?嗯,因为DRM框架的设计哲学是:先让设备存在,再告诉它你能做什么。就像一个人,得先出生,再学本事,对吧?

个人经验:调试初始化流程时,我建议在probe函数里多加几个dev_dbg()打印。这样能清楚地看到每一步的执行情况。我曾经靠这个方法,半小时就定位到了一个因为drm_dev_alloc失败导致的黑屏问题。

好了,DRM驱动初始化的核心内容就这些。记住三步走:注册platform driver、创建DRM device、初始化mode_config。每一步都有它的道理,也有它的坑。多写几次,自然就熟练了。


公众号:蓝海资料掘金营,微信deep3321