2. DRM驱动初始化:platform driver注册、drm_device分配与初始化、mode_config设置
DRM驱动的初始化是整个显示子系统工作的起点。对于嵌入式平台(如i.MX、Rockchip、Allwinner等),驱动通常以platform driver的形式注册到内核,随后完成drm_device的分配、初始化以及mode_config的配置。本章将拆解这三个关键步骤,并给出典型代码实现与调试要点。
2.1 platform driver注册
大多数嵌入式GPU/Display Controller都挂载在platform总线上。驱动入口通过module_platform_driver()宏完成注册,核心结构体为struct platform_driver。
| 成员 | 说明 | 典型值/回调 |
|---|---|---|
| .probe | 设备匹配成功后的初始化入口 | xxx_drm_probe |
| .remove | 设备移除时的清理入口 | xxx_drm_remove |
| .driver.name | 驱动名称,用于与DT compatible匹配 | "xxx-display" |
| .driver.of_match_table | 设备树匹配表 | xxx_of_match |
调试要点: 如果
probe未被调用,请检查设备树中compatible属性是否与of_match_table一致,以及内核是否编译了对应的驱动模块。
// 典型 platform driver 注册示例
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "vendor,display-controller", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, xxx_of_match);
static struct platform_driver xxx_drm_platform_driver = {
.probe = xxx_drm_probe,
.remove = xxx_drm_remove,
.driver = {
.name = "xxx-display",
.of_match_table = xxx_of_match,
},
};
module_platform_driver(xxx_drm_platform_driver);
2.2 drm_device分配与初始化
在probe回调中,驱动需要完成drm_device的分配与核心初始化。标准流程分为三步:
- 分配:调用
drm_dev_alloc()或devm_drm_dev_alloc()分配struct drm_device。 - 驱动私有数据设置:通过
container_of或drm_device->dev_private关联硬件上下文。 - 注册:调用
drm_dev_register()将设备注册到DRM子系统。
推荐做法: 使用
devm_drm_dev_alloc()可以自动管理生命周期,避免手动释放遗漏。
// drm_device 分配与初始化典型代码
static int xxx_drm_probe(struct platform_device *pdev)
{
struct drm_device *drm;
struct xxx_drm_private *priv;
int ret;
// 1. 分配 drm_device + 私有数据(推荐 devm 版本)
drm = devm_drm_dev_alloc(&pdev->dev, &xxx_drm_driver,
struct xxx_drm_private, drm);
if (IS_ERR(drm))
return PTR_ERR(drm);
priv = to_xxx_private(drm);
priv->dev = &pdev->dev;
platform_set_drvdata(pdev, drm);
// 2. 初始化硬件相关资源(IRQ、MMIO、时钟等)
ret = xxx_hw_init(priv);
if (ret)
return ret;
// 3. 设置 mode_config 回调与参数
xxx_mode_config_init(drm);
// 4. 注册 DRM 设备
ret = drm_dev_register(drm, 0);
if (ret)
return ret;
drm_fbdev_generic_setup(drm, 32);
return 0;
}
2.3 mode_config设置
mode_config是DRM驱动的显示能力描述结构,必须在注册前完成初始化。关键设置项包括:
| 字段 | 含义 | 典型值 |
|---|---|---|
| min_width / max_width | 帧缓冲最小/最大宽度 | 16 / 4096 |
| min_height / max_height | 帧缓冲最小/最大高度 | 16 / 4096 |
| funcs | mode_config 回调函数集 | &xxx_mode_config_funcs |
| fb_base | 帧缓冲物理基地址(可选) | 0 / 实际地址 |
| preferred_depth | 默认颜色深度 | 32 |
| async_page_flip | 是否支持异步 page flip | true / false |
// mode_config 初始化示例
static void xxx_mode_config_init(struct drm_device *drm)
{
drm->mode_config.min_width = 16;
drm->mode_config.max_width = 4096;
drm->mode_config.min_height = 16;
drm->mode_config.max_height = 4096;
drm->mode_config.funcs = &xxx_mode_config_funcs;
drm->mode_config.preferred_depth = 32;
drm->mode_config.async_page_flip = true;
}
常见错误: 忘记设置
funcs会导致drm_mode_config_init()内部校验失败,驱动加载时出现WARNING或BUG。务必在drm_dev_register()之前完成所有mode_config字段的赋值。
2.4 初始化流程总览
下图展示了从platform driver匹配到mode_config设置完成的完整调用链:
2.5 关键数据结构关系
以下SVG展示了platform_driver、drm_device、mode_config三者之间的引用关系:
2.6 调试检查清单
- probe是否被调用:在
xxx_drm_probe入口添加dev_info()打印,确认设备树匹配成功。 - drm_device分配是否成功:检查
devm_drm_dev_alloc()返回值,避免空指针解引用。 - mode_config完整性:确认
funcs、min/max已赋值,否则内核会抛出WARN_ON。 - drm_dev_register返回值:注册失败通常是因为
mode_config校验不通过或IRQ申请失败。 - 查看注册状态:
ls /sys/class/drm/应出现card0或card1等设备节点。
实战经验: 在调试初期,建议在
xxx_drm_probe中逐段添加dev_dbg(),配合dmesg -w实时观察初始化进度。一旦drm_dev_register成功,用户空间即可通过libdrm的drmOpen()访问设备。