4. DRM架构详解
DRM(Direct Rendering Manager)是Linux内核中负责显示与GPU渲染资源管理的子系统。在车载Linux平台上,GPU驱动、显示控制器、HDMI/LVDS输出以及合成器(如Wayland合成器)几乎都通过DRM接口协同工作。理解DRM的架构,核心是理解四件事:设备节点如何暴露给用户空间、ioctl如何成为用户态与内核态的通信桥梁、DRM对象模型如何描述显示资源,以及驱动如何注册并接入DRM框架。
一、DRM设备节点:用户空间的入口
DRM驱动加载成功后,会在/dev/dri/目录下创建字符设备节点。最常见的节点是card0和renderD128。前者是主设备节点(primary node),用于显示控制、模式设置和帧缓冲管理;后者是渲染节点(render node),仅用于GPU计算与渲染任务,不涉及显示权限。车载系统中,合成器通常打开card0进行显示输出,而计算任务或离屏渲染则使用renderD128,从而实现权限隔离。
# 查看DRM设备节点
ls -l /dev/dri/
crw-rw---- 1 root video 226, 0 Jan 1 00:00 card0
crw-rw---- 1 root video 226, 128 Jan 1 00:00 renderD128
# 查看DRM设备信息
cat /sys/class/drm/card0/device/uevent
DRIVER=panfrost
PCI_CLASS=30000
...
每个节点对应一个drm_device实例。用户空间通过open()打开节点后,获得一个文件描述符,后续所有显示与渲染操作都围绕该fd展开。
二、ioctl接口:用户态与内核态的桥梁
DRM的用户空间接口以ioctl为核心。用户程序通过ioctl(fd, DRM_IOCTL_XXX, &arg)向内核发送命令,内核DRM核心层根据命令号分发到对应的驱动回调。常见的ioctl命令包括:
DRM_IOCTL_MODE_GETRESOURCES:获取显示资源,如CRTC、编码器、连接器数量。DRM_IOCTL_MODE_GETCONNECTOR:获取连接器支持的显示模式。DRM_IOCTL_MODE_CREATE_DUMB:创建用于显示的dumb buffer。DRM_IOCTL_MODE_MAP_DUMB:将buffer映射到用户空间。DRM_IOCTL_MODE_ADDFB:将buffer注册为framebuffer对象。DRM_IOCTL_MODE_SETCRTC:设置CRTC扫描输出,完成页面翻转。DRM_IOCTL_GEM_CLOSE:释放GEM对象。
这些ioctl构成了用户空间显示控制的最小闭环:获取资源 → 创建buffer → 映射 → 注册framebuffer → 设置CRTC → 页面翻转。在车载场景中,合成器正是通过这一组接口完成多图层合成与显示刷新。
strace -e ioctl跟踪用户空间调用,快速定位是哪个环节返回了EINVAL或ENOSPC。三、DRM对象模型:显示资源的抽象
DRM将显示管线抽象为五类核心对象,它们之间通过ID和指针相互关联:
| 对象 | 内核结构 | 职责 | 车载典型实例 |
|---|---|---|---|
| Framebuffer | drm_framebuffer | 描述可扫描输出的像素缓冲 | 合成器输出的UI图层 |
| Plane | drm_plane | 图层,负责从FB取像素并混合 | 主图层、光标层、视频层 |
| CRTC | drm_crtc | 扫描控制器,决定时序与刷新 | 显示控制器通道0/1 |
| Encoder | drm_encoder | 将CRTC信号转换为接口信号 | LVDS编码器、HDMI编码器 |
| Connector | drm_connector | 物理连接器,探测EDID与热插拔 | LVDS面板、HDMI接口 |
对象之间的关系是:Connector → Encoder → CRTC → Plane → Framebuffer。用户空间通过GETRESOURCES获得各对象ID,再通过GETCONNECTOR、GETCRTC等命令获取详细属性。属性(property)机制是DRM对象模型的另一大特色,每个对象都有一组可读写属性,如CRTC_X、CRTC_Y、SRC_W、SRC_H、FB_ID等,用户空间通过原子提交(atomic commit)一次性更新多个属性,保证显示状态的一致性。
四、驱动注册流程:从probe到用户可见
一个DRM驱动从内核模块加载到用户空间可见,通常经历以下步骤:
- 平台设备匹配:驱动通过
of_match_table或pci_device_id与设备树/PCI设备匹配,进入probe()。 - 分配drm_device:调用
drm_dev_alloc()分配并初始化drm_device,指定drm_driver结构体。 - 初始化模式配置:调用
drm_mode_config_init(),设置min_width、max_width等约束。 - 创建对象:驱动创建CRTC、Plane、Encoder、Connector,并调用
drm_crtc_init_with_planes()、drm_connector_init()等注册到DRM核心。 - 注册设备:调用
drm_dev_register(),内核创建/dev/dri/cardX节点,并注册sysfs、debugfs接口。 - 用户空间可见:合成器打开节点,通过ioctl枚举资源并完成显示配置。
static int my_drm_probe(struct platform_device *pdev)
{
struct drm_device *drm;
int ret;
drm = drm_dev_alloc(&my_drm_driver, &pdev->dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
drm_mode_config_init(drm);
ret = my_create_crtc(drm);
ret = my_create_connector(drm);
ret = drm_dev_register(drm, 0);
return ret;
}
五、案例与排障:车载双屏显示
某车载项目使用一颗SoC驱动中控屏(LVDS)和仪表屏(HDMI)。系统启动后中控屏正常,仪表屏黑屏。排查思路如下:
- 执行
modetest -M panfrost,确认两个Connector是否都被识别。 - 检查
/sys/class/drm/card0-HDMI-A-1/status,若为disconnected,说明HPD或EDID读取异常。 - 若status为
connected但无输出,检查CRTC与Encoder的绑定关系,确认HDMI Encoder是否已drm_encoder_init()并attach到正确CRTC。 - 查看
dmesg | grep drm,定位atomic commit失败的具体对象与属性。
renderD128也能做显示控制。实际上render node不暴露模式设置接口,合成器必须使用card0。若权限配置错误,会导致合成器启动失败但GPU计算正常,现象具有迷惑性。六、复盘要点
DRM架构的学习应围绕“一条链路、两类节点、五类对象、一组ioctl”展开。一条链路是用户空间到硬件的显示通路;两类节点是card与renderD;五类对象是FB、Plane、CRTC、Encoder、Connector;一组ioctl是资源枚举、缓冲创建、framebuffer注册与CRTC提交。掌握这些,就能在车载多屏、多图层、热插拔等复杂场景中快速定位问题,并为后续GPU渲染与显示合成的集成打下基础。