RK3588 开机 Logo 为什么能无缝显示?从 U-Boot、VOP2 到 Kernel DRM 的一次实战总结


📺 B站 嵌入式孙老师博主个人介绍
📘 博主书籍-京东购买链接Yocto项目实战教程
📘 加博主微信,进技术交流群jerrydev


最近在调试 RK3588 开机 Logo 时,我遇到了一组看起来互相矛盾的现象:eDP Link Training 已经成功,VOP2 也配置到了 2560×1600@60Hz,Kernel DRM 最终能够正常启动,但 U-Boot 阶段的 Logo 仍然无法显示。
在这里插入图片描述

排查过程中,我先后碰到了几个比较典型的问题:

  • U-Boot 已经读取到 BMP,为什么屏幕仍然没有 Logo?
  • Linux Kernel 尚未启动,U-Boot 是如何独立完成显示初始化的?
  • U-Boot 使用的 Framebuffer,与 Linux 中的 /dev/fb0 是什么关系?
  • VOP2、Framebuffer、eDP 和 DRM 分别负责什么?
  • Kernel DRM 重新初始化显示时,U-Boot Logo 为什么可以继续保持?
  • 一张约 11.7 MiB 的 2560×1600 BMP,为什么在 32 MiB 内存配置下仍然解码失败?

这些问题表面上分散在 U-Boot、DRM、VOP2、Framebuffer 和背光等不同模块中,实际都属于同一条开机显示链路。如果没有先理清各层之间的关系,很容易在 DTS、显存、Kernel 驱动和硬件之间反复排查。

最终定位发现,这次 Logo 不显示的直接原因并不在 VOP2 或 eDP,而是大分辨率 BMP 解码时,U-Boot 通用堆的峰值内存不足。

这篇文章就是对这次实战调试的一次总结。下面从驱动原理入手,梳理 U-Boot 如何显示 Logo、VOP2 如何读取 Framebuffer,以及画面如何平滑交给 Kernel DRM。

一、U-Boot 为什么能够显示 Logo

显示不是 Kernel 的专属能力

显示一张静态图片,本质上只需要完成以下工作:

读取并解码 BMP
        ↓
写入 DDR Framebuffer
        ↓
配置 VOP2 读取 Framebuffer
        ↓
配置 eDP 和 Panel
        ↓
打开背光

Rockchip SDK 的 U-Boot 内置了一套精简的 DRM 显示驱动,可以完成以上流程。因此,在 Linux Kernel 尚未启动时,U-Boot 就能够显示开机 Logo。

相关主流程可以简化为:

rockchip_show_logo()
    ├── load_bmp_logo()   读取并解码BMP
    └── display_logo()    配置Framebuffer和VOP2

U-Boot 负责准备第一帧画面。配置完成后,VOP2 会自行从 DDR 中持续读取像素并输出,CPU 不需要逐帧刷新。

U-Boot 可以复用 Kernel DTB

在 Rockchip SDK 中,U-Boot 可以使用 Kernel 构建出的 DTB,从中读取:

  • Panel 显示时序;
  • VOP2 到 eDP 的显示通路;
  • Panel enable GPIO;
  • PWM backlight;
  • regulator 和 pinctrl 配置。

但是,U-Boot 和 Kernel 使用的并不是同一套驱动:

同一份DTB
   ├── U-Boot自己的DRM、GPIO、PWM驱动解析
   └── Kernel自己的DRM、GPIO、PWM驱动解析

所以准确来说:

配置可以共用,驱动不能共用,显示硬件由 U-Boot 和 Kernel 分阶段接管。

这也意味着,如果 DTB 中已经使能 pwm-backlight,U-Boot 显示 Logo 时就可能拉高背光 GPIO、启动 PWM 和背光电源,而不是等到 Kernel 启动后才操作背光。

二、Framebuffer、VOP2 与 DRM 的关系

VOP2 是显示控制器硬件

VOP2 全称为 Video Output Processor 2,是集成在 RK3588 SoC 内部的显示控制器硬件。

它位于 DDR Framebuffer 和显示接口之间:

CPU / GPU / RGA生成画面
           ↓
DDR Framebuffer
           ↓
VOP2 Plane读取图像
           ↓
VOP2 VP完成合成、缩放和格式转换
           ↓
eDP / HDMI / DP / MIPI-DSI
           ↓
屏幕

Framebuffer 只是一块保存像素的内存。

如果没有 VOP2 这样的显示控制器持续读取,即使 Framebuffer 中已经存在一张完整图片,也不会自动显示到屏幕上。

VOP2 主要负责:

  • 从 DDR 读取 Framebuffer;
  • 管理多个显示 Plane;
  • 图层合成和 Alpha 混合;
  • 图像缩放;
  • RGB、YUV 格式转换;
  • 色彩空间转换;
  • 生成显示时序;
  • 把最终画面送到 eDP、HDMI、DP 或 MIPI-DSI。

下面是一组典型的 U-Boot 日志:

