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;所有操作都通过 ID + 属性 ID + 属性值完成。

二、属性枚举与属性类型

属性是 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,把新状态写入硬件。

原子提交的核心步骤:

  1. 用户空间调用 drmModeGetResources() 枚举 CRTC、Connector、Encoder、Plane。
  2. 通过 drmModeObjectGetProperties() 获取对象属性 ID 和当前值。
  3. 构造 drmModeAtomicReq,添加要修改的属性,如 CRTC_ID、MODE_ID、FB_ID、SRC_X/Y、CRTC_X/Y。
  4. 调用 drmModeAtomicCommit(),可带 DRM_MODE_ATOMIC_TEST_ONLY 做试提交。
  5. 内核执行 atomic_check,失败则返回 -EINVAL 并给出原因。
  6. 成功后,新状态在下一个垂直消隐期生效,用户空间收到 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 能力匹配。若匹配失败,提交返回错误,用户空间需要回退到其他模式。

Blob 属性是用户空间与内核之间传递“不可变配置块”的标准方式。它避免了在 ioctl 中嵌入大块结构体,也便于内核做缓存和引用计数。

五、典型场景与常见误区

场景:一台笔记本通过 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 驱动适配中稳定地控制显示管线。

KMS 对象、属性、Blob 与原子提交链路 用户空间 libdrm / Weston 枚举对象与属性 构造原子请求 内核 DRM 核心 drm_mode_config atomic_check / commit 属性与 Blob 管理 显示硬件 CRTC / Encoder Connector / Plane 扫描输出与合成 ioctl 寄存器配置 KMS 对象(内核创建,用户空间按 ID 引用) CRTC 扫描引擎 Encoder 信号转换 Connector 物理接口 Plane 图层合成 Framebuffer 显存缓冲 属性 Property • Range / Enum / Bitmask • Object 引用:CRTC_ID、FB_ID、PLANE_ID • Immutable 只读,Mutable 可原子提交 • 通过 GETPROPERTY / OBJ_GETPROPERTIES 枚举 Blob 属性 • MODE_ID:drmModeModeInfo 时序 • EDID:Connector 能力描述 • GAMMA_LUT / CTM:颜色变换表 • 创建返回 Blob ID,引用计数管理生命周期