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 → 页面翻转。在车载场景中,合成器正是通过这一组接口完成多图层合成与显示刷新。

调试DRM ioctl时,可使用strace -e ioctl跟踪用户空间调用,快速定位是哪个环节返回了EINVAL或ENOSPC。

三、DRM对象模型:显示资源的抽象

DRM将显示管线抽象为五类核心对象,它们之间通过ID和指针相互关联:

对象内核结构职责车载典型实例
Framebufferdrm_framebuffer描述可扫描输出的像素缓冲合成器输出的UI图层
Planedrm_plane图层,负责从FB取像素并混合主图层、光标层、视频层
CRTCdrm_crtc扫描控制器,决定时序与刷新显示控制器通道0/1
Encoderdrm_encoder将CRTC信号转换为接口信号LVDS编码器、HDMI编码器
Connectordrm_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)一次性更新多个属性,保证显示状态的一致性。

DRM对象模型与用户空间交互链路 用户空间 Wayland合成器 libdrm / ioctl open(/dev/dri/card0) 设备节点 card0 renderD128 字符设备 226:x DRM核心层 drm_ioctl分发 对象管理 原子提交 GPU/显示驱动 panfrost / i915 CRTC/Plane Encoder/Connector DRM显示对象模型 Connector LVDS/HDMI Encoder 信号转换 CRTC 扫描时序 Plane 图层混合 FB 缓冲

四、驱动注册流程:从probe到用户可见

一个DRM驱动从内核模块加载到用户空间可见,通常经历以下步骤:

  1. 平台设备匹配:驱动通过of_match_table或pci_device_id与设备树/PCI设备匹配,进入probe()。
  2. 分配drm_device:调用drm_dev_alloc()分配并初始化drm_device,指定drm_driver结构体。
  3. 初始化模式配置:调用drm_mode_config_init(),设置min_width、max_width等约束。
  4. 创建对象:驱动创建CRTC、Plane、Encoder、Connector,并调用drm_crtc_init_with_planes()、drm_connector_init()等注册到DRM核心。
  5. 注册设备:调用drm_dev_register(),内核创建/dev/dri/cardX节点,并注册sysfs、debugfs接口。
  6. 用户空间可见:合成器打开节点,通过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渲染与显示合成的集成打下基础。