显示框架(DRM/KMS)
文档版本: V1.0 适用系统: Linux 6.1(Debian 12 / Ubuntu 22.04) 适用硬件: RK3588(C-3588LQ V1.0 核心板 + SHIMETAPI-M5-R2A V1.0 底板) 更新日期: 2026-08-19
1. 概述
1.1 功能简介
RK3588 的显示框架建立在 Linux 内核的 DRM/KMS 子系统(Direct Rendering Manager / Kernel Mode Setting)之上。DRM/KMS 是内核里统一管理 GPU 和显示控制器的标准框架,负责帧缓冲分配、显示时序控制、图层合成和多屏管理。芯片内部的显示控制器是 VOP2(Video Output Processor v2),最多支持 4 路独立显示输出,配合 Mali-G610 MP4 GPU 完成高性能图形渲染。
和老的 Framebuffer 设备(/dev/fb*)相比,DRM/KMS 的硬件抽象能力强得多:支持 Atomic Mode Setting(原子模式设置,把所有显示参数打包成一次提交,要么全部生效、要么全部不变)、硬件 Plane 图层合成、DMA-BUF 缓冲区共享(DMA-BUF 是让多个设备共用同一块内存的机制,可以省去数据拷贝)和 DRM GEM(Graphics Execution Manager,DRM 自己的显存管理器)。现代 Linux 发行版已经全面转向 DRM/KMS,Framebuffer 只作为兼容层保留。
这块板卡上与显示相关的核心组件如下:
| 组件 | 说明 |
|---|---|
| VOP2 显示控制器 | 寄存器基地址 vop@fe040000,4 路独立 Video Port |
| HDMI 2.1 控制器 | 寄存器基地址 hdmi@fe0a0000,最高 8K@60fps / 4K@120fps |
| MIPI DSI 控制器 | 寄存器基地址 dsi@fe060000,双通道 4-Lane MIPI DSI 2.0 |
| Display Port 控制器 | 寄存器基地址 dp@fe960000,DP 1.4 |
| eDP 控制器 | 寄存器基地址 edp@fe070000,eDP 1.4 |
| Mali-G610 MP4 | GPU,支持 OpenGL ES 3.2、Vulkan 1.1 |
| DRM/KMS 内核框架 | 管理 CRTC、Plane、Encoder、Connector 等抽象对象 |
各显示接口的能力对比:
| 接口 | 寄存器基地址 | 最大分辨率 | 协议版本 | 物理接口 |
|---|---|---|---|---|
| HDMI | hdmi@fe0a0000 | 8K@60fps / 4K@120fps | HDMI 2.1 | Type-A |
| DP | dp@fe960000 | 8K@30fps / 4K@60fps | DP 1.4 | USB Type-C |
| MIPI DSI | dsi@fe060000 | 2.5K@60fps | MIPI DSI 2.0 | FPC 连接器(4-Lane) |
| eDP | edp@fe070000 | 4K@60fps | eDP 1.4 | eDP 接口 |
1.2 应用场景
常见的几种用法,以及各自适合的显示接口:
| 应用场景 | 显示接口 | 说明 |
|---|---|---|
| 多屏异显 | HDMI + DP / HDMI + MIPI DSI | 同时输出不同内容 |
| 桌面系统 | HDMI + Weston | Linux 桌面图形界面 |
| 数字标牌 | 多路 HDMI | 广告机、信息发布系统 |
| 工业 HMI | MIPI DSI / eDP | 工业控制触摸屏界面 |
| 视频播放 | HDMI + DRM Direct Show | 硬件解码 + DRM 直接显示 |
| 8K 大屏 | HDMI 2.1 | 8K@60fps 或 4K@120fps |
| 嵌入式 GUI | eDP / LVDS | 嵌入式设备人机交互 |
1.3 工作原理
先看整体架构,了解一帧画面从应用走到屏幕要经过哪些环节:
+--------------------------------------------------------------+
| Application Layer |
| libdrm / GBM / EGL / Mali GPU / Weston / X11 |
+--------------------------------------------------------------+
| Kernel DRM/KMS Driver Layer |
| +----------------------------------------------------+ |
| | VOP2 Display Controller | |
| | vop@fe040000 | |
| | +------+ +------+ +------+ +------+ | |
| | | VP0 | | VP1 | | VP2 | | VP3 | | |
| | | (4K) | | (4K) | |(2K) | |(2K) | | |
| | +--+---+ +--+---+ +--+---+ +--+---+ | |
| +-----|--------|--------|--------|---------+ |
| | | | | |
| +-----|--------|--------|--------|---------+ |
| | Encoder / Connector Layer | |
| | HDMI DP MIPI DSI0 MIPI DSI1 eDP | |
| +---------------------|---------------------+ |
+--------------------------|-----------------------------+
+-------------+-----------+
| Display Device |
+-------------------------+再看 DRM 设备内部的抽象对象关系。后面排查问题时,经常要对着这张图找某个 connector 或 CRTC 对应哪个硬件:
+--------------------------------------------------------------+
| DRM Device Node |
| /dev/dri/card0 |
+--------------------------------------------------------------+
| |
| Connector ------> Encoder ------> CRTC |
| (Physical I/F) (Protocol) (Display Controller) |
| HDMI-A-1 dw_hdmi VP0 |
| DP-1 dw_dp VP1 |
| DSI-1 dw_mipi_dsi2 VP2 |
| DSI-2 analogix_dp VP3 |
| |
| CRTC <--------- Plane (Layer) |
| (VP0) Cluster (AFBC decode) |
| Esmart (Hardware scaling) |
| Smart (Basic layer) |
| |
| Plane <--------- Framebuffer (Framebuffer Object) |
| GEM / DMA-BUF |
+--------------------------------------------------------------+图里出现的名词对照如下:
| 概念 | 说明 | RK3588 对应 |
|---|---|---|
| CRTC | 显示控制器抽象,管理显示时序和 Plane 合成 | VOP2 中的 Video Port |
| Plane | 硬件图层,支持多种像素格式和缩放 | Cluster、Esmart、Smart 三种类型 |
| Encoder | 将像素数据编码为特定接口协议信号 | dw_hdmi、dw_dp 等 |
| Connector | 物理显示接口,支持热插拔检测 | HDMI-A、DP、DSI 等 |
| Bridge | Encoder 与 Panel 之间的桥接设备 | HDMI bridge、DP bridge |
| Framebuffer | 帧缓冲对象,存储像素数据 | GEM 对象,支持 DMA-BUF 共享 |
| GEM | DRM 缓冲区管理与分配 | 类似 ION / DMA-BUF heap |
VOP2 提供三种硬件图层,能力各不相同,选图层时先对照下表:
| Plane 类型 | 说明 | AFBC 解码 | 硬件缩放 | 最大分辨率 |
|---|---|---|---|---|
| Cluster | 高性能图层,支持 AFBC 解码 | 支持 | 不支持 | 4096x2160 |
| Esmart | 智能缩放图层 | 不支持 | 支持 | 4096x2160 |
| Smart | 基础图层 | 不支持 | 不支持 | 4096x2160 |
Framebuffer 和 DRM 两种设备节点的差异:
| 对比项 | Framebuffer (/dev/fb*) | DRM (/dev/dri/card0) |
|---|---|---|
| 硬件抽象 | 简单的像素缓冲区 | CRTC + Plane + Encoder + Connector |
| 模式设置 | 内核固定分辨率 | Atomic Mode Setting(用户态控制) |
| 多图层 | 不支持(单层) | 支持多硬件 Plane 合成 |
| 缓冲区管理 | mmap 直接访问 | GEM / DMA-BUF / GBM |
| 热插拔 | 不支持 | 原生支持 |
| GPU 合成 | 不支持 | 支持 EGL/Vulkan 渲染输出 |
| 用途 | 嵌入式简单显示 | 现代图形系统 |
注意:RK3588 同时支持两种模式:DRM 设备节点
/dev/dri/card0用于 DRM/KMS 框架,Framebuffer 设备节点/dev/fb0用于传统兼容模式。
2. 硬件准备
2.1 硬件清单
| 序号 | 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|---|
| 1 | 核心板 | C-3588LQ V1.0(660-pin LGA) | 1 | 含 RK3588 SoC、4GB/8GB LPDDR4x |
| 2 | 底板 | SHIMETAPI-M5-R2A V1.0 | 1 | 含 HDMI、DP、MIPI DSI、eDP 接口 (S-03) |
| 3 | 电源适配器 | 12V/3A DC | 1 | 底板 DC 输入,至少 36W |
| 4 | HDMI 显示器 | 支持 HDMI 2.0+ | 1 | 用于 HDMI 输出测试 |
| 5 | HDMI 线缆 | HDMI 2.0 / 2.1 | 1 | 建议使用认证线缆 |
| 6 | DP 显示器(可选) | 支持 DP 1.4 | 1 | 用于 DP 输出测试 |
| 7 | MIPI DSI 屏(可选) | 支持 MIPI DSI 2.0, 4-Lane | 1 | 用于 DSI 输出测试 |
| 8 | 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| 9 | TF 卡或 eMMC | 16GB+ | 1 | 系统存储 |
2.2 显示接口引出
底板引出了下面这些显示接口(参考 S-03 底板原理图):
| 接口 | 说明 |
|---|---|
| HDMI TX | HDMI Type-A,HDMI2.1,最高 8K@60fps |
| MIPI DSI0 | FPC 连接器,4-Lane DSI,用于连接 LCD 模组 |
| MIPI DSI1 | FPC 连接器,4-Lane DSI,副屏 |
| eDP | 嵌入式 DisplayPort |
| DisplayPort | DP 1.4 |
注意:具体连接器编号请查阅 S-03 底板原理图,不同批次底板布局可能略有差异。
2.3 硬件确认
接好硬件、上电进入系统后,用下面的命令逐项确认显示子系统是否被内核正常接管。每条命令的预期输出写在注释里,都能对上就说明硬件这一关过了:
# 查看 DRM 设备节点
ls /sys/class/drm/
# 输出示例:card0 card0-HDMI-A-1 card0-DP-1 card0-DSI-1 version
# 查看 HDMI 连接状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 输出:connected 或 disconnected
# 查看所有显示接口状态
for f in /sys/class/drm/card0-*/status; do
echo "$(basename $(dirname $f)): $(cat $f)"
done
# 查看 VOP2 设备树状态
cat /proc/device-tree/soc/vop@fe040000/status
# 预期输出:okay
# 查看 HDMI 控制器设备树状态
cat /proc/device-tree/soc/hdmi@fe0a0000/status
# 预期输出:okay
# 查看 DSI 控制器设备树状态
cat /proc/device-tree/soc/dsi@fe060000/status
# 查看 DP 控制器设备树状态
cat /proc/device-tree/soc/dp@fe960000/status
# 查看 framebuffer 信息
ls /dev/fb*
cat /sys/class/graphics/fb0/virtual_size
# 查看 DRM 设备版本
cat /sys/class/drm/version
# 检查 Mali-G610 GPU 设备
ls /dev/dri/renderD128
cat /sys/class/misc/mali0/device/uevent3. 软件准备
3.1 依赖安装
本章用到的测试工具和库都来自软件源,直接用 apt 安装:
# 安装 DRM 测试工具
apt-get update
apt-get install -y libdrm-tests libdrm-dev
# 安装显示相关库
apt-get install -y \
libegl-dev libgles-dev \
libgbm-dev \
libwayland-dev libwayland-bin \
weston wayland-protocols \
xwayland
# 安装 Mesa(用户态 GPU 驱动)
apt-get install -y \
mesa-utils libgl1-mesa-dev \
mesa-vulkan-drivers
# 安装 edid-decode(EDID 解析工具)
apt-get install -y edid-decode
# 安装 Westo n 配置工具
apt-get install -y weston-init
# 确认 Mali-G610 GPU 驱动
# RK3588 使用 panfrost(开源)或 mali-g610(闭源)驱动
apt-get install -y libmali # 闭源驱动(如有提供)3.2 内核配置
自己编译内核时,需要打开以下 DRM/KMS 相关驱动配置(出厂镜像一般已经配好):
# DRM 核心框架
CONFIG_DRM=y
CONFIG_DRM_ROCKCHIP=y
# VOP2 显示控制器
CONFIG_ROCKCHIP_VOP2=y
# HDMI 2.1 输出
CONFIG_ROCKCHIP_ANALOGIX_DIGITAL=y
CONFIG_ROCKCHIP_HDMI=y
CONFIG_DRM_ROCKCHIP_ANALOGIX_HDMI=y
# MIPI DSI 输出
CONFIG_ROCKCHIP_DW_MIPI_DSI=y
# eDP 输出
CONFIG_ROCKCHIP_ANALOGIX_DP=y
# Display Port 输出
CONFIG_ROCKCHIP_DW_DP=y
# 通用 Panel 驱动
CONFIG_DRM_PANEL_SIMPLE=y
# DRM GEM / CMA 缓冲区管理
CONFIG_DRM_GEM_CMA_HELPER=y
CONFIG_DRM_CMA_HELPER=y
# Framebuffer 兼容层
CONFIG_DRM_FBDEV_EMULATION=y
CONFIG_FB=y
CONFIG_FB_ROCKCHIP=yRK3588 使用
CONFIG_ROCKCHIP_VOP2驱动 VOP2 显示控制器,支持 4 路独立 Video Port。
在正在运行的系统上,可以这样查看当前内核配置:
# 查看 VOP2 配置
grep -i "VOP2\|ROCKCHIP_VOP" /boot/config-$(uname -r)
# 查看 DRM 配置
grep -i "DRM_ROCKCHIP" /boot/config-$(uname -r)
# 查看 HDMI 配置
grep -i "HDMI\|ROCKCHIP_HDMI" /boot/config-$(uname -r)
# 检查已加载的 DRM 模块
lsmod | grep -i "drm\|rockchip"
# 预期看到:drm rockchip vop2 dw_hdmi 等模块3.3 设备树配置
RK3588 VOP2 显示控制器的设备树定义:
vop2: vop@fe040000 {
compatible = "rockchip,rk3588-vop2";
reg = <0x0 0xfe040000 0x0 0x2000>,
<0x0 0xfe044000 0x0 0x1000>;
reg-names = "regs", "gamma_luts";
interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_VOP>, <&cru HCLK_VOP>,
<&cru DCLK_VOP0>, <&cru DCLK_VOP1>,
<&cru DCLK_VOP2>, <&cru DCLK_VOP3>;
clock-names = "aclk", "hclk",
"dclk_vp0", "dclk_vp1",
"dclk_vp2", "dclk_vp3";
resets = <&cru SRST_A_VOP>, <&cru SRST_H_VOP>;
reset-names = "aclk", "hclk";
power-domains = <&power RK3588_PD_VOP>;
iommu = <&vop2_mmu>;
status = "disabled";
};HDMI 控制器的设备树定义:
hdmi0: hdmi@fe0a0000 {
compatible = "rockchip,rk3588-dp-hdmi";
reg = <0x0 0xfe0a0000 0x0 0x20000>;
interrupts = <GIC_SPI 168 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_HDMI_TX>, <&cru CLK_HDMI_REF>,
<&cru CLK_HDMI_TX>;
clock-names = "pclk", "ref", "phy";
resets = <&cru SRST_HDMI_TX>;
reset-names = "hdmi";
rockchip,phy = <&hdmi_phy>;
pinctrl-names = "default";
pinctrl-0 = <&hdmi0_tx_cec &hdmim0_tx_hpd>;
status = "disabled";
};MIPI DSI 控制器的设备树定义:
dsi0: dsi@fe060000 {
compatible = "rockchip,rk3588-mipi-dsi";
reg = <0x0 0xfe060000 0x0 0x10000>;
interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_DSI0>, <&cru CLK_DSI0_TXCLK>,
<&cru CLK_DSI0_RXCLK>, <&cru CLK_DSI0_24M>;
clock-names = "pclk", "txclk", "rxclk", "clk24m";
resets = <&cru SRST_DSI0_P>,
<&cru SRST_DSI0_TX>,
<&cru SRST_DSI0_RX>,
<&cru SRST_DSI0>;
reset-names = "pclk", "tx", "rx", "dsi";
power-domains = <&power RK3588_PD_VO1>;
rockchip,grf = <&sysgrf>;
status = "disabled";
};Display Port 控制器的设备树定义:
dp: dp@fe960000 {
compatible = "rockchip,rk3588-dp";
reg = <0x0 0xfe960000 0x0 0x10000>;
clocks = <&cru PCLK_DP>, <&cru CLK_DP_Ref>,
<&cru CLK_DP_TXPHY>;
clock-names = "pclk", "ref", "txphy";
resets = <&cru SRST_DP_TXPHY>,
<&cru SRST_DP>;
reset-names = "txphy", "dp";
rockchip,usbdpcom = <&usbdpcom>;
status = "disabled";
};3.4 display_subsystem 路由配置
display_subsystem 下的 route 节点决定哪个显示接口接哪个 VP。下面是启用 HDMI + MIPI DSI 双屏的路由配置:
display_subsystem: display-subsystem {
compatible = "rockchip,display-subsystem";
memory-region = <&drm_logo>, <&drm_cubic_lut>;
ports = <&vop2_out>;
route {
route_hdmi: route-hdmi {
status = "okay";
logo,uboot = "logo.bmp";
logo,kernel = "logo_kernel.bmp";
logo,mode = "center";
connect = <&vp0_out_hdmi>;
};
route_dsi0: route-dsi0 {
status = "okay";
logo,uboot = "logo.bmp";
logo,kernel = "logo_kernel.bmp";
logo,mode = "center";
connect = <&vp1_out_dsi0>;
};
route_dp1: route-dp1 {
status = "disabled";
connect = <&vp0_out_dp>;
};
};
};对应的 VP 与显示接口连接关系配置:
&hdmi0 {
status = "okay";
};
&hdmi0_in_vp0 {
status = "okay";
};
&hdmi0_in_vp1 {
status = "disabled";
};
&route_hdmi {
status = "okay";
connect = <&vp0_out_hdmi>;
};
&dsi0 {
status = "okay";
rockchip,lane-rate = <1500>;
};
&route_dsi0 {
status = "okay";
connect = <&vp1_out_dsi0>;
};注意:一个显示接口在同一时刻只能连接一个 VP。改板级配置时,记得把不使用的通路设为
disabled。
3.5 Weston 合成器配置
Weston 是 Wayland 的参考合成器,也就是把各个应用的画面拼成最终屏幕内容的程序。先安装:
apt-get install -y weston libwayland-dev wayland-protocolsweston.ini 基本配置:
[core]
backend=drm-backend.so
shell=desktop-shell.so
[output]
name=HDMI-A-1
mode=1920x1080-603.5.1 Weston 动态配置
Weston 支持通过环境变量和配置文件动态调整显示输出,不必改主配置文件:
# 环境变量
export WESTON_DRM_PRIMARY=HDMI-A-1
export WESTON_OUTPUT_FLOW=output1,eDP-1:clone,output1
# 动态配置文件
echo "[output]
name=HDMI-A-1
mode=3840x2160-60" > /tmp/.weston_drm.conf动态配置文件
/tmp/.weston_drm.conf在运行时被 Weston 监控,修改后自动生效,无需重启 Weston 进程。
4. 操作步骤
4.1 DRM 基础测试
先做最基础的检查:列出 DRM 资源,再用 modetest 直接点亮一块 plane。modetest 是 3.1 节安装的 libdrm-tests 自带的测试程序。命令里出现的 connector ID 和 plane ID 每台机器不一样,以自己机器上 modetest 的输出为准:
# 查看 DRM 设备信息
modetest -M rockchip -c
# 查看所有 connector 信息(包含分辨率列表)
modetest -M rockchip -c | grep -A 30 "Connectors:"
# 测试 HDMI 1080p 输出(connector ID 需根据实际 modetest 输出确定)
# 本机 connector ID = 226,plane ID = 58/83(Esmart0/1-win0)
modetest -M rockchip -s 226:1920x1080 -P 83:1920x1080
# 注意:Xorg 桌面在跑时 -s 会抢占显示(X 自动恢复),无人值守测试建议先停 lightdm
# 使用 solid color 测试平面
modetest -M rockchip -r -s 226:1920x1080 -P 83:1920x1080 -F solid
# 查看 VOP2 当前状态信息(需 root)
sudo cat /sys/kernel/debug/dri/0/state下面的输出取自本机(modetest -M rockchip -c 摘要 + VOP state):
#0 1920x1080 60.00 1920 2008 2052 2200 1080 1084 1089 1125 148500 flags: phsync, pvsync; type: preferred, driver
#1 1920x1080 100.00 ...
#2 1920x1080 75.00 ...
...(共 32 个 mode,1080p 及以下——本机所接显示器不支持 4K)
plane[58]: Esmart0-win0
crtc=(null) ← 未使用
plane[83]: Esmart1-win0
crtc=video_port1
fb=256 allocated by = Xorg format=XR24 size=1920x1080结论:Xorg 正以 XR24 格式占用 Esmart1-win0 图层输出 1080p;4K 输出本机无法验证,因为所接显示器的 EDID 只报 1080p 模式。
4.2 DRM 设备节点检查
这一节检查设备节点、VOP2 时钟和 debugfs 信息。本机有两处和常见资料不一样的地方,都写在命令注释里了:出厂内核没有 fbdev 兼容层(/dev/fb* 不存在),以及 debugfs 时钟树里查不到 VOP 的时钟名,要改走 devfreq 接口:
# 查看 DRM 设备信息
ls -la /dev/dri/
# card0 - DRM 主设备节点
# renderD128 - GPU 渲染节点
# 查看 framebuffer 设备节点
ls -la /dev/fb*
# 不存在!本内核未启用 fbdev 兼容层(无 /sys/class/graphics/),
# 传统 fb 应用不可用,一律走 DRM(见 4.7)
# 查看 VOP2 时钟频率
# debugfs 时钟树里没有 clk_dclk_vp0~3 / clk_aclk_vop 这些名字!
# 可用替代(sysfs devfreq,fdd90000.vop 即 VOP):
cat /sys/class/devfreq/fdd90000.vop/cur_freq
cat /sys/class/devfreq/fdd90000.vop/available_frequencies
# 查看 DRM debug 信息(需 root)
sudo cat /sys/kernel/debug/dri/0/name
# 输出: rockchip dev=display-subsystem unique=display-subsystem
sudo cat /sys/kernel/debug/dri/0/state
# 输出: 4 个 crtc(video_port0~3)+ plane 列表(见 4.1)
# debugfs dri/0 下的全部节点
sudo ls /sys/kernel/debug/dri/0/
# active_regs clients crtc-0 crtc-1 crtc-2 crtc-3
# framebuffer gem_names HDMI-A-1 internal_clients ...4.3 验证 HDMI2.1 输出
验证 HDMI 输出的顺序是:先确认连接状态,再列出支持的分辨率,需要细节时读 EDID(EDID 是显示器里存的一小段数据,向主机报告自己支持的分辨率和参数):
# 查看 HDMI 连接状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 应输出: connected
# 查看 HDMI 支持的分辨率
cat /sys/class/drm/card0-HDMI-A-1/modes
# 查看 EDID 信息( 需先安装 edid-decode;EDID 共 256 字节)
sudo apt install -y edid-decode
cat /sys/class/drm/card0-HDMI-A-1/edid | edid-decode
# 查询当前分辨率(X11 会话内,可用)
DISPLAY=:0 xrandr
# 输出:
# Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
# HDMI-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis)
# 1920x1080 60.00*+ 100.00 75.00 60.00 50.00 59.94
# 1920x1080i 60.00 50.00 59.94
# 1680x1050 59.88 ...
# 1280x720 60.00 ...
# 强制设置 HDMI 分辨率(显示器支持时)
DISPLAY=:0 xrandr --output HDMI-1 --mode 1920x1080 --rate 60
# 4K 输出:本机显示器 EDID 仅报 1080p 模式,4K 未验证;
# 接 4K 屏后用 xrandr --output HDMI-1 --mode 3840x2160 --rate 604.4 配置 MIPI DSI 输出
DSI 屏和 HDMI 显示器不一样:内核要等设备树里配置了 panel 节点之后,才会出现对应的 connector。先看连接状态:
# 查看 DSI 连接状态
# 本机出厂镜像没有 card0-DSI* connector(仅 HDMI-A-1 和 Writeback-1),
# DSI 输出需在 DTS 挂 panel 节点后才会出现
cat /sys/class/drm/card0-DSI*/status 2>&1
# cat: '/sys/class/drm/card0-DSI*/status': 没有那个文件或目录
# 本机全部 connector:
for f in /sys/class/drm/card0-*/status; do echo -n "$f: "; cat $f; done
# /sys/class/drm/card0-HDMI-A-1/status: connected
# /sys/class/drm/card0-Writeback-1/status: unknown
# DSI 屏幕需要在设备树中配置 panel-timing
# 常见方式:在 DTS 中添加 panel 节点,如 panel-simple 或厂家自定义驱动
dmesg | grep -i panel4.5 多屏同时显示配置
RK3588 VOP2 支持最多 4 路独立显示输出。先查所有连接器状态,再用 modetest 同时点亮两块屏(connector 和 plane ID 同样以 modetest 实际输出为准):
# 查看当前所有连接器状态
for f in /sys/class/drm/card0-*/status; do
echo "$(dirname $f | xargs basename): $(cat $f)"
done
# 示例输出:
# card0-HDMI-A-1: connected
# card0-DP-1: disconnected
# card0-DSIA: connected
# 同时启用 HDMI 和 DSI 显示(connector/plane ID 需根据 modetest 输出确定)
modetest -M rockchip \
-s 39:1920x1080-60 \
-s 46:1080x1920-60 \
-P 31:1920x1080 \
-P 49:1080x19204.6 Framebuffer 设备操作
先说明:本机出厂内核没有启用 fbdev 兼容层,本节命令在这块板子上跑不通,仅作传统系统参考;新开发一律走 DRM(见 4.7、4.8)。情况见命令注释:
# 查看 framebuffer 信息
# 本机无 fbdev 兼容层——/sys/class/graphics/fb0 不存在、/dev/fb* 不存在!
# 本节命令在出厂内核上全部不可用,仅作传统系统参考;新开发一律走 DRM(4.7/4.8)
cat /sys/class/graphics/fb0/virtual_size # No such file or directory
cat /sys/class/graphics/fb0/bits_per_pixel
cat /sys/class/graphics/fb0/stride
# 使用 fbset 修改 framebuffer 参数
fbset -fb /dev/fb0 -info
fbset -fb /dev/fb0 -g 1920 1080 1920 2160 32如果系统里有 /dev/fb0(比如启用了兼容层的内核),可以用下面的 Python 脚本往屏幕写一张渐变测试图案:
#!/usr/bin/env python3
"""
fb_test.py - Framebuffer 测试图案写入工具
适用平台: RK3588, Linux 6.1, Debian 12/Ubuntu 22.04
"""
import struct
import os
FB_DEV = "/dev/fb0"
def get_fb_info():
"""获取 framebuffer 基本信息"""
info = {}
with open("/sys/class/graphics/fb0/virtual_size", "r") as f:
size = f.read().strip()
width, height = [int(x) for x in size.split(",")]
info["width"] = width
info["height"] = height
with open("/sys/class/graphics/fb0/bits_per_pixel", "r") as f:
info["bpp"] = int(f.read().strip())
with open("/sys/class/graphics/fb0/stride", "r") as f:
info["stride"] = int(f.read().strip())
return info
def create_gradient(width, height, bpp=32):
"""创建 RGB 渐变测试图案"""
stride = ((width * bpp + 255) >> 8) << 8
line_size = stride // 8
framebuffer = bytearray(line_size * height)
for y in range(height):
for x in range(width):
offset = y * line_size + (x * bpp) // 8
r = int(255 * x / width) # X 轴红色渐变
g = 0
b = int(255 * y / height) # Y 轴蓝色渐变
a = 255
# BGRA8888 格式(Rockchip 默认)
struct.pack_into("<BBBB", framebuffer, offset, b, g, r, a)
return framebuffer
def main():
print("=" * 50)
print("RK3588 Framebuffer 测试")
print("=" * 50)
# 获取 framebuffer 信息
info = get_fb_info()
print(f" 分辨率: {info['width']}x{info['height']}")
print(f" 位深: {info['bpp']} bpp")
print(f" 步长: {info['stride']} bytes")
# 生成测试图案
buffer = create_gradient(info["width"], info["height"], info["bpp"])
# 写入 framebuffer
fb = open(FB_DEV, "wb")
fb.write(buffer)
fb.close()
print(" 测试图案已写入")
print(" 显示器应显示红蓝渐变图案")
if __name__ == "__main__":
main()4.7 DRM Framebuffer 操作(C 语言)
下面是一个完整的 DRM framebuffer 示例程序:打开 /dev/dri/card0,找到一块已连接的显示器,创建纯色 dumb buffer(dumb buffer 是由内核代为分配、可以直接 mmap 写像素的简单缓冲区),再设置 CRTC 把它显示出来。编译命令在代码开头的注释里,注意必须加 -I/usr/include/libdrm,否则头文件找不到:
/*
* drm_fb_test.c
* RK3588 DRM/KMS framebuffer 操作示例
*
* 编译(必须加 -I/usr/include/libdrm,否则 xf86drm.h 内部的
* #include <drm.h> 找不到):
* gcc -o drm_fb_test drm_fb_test.c -I/usr/include/libdrm -ldrm
* 运行: sudo ./drm_fb_test
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <libdrm/drm.h>
#include <libdrm/drm_mode.h>
#define DRM_DEVICE "/dev/dri/card0"
/* DRM 设备资源结构体 */
struct drm_res {
int fd;
uint32_t conn_id, enc_id, crtc_id, fb_id;
drmModeModeInfo mode;
uint32_t width, height, pitch, handle, size;
uint8_t *map;
};
/* 获取 DRM 设备资源 */
static int get_drm_res(struct drm_res *res)
{
drmModeRes *resources;
drmModeConnector *conn;
drmModeEncoder *encoder;
int i;
res->fd = open(DRM_DEVICE, O_RDWR);
if (res->fd < 0) {
perror("open DRM device");
return -1;
}
resources = drmModeGetResources(res->fd);
if (!resources) {
perror("get DRM resources");
close(res->fd);
return -1;
}
printf("DRM: %d connectors, %d encoders, %d CRTCs\n",
resources->count_connectors, resources->count_encoders,
resources->count_crtcs);
/* 遍历 connector,找到第一个已连接的 */
res->conn_id = 0;
for (i = 0; i < resources->count_connectors; i++) {
conn = drmModeGetConnector(res->fd, resources->connectors[i]);
if (!conn)
continue;
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
const char *type_name = "Unknown";
if (conn->connector_type == DRM_MODE_CONNECTOR_HDMIA)
type_name = "HDMI";
else if (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort)
type_name = "DP";
else if (conn->connector_type == DRM_MODE_CONNECTOR_DSI)
type_name = "DSI";
printf("Connector %d: %s, %d modes\n",
conn->connector_id, type_name, conn->count_modes);
res->conn_id = conn->connector_id;
memcpy(&res->mode, &conn->modes[0], sizeof(drmModeModeInfo));
res->width = conn->modes[0].hdisplay;
res->height = conn->modes[0].vdisplay;
printf("Mode: %dx%d@%dHz\n",
res->width, res->height, res->mode.vrefresh);
if (conn->encoder_id) {
encoder = drmModeGetEncoder(res->fd, conn->encoder_id);
if (encoder) {
res->enc_id = encoder->encoder_id;
res->crtc_id = encoder->crtc_id;
drmModeFreeEncoder(encoder);
}
}
drmModeFreeConnector(conn);
break;
}
drmModeFreeConnector(conn);
}
drmModeFreeResources(resources);
if (!res->conn_id) {
fprintf(stderr, "No connected display found\n");
return -1;
}
return 0;
}
/* 创建 DRM framebuffer 并填充纯色 */
static int create_fb(struct drm_res *res, uint32_t color)
{
struct drm_mode_create_dumb creq = {0};
struct drm_mode_map_dumb mreq = {0};
uint32_t i;
uint32_t bpp = 32;
/* 创建 dumb buffer(pitch 必须用内核返回的 creq.pitch,
手动按 ((w*bpp+255)>>8)<<8 计算是把 bpp 当字节用,1920 宽会算出
61696 ≠ 内核的 7680,导致 drmModeAddFB 返回 EINVAL) */
creq.width = res->width;
creq.height = res->height;
creq.bpp = bpp;
if (drmIoctl(res->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq)) {
perror("create dumb buffer");
return -1;
}
res->pitch = creq.pitch; /* 1920x32bpp 内核返回 7680 */
res->handle = creq.handle;
res->size = creq.size;
/* 创建 framebuffer */
if (drmModeAddFB(res->fd, res->width, res->height, 24,
bpp, res->pitch, res->handle, &res->fb_id)) {
perror("add framebuffer");
return -1;
}
printf("Framebuffer created: fb_id=%u, %ux%u\n",
res->fb_id, res->width, res->height);
/* 映射到用户空间 */
mreq.handle = res->handle;
if (drmIoctl(res->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)) {
perror("map dumb buffer");
return -1;
}
res->map = mmap(0, res->size, PROT_READ | PROT_WRITE,
MAP_SHARED, res->fd, mreq.offset);
if (res->map == MAP_FAILED) {
perror("mmap");
return -1;
}
/* 填充纯色 */
uint32_t *pixels = (uint32_t *)res->map;
uint32_t pixel_count = (res->pitch / 4) * res->height;
for (i = 0; i < pixel_count; i++)
pixels[i] = color;
return 0;
}
/* 设置 CRTC 显示模式 */
static int set_crtc(struct drm_res *res)
{
if (!res->crtc_id) {
/* 如果 encoder 没有绑定 CRTC,获取第一个可用的 */
drmModeRes *r = drmModeGetResources(res->fd);
if (r && r->count_crtcs > 0)
res->crtc_id = r->crtcs[0];
if (r)
drmModeFreeResources(r);
}
if (drmModeSetCrtc(res->fd, res->crtc_id, res->fb_id,
0, 0, &res->conn_id, 1, &res->mode)) {
perror("set crtc mode");
return -1;
}
printf("CRTC %u: %ux%u@%uHz\n",
res->crtc_id, res->width, res->height, res->mode.vrefresh);
return 0;
}
/* 清理资源 */
static void cleanup(struct drm_res *res)
{
if (res->map && res->map != MAP_FAILED)
munmap(res->map, res->size);
if (res->fb_id)
drmModeRmFB(res->fd, res->fb_id);
if (res->handle) {
struct drm_mode_destroy_dumb d = {0};
d.handle = res->handle;
drmIoctl(res->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &d);
}
if (res->fd >= 0)
close(res->fd);
}
int main(void)
{
struct drm_res res = {0};
printf("RK3588 DRM/KMS Framebuffer Test\n");
printf("==============================\n\n");
/* 获取 DRM 设备资源 */
if (get_drm_res(&res) < 0) {
fprintf(stderr, "Failed to get DRM resources\n");
return 1;
}
/* 创建红色 framebuffer */
printf("\nCreating framebuffer with red color (0xFF0000FF)...\n");
if (create_fb(&res, 0xFF0000FF) < 0) {
cleanup(&res);
return 1;
}
/* 设置 CRTC 显示模式 */
if (set_crtc(&res) < 0) {
cleanup(&res);
return 1;
}
printf("\nPress Enter to exit...\n");
getchar();
/* 清理资源 */
cleanup(&res);
printf("Done.\n");
return 0;
}输出(普通用户,Xorg 会话下运行):
RK3588 DRM/KMS Framebuffer Test
==============================
DRM: 1 connectors, 2 encoders, 4 CRTCs
Connector 226: HDMI, 32 modes
Mode: 1920x1080@60Hz
Creating framebuffer with red color (0xFF0000FF)...
Framebuffer created: fb_id=266, 1920x1080
CRTC 99: 1920x1080@60Hz(普通用户与 sudo 运行均成功,本机 CRTC=99、每次运行 fb_id 递增;屏幕红色闪现后程序退出,Xorg 自动恢复)
要点:
- connector 226 / CRTC 99 / 2 encoders / 4 CRTCs 为本机真实 ID。
- 修复 pitch 前
add framebuffer: Invalid argument(原因见 create_fb 内注释)。- Xorg 会话下 drmModeSetCrtc 需 DRM master:普通用户会被 Xorg 挡(-EACCES)或抢屏后 X 自动恢复;无人值守建议先
sudo systemctl stop lightdm。
4.8 Atomic Mode Setting 示例
Atomic 模式设置把所有显示参数打包成一次提交,是现代 DRM 推荐的用法。示例代码如下,编译同样需要 -I/usr/include/libdrm:
/*
* drm_atomic_test.c
* RK3588 DRM Atomic Mode Setting 示例
* 展示如何使用 atomic commit 进行模式设置
*
* 编译(需 -I/usr/include/libdrm):
* gcc -o drm_atomic_test drm_atomic_test.c -I/usr/include/libdrm -ldrm
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#define DRM_DEVICE "/dev/dri/card0"
/*
* Atomic commit 流程:
* 1. 获取 DRM 资源(CRTC, Plane, Connector)
* 2. 创建 dumb buffer -> framebuffer
* 3. 创建 atomic request
* 4. 设置 CRTC 属性(mode, active)
* 5. 设置 Plane 属性(fb_id, src, dest)
* 6. 提交 atomic commit
*/
static int atomic_set_mode(int fd, uint32_t crtc_id, uint32_t conn_id,
drmModeModeInfo *mode, uint32_t fb_id)
{
drmModeAtomicReq *req;
int ret;
req = drmModeAtomicAlloc();
if (!req) {
perror("atomic alloc");
return -1;
}
/* 获取 connector 和 CRTC 的 property ID
(勘误:libdrm 2.4.114 里类型是 drmModeObjectProperties、
函数名是 drmModeObjectGetProperties,原文的 drmModeObjectRes /
drmModeGetObjectProperties 均不存在,编译报 unknown type name) */
drmModeObjectProperties *obj = drmModeObjectGetProperties(fd, crtc_id,
DRM_MODE_OBJECT_CRTC);
if (!obj) {
perror("get crtc properties");
drmModeAtomicFree(req);
return -1;
}
/* 设置 CRTC active 属性 */
for (uint32_t i = 0; i < obj->count_props; i++) {
drmModePropertyRes *prop = drmModeGetProperty(fd, obj->props[i]);
if (!prop) continue;
if (strcmp(prop->name, "ACTIVE") == 0) {
drmModeAtomicAddProperty(req, crtc_id, prop->prop_id, 1);
printf("Set CRTC %u ACTIVE = 1\n", crtc_id);
}
if (strcmp(prop->name, "MODE_ID") == 0) {
/* 创建 mode blob */
uint32_t blob_id;
ret = drmModeCreatePropertyBlob(fd, mode, sizeof(*mode), &blob_id);
if (ret < 0) {
perror("create mode blob");
drmModeFreeProperty(prop);
drmModeFreeObjectProperties(obj);
drmModeAtomicFree(req);
return -1;
}
drmModeAtomicAddProperty(req, crtc_id, prop->prop_id, blob_id);
printf("Set CRTC %u MODE_ID = %u (%dx%d@%d)\n",
crtc_id, blob_id, mode->hdisplay,
mode->vdisplay, mode->vrefresh);
}
drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(obj);
/* 提交 atomic commit */
ret = drmModeAtomicCommit(fd, req,
DRM_MODE_ATOMIC_ALLOW_MODESET |
DRM_MODE_ATOMIC_TEST_ONLY, NULL);
if (ret < 0) {
fprintf(stderr, "Atomic commit test failed: %s\n", strerror(-ret));
drmModeAtomicFree(req);
return -1;
}
/* 正式提交 */
ret = drmModeAtomicCommit(fd, req,
DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
if (ret < 0) {
fprintf(stderr, "Atomic commit failed: %s\n", strerror(-ret));
} else {
printf("Atomic commit succeeded\n");
}
drmModeAtomicFree(req);
return ret;
}
int main(void)
{
int fd;
drmModeRes *resources;
drmModeConnector *conn = NULL;
drmModeEncoder *encoder = NULL;
drmModeModeInfo mode;
uint32_t crtc_id = 0;
printf("RK3588 DRM Atomic Mode Setting Test\n\n");
fd = open(DRM_DEVICE, O_RDWR);
if (fd < 0) {
perror("open DRM");
return 1;
}
resources = drmModeGetResources(fd);
if (!resources) {
perror("get resources");
close(fd);
return 1;
}
/* 找到已连接的 connector */
for (int i = 0; i < resources->count_connectors; i++) {
conn = drmModeGetConnector(fd, resources->connectors[i]);
if (conn && conn->connection == DRM_MODE_CONNECTED &&
conn->count_modes > 0) {
printf("Found connected display: connector %u\n", conn->connector_id);
memcpy(&mode, &conn->modes[0], sizeof(drmModeModeInfo));
crtc_id = resources->crtcs[0];
break;
}
if (conn) drmModeFreeConnector(conn);
conn = NULL;
}
if (!conn) {
fprintf(stderr, "No connected display found\n");
drmModeFreeResources(resources);
close(fd);
return 1;
}
printf("Using mode: %dx%d@%dHz\n",
mode.hdisplay, mode.vdisplay, mode.vrefresh);
/* 执行 atomic mode setting */
atomic_set_mode(fd, crtc_id, conn->connector_id, &mode, 0);
/* 释放资源 */
drmModeFreeConnector(conn);
drmModeFreeResources(resources);
close(fd);
printf("Done.\n");
return 0;
}修正类型/函数名后,普通用户与 sudo 都能运行,输出:
RK3588 DRM Atomic Mode Setting Test
Found connected display: connector 226
Using mode: 1920x1080@60Hz
Atomic commit succeeded
Done.注意:本例的 atomic request 只设置了 CRTC 的 ACTIVE 和 MODEID,没有挂 plane(fb_id 传 0),本质是"空提交",能验证调用链但不产生可见输出;要真正上屏还需给 plane 加 FB_ID/CRTC_ID/SRC_/CRTC__ 属性。TEST_ONLY 标志先试提交再真提交的写法本身正确。
4.9 Weston/Wayland 合成器启动
Weston 装好后,既可以从命令行手动启动,也可以交给 systemd 服务管理:
# 基础启动(DRM 后端)
weston --backend=drm-backend.so &
# 或使用 weston-init 服务
systemctl start weston
# 多屏 Weston 配置文件
cat > /etc/wayland/weston.ini << 'EOF'
[core]
backend=drm-backend.so
[output]
name=HDMI-A-1
mode=3840x2160@60
[output]
name=DSIA
mode=1080x1920@60
transform=90
EOF
# 启动 Weston
weston4.9.1 测试 Weston 显示输出
Weston 起来之后,运行下面任意一个小程序验证显示输出,屏幕上出现动画或窗口就说明 Wayland 链路通了:
# OpenGL ES 渲染测试
weston-simple-egl
# CPU 渲染测试
weston-simple-shm
# 花朵窗口演示
weston-flower
# 启动终端窗口(Wayland 协议客户端)
weston-terminal4.10 屏幕旋转配置
旋转有三种做法,按自己使用的显示协议选一种:
# === 方式一:Weston transform 配置(推荐) ===
# 在 /etc/wayland/weston.ini 中添加:
#
# [output]
# name=DSIA
# mode=1080x1920@60
# transform=90
#
# transform 取值:
# 0 = 正常 (0°)
# 1 = 顺时针 90°
# 2 = 180°
# 3 = 逆时针 90° (即 270° 顺时针)
# 4 = 水平翻转
# 5 = 水平翻转 + 90°
# 6 = 垂直翻转
# 7 = 垂直翻转 + 90°
# === 方式二:X11 xrandr 旋转 ===
xrandr --output DSIA --rotate left
# === 方式三:DRM Plane rotation 属性 ===
# 通过 modetest 查看 plane 的 rotation 属性
modetest -M rockchip -c | grep -A 20 "Planes:"
# 然后设置 rotation 属性值注意:旋转 90/270° 后,宽高需要交换。例如 1920x1080 旋转后变为 1080x1920。
4.11 冻屏(Freeze Display)
冻结当前显示内容,防止开机 logo 到 UI 之间的黑屏过渡:
# 冻结当前显示
cat /sys/kernel/debug/dri/0/state | grep "ACTIVE"
# 设置 Framebuffer 固定显示
echo 1 > /sys/class/graphics/fb0/blank5. 代码详解
5.1 内核驱动结构
RK3588 DRM 显示驱动源码目录:
drivers/gpu/drm/rockchip/ # Rockchip DRM 核心驱动
drivers/gpu/drm/bridge/analogix/ # eDP 桥接驱动
drivers/gpu/drm/bridge/synopsys/ # HDMI/DP 桥接驱动
drivers/phy/rockchip/ # 物理层驱动
drivers/video/rockchip/ # Framebuffer 驱动核心驱动文件:
| 模块 | 驱动文件 | 说明 |
|---|---|---|
| Core | rockchip_drm_drv.c | DRM 驱动核心,注册 DRM 设备 |
| Core | rockchip_drm_fb.c | 帧缓冲管理 |
| Core | rockchip_drm_gem.c | GEM 缓冲区管理 |
| Core | rockchip_drm_logo.c | Logo 显示 |
| VOP2 | rockchip_drm_vop2.c | VOP2 显示控制器驱动 |
| VOP2 | rockchip_vop2_reg.c | VOP2 寄存器定义 |
| HDMI | dw_hdmi.c | DesignWare HDMI 驱动 |
| HDMI | rockchip_dw_hdmi.c | Rockchip HDMI 封装 |
| HDMI | phy-rockchip-samsung-hdptx-hdmi.c | HDMI PHY |
| DP | dw-dp.c | DesignWare DP 驱动 |
| DP | phy-rockchip-usbdp.c | USB DP PHY |
| MIPI DSI | dw-mipi-dsi2.c | DesignWare MIPI DSI 驱动 |
| eDP | analogix_dp_core.c | Analogix eDP 驱动 |
| Panel | panel-simple.c | 通用 Panel 驱动 |
| FB | fb_rockchip.c | 传统 Framebuffer 驱动 |
5.2 驱动加载流程
Kernel boot up
|
+-- reserved-memory: drm_logo (保留 logo 显示缓冲区)
|
+-- 各显示设备 probe(顺序无关,使用 deferred probe)
| +-- edp_probe :component_add
| +-- dw_hdmi_rockchip_probe :component_add
| +-- cdn_dp_probe :component_add
| +-- dw_mipi_dsi2_probe :component_add
| +-- vop_probe :component_add
|
+-- rockchip_drm_platform_probe
| +-- 解析 display 节点,将显示设备加入 component
|
+-- rockchip_drm_bind
| +-- component_bind_all: 执行各子设备 bind 程序
| +-- vop_bind
| +-- rockchip_dp_bind
| +-- dw_hdmi_rockchip_bind
| +-- cdn_dp_bind
| +-- dw_mipi_dsi_bind
|
+-- show_loader_logo()
| +-- 解析 dts 中 logo 显示链路,将 logo 显示到对应设备
|
+-- rockchip_drm_fbdev_init
| +-- 加入 framebuffer 兼容支持(创建 /dev/fb0)
|
+-- DRM kernel 阶段加载完成DRM 驱动利用了 Linux 内核的 deferred probe 机制:当某个依赖资源未就绪时,驱动返回 -EPROBE_DEFER(-517),内核稍后会重新尝试加载,直到所有依赖资源就绪。这样各显示设备的 probe 顺序就无关紧要了。
启动日志示例(正常时应能看到各显示设备陆续 bound):
[1.747198] rockchip-drm display-subsystem: bound fe040000.vop (ops vop2_component_ops)
[1.748822] rockchip-drm display-subsystem: bound fe0a0000.hdmi (ops dw_hdmi_rockchip_ops)
[1.748676] dw-hdmi-rockchip fe0a0000.hdmi: registered ddc I2C bus driver
[1.755174] dw-mipi-dsi2 : [drm:dw_mipi_dsi2_bind] *ERROR* Failed to find panel or bridge: -517
...
[2.472319] rockchip-drm display-subsystem: bound fe960000.dp (ops dw_dp_component_ops)
[2.531892] rockchip-drm display-subsystem: [drm] fb0: rockchipdrmfb frame buffer device
[2.532850] [drm] Initialized rockchip 3.0.0 20140818 for display-subsystem on minor 05.3 用户空间调用链
一个典型的 DRM 应用,从打开设备到画面上屏的调用链:
User Space Application (modetest / program)
|
+-- open("/dev/dri/card0")
|
+-- drmIoctl(DRM_IOCTL_GET_MAGIC) // 获取 DRM 魔数
|
+-- drmIoctl(DRM_IOCTL_MODE_GETRESOURCES) // 获取 CRTC/Connector/Encoder 资源
|
+-- drmIoctl(DRM_IOCTL_MODE_GETCONNECTOR) // 获取 Connector 支持的显示模式
|
+-- drmIoctl(DRM_IOCTL_MODE_SETCRTC) // 设置 CRTC 显示模式
| +-- vop2_crtc_enable()
| +-- 启动 VOP2 显示控制器
|
+-- drmIoctl(DRM_IOCTL_MODE_GETPlane) // 获取可用 Plane
|
+-- drmIoctl(DRM_IOCTL_MODE_SETPLANE) // 设置 Plane 显示内容
| +-- vop2_plane_atomic_update()
| +-- 向 CRTC 写入帧缓冲数据
|
+-- drmIoctl(DRM_IOCTL_MODE_PAGE_FLIP) // 页面翻转
+-- vop2_crtc_atomic_flush()
+-- 提交显示帧5.4 DRM/KMS 与 Framebuffer 关系
+-----------------------------------------------------------+
| User Space Application |
+-----------------+-----------------------------------------+
| DRM Application| Framebuffer Compatibility Layer |
| (modetest) | (/dev/fb0) |
+-----------------+-----------------------------------------+
| libdrm | fb_rockchip.c |
| GBM | (auto-creates fb0 device node) |
+-----------------+-----------------------------------------+
| DRM/KMS Kernel Framework |
| /dev/dri/card0 |
+-----------------------------------------------------------+
| VOP2 Display Controller |
| vop@fe040000 |
+-----------------------------------------------------------+
| Display Interfaces |
| HDMI DP MIPI DSI eDP LVDS |
+-----------------------------------------------------------+Rockchip 的 DRM 驱动在初始化时会自动创建 Framebuffer 设备节点(/dev/fb0),该节点作为 DRM 框架的兼容层存在。传统 Framebuffer 应用可以通过 /dev/fb0 直接操作,但功能受限于单图层、无硬件合成。
6. 常见问题
6.1 显示无输出
现象:连接显示器后无信号显示。
排查步骤:
# 1. 检查设备树中 VOP2 节点状态
cat /proc/device-tree/soc/vop@fe040000/status
# 应输出:okay
# 2. 检查 HDMI 节点状态
cat /proc/device-tree/soc/hdmi@fe0a0000/status
# 应输出:okay
# 3. 检查连接状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 应输出:connected
# 4. 检查 VOP2 时钟
cat /sys/kernel/debug/clk/clk_dclk_vp0/clk_rate
# 5. 查看 DRM 驱动加载日志
dmesg | grep -i "rockchip-drm"
# 6. 检查设备树 route 节点
cat /proc/device-tree/soc/display-subsystem/route/route_hdmi/status
# 应输出:okay解决方案:确认设备树中 VOP2 和对应显示接口节点 status = "okay",检查 route 节点中 connect 属性是否正确连接到对应 VP。
6.2 HDMI 未检测到
现象:连接 HDMI 线缆后,status 始终显示 disconnected。
排查步骤:
# 1. 检查 HPD(Hot Plug Detect)引脚状态
cat /sys/kernel/debug/gpio | grep HDMI
# 2. 检查 HDMI PHY 状态
cat /sys/kernel/debug/clk/clk_hdmi_ref/clk_rate
# 3. 查看 HDMI 驱动日志
dmesg | grep -i "hdmi"
# 4. 检查 EDID 读取
cat /sys/class/drm/card0-HDMI-A-1/edid | edid-decode解决方案:检查 HPD 引脚的 pinctrl 配置是否正确,确认 HDMI PHY 时钟配置。对于不支持热插拔的显示器,可在设备树中将 HDMI connector 的 status 设置为 fixed。
6.3 分辨率不正确或显示异常
现象:显示分辨率与预期不符,或画面拉伸/压缩。
# 查看显示器支持的分辨率
cat /sys/class/drm/card0-HDMI-A-1/modes
# 使用 modetest 设置分辨率
modetest -M rockchip -s 32:1920x1080-60
# 查看当前 EDID 信息
cat /sys/class/drm/card0-HDMI-A-1/edid | edid-decode
# 查看 VOP2 当前显示时序
cat /sys/kernel/debug/dri/0/state解决方案:检查 EDID 读取是否正常,确认 HDMI 线缆支持所需带宽。对于不支持自动协商的显示器,可在设备树中手动配置显示时序(display-timings 节点)。
6.4 Framebuffer 设备节点不存在
现象:/dev/fb0 不存在,但 /dev/dri/card0 存在。
# 检查 DRM 设备
ls -la /dev/dri/
# 应看到 card0 和 renderD128
# 检查 Framebuffer 相关配置
grep "FB_ROCKCHIP\|DRM_FBDEV_EMULATION" /boot/config-$(uname -r)
# 手动创建 framebuffer 设备节点(临时方案)
mknod /dev/fb0 c 29 0解决方案:确认内核配置中启用了 CONFIG_DRM_FBDEV_EMULATION=y 和 CONFIG_FB_ROCKCHIP=y。DRM 驱动加载成功后会自动创建 /dev/fb0 设备节点。
6.5 DRM 设备权限不足
现象:非 root 用户无法打开 /dev/dri/card0。
# 检查设备权限
ls -la /dev/dri/card0
# 预期输出:crw-rw---- 1 root video ...
# 检查用户组
groups
# 当前用户应在 video 组中
# 添加用户到 video 组
sudo usermod -a -G video $USER
# 重新登录后生效解决方案:确认当前用户在 video 组中,或修改 /dev/dri/card0 的设备权限。
6.6 VOP2 驱动加载失败
现象:DRM 设备节点不存在,dmesg 中有 VOP2 相关错误。
# 查看 VOP2 驱动加载日志
dmesg | grep -i "vop2\|vop"
# 检查设备树节点状态
cat /proc/device-tree/soc/vop@fe040000/status
# 检查 VOP2 时钟
cat /sys/kernel/debug/clk/clk_aclk_vop/clk_rate
# 检查 power domain
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep vop解决方案:确认设备树中 vop2 节点 status = "okay",检查时钟和 power domain 配置是否正确。VOP2 驱动加载失败通常是设备树配置问题。
6.7 DRM Framebuffer 创建失败
现象:drmModeAddFB 或 drmModeAddFB2 返回 -ENOMEM 或 -EINVAL。
# 1. 检查像素格式是否支持
# RK3588 支持: DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888 等
# 2. 检查可用内存
cat /proc/meminfo | grep MemAvail
# 3. 检查 GEM handle 是否有效
# 确保 create_dumb 成功后再创建 FB解决方案:确认使用 Rockchip 支持的像素格式(优先使用 DRM_FORMAT_XRGB8888),检查 dumb buffer 创建是否成功,确认 GEM handle 有效。
6.8 Mali-G610 GPU 驱动问题
现象:GPU 渲染节点不存在或 EGL 初始化失败。
# 检查 GPU 驱动是否加载
lsmod | grep panfrost # 开源驱动(Linux 6.1 默认)
# 或
lsmod | grep mali # 闭源驱动
# 检查 GPU 渲染节点
ls -la /dev/dri/renderD12*
# renderD128 -> GPU 0
# 使用 eglinfo 查看 GPU 能力
eglinfo | head -30
# 检查 Mesa 版本
glxinfo | grep "OpenGL version"解决方案:Linux 6.1 内核默认使用 panfrost 开源驱动支持 Mali-G610 MP4。如需使用闭源驱动以获得更好性能,需安装 Arm 提供的 libmali 驱动包。
6.9 Weston 无法启动
现象:执行 weston 命令后无窗口显示或报错退出。
# 检查 DRM 设备
ls -la /dev/dri/card0
# 查看 Weston 日志
WESTON_DEBUG=1 weston 2>&1 | head -50
# 检查用户权限
groups # 确认在 video 组中解决方案:确认当前用户在 video 组中,DRM 设备权限正确(crw-rw---- root video)。
6.10 冻屏后画面不显示
现象:执行 echo 1 > /sys/class/graphics/fb0/blank 后画面不恢复。
# 恢复显示
echo 0 > /sys/class/graphics/fb0/blank
# 检查 fb0 状态
cat /sys/class/graphics/fb0/blank解决方案:使用 echo 0 恢复显示,或重启 Weston 合成器重新初始化显示输出。
7. 相关文档
| 文档 | 说明 |
|---|---|
| Rockchip Linux 显示开发指南 (S-01) | DRM/KMS 驱动开发参考 |
| C-3588LQ 核心板规格书 (S-02) | 核心板硬件规格 |
| SHIMETAPI-M5-R2A 底板原理图 (S-03) | 底板显示接口定义 |
| DRM/KMS 官方文档 | https://docs.kernel.org/gpu/drm-internals.html |
| libdrm API 参考 | https://gitlab.freedesktop.org/mesa/drm/ |
| Weston 合成器文档 | https://wayland.freedesktop.org/ |
| 03_视频编解码(MPP).md | MPP 视频编解码框架 |
| 04_音频开发与ALSA.md | ALSA 音频框架 |
| ch05-rga-image-processing.md | RGA 2D 图形加速 |
| 03_设备树详解与实践 | 设备树配置方法 |
| 02_内核配置与裁剪 | 内核配置方法 |