VOP update mode to: 2560x1600p60, type: eDP0 for VP0
VOP VP0 enable Esmart0[2560x1600->2560x1600@0x0] fmt[0] addr[0xedf00000]
Link Training success!

可以拆解为:

日志内容含义
2560x1600p60输出分辨率为2560×1600@60Hz
eDP0 for VP0VOP2的VP0连接到eDP0
Esmart0使用VOP2的一个显示Plane
2560x1600->2560x1600输入和输出尺寸相同,没有缩放
addr[0xedf00000]Framebuffer物理地址
Link Training successeDP高速链路训练成功

这说明 U-Boot 已经把 Framebuffer 地址、显示尺寸和输出通路配置给 VOP2,并成功建立了 eDP 链路。

Framebuffer 不等于 /dev/fb0

Framebuffer 的广义含义是一块存放像素的内存,而 /dev/fb0 是 Linux 传统 fbdev 框架暴露出来的设备节点。

现代 RK3588 系统通常使用 DRM/KMS:

Weston / 图形应用
        ↓
/dev/dri/card0
        ↓
DRM GEM / DMA-BUF
        ↓
DRM Framebuffer + Plane + CRTC
        ↓
VOP2

因此:

没有 /dev/fb0
不代表没有 Framebuffer
也不代表 Kernel DRM 没有工作

/dev/fb0 如果存在,很多时候也是 DRM fb-helper 提供的兼容接口。现代 Weston 系统一般直接使用 /dev/dri/card0,不依赖 /dev/fb0

U-Boot Framebuffer 和 Kernel DRM 的效率

U-Boot Framebuffer 与 Kernel DRM Framebuffer 的底层原理相同:

DDR Framebuffer → VOP2 → eDP → LCD

对于一张静态 Logo,VOP2 开始工作后,由硬件持续扫描 Framebuffer。无论最初是 U-Boot 还是 Kernel DRM 配置,持续输出效率都没有本质区别。

Kernel DRM 的优势主要体现在:

  • 多 Buffer 切换;
  • Atomic Commit;
  • VBlank 同步;
  • DMA-BUF 零拷贝;
  • 多 Plane 合成;
  • GPU、RGA、VPU 共享 Buffer;
  • 动态电源和带宽管理。

因此,U-Boot 适合尽早显示静态 Logo,Kernel DRM 则适合桌面、动画和视频等动态显示场景。

三、Logo 如何从 U-Boot 交给 Kernel

两套驱动之间传递的是物理内存

U-Boot 和 Kernel 是两个独立的软件阶段,它们不能共享驱动对象,但是可以通过 DTB 传递 Logo Framebuffer 的物理地址和大小。

设备树中通常预留一个占位节点:

reserved-memory {
    drm_logo: drm-logo@0 {
        compatible = "rockchip,drm-logo";
        reg = <0x0 0x0 0x0 0x0>;
    };
};

这里的:

reg = <0x0 0x0 0x0 0x0>;

只是一个占位值。

U-Boot 成功解码并显示 Logo 后,会在跳转 Kernel 前,把真实的 Framebuffer 地址和大小写回 DTB。

完整过程如下:

U-Boot读取并解码Logo
        ↓
将像素写入DDR
        ↓
配置VOP2开始显示
        ↓
把Logo地址和大小写入Kernel DTB
        ↓
Kernel保护drm-logo reserved-memory
        ↓
Kernel DRM初始化并接管VOP2
        ↓
Weston提交新的DRM Buffer
        ↓
Kernel释放U-Boot Logo内存

实测日志中可以看到:

VOP ... addr[0xedf00000]

随后 U-Boot 打印:

## reserved-memory:
  drm-logo@0: addr=edf00000 size=fa0000

Kernel 接管完成后又打印:

[    7.303767] Freeing drm_logo memory: 16000K

三处信息完全对应:

Framebuffer地址:0xEDF00000
Framebuffer大小:0xFA0000
换算大小:16000 KiB

说明这块内存最初由 U-Boot 保存并显示 Logo,Kernel 启动时先保护它,完成 DRM 接管后再释放。

无缝显示不等于 Kernel 不初始化 DRM

所谓无缝显示,不是:

U-Boot初始化显示
        ↓
Kernel跳过DRM初始化

而是:

U-Boot建立显示
        ↓
Kernel启动时保持旧画面
        ↓
Kernel DRM重新初始化并接管

Linux Kernel 仍然必须初始化自己的 DRM 驱动,因为后续 Weston、Qt 和视频应用都需要完整的 DRM/KMS 能力。

为了减少黑屏和闪屏,需要满足几个条件:

  • U-Boot 跳转前不能主动关闭 VOP2;
  • eDP、Panel 和背光状态需要保持;
  • U-Boot 与 Kernel 使用相同的分辨率和时序;
  • Logo Framebuffer 必须通过 reserved-memory 保护;
  • Kernel DRM 接管前不能覆盖这块内存。

背光如何接管

背光不像 Framebuffer 那样有一块内存可以传递,它主要依赖硬件状态保持:

U-Boot:
GPIO = High
PWM = Enable
背光电源 = On
        ↓
