从 modetest 输出反推 DRM PLANE 初始化:用走 TaoToken 的 Codex 对照drm_universal_plane_init
调 DRM 显示通路时,最让人卡壳的不是编译报错,而是"参数看不懂":modetest打印出来的 plane 属性表里,CRTC_W/CRTC_H和SRC_W/SRC_H两组 W/H 到底谁管剪裁、谁管缩放?zpos的叠层顺序又是在哪一处代码里被写死的?dmesg里atomic_set_planes()打出Added [PLANE:32:plane-0] state,回头对着drm_universal_plane_init()的调用和那一长串drm_object_attach_property(),很容易分不清哪个 property 对应哪一行输出。这篇不打算改任何内核逻辑,只把"翻源码硬啃"这一步换成:先打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册并创建一把 Key,再把 Codex 的base_url填成https://taotoken.net/api、Key 填刚创建的那把,让走 TaoToken 的 Codex 对着drm_universal_plane_init()的初始化调用与attach_property列表逐行核,把plane_configs字段、property 列表和modetest输出对成一张对照表。TaoToken 在这条链路里只负责两件事:发 Key、给统一 Base URL;坐标换算、剪裁缩放和多图层合成依旧由内核 DRM 侧完成,与它无关。
一、原问题与场景:三组参数对不上号
以 exynos 驱动为例,plane_configs[MIXER_WIN_NR]用三个条目分别描述 PRIMARY、CURSOR、OVERLAY 三层 plane 的zpos、pixel_formats与capabilities:
static const struct exynos_drm_plane_config plane_configs[MIXER_WIN_NR] = { { .zpos = 0, .type = DRM_PLANE_TYPE_PRIMARY, .pixel_formats = mixer_formats, .num_pixel_formats = ARRAY_SIZE(mixer_formats), .capabilities = EXYNOS_DRM_PLANE_CAP_DOUBLE | EXYNOS_DRM_PLANE_CAP_ZPOS, }, { .zpos = 1, .type = DRM_PLANE_TYPE_CURSOR, .pixel_formats = mixer_formats, .num_pixel_formats = ARRAY_SIZE(mixer_formats), .capabilities = EXYNOS_DRM_PLANE_CAP_DOUBLE | EXYNOS_DRM_PLANE_CAP_ZPOS, }, { .zpos = 2, .type = DRM_PLANE_TYPE_OVERLAY, .pixel_formats = vp_formats, .num_pixel_formats = ARRAY_SIZE(vp_formats), .capabilities = EXYNOS_DRM_PLANE_CAP_SCALE | EXYNOS_DRM_PLANE_CAP_ZPOS | EXYNOS_DRM_PLANE_CAP_TILE, }, };初始化阶段,驱动通过drm_object_attach_property()把 FB_ID、CRTC_X/Y/W/H、SRC_X/Y/W/H 等属性挂到 plane 上:
drm_object_attach_property(&plane->base, config->prop_fb_id, 0); drm_object_attach_property(&plane->base, config->prop_crtc_id, 0); drm_object_attach_property(&plane->base, config->prop_crtc_x, 0); drm_object_attach_property(&plane->base, config->prop_crtc_y, 0); drm_object_attach_property(&plane->base, config->prop_crtc_w, 0); drm_object_attach_property(&plane->base, config->prop_crtc_h, 0); drm_object_attach_property(&plane->base, config->prop_src_x, 0); drm_object_attach_property(&plane->base, config->prop_src_y, 0); drm_object_attach_property(&plane->base, config->prop_src_w, 0); drm_object_attach_property(&plane->base, config->prop_src_h, 0);而modetest打印出来的属性表长这样(截取 plane id 32):
Planes: id crtc fb CRTC x,y x,y gamma size possible crtcs 32 36 78 0,0 0,0 0 0x00000001 formats: XR15 RG16 RG24 XR24 AR24 props: 7 type: flags: immutable enum enums: Overlay=0 Primary=1 Cursor=2 value: 1 16 FB_ID: flags: object value: 78 17 IN_FENCE_FD: flags: signed range values: -1 2147483647 value: -1 19 CRTC_ID: flags: object value: 36 12 CRTC_X: flags: signed range values: -2147483648 2147483647 value: 0 13 CRTC_Y: flags: signed range values: -2147483648 2147483647 value: 0 14 CRTC_W: flags: range values: 0 2147483647 value: 800 15 CRTC_H: flags: range values: 0 2147483647 value: 600 8 SRC_X: flags: range values: 0 4294967295 value: 0 9 SRC_Y: flags: range values: 0 4294967295 value: 0 10 SRC_W: flags: range values: 0 4294967295 value: 52428800 11 SRC_H: flags: range values: 0 4294967295 value: 39321600再配合dmesg里atomic_set_planes()打出的Added [PLANE:32:plane-0] state,问题就集中成三句话:
CRTC_W/CRTC_H与SRC_W/SRC_H分别对应显示管道的哪一段,谁决定剪裁、谁决定缩放?zpos的叠层顺序是在plane_configs里写死的,还是在attach_property阶段动态挂上去的?modetest里type的value: 1(Primary)和plane_configs里.type = DRM_PLANE_TYPE_PRIMARY是怎么对应上的?
这类问题靠肉眼在drm_plane.c、exynos_drm_plane.c、drm_atomic.c之间来回跳,效率很低。把这一步交给能读代码、能逐行对照的 Codex,前提是先把它的请求通道配通。
二、TaoToken 前置:发 Key、给统一 Base URL
TaoToken 在这条链路里只做两件事,先说清楚边界:
- 发一把 API Key,供 Codex 调用时鉴权;
- 给一个统一的 Base URL
https://taotoken.net/api,Codex 的请求都往这里发。
它不参与 DRM 的任何计算:drm_universal_plane_init()怎么初始化、attach_property挂了哪些属性、zpos怎么排序、SRC_W/SRC_H怎么换算成像素,全部由内核 DRM 侧完成。TaoToken 只是把"Codex 读内核 DRM PLANE 驱动代码"这条请求链路打通。
操作顺序:
- 打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册账号;
- 进入控制台创建一把 Key,形如
YOUR_API_KEY,先复制保存; - 记住 Base URL 是
https://taotoken.net/api,不加/v1,不加其它后缀; - 把这两项填进 Codex 的配置里(下一节给可复制配置)。
如果你还没装 Codex CLI,可以用 npm 全局安装:
npm i -g @taotoken/taotoken装完后用一条命令把 Key、Base URL、模型 ID 一起传进去:
taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m MODEL_ID其中-u后面就是统一 Base URL,-m后面填你在控制台选定的模型 ID。这条命令只是把 Codex 的请求指向 TaoToken,不改变 Codex 读代码、分析代码的行为。
三、可复制配置:Codex 的 base_url 与 Key
Codex 的配置核心是两处:base_url和api_key。把base_url填成https://taotoken.net/api,api_key填刚创建的那把 Key。下面给一份可直接复制的配置片段(以 Codex 常见的 TOML 配置为例,字段名以你本地 Codex 版本为准):
# Codex 配置:走 TaoToken 的统一 Base URL model = "MODEL_ID" base_url = "https://taotoken.net/api" api_key = "YOUR_API_KEY" # 可选:请求超时与重试 request_timeout = 120 max_retries = 2如果你用的是环境变量方式,对应设置:
export OPENAI_BASE_URL="https://taotoken.net/api" export OPENAI_API_KEY="YOUR_API_KEY"注意两点:
base_url结尾不要带/v1,也不要带/chat/completions之类的路径,Codex 会自己拼接;- Key 只填一次,不要在多处重复配置导致覆盖。
配置完成后,Codex 发出的每一次代码分析请求都会经过https://taotoken.net/api,由 TaoToken 转发到对应模型。DRM 代码本身仍然在你本地仓库里,Codex 读的是你本地的drivers/gpu/drm/目录。
四、验证请求与成功结果:把三组参数对成一张表
配置好之后,先做一次最小验证:让 Codex 读drm_universal_plane_init()的调用点,并列出attach_property挂载的属性清单。可以这样提问:
请阅读 drivers/gpu/drm/exynos/exynos_drm_plane.c 中 plane 初始化部分, 找出 drm_universal_plane_init() 的调用,并列出紧随其后的所有 drm_object_attach_property() 调用,按属性名、默认值、对应 modetest 字段 整理成一张表。如果通道配通,Codex 会返回类似下面的对照结果(示意):
| attach_property 属性 | 默认值 | modetest 字段 | 作用 |
|---|---|---|---|
prop_fb_id | 0 | FB_ID | 指定该 plane 使用的 framebuffer |
prop_crtc_id | 0 | CRTC_ID | 指定该 plane 绑定到哪个 CRTC |
prop_crtc_x/y | 0 | CRTC_X/CRTC_Y | plane 在 CRTC 坐标系中的左上角位置 |
prop_crtc_w/h | 0 | CRTC_W/CRTC_H | plane 在 CRTC 上占用的显示宽高(缩放后的目标尺寸) |
prop_src_x/y | 0 | SRC_X/SRC_Y | 从 framebuffer 中取图的起始坐标(16.16 定点) |
prop_src_w/h | 0 | SRC_W/SRC_H | 从 framebuffer 中取图的宽高(剪裁区域,16.16 定点) |
这张表把之前的三组疑问一次性对齐:
- 剪裁:由
SRC_X/SRC_Y/SRC_W/SRC_H决定,即从 framebuffer 里取哪一块; - 缩放:由
SRC_W/SRC_H与CRTC_W/CRTC_H的比值决定,前者是源尺寸,后者是目标尺寸; - 叠层顺序:
zpos在plane_configs里静态声明(PRIMARY=0、CURSOR=1、OVERLAY=2),初始化时通过EXYNOS_DRM_PLANE_CAP_ZPOS能力位暴露给上层,modetest里看到的type枚举值(Overlay=0、Primary=1、Cursor=2)与plane_configs的.type字段一一对应。
再让 Codex 对照dmesg日志:
dmesg 里有 "Added [PLANE:32:plane-0] state", 请解释这条日志是在 drm_atomic_get_plane_state() 的哪个阶段打印的, 以及它和 modetest 里 plane id 32 的对应关系。Codex 会指出:drm_atomic_get_plane_state()在 atomic 提交路径中为 plane 创建或获取 state,日志里的PLANE:32就是modetest中id 32的 plane,plane-0是该 plane 在驱动内部的命名。这样dmesg和modetest就通过 plane id 串起来了。
验证成功的标志:
- Codex 能准确列出
attach_property的属性清单,且与modetest字段对得上; - 能解释
SRC_W/SRC_H的 16.16 定点格式(例如52428800对应800 << 16); - 能说明
zpos的静态声明位置与type枚举的对应关系。
五、本篇常见错排查
错误 1:base_url多写了/v1
现象:Codex 请求返回 404 或路径拼接错误。原因:base_url填成了https://taotoken.net/api/v1。修正:改成https://taotoken.net/api,不要带/v1。
错误 2:Key 填错或未生效
现象:返回 401 未授权。排查:确认 Key 是从控制台复制的那把,没有多余空格;确认配置里没有旧 Key 覆盖。可到 API Keys 页面重新创建一把。
错误 3:Codex 读不到本地 DRM 代码
现象:Codex 回答泛泛而谈,没有引用具体文件行号。原因:提问时没有指定文件路径,或 Codex 的工作目录不在内核源码根目录。修正:在提问中明确给出drivers/gpu/drm/exynos/exynos_drm_plane.c这类路径,并确认 Codex 的工作目录。
错误 4:把SRC_W/SRC_H当成像素值
现象:对照modetest时发现SRC_W是52428800,与CRTC_W的800对不上。原因:SRC_W/SRC_H是 16.16 定点格式,需要右移 16 位才是像素值。52428800 >> 16 = 800,正好等于CRTC_W,说明该 plane 没有缩放,1:1 显示。
错误 5:zpos找不到挂载点
现象:在attach_property列表里找不到zpos。原因:zpos不是通过drm_object_attach_property()挂的,而是在drm_universal_plane_init()内部根据capabilities里的EXYNOS_DRM_PLANE_CAP_ZPOS自动创建。修正:让 Codex 读drm_universal_plane_init()的实现,确认zposproperty 的创建条件。
错误 6:dmesg日志级别没开
现象:dmesg里看不到atomic_set_planes()相关日志。原因:DRM debug 未开启。修正:echo 0xf > /sys/module/drm/parameters/debug,再重新跑modetest。
六、语义一致 CTA
这条链路的目标很明确:把"翻源码硬啃 DRM PLANE 初始化"换成"让走 TaoToken 的 Codex 逐行对照"。如果你在配置base_url、填 Key、或对照attach_property与modetest字段时卡住,先去 API Keys 页面确认 Key 状态,再对照接入文档检查base_url是否写成了https://taotoken.net/api(不带/v1)。需要验证模型是否能正常读代码,可以到模型对话页面发一条最小请求试跑。如果你打算长期用 Codex 读内核 DRM 代码、做驱动层的持续排查,Coding Plan 更适合这种长期编码与 Agent 场景。通道跑通之后,再继续排查 CRTC 与 plane 的参数关系,会顺很多。