2. DRM 核心概念:框架组成、CRTC/Encoder/Connector/Plane 模型与设备注册流程
DRM (Direct Rendering Manager) 是 Linux 内核中负责显示输出与图形加速的核心框架。理解 DRM 的组成、对象模型以及设备注册流程,是开发 LCD 控制器驱动的基石。本章将深入剖析 DRM 框架的层次结构、四大核心对象(CRTC、Encoder、Connector、Plane)的职责与协作关系,并详细梳理 DRM 设备的注册与初始化流程。
2.1 DRM 框架组成与层次结构
DRM 框架位于内核图形子系统的最底层,向上提供统一的用户空间接口(通过 /dev/dri/cardX 设备节点),向下对接具体的 GPU 或显示控制器硬件。其整体架构可分为三层:
- 核心层 (DRM Core):提供框架基础设施,包括设备管理、文件操作、内存管理(GEM/CMA)、对象模型(KMS)以及 IOCTL 分发。
- 驱动层 (Driver):由具体的硬件厂商或开发者实现,负责填充
struct drm_driver和struct drm_mode_config_funcs等回调函数,并注册 CRTC、Encoder、Connector、Plane 等 KMS 对象。 - 硬件层 (Hardware):实际的显示控制器、GPU、HDMI/DP 收发器、LCD 面板等物理设备。
关键点: DRM 核心层与驱动层通过
drm_device 结构体连接。驱动层只需关注硬件差异,核心层提供通用逻辑,这种分层设计极大地提高了代码复用性和可移植性。
2.2 CRTC / Encoder / Connector / Plane 模型
DRM 的 KMS (Kernel Mode Setting) 子系统采用面向对象的设计,将显示流水线抽象为四个核心对象。它们共同协作,将帧缓冲区(Framebuffer)中的图像数据最终显示在物理显示器上。
| 对象 | 结构体 | 职责描述 | 硬件对应 |
|---|---|---|---|
| CRTC | drm_crtc |
控制显示时序(行/场同步、像素时钟)、扫描驱动、色彩空间转换、Gamma 校正。是显示流水线的“大脑”。 | LCD 控制器核心时序模块、DMA 引擎 |
| Encoder | drm_encoder |
将 CRTC 输出的并行像素数据编码为特定接口标准(如 RGB、LVDS、MIPI DSI、HDMI、DP)的串行信号。 | LVDS 发送器、MIPI DSI 主机控制器、HDMI 编码器 |
| Connector | drm_connector |
代表物理连接器(HDMI 口、VGA 口、eDP 座子)和显示器。负责检测热插拔、读取 EDID、获取显示模式列表。 | HDMI 座子、VGA 座子、eDP 连接器、LCD 面板接口 |
| Plane | drm_plane |
代表一个独立的图像层(Overlay)。每个 Plane 绑定一个 Framebuffer,CRTC 将多个 Plane 合成后输出。主平面(Primary Plane)是必选的。 | 硬件图层(如 OSD 层、视频层)、Alpha 混合器 |
类比理解: 将显示流水线想象成一场电影放映。Plane 是胶片(帧缓冲区),CRTC 是放映机(控制帧率与扫描),Encoder 是信号转换器(将胶片内容转为电信号),Connector 是投影幕布接口(连接屏幕并报告其能力)。
2.3 DRM 设备注册流程
在嵌入式 LCD 控制器驱动中,DRM 设备的注册通常遵循一套标准化的流程。以下是以 struct drm_driver 和 struct drm_device 为核心的注册步骤:
- 分配 drm_device:调用
drm_dev_alloc()或devm_drm_dev_alloc()分配并初始化一个drm_device实例,同时传入驱动私有数据(如寄存器基址、时钟、GPIO 等)。 - 设置驱动回调:填充
struct drm_driver结构体,包括load()、unload()、open()、postclose()、gem_create_object()等回调函数。 - 初始化模式配置:调用
drm_mode_config_init()初始化drm_device->mode_config,并设置min_width、max_width、funcs等字段。 - 创建 KMS 对象:依次创建并初始化 CRTC、Encoder、Connector、Plane 对象,并调用
drm_crtc_init()、drm_encoder_init()、drm_connector_init()、drm_universal_plane_init()将它们注册到 DRM 核心。 - 建立对象连接:通过
drm_connector_attach_encoder()将 Connector 与 Encoder 绑定;通过drm_crtc_init_with_planes()将 CRTC 与 Primary Plane 和 Cursor Plane 关联。 - 注册 DRM 设备:调用
drm_dev_register()将设备注册到系统,此时会在/dev/dri/下创建设备节点(如card0、controlD64)。 - 注册输出接口:对于嵌入式 LCD 控制器,通常还需要调用
drm_fbdev_generic_setup()或drm_fbdev_cma_init()创建帧缓冲设备(fbdev),提供传统的/dev/fb0接口。
常见错误: 在注册 CRTC 之前,必须确保硬件时钟(如 LCD 控制器像素时钟)已经使能并稳定。如果时序生成模块尚未初始化,CRTC 的
mode_set() 回调可能会因访问未就绪的寄存器而崩溃。
2.4 关键数据结构与代码骨架
以下是一个典型的嵌入式 LCD 控制器 DRM 驱动的初始化代码骨架,展示了核心对象的创建与注册过程:
#include <drm/drm_device.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder.h>
#include <drm/drm_connector.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_fbdev_generic.h>
/* 驱动私有数据结构 */
struct lcdc_drm_private {
struct drm_device *drm;
void __iomem *base; /* 寄存器基址 */
struct clk *pclk; /* 像素时钟 */
/* ... 其他硬件资源 */
};
/* CRTC 模式设置回调 */
static void lcdc_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct lcdc_drm_private *priv = crtc->dev->dev_private;
/* 配置 LCD 控制器的时序寄存器:HBP, HFP, VBP, VFP, HSW, VSW, PCLK 分频 */
writel(0, priv->base + LCDC_TIMING_CTRL);
/* ... 具体寄存器配置 */
}
/* 驱动 probe 函数 */
static int lcdc_drm_probe(struct platform_device *pdev)
{
struct lcdc_drm_private *priv;
struct drm_device *drm;
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_plane *primary_plane;
/* 1. 分配 drm_device 并关联私有数据 */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
drm = devm_drm_dev_alloc(&pdev->dev, &lcdc_drm_driver,
struct lcdc_drm_private, drm);
priv->drm = drm;
drm->dev_private = priv;
/* 2. 初始化 mode_config */
drm_mode_config_init(drm);
drm->mode_config.min_width = 320;
drm->mode_config.max_width = 1920;
drm->mode_config.min_height = 240;
drm->mode_config.max_height = 1080;
drm->mode_config.funcs = &lcdc_mode_config_funcs;
/* 3. 创建 Primary Plane */
primary_plane = drm_universal_plane_init(drm, &lcdc_primary_plane,
0, &lcdc_plane_funcs,
lcdc_formats,
ARRAY_SIZE(lcdc_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
/* 4. 创建 CRTC,并关联 Primary Plane */
crtc = drm_crtc_init_with_planes(drm, &lcdc_crtc, primary_plane,
NULL, &lcdc_crtc_funcs, NULL);
drm_crtc_helper_add(crtc, &lcdc_crtc_helper_funcs);
/* 5. 创建 Encoder */
encoder = drm_encoder_init(drm, &lcdc_encoder, &lcdc_encoder_funcs,
DRM_MODE_ENCODER_LVDS, NULL);
/* 6. 创建 Connector */
connector = drm_connector_init(drm, &lcdc_connector,
&lcdc_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
drm_connector_helper_add(connector, &lcdc_connector_helper_funcs);
/* 7. 连接 Encoder 与 Connector */
drm_connector_attach_encoder(connector, encoder);
/* 8. 注册 DRM 设备 */
drm_dev_register(drm, 0);
/* 9. 创建 fbdev 兼容接口 */
drm_fbdev_generic_setup(drm, 32);
return 0;
}
注意事项: 在
drm_dev_register() 调用之前,必须确保所有 KMS 对象(CRTC、Encoder、Connector、Plane)已经完成初始化并添加到 DRM 核心的链表中。否则,用户空间通过 IOCTL 查询显示资源时可能会得到不完整的结果。