MTK Linux DRM分析(十)- KMS drm_connector.c
一、简介
在 DRM 框架中,Connector 代表的是物理显示设备的连接接口。它是对现实世界中显卡输出端口(如 HDMI, DisplayPort, DVI, VGA 等)的软件抽象。
1. drm_connector 的作用
drm_connector 是 Linux DRM 子系统中用于抽象显示输出设备(display sink)的核心数据结构。显示输出设备可以是外部显示器(如通过 HDMI、DisplayPort 连接的显示器)、内置面板(如笔记本的 LCD 屏幕)或其他能够显示像素的设备。以下是其主要作用的概述,结合代码中的注释:
1.1 显示输出设备的抽象
根据代码中的 DOC: overview 部分:
In DRM connectors are the general abstraction for display sinks, and include also fixed panels or anything else that can display pixels in some form.
drm_connector 是 DRM 框架中对显示输出设备的通用抽象,涵盖了各种类型的显示设备,包括但不限于:
- 外部显示器(如 VGA、HDMI、DisplayPort)
- 内置显示面板(如 LVDS、eDP、DSI)
- 虚拟显示器(如虚拟输出或写回连接器)
- 其他特殊设备(如 USB 显示设备)
它负责描述这些设备的连接状态、属性、支持的显示模式以及与硬件的交互方式。
1.2 支持热插拔
As opposed to all other KMS objects representing hardware (like CRTC, encoder or plane abstractions) connectors can be hotplugged and unplugged at runtime. Hence they are reference-counted using drm_connector_get() and drm_connector_put().
与 DRM 中的其他硬件抽象(如 CRTC、编码器、平面)不同,drm_connector 支持运行时的热插拔(hotplug)。这意味着显示设备可以在系统运行时动态连接或断开,例如插入或拔出 HDMI 显示器。为此,drm_connector 使用引用计数机制(通过 drm_connector_get() 和 drm_connector_put())来管理其生命周期。
1.3 连接到编码器
Connectors must be attached to an encoder to be used. For devices that map connectors to encoders 1:1, the connector should be attached at initialization time with a call to drm_connector_attach_encoder().
drm_connector 需要与 drm_encoder(编码器)关联才能正常工作。编码器负责将 CRTC(控制器)的输出信号编码为适合特定连接器类型的信号(如 HDMI 或 DisplayPort)。代码中的 drm_connector_attach_encoder() 函数用于建立这种关联,并通过 possible_encoders 位掩码记录所有可能的编码器。
1.4 属性和用户空间交互
drm_connector 暴露了许多标准属性(如 EDID、DPMS、link-status 等)给用户空间,允许用户空间查询和配置显示设备的行为。这些属性通过 drm_object_attach_property() 附加到连接器上,并在 drm_mode_getconnector() 等函数中返回给用户空间。
2. drm_connector 的核心原理
drm_connector 的实现原理可以从其初始化、管理、属性设置和清理等几个方面来分析,结合代码中的关键函数和数据结构进行说明。
2.1 初始化
drm_connector 的初始化主要通过 drm_connector_init() 或 drm_connector_init_with_ddc() 完成。以下是关键步骤:
-
分配和设置基本属性:
c
int drm_connector_init(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
int connector_type)
- dev:关联的 DRM 设备。
- connector:预分配的连接器对象。
- funcs:指向连接器回调函数的结构体,定义了连接器的行为(如 destroy、dpms、set_property 等)。
- connector_type:连接器类型(如 DRM_MODE_CONNECTOR_HDMI、DRM_MODE_CONNECTOR_DisplayPort 等)。
初始化过程包括:
- 将连接器注册为 DRM 模式对象(__drm_mode_object_add())。
- 分配一个唯一的索引(connector->index)和类型 ID(connector->connector_type_id)。
- 设置连接器名称(如 HDMI-A-1)。
- 初始化模式列表(probed_modes 和 modes)和互斥锁(mutex)。
- 附加标准属性(如 EDID、DPMS、link-status 等)。
如果需要支持 DDC(Display Data Channel,通常用于读取 EDID),可以调用 drm_connector_init_with_ddc(),它额外设置了 ddc 字段:
c
connector->ddc = ddc;
-
热插拔支持: 代码注释提到,驱动程序需要支持热插拔通知。对于没有硬件中断支持的连接器,可以使用 drm_kms_helper_poll_init() 进行轮询检测。对于支持硬件中断的连接器,可以使用 drm_helper_hpd_irq_event() 来处理热插拔事件。
2.2 属性管理
drm_connector 支持多种标准属性,这些属性通过 drm_object_attach_property() 附加到连接器上,并在用户空间通过 IOCTL(如 drm_mode_getconnector)访问。以下是一些关键属性的实现原理(参考 DOC: standard connector properties):
- EDID:
- 存储显示器的 EDID 数据(通过 drm_connector_update_edid_property() 更新)。
- EDID 数据以 blob 形式存储在 connector->edid_blob_ptr 中,供用户空间解析显示器信息(如厂商、型号、分辨率)。
- 驱动程序通过 drm_add_edid_modes() 解析 EDID 并更新支持的显示模式。
- DPMS:
- 用于控制连接器的电源状态(如 On、Standby、Suspend、Off)。
- 在原子驱动中,DPMS 映射到 CRTC 的 ACTIVE 属性,由 DRM 核心通过 drm_connector_funcs.dpms 回调处理。
- link-status:
- 表示连接器的链接状态(GOOD 或 BAD)。
- 当模式设置失败或链接断开时,驱动程序通过 drm_connector_set_link_status_property() 设置为 BAD,并触发热插拔事件通知用户空间。
- Content Protection 和 HDCP Content Type:
- 用于支持 HDCP(High-bandwidth Digital Content Protection)加密。
- Content Protection 属性控制加密状态(UNDESIRED、DESIRED、ENABLED)。
- HDCP Content Type 属性指定内容类型(Type0 或 Type1),影响 HDCP 认证过程。
- Colorspace:
- 允许用户空间设置输出颜色空间(如 BT.2020、BT.709),通过 drm_mode_create_hdmi_colorspace_property() 或 drm_mode_create_dp_colorspace_property() 创建。
- HDR_OUTPUT_METADATA:
- 允许用户空间发送 HDR 元数据,驱动程序根据连接器类型(HDMI 或 DP)生成相应的 Infoframe 或 SDP 包。
2.3 模式管理和模式设置
drm_connector 管理显示模式(drm_display_mode),包括:
- 探测模式(probed_modes):通过 EDID 或其他方式从显示器获取的模式。
- 用户模式(modes):驱动程序最终确定的可用模式列表。
在 drm_mode_getconnector() 中,驱动程序会调用 fill_modes() 来探测和填充支持的模式,并通过 drm_mode_expose_to_userspace() 过滤出适合暴露给用户空间的模式(例如,过滤掉不支持的立体 3D 模式或不符合宽高比的模式)。
2.4 热插拔和动态管理
热插拔的实现依赖于:
- 硬件中断:通过 drm_helper_hpd_irq_event() 触发热插拔事件。
- 轮询:通过 drm_kms_helper_poll_init() 定期检查连接状态。
- 用户空间通知:通过 drm_sysfs_hotplug_event() 通知用户空间连接器状态变化。
连接器的状态(connector->status)可以是 connected、disconnected 或 unknown,通过 drm_get_connector_status_name() 获取状态名称。
2.5 清理和销毁
drm_connector_cleanup() 负责清理连接器资源,包括:
- 移除模式列表(probed_modes 和 modes)。
- 释放 EDID 和瓦片(tile)数据。
- 移除连接器索引和类型 ID。
- 销毁互斥锁。
当引用计数降为 0 时,drm_connector_free() 或 drm_connector_free_work_fn() 会释放连接器对象。
2.6 瓦片组(Tile Group)支持
对于拼接显示器(如高分辨率外部显示器或双链路 DSI 面板),drm_connector 支持瓦片组(tile group)管理:
- drm_mode_create_tile_group() 创建瓦片组,分配唯一 ID。
- drm_connector_set_tile_property() 设置瓦片属性,描述拼接显示器的布局(如瓦片数量、位置、大小)。
2.7 参考计数管理
drm_connector 使用 kref 机制管理引用计数:
- drm_connector_get() 增加引用计数。
- drm_connector_put() 减少引用计数,当计数为 0 时释放连接器。
- 特殊的 __drm_connector_put_safe() 用于连接器列表迭代,确保安全释放。
3. 关键函数和流程
以下是一些关键函数的简要说明,结合其作用:
- drm_connector_init():
- 初始化连接器,分配索引和类型 ID,设置名称和属性。
- 核心函数,用于创建新的连接器实例。
- drm_connector_attach_encoder():
- 将连接器关联到编码器,设置 possible_encoders 位掩码。
- 确保连接器可以与特定的编码器一起工作。
- drm_connector_update_edid_property():
- 更新 EDID 属性,解析显示器信息并设置瓦片属性。
- 如果 EDID 发生变化,增加 epoch_counter 以通知用户空间。
- drm_connector_register() 和 drm_connector_unregister():
- 注册/注销连接器到用户空间,创建 sysfs 节点并触发热插拔事件。