显示框架(DRM/KMS)
目标平台: RK3576 | NPU 算力: 6 TOPS | 文档版本: V1.0
适用板型: C-3576BQ 核心板 + M4-R2 底板
最后更新: 2026-10-23
1. 概述
1.1 功能简介
RK3576 的显示框架基于 Linux DRM/KMS(Direct Rendering Manager / Kernel Mode Setting)子系统构建。DRM/KMS 是 Linux 内核中统一管理 GPU 和显示控制器的标准框架,负责帧缓冲分配、显示时序控制、图层合成和多屏管理。RK3576 采用 Rockchip VOP(Video Output Processor)系列显示控制器,配合 Mali-G52 MC3 GPU 实现图形渲染与多屏输出。
与传统 Framebuffer 设备(/dev/fb*)相比,DRM/KMS 提供更强大的硬件抽象能力,包括 Atomic Mode Setting(原子模式设置)、硬件 Plane 合成、DMA-BUF 缓冲区共享和 DRM GEM(Graphics Execution Manager)内存管理。现代 Linux 发行版已全面转向 DRM/KMS,Framebuffer 仅作为兼容层保留。
核心组件:
| 组件 | 说明 |
|---|---|
| VOP 显示控制器 | Rockchip VOP 系列,具体寄存器基地址以实际发布的 RK3576 SDK 设备树为准 |
| HDMI 控制器 | 1 路,Type-A 母座,最高 4K×2K(规格书标注 4Kx2K@120Hz) |
| MIPI DSI 控制器 | 1 路,40pin/0.5mm FPC,4-Lane,实测支持 720×1280 |
| Mali-G52 MC3 | GPU,支持 OpenGL ES 1.1/2.0/3.2、OpenCL 2.0、Vulkan 1.1,内嵌高性能 2D 加速 |
| DRM/KMS 内核框架 | 管理 CRTC、Plane、Encoder、Connector 等抽象对象 |
说明: RK3576 内核镜像中显示控制器节点、寄存器基地址、中断号与时钟标识以实际发布的 RK3576 SDK 设备树为准(待板上确认),本文档以通用结构
vop@<base_addr>等占位符表示。
显示接口能力(依据 M4-R2 规格书):
| 接口 | 最大分辨率 | 协议版本 | 物理接口 | 备注 |
|---|---|---|---|---|
| HDMI | 4096×2160(标注 4Kx2K@120Hz) | HDMI 1.4 / 2.0 | Type-A 母座 | 支持 1920x1080、1280x720、720x576、720x480 等 |
| MIPI DSI | 2560×1600(标注最大值) | MIPI DSI 2.0 | FPC 连接器(4-Lane) | 实测已支持 720×1280,可直接驱动多种分辨率 MIPI 液晶屏 |
实际可用的最大刷新率与分辨率受显示器 EDID、线缆质量和系统驱动配置影响,需在板端实测确认(待板上验证)。
1.2 应用场景
| 应用场景 | 显示接口 | 说明 |
|---|---|---|
| 双屏异显 | HDMI + MIPI DSI | 同时输出不同内容 |
| 桌面系统 | HDMI + Weston | Linux 桌面图形界面 |
| 数字标牌 | HDMI | 广告机、信息发布系统 |
| 工业 HMI | MIPI DSI | 工业控制触摸屏界面 |
| 视频播放 | HDMI + DRM Direct Show | 硬件解码 + DRM 直接显示 |
| 嵌入式 GUI | MIPI DSI / HDMI | 嵌入式设备人机交互 |
1.3 工作原理
RK3576 DRM/KMS 显示系统硬件架构:
+--------------------------------------------------------------+
| Application Layer |
| libdrm / GBM / EGL / Mali GPU / Weston / X11 |
+--------------------------------------------------------------+
| Kernel DRM/KMS Driver Layer |
| +----------------------------------------------------+ |
| | VOP Display Controller (RK3576) | |
| | (Video Port 数量、基地址以 SDK 设备树为准) | |
| +-----|--------|-------------------------------------+ |
| | | |
| +-----|--------|----------------------------+ |
| | Encoder / Connector Layer | |
| | HDMI MIPI DSI | |
| +---------------------|---------------------+ |
+--------------------------|-----------------------------+
+-------------+-----------+
| Display Device |
+-------------------------+DRM/KMS 核心对象关系:
+--------------------------------------------------------------+
| DRM Device Node |
| /dev/dri/card0 |
+--------------------------------------------------------------+
| |
| Connector ------> Encoder ------> CRTC |
| (Physical I/F) (Protocol) (Display Controller) |
| HDMI-A-1 dw_hdmi VP <n> |
| DSIA dw_mipi_dsi2 VP <n> |
| |
| CRTC <--------- Plane (Layer) |
| (VP<n>) Cluster / Esmart (具体以 SDK 内核为准) |
| |
| Plane <--------- Framebuffer (Framebuffer Object) |
| GEM / DMA-BUF |
+--------------------------------------------------------------+DRM 子系统核心概念:
| 概念 | 说明 | RK3576 对应 |
|---|---|---|
| CRTC | 显示控制器抽象,管理显示时序和 Plane 合成 | VOP 中的 Video Port |
| Plane | 硬件图层,支持多种像素格式和缩放 | Cluster / Esmart / Smart 等 |
| Encoder | 将像素数据编码为特定接口协议信号 | dw_hdmi、dw_mipi_dsi2 等 |
| Connector | 物理显示接口,支持热插拔检测 | HDMI-A、DSI 等 |
| Bridge | Encoder 与 Panel 之间的桥接设备 | HDMI bridge、DSI bridge |
| Framebuffer | 帧缓冲对象,存储像素数据 | GEM 对象,支持 DMA-BUF 共享 |
| GEM | DRM 缓冲区管理与分配 | 类似 ION / DMA-BUF heap |
Framebuffer 设备 vs DRM 设备:
| 对比项 | Framebuffer (/dev/fb*) | DRM (/dev/dri/card0) |
|---|---|---|
| 硬件抽象 | 简单的像素缓冲区 | CRTC + Plane + Encoder + Connector |
| 模式设置 | 内核固定分辨率 | Atomic Mode Setting(用户态控制) |
| 多图层 | 不支持(单层) | 支持多硬件 Plane 合成 |
| 缓冲区管理 | mmap 直接访问 | GEM / DMA-BUF / GBM |
| 热插拔 | 不支持 | 原生支持 |
| GPU 合成 | 不支持 | 支持 EGL/Vulkan 渲染输出 |
| 用途 | 嵌入式简单显示 | 现代图形系统 |
RK3576 同其它 Rockchip 平台一样,同时支持两种模式:DRM 设备节点
/dev/dri/card0用于 DRM/KMS 框架,Framebuffer 设备节点/dev/fb0用于传统兼容模式(需启用CONFIG_DRM_FBDEV_EMULATION)。
2. 硬件准备
2.1 硬件清单
| 序号 | 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|---|
| 1 | 核心板 | C-3576BQ(RK3576) | 1 | 含 RK3576 SoC、LPDDR5 4GB、eMMC 32GB |
| 2 | 底板 | ShimetaPi M4-R2 V10 | 1 | 含 HDMI、MIPI DSI、MIPI Camera 接口 |
| 3 | 电源适配器 | 5V/2A(Type-C 或 6pin) | 1 | 空载最小 1000mA |
| 4 | HDMI 显示器 | 支持 HDMI 1.4+ | 1 | 用于 HDMI 输出测试 |
| 5 | HDMI 线缆 | HDMI 1.4 / 2.0 | 1 | 建议使用认证线缆 |
| 6 | MIPI DSI 屏(可选) | 支持 MIPI DSI,4-Lane | 1 | 用于 DSI 输出测试(实测支持 720×1280) |
| 7 | 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| 8 | TF 卡 | 32GB+,Class10 | 1 | 系统存储 |
2.2 显示接口引出
M4-R2 底板引出以下显示接口(参考 S-04 底板资料):
| 接口 | 说明 |
|---|---|
| HDMI TX | 1 路,标准 HDMI Type-A 母座(S-01 规格书/S-04 底板标注) |
| MIPI DSI | 1 路,40pin/0.5mm 立式 FPC 插座,4-Lane |
| MIPI Camera | 1 路,30pin/0.5mm FPC 插座,最高支持 1600W 摄像头(用于摄像头应用,见 ch02) |
注意:M4-R2 板卡只引出 1 路 HDMI 和 1 路 MIPI DSI 输出,无 DP / eDP 接口。具体连接器编号请查阅 S-04 底板资料。
2.3 硬件确认
# 查看 DRM 设备节点
ls /sys/class/drm/
# 输出示例:card0 card0-HDMI-A-1 card0-DSIA 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
# 查看 VOP 显示控制器设备树状态(节点名以实际 RK3576 SDK 设备树为准)
cat /proc/device-tree/soc/vop@<base_addr>/status
# 预期输出:okay
# 查看 HDMI 控制器设备树状态
cat /proc/device-tree/soc/hdmi@<base_addr>/status
# 预期输出:okay
# 查看 DSI 控制器设备树状态
cat /proc/device-tree/soc/dsi@<base_addr>/status
# 查看 framebuffer 信息
ls /dev/fb*
cat /sys/class/graphics/fb0/virtual_size
# 查看 DRM 设备版本
cat /sys/class/drm/version
# 检查 Mali-G52 GPU 设备
ls /dev/dri/renderD128
cat /sys/class/misc/mali0/device/uevent
vop@<base_addr>、hdmi@<base_addr>、dsi@<base_addr>为通用结构占位符,具体节点名与寄存器基地址以实际发布的 RK3576 SDK 设备树为准(待板上确认)。
3. 软件准备
3.1 依赖安装
# 安装 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
# 安装 Weston 配置工具
apt-get install -y weston-init
# 确认 Mali-G52 GPU 驱动
# RK3576 使用 panfrost(开源)或 mali(闭源)驱动,具体以实际发布 SDK 为准
# apt-get install -y libmali # 闭源驱动(如有提供)3.2 内核配置
在内核配置中启用 DRM/KMS 相关驱动(具体配置项名称以实际发布的 RK3576 SDK 内核为准):
# DRM 核心框架
CONFIG_DRM=y
CONFIG_DRM_ROCKCHIP=y
# VOP 显示控制器(RK3576 SDK 内核中的具体配置项)
CONFIG_ROCKCHIP_VOP2=y
# HDMI 输出
CONFIG_ROCKCHIP_HDMI=y
CONFIG_DRM_ROCKCHIP_ANALOGIX_HDMI=y
# MIPI DSI 输出
CONFIG_ROCKCHIP_DW_MIPI_DSI=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=y查看当前内核配置:
# 查看 VOP 配置
grep -i "VOP\|ROCKCHIP" /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 vop dw_hdmi 等模块3.3 设备树配置
以下为 RK 平台显示控制器设备树结构的通用示例,寄存器基地址、中断号与时钟标识均以实际发布的 RK3576 SDK 设备树为准(待板上确认):
vop: vop@<base_addr> {
compatible = "rockchip,rk3576-vop"; /* 具体 compatible 以 SDK 为准 */
reg = <0x0 <base_addr> 0x0 0x2000>;
reg-names = "regs", "gamma_luts";
interrupts = <GIC_SPI <irq> IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_VOP>, <&cru HCLK_VOP>,
<&cru DCLK_VOP0>, <&cru DCLK_VOP1>;
clock-names = "aclk", "hclk",
"dclk_vp0", "dclk_vp1";
power-domains = <&power RK3576_PD_VOP>;
status = "disabled";
};HDMI 控制器设备树通用结构:
hdmi0: hdmi@<base_addr> {
compatible = "rockchip,rk3576-hdmi"; /* 具体 compatible 以 SDK 为准 */
reg = <0x0 <base_addr> 0x0 0x20000>;
interrupts = <GIC_SPI <irq> IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_HDMI_TX>, <&cru CLK_HDMI_REF>,
<&cru CLK_HDMI_TX>;
clock-names = "pclk", "ref", "phy";
rockchip,phy = <&hdmi_phy>;
pinctrl-names = "default";
pinctrl-0 = <&hdmi0_tx_cec &hdmim0_tx_hpd>;
status = "disabled";
};MIPI DSI 控制器设备树通用结构:
dsi0: dsi@<base_addr> {
compatible = "rockchip,rk3576-mipi-dsi"; /* 具体 compatible 以 SDK 为准 */
reg = <0x0 <base_addr> 0x0 0x10000>;
interrupts = <GIC_SPI <irq> 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";
power-domains = <&power RK3576_PD_VOP>;
rockchip,grf = <&sysgrf>;
status = "disabled";
};以上设备树节点仅为平台通用结构示例。RK3576 的实际寄存器基地址、中断号、时钟与 reset 标识、IOMMU 与电源域引用等,均需以实际发布的 RK3576 SDK 设备树源码为准(待板上确认),切勿直接套用其它平台的数值。
3.4 display_subsystem 路由配置
启用 HDMI + MIPI DSI 双屏路由配置(节点名与 connect 属性以 SDK 设备树为准):
display_subsystem: display-subsystem {
compatible = "rockchip,display-subsystem";
ports = <&vop_out>;
route {
route_hdmi: route-hdmi {
status = "okay";
logo,uboot = "logo.bmp";
logo,kernel = "logo_kernel.bmp";
logo,mode = "center";
};
route_dsi0: route-dsi0 {
status = "okay";
logo,uboot = "logo.bmp";
logo,kernel = "logo_kernel.bmp";
logo,mode = "center";
};
};
};VP 与显示接口连接关系配置:
&hdmi0 {
status = "okay";
};
&dsi0 {
status = "okay";
rockchip,lane-rate = <1500>;
};
&route_hdmi {
status = "okay";
};
&route_dsi0 {
status = "okay";
};一个显示接口在同一时刻只能连接一个 VP。在板级配置中,需要把不使用的通路设置为
disabled状态。具体connect通路名称以 SDK 设备树为准。
3.5 Weston 合成器配置
安装 Weston:
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
# 动态配置文件
echo "[output]
name=HDMI-A-1
mode=1920x1080-60" > /tmp/.weston_drm.conf动态配置文件
/tmp/.weston_drm.conf在运行时被 Weston 监控,修改后自动生效,无需重启 Weston 进程(需 Weston 版本支持该特性)。
4. 操作步骤
4.1 DRM 基础测试
# 查看 DRM 设备信息
modetest -M rockchip -c
# 查看所有 connector 信息(包含分辨率列表)
modetest -M rockchip -c | grep -A 30 "Connectors:"
# 测试 HDMI 1080p 输出(connector ID 需根据实际 modetest 输出确定)
modetest -M rockchip -s 32:1920x1080-60 -P 38:1920x1080
# 测试 HDMI 4K 输出(需显示器与驱动支持,待板上验证)
modetest -M rockchip -s 32:3840x2160-60 -P 38:3840x2160
# 使用 solid color 测试平面
modetest -M rockchip -r -s 32:1920x1080-60 -P 38:1920x1080 -F solid
# 查看 VOP 当前状态信息
cat /sys/kernel/debug/dri/0/state命令中的 connector ID(示例 32)与 plane ID(示例 38)只是示例,实际 ID 需根据本机
modetest -M rockchip -c的输出确定。
4.2 DRM 设备节点检查
# 查看 DRM 设备信息
ls -la /dev/dri/
# card0 - DRM 主设备节点
# renderD128 - GPU 渲染节点
# 查看 framebuffer 设备节点
ls -la /dev/fb*
# fb0 - 传统 framebuffer 兼容节点
# 查看 VOP 时钟频率(节点名以实际 SDK 内核为准)
cat /sys/kernel/debug/clk/clk_dclk_vp0/clk_rate
# 查看 ACLK VOP 时钟(内存带宽相关)
cat /sys/kernel/debug/clk/clk_aclk_vop/clk_rate
# 查看 DRM debug 信息
cat /sys/kernel/debug/dri/0/name
cat /sys/kernel/debug/dri/0/state4.3 验证 HDMI 输出
# 查看 HDMI 连接状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 应输出: connected
# 查看 HDMI 支持的分辨率
cat /sys/class/drm/card0-HDMI-A-1/modes
# 查看 EDID 信息
cat /sys/class/drm/card0-HDMI-A-1/edid | edid-decode
# 强制设置 HDMI 分辨率
xrandr --output HDMI-1 --mode 1920x1080 --rate 60
# 使用 modetest 设置 HDMI 输出
modetest -M rockchip -s 32:1920x1080-60 -P 38:1920x1080M4-R2 规格书标注 HDMI 最高支持 4096×2160(4Kx2K@120Hz)。实际可用的最大分辨率与刷新率受显示器、线缆及驱动配置影响,需在板端实测(待板上验证)。
4.4 配置 MIPI DSI 输出
# 查看 DSI 连接状态
cat /sys/class/drm/card0-DSIA/status
# 查看 DSI 支持的模式
cat /sys/class/drm/card0-DSIA/modes
# DSI 屏幕需要在设备树中配置 panel-timing
# 常见方式:在 DTS 中添加 panel 节点,如 panel-simple 或厂家自定义驱动
dmesg | grep -i panelM4-R2 规格书标注 MIPI 屏最大支持 2560×1600,实测已支持 720×1280。接入特定 LCD 模组时,需在设备树中配置对应的 panel 时序(panel-timing)并确认背光与复位引脚配置。
4.5 多屏同时显示配置
M4-R2 板卡引出 1 路 HDMI + 1 路 MIPI DSI,规格书声明支持双屏显示、双屏异显:
# 查看当前所有连接器状态
for f in /sys/class/drm/card0-*/status; do
echo "$(dirname $f | xargs basename): $(cat $f)"
done
# 示例输出:
# card0-HDMI-A-1: connected
# 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 设备操作
# 查看 framebuffer 信息
cat /sys/class/graphics/fb0/virtual_size
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使用 Python 写入 framebuffer 测试图案:
#!/usr/bin/env python3
"""
fb_test.py - Framebuffer 测试图案写入工具
适用平台: RK3576 (ShimetaPi M4-R2), Linux 6.1
"""
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("RK3576 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_fb_test.c
* RK3576 (ShimetaPi M4-R2) DRM/KMS framebuffer 操作示例
*
* 编译: gcc -o drm_fb_test drm_fb_test.c -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_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;
res->pitch = ((res->width * bpp + 255) >> 8) << 8;
/* 创建 dumb buffer */
creq.width = res->width;
creq.height = res->height;
creq.bpp = bpp;
creq.pitch = res->pitch;
if (drmIoctl(res->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq)) {
perror("create dumb buffer");
return -1;
}
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("RK3576 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;
}4.8 Atomic Mode Setting 示例
/*
* drm_atomic_test.c
* RK3576 (ShimetaPi M4-R2) DRM Atomic Mode Setting 示例
* 展示如何使用 atomic commit 进行模式设置
*
* 编译: gcc -o drm_atomic_test drm_atomic_test.c -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 */
drmModeObjectRes *obj = drmModeGetObjectProperties(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("RK3576 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;
}4.9 Weston/Wayland 合成器启动
# 基础启动(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=1920x1080@60
[output]
name=DSIA
mode=720x1280@60
transform=90
EOF
# 启动 Weston
weston4.9.1 测试 Weston 显示输出
启动 Weston 后,运行以下测试程序验证显示输出:
# 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=720x1280@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:"旋转 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 内核驱动结构
Rockchip DRM 显示驱动源码目录(以 RK3576 Linux SDK 内核为准):
drivers/gpu/drm/rockchip/ # Rockchip DRM 核心驱动
drivers/gpu/drm/bridge/synopsys/ # HDMI / MIPI DSI 桥接驱动
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 显示 |
| VOP | rockchip_drm_vop.c / vop2*.c | VOP 显示控制器驱动 |
| HDMI | dw_hdmi.c | DesignWare HDMI 驱动 |
| HDMI | rockchip_dw_hdmi.c | Rockchip HDMI 封装 |
| MIPI DSI | dw-mipi-dsi2.c | DesignWare MIPI DSI 驱动 |
| Panel | panel-simple.c | 通用 Panel 驱动 |
| FB | fb_rockchip.c | 传统 Framebuffer 驱动 |
驱动文件具体名称与版本以实际发布的 RK3576 Linux SDK 为准。
5.2 驱动加载流程
Kernel boot up
|
+-- reserved-memory: drm_logo (保留 logo 显示缓冲区)
|
+-- 各显示设备 probe(顺序无关,使用 deferred probe)
| +-- dw_hdmi_rockchip_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
| +-- dw_hdmi_rockchip_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),Linux kernel 会在稍后重新尝试加载该驱动,直到所有依赖资源就绪。
具体启动日志中的 base 地址、设备名与模块名以 RK3576 SDK 内核实际输出为准。
5.3 用户空间调用链
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 显示模式
| +-- vop_crtc_enable()
| +-- 启动 VOP 显示控制器
|
+-- drmIoctl(DRM_IOCTL_MODE_GETPlane) // 获取可用 Plane
|
+-- drmIoctl(DRM_IOCTL_MODE_SETPLANE) // 设置 Plane 显示内容
| +-- vop_plane_atomic_update()
| +-- 向 CRTC 写入帧缓冲数据
|
+-- drmIoctl(DRM_IOCTL_MODE_PAGE_FLIP) // 页面翻转
+-- vop_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 |
+-----------------------------------------------------------+
| VOP Display Controller |
| vop@<base_addr>(以 SDK 设备树为准) |
+-----------------------------------------------------------+
| Display Interfaces |
| HDMI MIPI DSI |
+-----------------------------------------------------------+Rockchip 的 DRM 驱动在初始化时会自动创建 Framebuffer 设备节点(/dev/fb0),该节点作为 DRM 框架的兼容层存在。传统 Framebuffer 应用可以通过 /dev/fb0 直接操作,但功能受限于单图层、无硬件合成。
6. 常见问题
6.1 显示无输出
现象: 连接显示器后无信号显示。
排查步骤:
# 1. 检查设备树中 VOP 节点状态(节点名以实际 SDK 设备树为准)
cat /proc/device-tree/soc/vop@<base_addr>/status
# 应输出:okay
# 2. 检查 HDMI 节点状态
cat /proc/device-tree/soc/hdmi@<base_addr>/status
# 应输出:okay
# 3. 检查连接状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 应输出:connected
# 4. 检查 VOP 时钟
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解决方案: 确认设备树中 VOP 和对应显示接口节点 status = "okay",检查 route 节点中 connect 属性是否正确连接到对应 VP。若为 MIPI DSI 屏,还需确认 panel 节点与背光、复位引脚配置正确。
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。确认电源供电满足要求(M4-R2 采用 5V/2A 供电,空载最小 1000mA)。
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
# 查看 VOP 当前显示时序
cat /sys/kernel/debug/dri/0/state解决方案: 检查 EDID 读取是否正常,确认 HDMI 线缆支持所需带宽。M4-R2 规格书标注 HDMI 最高支持 4096×2160,实际最大分辨率需在板端实测(待板上验证)。对于不支持自动协商的显示器,可在设备树中手动配置显示时序(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 VOP 驱动加载失败
现象: DRM 设备节点不存在,dmesg 中有 VOP 相关错误。
# 查看 VOP 驱动加载日志
dmesg | grep -i "vop"
# 检查设备树节点状态
cat /proc/device-tree/soc/vop@<base_addr>/status
# 检查 VOP 时钟
cat /sys/kernel/debug/clk/clk_aclk_vop/clk_rate
# 检查 power domain
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep vop解决方案: 确认设备树中 vop 节点 status = "okay",检查时钟和 power domain 配置是否正确。VOP 驱动加载失败通常是设备树配置问题。
6.7 DRM Framebuffer 创建失败
现象: drmModeAddFB 或 drmModeAddFB2 返回 -ENOMEM 或 -EINVAL。
# 1. 检查像素格式是否支持
# RK3576 支持: 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-G52 GPU 驱动问题
现象: GPU 渲染节点不存在或 EGL 初始化失败。
# 检查 GPU 驱动是否加载
lsmod | grep panfrost # 开源驱动
# 或
lsmod | grep mali # 闭源驱动
# 检查 GPU 渲染节点
ls -la /dev/dri/renderD12*
# renderD128 -> GPU 0
# 使用 eglinfo 查看 GPU 能力
eglinfo | head -30
# 检查 Mesa 版本
glxinfo | grep "OpenGL version"解决方案: RK3576 使用 Mali-G52 MC3 GPU,具体采用 panfrost 开源驱动还是 mali 闭源驱动(libmali),以实际发布的 RK3576 SDK 为准。如需使用闭源驱动以获得更好性能,需安装 SDK 提供的 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. 相关文档
| 文档名称 | 路径 | 说明 |
|---|---|---|
| 摄像头应用 | 摄像头应用 | MIPI/USB 摄像头与预览 |
| 视频编解码 (MPP) | 视频编解码(MPP) | MPP 视频编解码框架 |
| 音频开发与 ALSA | 音频开发与ALSA | ALSA 音频框架 |
| RGA 图像处理 | RGA图像处理 | RGA 2D 图形加速 |
| 内核配置与裁剪 | 内核配置与裁剪 | 内核配置方法 |
| 设备树详解与实践 | 设备树详解与实践 | 设备树配置方法 |
| Linux SDK 编译 | Linux_SDK编译 | SDK 获取与编译 |
| RKNN 模型转换与量化 | 模型转换与量化 | ONNX → RKNN 模型转换 |
