4. 显示管线初始化实战
i.MX 平台的 DRM 显示子系统并不是一个单一驱动,而是由 imx-drm 主驱动、多个 IP 子驱动(ipu-v3、ldb、parallel-display、dw-hdmi 等)以及通用 DRM 框架共同拼装而成。理解这条管线如何从设备树匹配一路走到用户空间看到 /dev/fb0 或 /dev/dri/card0,是排查“屏幕不亮”“分辨率不对”“fb 节点缺失”等问题的前提。本节沿着内核启动的真实顺序,把 probe、component master 绑定、CRTC/encoder/connector 注册、fbdev 兼容层这几件事串成一条可跟踪的链路。
一、设备树先描述“谁和谁连在一起”
在 i.MX6Q 上,典型显示通路是 IPU 的 DI 输出 → LDB 桥 → LVDS 面板,或者 IPU DI → parallel-display → RGB 屏。设备树里用 ports/endpoint 和 remote-endpoint 把这些节点连成图。例如:
&ipu1_di0 {
status = "okay";
port {
ipu1_di0_lvds: endpoint {
remote-endpoint = &ldb_in;
};
};
};
&ldb {
status = "okay";
ports {
port@0 { ldb_in: endpoint { remote-endpoint = &ipu1_di0_lvds; }; };
port@1 { ldb_out: endpoint { remote-endpoint = &panel_in; }; };
};
};
这些 endpoint 不只是描述连接,它们决定了 component 框架的匹配关系:每个子驱动在 probe 时把自己的 component 注册进全局列表,主驱动则根据设备树里的 ports 关系判断“谁依赖谁”。如果 endpoint 写错或漏写,后面就会出现“component master 一直不 complete”的经典现象。
二、probe 顺序与 component 绑定
内核启动时,各平台驱动按设备树匹配依次 probe。ipu-v3 驱动先注册 IPU 的 CRTC 相关资源,LDB、parallel-display、dw-hdmi 等 encoder/bridge 驱动随后注册自己的 component。每个子驱动调用 component_add(),把 struct component_ops 挂到全局 component 列表上。
imx-drm 主驱动在 probe 时调用 component_master_add_with_match(),并提供一个 match 函数。这个 match 函数会遍历设备树中主驱动节点下的所有 remote-endpoint,判断每个子 component 是否已经就绪。只有当所有被引用的 component 都注册完成后,component 框架才会回调主驱动的 bind()。这就是“master 绑定”的本质:不是主驱动主动去抓子驱动,而是框架在所有依赖到齐后统一触发。
三、CRTC、encoder、connector 的注册
主驱动 bind 回调里,imx-drm 会创建 DRM device,并依次注册显示对象。以 IPU + LDB + LVDS 面板为例:
- CRTC:由 ipu-v3 提供,对应 IPU 的 DI 通道。它负责扫描时序生成、framebuffer 读取和 vblank 中断。
- Encoder:LDB 作为 encoder,把 IPU 的并行 RGB 信号转换成 LVDS 差分信号。它实现
atomic_enable/disable和mode_set。 - Connector:由 panel 或 bridge 提供,负责读取 EDID(HDMI 场景)或固定 mode(LVDS 场景),并报告连接状态。
注册完成后,DRM 框架会把这些对象挂到 drm_mode_config 上,并创建 /dev/dri/card0。此时用户空间可以通过 libdrm 或 modetest 枚举资源。一个典型的 modetest 输出会显示 CRTC、encoder、connector 的 ID 和可用 mode 列表。
四、fbdev 兼容层:为什么还有 /dev/fb0
很多老应用和 Qt/EGLFS 配置仍依赖 framebuffer 接口。imx-drm 通过 drm_fbdev_generic_setup() 或早期的 imx_drm_fbdev 创建 fbdev 兼容层。它把 DRM 的 primary plane 包装成一个 fb_info,并注册 /dev/fb0。当应用对 fb0 做 mmap 并写像素时,fbdev 层通过 drm_framebuffer 把数据映射到 GEM buffer,再由 CRTC 扫描输出。
需要注意的是,fbdev 兼容层通常只支持一个固定分辨率,且不参与 atomic modeset。如果同时使用 DRM/KMS 应用和 fbdev 应用,可能出现资源冲突。建议在最终产品中明确选择一种显示接口。
五、从 probe 到 modeset 的实操跟踪
实际调试时,可以按以下顺序观察内核日志和 sysfs 节点:
- 启动后执行
dmesg | grep -E "imx-drm|ipu|ldb|component",确认各子驱动 probe 成功,并看到imx-drm display-subsystem: bound之类的绑定日志。 - 检查
/sys/class/drm/下是否出现card0-LVDS-1等 connector 目录,status文件应显示connected。 - 用
modetest -M imx-drm枚举资源,确认 CRTC、encoder、connector 的 ID 和可用 mode。 - 执行
modetest -M imx-drm -s <connector_id>@<crtc_id>:1024x600触发 modeset,观察屏幕是否点亮。 - 若使用 fbdev,检查
/dev/fb0是否存在,并用fbset查看当前分辨率。
一个常见的失败场景是:LDB 的 status 在设备树里是 disabled,导致 component 不注册,master 永远不 bind,最终 /dev/dri/card0 不出现。此时 dmesg 里通常只有各子驱动的 probe 日志,没有 imx-drm 的绑定日志。修正设备树后重新编译 dtb 即可。
六、关键检查点速查
| 阶段 | 观察对象 | 正常表现 | 异常线索 |
|---|---|---|---|
| 设备树 | endpoint / remote-endpoint | 连接关系完整 | master match 失败 |
| 子驱动 probe | dmesg 中 ipu/ldb/panel 日志 | 各自 probe 成功 | 时钟、引脚、电源报错 |
| component 绑定 | imx-drm bind 日志 | master bind 触发 | 无 bind 日志,卡在 match |
| DRM 注册 | /dev/dri/card0 | 节点存在 | 节点缺失 |
| connector 状态 | /sys/class/drm/card0-*/status | connected | disconnected / unknown |
| modeset | 屏幕输出 | 按指定分辨率点亮 | 黑屏、花屏、时序错误 |
| fbdev | /dev/fb0 | 节点存在且可 mmap | 节点缺失或分辨率固定错误 |
把这条链路走通之后,后续的 plane 配置、atomic commit、DMA-BUF 导入导出才有稳定的基础。建议在板子上保留一份可用的 dmesg 基线日志,每次修改设备树或驱动后对比差异,能快速定位是哪个 component 没有就绪。