进入Kernel时保持状态
        ↓
Kernel pwm-backlight驱动重新接管

如果 Kernel probe 时执行了一次:

Disable → 重新配置 → Enable

屏幕就可能短暂变黑。

因此,Logo 无缝显示不仅取决于 Framebuffer,还取决于 Panel、PWM、GPIO 和背光电源的接管过程。

四、一次 2K Logo 解码失败的实战分析

BMP 文件本身没有损坏

本次使用的是一张 2560×1600、24bpp BMP,文件大小为:

2560 × 1600 × 3 + 54
= 12,288,054 Byte
≈ 11.7 MiB

U-Boot 日志为:

DBG: bmp[logo.bmp] read len=12288054

实际读取长度与理论文件大小完全一致。

进一步检查 BMP Header:

42 4d

对应 ASCII 字符:

BM

宽度、有效高度、位深和像素偏移也都正确,因此可以排除文件损坏和读取不完整的问题。

解码后的内存比BMP文件更大

虽然原始 BMP 只有约 11.7 MiB,但 U-Boot 解码后统一按 32bpp 保存:

2560 × 1600 × 4
= 16,384,000 Byte
≈ 15.625 MiB

一次 Logo 解码过程中,同时存在三块 Buffer:

Buffer大小所属内存池
原始BMP读取缓冲20 MiBU-Boot通用堆
libnsbmp解码缓冲约15.625 MiBU-Boot通用堆
最终显示Buffer约15.625 MiBDRM保留内存池

关键问题在于,原始 BMP 缓冲和 libnsbmp 解码缓冲会同时存在:

20 MiB + 15.625 MiB
≈ 35.625 MiB

而平台原来的 U-Boot 通用堆只有:

#define CONFIG_SYS_MALLOC_LEN (32 << 20)

因此在 libnsbmp 执行:

calloc(width * height, 4);

时,通用堆已经无法再提供约 15.6 MiB 连续内存,最终返回:

BMP_INSUFFICIENT_MEMORY

这就是 Logo 已经成功读取,但仍然无法完成显示的直接原因。

两个32 MiB不是同一块内存

这里最容易混淆的是两个配置:

配置用途
CONFIG_SYS_MALLOC_LENU-Boot通用堆
CONFIG_DRM_MEM_RESERVED_SIZE_MBYTESDRM最终显示Buffer使用的内存池

它们即使都配置为 32 MiB,也属于两个不同的内存池。

本次 BMP 解码失败发生在 U-Boot 通用堆中,因此单独增大 DRM 显存池并不能解决问题。

同样,MAX_IMAGE_BYTES 主要决定原始 BMP 读取缓冲的申请上限;解码后的:

宽 × 高 × 4

是另外一笔内存。

所以不能只根据 BMP 文件大小判断 U-Boot 堆是否足够。

修复方法

对于 DDR 容量充足的 RK3588,最直接的处理是将板级 U-Boot 通用堆提高到 64 MiB:

#undef CONFIG_SYS_MALLOC_LEN
#define CONFIG_SYS_MALLOC_LEN (64 << 20)

修改后重点验证:

1. BMP读取长度正确
2. 不再出现failed to parse bmp
3. VOP2打印Plane和Framebuffer地址
4. eDP Link Training成功
5. Kernel识别到非零的drm-logo reserved-memory
6. Kernel DRM接管后释放旧Logo内存
7. 屏幕实际显示正常且没有闪屏

如果已经看到:

VOP update mode...
VOP VP0 enable...
Link Training success!

但屏幕仍然黑,就不应该继续只检查 BMP 和 DRM,而应转向:

  • Panel供电;
  • Panel enable GPIO;
  • 背光 enable GPIO;
  • PWM输出;
  • 背光升压电源;
  • eDP实际硬件连接。

总结

RK3588 的开机 Logo 显示可以归纳为一条清晰的驱动链路:

BMP文件
   ↓ U-Boot读取和解码
DDR Framebuffer
   ↓ VOP2扫描
eDP / Panel显示
   ↓ DTB传递物理内存
Kernel DRM接管

其中:

  • Framebuffer 是保存像素的内存;
  • VOP2 是读取、处理并输出画面的显示控制器硬件;
  • U-Boot DRM 负责启动早期显示;
  • Kernel DRM 负责系统运行阶段的完整显示管理;
  • /dev/fb0 只是传统 fbdev 接口,并不是现代 DRM 显示的必要条件;
  • 无缝显示依赖 Logo 内存保护和硬件状态接管,而不是 Kernel 跳过初始化。

这次问题的根因也说明,大分辨率 Logo 调试不能只看图片文件大小,还需要同时计算:

原始文件读取缓冲
+ 解码中间缓冲
+ 最终显示Buffer

把 U-Boot、Framebuffer、VOP2、eDP 和 Kernel DRM 这几个层次分开以后,很多黑屏、闪屏和 Logo 解码失败问题,就可以沿着显示链路逐层定位,而不必在 DTS、DRM 和硬件之间反复猜测。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值