4. KMS对象与属性
KMS(Kernel Mode Setting)并不是一组孤立的 ioctl,而是一套以“对象 + 属性”为核心的资源模型。显示驱动把 CRTC、Encoder、Connector、Plane、Framebuffer 等硬件能力抽象成 KMS 对象,再通过属性(Property)把这些对象的可配置状态暴露给用户空间。用户空间(如 libdrm、Weston、KWin、Android SurfaceFlinger)正是通过枚举对象、读取属性、提交属性值来完成显示配置。理解对象与属性的生命周期,是掌握原子模式设置和后续 GPU 驱动适配的基础。
一、KMS 对象模型与创建流程
KMS 对象由内核在驱动初始化阶段创建,用户空间只能通过 DRM ioctl 枚举和引用它们,不能直接创建。典型对象包括:
- CRTC:代表扫描输出引擎,负责从 Framebuffer 取像素并生成时序信号。
- Encoder:把 CRTC 输出的像素流转换为 Connector 所需的信号格式(如 TMDS、DP、DSI)。
- Connector:代表物理输出接口,如 HDMI-A-1、eDP-1,承载 EDID、连接状态和模式列表。
- Plane:代表图层叠加单元,支持 Primary、Cursor、Overlay 等类型,决定像素如何合成。
- Framebuffer:描述一块可扫描的显存缓冲,包含格式、宽高、pitch、modifier 等信息。
对象创建通常发生在 drm_dev_register() 之前,驱动通过 drm_crtc_init_with_planes()、drm_encoder_init()、drm_connector_init()、drm_universal_plane_init() 等接口注册对象,并调用 drm_mode_config_init() 初始化全局模式配置。对象创建完成后,内核会为每个对象分配唯一的对象 ID,并挂入 drm_mode_config 的链表,供后续枚举使用。
二、属性枚举与属性类型
属性是 KMS 对象可配置状态的键值对。每个属性有唯一的属性 ID、名称、类型和取值范围。用户空间通过 DRM_IOCTL_MODE_GETPROPERTY 获取属性元数据,通过 DRM_IOCTL_MODE_OBJ_GETPROPERTIES 获取某个对象当前所有属性的值。属性类型主要包括:
| 属性类型 | 典型用途 | 示例 |
|---|---|---|
| Range | 整数范围,用于亮度、对比度、gamma 等 | “brightness” 0~100 |
| Enum | 枚举值,用于旋转、缩放、颜色编码等 | “rotation” 支持 rotate-0/90/180/270 |
| Bitmask | 位掩码,用于多选能力 | “plane-type” primary/cursor/overlay |
| Object | 引用其他 KMS 对象 ID | “CRTC_ID”、“FB_ID”、“PLANE_ID” |
| Blob | 二进制大对象,用于模式、EDID、LUT 等 | “MODE_ID”、“EDID”、“GAMMA_LUT” |
| Signed Range | 有符号整数范围 | “tv-brightness” -128~127 |
属性分为不可变属性(Immutable)和可变属性。不可变属性在对象创建时确定,用户空间只能读取,例如 Connector 的 “EDID”。可变属性可以在原子提交中修改,例如 CRTC 的 “ACTIVE”、Plane 的 “CRTC_ID” 和 “FB_ID”。属性枚举的顺序和命名由驱动决定,但必须遵循 DRM 的通用命名约定,否则用户空间无法识别。
三、原子模式设置:从属性到提交
传统模式设置使用 DRM_IOCTL_MODE_SETCRTC 等“立即生效”接口,容易产生中间状态和闪烁。原子模式设置(Atomic Mode Setting)把一次显示配置抽象为一个不可分割的提交:用户空间先构造一个 drm_mode_atomic 请求,列出要修改的对象、属性 ID 和新值,然后通过 DRM_IOCTL_MODE_ATOMIC 一次性提交。内核在提交前执行 atomic_check,验证资源冲突、带宽、时钟和格式约束;只有全部通过才执行 atomic_commit,把新状态写入硬件。
原子提交的核心步骤:
- 用户空间调用
drmModeGetResources()枚举 CRTC、Connector、Encoder、Plane。 - 通过
drmModeObjectGetProperties()获取对象属性 ID 和当前值。 - 构造
drmModeAtomicReq,添加要修改的属性,如CRTC_ID、MODE_ID、FB_ID、SRC_X/Y、CRTC_X/Y。 - 调用
drmModeAtomicCommit(),可带DRM_MODE_ATOMIC_TEST_ONLY做试提交。 - 内核执行
atomic_check,失败则返回-EINVAL并给出原因。 - 成功后,新状态在下一个垂直消隐期生效,用户空间收到 page flip 事件。
drmModeAtomicReq *req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, crtc_id, prop_active, 1);
drmModeAtomicAddProperty(req, crtc_id, prop_mode_id, blob_id);
drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id);
int ret = drmModeAtomicCommit(fd, req,
DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT, NULL);
drmModeAtomicFree(req);
四、Blob 属性与用户空间交互
Blob 属性用于传递变长或结构化数据,典型场景是模式信息(MODE_ID)、EDID、Gamma LUT、颜色变换矩阵等。用户空间不能直接写 Blob 内容,而是先通过 DRM_IOCTL_MODE_CREATE_DUMB 或 drmModeCreatePropertyBlob() 创建 Blob,内核返回一个 Blob ID;随后在原子提交中把该 Blob ID 赋给对应属性。Blob 的生命周期由引用计数管理,提交完成后用户空间可调用 drmModeDestroyPropertyBlob() 释放。
以设置显示模式为例:用户空间从 Connector 的 modes 列表中选择一个 drmModeModeInfo,调用 drmModeCreatePropertyBlob(fd, &mode, sizeof(mode), &blob_id) 创建 Blob,再把 MODE_ID 属性设为该 Blob ID。内核在 atomic_check 中解析 Blob,校验时序参数是否与 Connector 能力匹配。若匹配失败,提交返回错误,用户空间需要回退到其他模式。
五、典型场景与常见误区
场景:一台笔记本通过 eDP 内置屏和 HDMI 外接屏同时显示。用户空间枚举到两个 Connector,分别读取 EDID 和模式列表,为每个 Connector 创建 MODE_ID Blob,再为两个 CRTC 分别绑定 Plane 和 Framebuffer,最后一次性原子提交。若 HDMI 带宽不足,atomic_check 会拒绝该组合,用户空间可降低刷新率或色深后重试。
常见误区:
- 把属性 ID 当作全局常量硬编码。属性 ID 是运行时分配的,必须通过枚举获取。
- 在原子提交中混用旧接口。原子提交要求所有相关对象状态一致,混用
SETCRTC会破坏原子性。 - 忘记释放 Blob。长期运行会导致内核 Blob 引用泄漏,最终创建失败。
- 忽略
TEST_ONLY。在正式提交前做试提交,可以避免闪烁和无效配置。 - 认为所有属性都可写。Immutable 属性只能读取,写入会返回
-EINVAL。
复盘时,建议用 modetest 或 drm_info 打印对象和属性列表,确认属性 ID、类型和当前值;再用 drmModeAtomicCommit 的返回值定位是哪个对象、哪个属性导致 atomic_check 失败。只有把对象、属性、Blob 和原子提交串成一条完整的链路,才能在后续 GPU 驱动适配中稳定地控制显示管线。