1. RK3588 VOP 图层分配到底在解决什么问题
如果你正在调试 RK3588 的多屏显示,大概率遇到过这类现象:HDMI 能出图但 MIPI DSI 黑屏、副屏只能显示一个纯色背景、视频层跑到错误的屏幕上、或者 dmesg 里报no available plane之类的错误。这些问题十有八九不是屏幕时序没配对,而是 VOP 图层分配没理清楚。
RK3588 的 VOP2 是一个统一显示架构,内部一共有 8 个图层资源,分成 Cluster 0/1/2/3 和 Esmart 0/1/2/3 两组。这些图层不是每个 Video Port 私有的,而是所有 VP 共享,并且同一时刻一个图层只能被一个 VP 独占。所以当你的板子同时点亮 HDMI、DP、MIPI DSI 三路输出时,谁拿 Cluster、谁拿 Esmart、谁是 primary,就必须在设备树里明确指定,否则 U-Boot 会按默认策略分配,一旦默认策略和你的硬件接口数量对不上,就会出现图层冲突或者某个屏幕没有可用图层。
rockchip,plane-mask和rockchip,primary-plane这两个属性就是干这个的。plane-mask 用位掩码的方式告诉驱动「这个 VP 能用哪些图层」,primary-plane 则指定这个 VP 的主图层是哪一个。主图层很关键,因为 legacy 的显示 API(比如很多 Linux 桌面环境)会默认把背景画在 primary 上,如果 primary 选错或者没分配,桌面背景就出不来。
这篇文章面向的是正在做 RK3588 多屏产品、需要自己定制图层分配策略的驱动和应用工程师。我会从图层资源的基本盘讲起,给出可以直接抄进 dts 的配置片段,然后一步步用 dmesg 验证分配结果,最后把常见的报错和排查路径理一遍。目标很明确:让你一次性把 VOP 图层分配链路走通,不用再靠猜。
先明确一个前提,RK3588 的图层 ID 定义在dt-bindings/display/rockchip_vop.h这个头文件里,配置之前先确认你的内核版本里这个文件存在,路径一般是include/dt-bindings/display/rockchip_vop.h。里面会定义类似ROCKCHIP_VOP2_CLUSTER0、ROCKCHIP_VOP2_ESMART0、ROCKCHIP_VOP2_SMART0这样的宏,后面写 plane-mask 全靠它们。
2. TaoToken 前置准备:把模型对话和接入文档放到手边
调试 VOP 图层分配的过程中,你会频繁需要查寄存器手册、对 dts 语法、看驱动源码里的绑定逻辑。这些资料分散在内核文档、Rockchip 的 SDK 和一堆论坛帖子里,来回切换很费时间。我的做法是把常用的查询入口固定下来,遇到不确定的宏定义或者属性含义,直接开一个模型对话窗口问,比翻 PDF 快得多。
TaoToken 在这里的角色就是一个统一的模型调用入口。你可以通过它的模型对话页面直接问「RK3588 VOP2 的 plane-mask 位掩码怎么算」「primary-plane 能不能选 Cluster 图层」这类具体问题,它会结合上下文给出可操作的答案。对于驱动开发来说,这种即时问答能省掉大量 grep 源码的时间。
具体入口我列一下,你按需取用:
模型对话入口在https://taotoken.net/api对应的对话服务里,适合临时查概念、对报错。接入文档在https://taotoken.net/api的 doc 路径下,里面有完整的 API 说明和示例,如果你想把模型能力集成到自己的调试脚本里,从这里开始看。API Keys 管理在 console 里,生成 key 之后就可以在脚本里调用。
如果你是要长期做 RK3588 驱动开发,建议直接上 Coding Plan,把模型对话和代码补全绑在一起用,写 dts 和排查驱动问题时效率会高不少。Coding Plan 的入口在官网导航里能找到,这里不展开。
需要提醒一点:TaoToken 只是帮你查资料和验证思路的工具,它不替代你本地的交叉编译环境和硬件调试。图层分配最终还是要落到 dts 修改、编译、烧录、看 log 这个闭环上。模型能告诉你「应该怎么配」,但「配了之后板子出不出图」得你自己上电验证。
另外,如果你在配置过程中遇到 OAuth 相关的报错,比如某些工具链在拉取依赖时提示认证失败,那通常是本地凭证过期,和 VOP 配置本身无关,重新走一遍授权流程即可。这个坑我在后面排障章节会再提一次。
3. 可复制的设备树配置:plane-mask 与 primary-plane 写法
这一节是核心,直接给可以抄的配置。先讲清楚位掩码怎么算,再给三屏异显的完整示例,最后说几个容易写错的细节。
图层 ID 的宏定义在rockchip_vop.h里,RK3588 的 8 个图层对应的宏大致是:
| 图层类型 | 宏名 | 说明 |
|---|---|---|
| Cluster | ROCKCHIP_VOP2_CLUSTER0 ~ CLUSTER3 | 高性能层,支持缩放、旋转、AFBC |
| Esmart | ROCKCHIP_VOP2_ESMART0 ~ ESMART3 | 支持缩放和多区域,适合视频 |
| Smart | ROCKCHIP_VOP2_SMART0 ~ SMART3 | 基础层,通常做 primary |
plane-mask 是一个位掩码,第 N 位为 1 表示该 VP 可以使用第 N 号图层。写法是1 << ROCKCHIP_VOP2_XXX,多个图层用按位或连起来。比如要分配 Cluster1 和 Smart1 给 vp0,就是:
rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER1 | 1 << ROCKCHIP_VOP2_SMART1)>;primary-plane 直接写图层宏,注意它必须是 plane-mask 里的一个:
rockchip,primary-plane = <ROCKCHIP_VOP2_SMART1>;下面是一个 RK3588 三屏异显的完整参考配置。假设你有 HDMI、DP、MIPI DSI 三路输出,分别对应 vp0、vp1、vp2,主屏是 MIPI DSI(vp2),给它多分图层:
#include <dt-bindings/display/rockchip_vop.h> &vp0 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER0 | 1 << ROCKCHIP_VOP2_ESMART0)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART0>; }; &vp1 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER1 | 1 << ROCKCHIP_VOP2_ESMART1)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART1>; }; &vp2 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER2 | 1 << ROCKCHIP_VOP2_ESMART2 | 1 << ROCKCHIP_VOP2_SMART0)>; rockchip,primary-plane = <ROCKCHIP_VOP2_SMART0>; };这里 vp2 分了三个图层,因为它是主屏,应用场景最多。vp0 和 vp1 各分两个,满足基本显示需求。注意 primary-plane 我选了 Esmart 和 Smart,没有选 Cluster,原因是 Cluster 图层性能强但资源紧张,通常留给视频或者需要缩放的场景做 overlay,primary 用 Esmart 或 Smart 更稳妥。
如果你需要把某个图层设成鼠标层(cursor),在对应的 vp 节点下加cursor-win-id:
&vp0 { cursor-win-id = <ROCKCHIP_VOP2_CLUSTER0>; };但要注意,Linux 系统(非 Android)下指定 Cluster 做鼠标层,需要配合 SDK 提供的libdrm-cursor库,否则鼠标可能显示异常。这个细节在 Rockchip 的 Debian 开发指南里有说明。
还有一个属性值得关注:disable-win-move。某些 Linux 系统希望每个 crtc 的图层独占,不允许在 crtc 之间迁移图层,可以在 vop 节点下打开:
&vop { disable-win-move; };打开之后,图层分配就完全按 plane-mask 来,不会出现运行时动态迁移导致的显示抖动。
配置写完之后,编译 dtb 并烧录。如果你用的是 Rockchip 的 SDK,编译命令一般是:
./build.sh kernel或者单独编译 dtb:
make ARCH=arm64 rk3588-your-board.dtb烧录之后重启,进入下一步验证。
4. 验证请求与成功结果:从 dmesg 确认图层分配生效
配置写完不代表生效,必须从启动 log 里确认驱动真的按你的意图分配了图层。RK3588 的 VOP 驱动在 bind 阶段会打印每个 VP 的 plane mask 和 primary plane 的物理 ID,这是最直接的验证依据。
先看 U-Boot 阶段的 log。U-Boot 的 DRM 驱动会先做一次分配,输出类似这样的信息:
VOP have 3 active VP vp0 have layer nr:2[1 5 ], primary plane: 5 vp1 have layer nr:2[0 4 ], primary plane: 4 vp2 have layer nr:3[2 3 6 ], primary plane: 6方括号里的数字是图层 ID,layer nr是数量,primary plane是主图层的 ID。你可以对照自己的 plane-mask 配置,看数量对不对、primary 是不是你指定的那个。
然后是 Linux kernel 阶段的 log,这个更详细,会直接打印 mask 的十六进制值:
[2.315764] rockchip-vop2 : [drm:vop2_bind] vp0 assign plane mask: 0x22, primary plane phy id: 5 [2.315807] rockchip-vop2 : [drm:vop2_bind] vp1 assign plane mask: 0x15, primary plane phy id: 4 [2.315828] rockchip-vop2 : [drm:vop2_bind] vp2 assign plane mask: 0x8, primary plane phy id: 3这里的plane mask是十六进制,primary plane phy id是物理图层号。你可以把 mask 转成二进制,看哪些位是 1,再对照rockchip_vop.h里的宏定义,确认分配是否符合预期。
抓 log 的命令很简单:
dmesg | grep -i "vop2_bind"或者直接看完整启动 log:
dmesg | grep -i "assign plane mask"如果 log 里出现了no available plane或者failed to assign plane,说明你的 plane-mask 配置有问题,可能是图层 ID 写错、或者多个 VP 抢同一个图层。这时候回到 dts 检查位掩码,确保每个图层只出现在一个 VP 的 mask 里。
验证通过之后,你还可以用 modetest 工具看当前 crtc 和 plane 的绑定关系:
modetest -M rockchip -p输出里会列出每个 crtc 支持的 plane,以及当前 primary plane 是哪个。如果 modetest 显示某个 crtc 没有 primary plane,那桌面背景肯定出不来,需要回头改 primary-plane。
实测下来,只要 dmesg 里的 mask 和 primary id 和你 dts 里写的一致,多屏显示基本就稳了。剩下的就是应用层怎么用这些图层的问题。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节把调试过程中最容易撞上的几类报错集中说一下。有些是 VOP 配置本身的,有些是工具链或者环境问题,但都会让你误以为是图层分配错了。
报错一:no available plane for crtc
这是最典型的图层分配错误。dmesg 里会看到类似:
rockchip-vop2: no available plane for vp2原因通常是 plane-mask 里指定的图层已经被别的 VP 占用了,或者 mask 写成了 0。排查方法:检查每个 VP 的 plane-mask,确保没有重叠。RK3588 的 8 个图层要平均分给使用的 VP,不用的 VP 不要分配。如果你只点亮两路输出,第三路 VP 的 plane-mask 应该留空或者不配置。
报错二:local proxy failed
这个报错和 VOP 无关,通常出现在你用某些工具拉取依赖或者访问模型服务时。意思是本地代理配置有问题,导致请求发不出去。如果你在调试脚本里调用了模型 API,遇到这个报错,先检查环境变量里的代理设置,确认没有残留的失效配置。TaoToken 的 API 调用不需要额外代理,直接走https://taotoken.net/api即可。
报错三:401 Unauthorized
调用模型 API 时返回 401,说明 API Key 无效或者没带上。检查你的请求头里有没有正确设置Authorization: Bearer <your-key>,key 是不是从 console 里新生成的。如果 key 刚生成就用,有时候会有几秒的生效延迟,等一下再试。
报错四:reading choices相关错误
这个通常出现在用某些 CLI 工具(比如 Claude Code 或者类似的 coding agent)时,工具在解析模型返回的 choices 字段时出错。原因可能是返回格式和工具预期的不一致,或者模型 ID 填错了。如果你在配置里写了 Model ID,确认它和 TaoToken 支持的模型列表一致。Base URL、Key、Model ID 这三件套要配套,缺一个都会出问题。
报错五:OAuth 认证失败
如果你用的工具链需要 OAuth 授权(比如某些 Git 操作或者包管理),报 OAuth 错误说明本地 token 过期了。重新走一遍授权流程即可,和 VOP 图层分配没有关系。但如果你在 dts 编译过程中遇到认证问题,那可能是 SDK 的 repo 同步需要重新登录,检查一下.repo目录下的配置。
排查顺序建议:先看 dmesg 里的 vop2_bind 输出,确认 mask 和 primary 是否符合预期;如果 log 正常但屏幕不亮,再查屏幕时序和接口配置;如果 log 里根本没有 vop2_bind,说明 VOP 驱动没加载,检查内核配置里CONFIG_ROCKCHIP_VOP2有没有打开。
6. 语义一致 CTA:把图层分配链路固化下来
图层分配这件事,配一次能管很久,但前提是你把验证方法也固化下来。我的习惯是在板子 bringup 阶段就把 dmesg 里的 vop2_bind 输出存一份基线,后面每次改 dts 都 diff 一下,确认没有意外变化。
如果你在调试过程中需要频繁查驱动源码里的绑定逻辑,或者想快速确认某个图层宏的定义,可以用 TaoToken 的模型对话来加速。入口在https://taotoken.net/api,直接问「RK3588 ROCKCHIP_VOP2_ESMART2 对应哪个物理图层」这类问题,比翻头文件快。
需要长期做 RK3588 驱动开发的话,Coding Plan 能把模型对话和代码补全串起来,写 dts 和排查 log 时省不少切换成本。API Keys 在 console 里管理,接入文档在 doc 路径下,按需取用。
最后留一个实用技巧:改完 plane-mask 之后,先别急着烧录整包,单独编译 dtb 替换进去重启,验证通过再合入完整固件。这样一轮调试能省好几分钟,多试几次就回本了。