4. Encoder与Connector的绑定
任务说明
本案例在Linux内核显示子系统中,实现一个最小可复现的Encoder与Connector创建及绑定流程。核心目标是:创建encoder和connector对象,完成两者的绑定关系,并验证connector的检测状态(`connector_status_connected`)以及模式列表(`modes`)能够正确生成。案例基于DRM框架的软件模拟路径,不涉及具体硬件驱动,使用虚拟设备模型完成闭环。
环境与依赖
需要Linux内核源码树,并已启用以下Kconfig选项:
CONFIG_DRM=y或=mCONFIG_DRM_KMS_HELPER=y
案例代码以内核模块形式实现,编译时需链接DRM核心与KMS辅助模块。以下代码可直接放入内核源码树的drivers/gpu/drm/下新建的enc_conn_bind.c文件中,并配套Makefile条目。
关键完整代码
// enc_conn_bind.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/drm_device.h>
#include <drm/drm_encoder.h>
#include <drm/drm_connector.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_modeset_helper.h>
static struct drm_device *virt_dev;
static struct drm_encoder *virt_encoder;
static struct drm_connector *virt_connector;
// Connector检测回调:固定返回已连接
static enum drm_connector_status virt_connector_detect(struct drm_connector *connector,
bool force)
{
return connector_status_connected;
}
// Connector模式列表回调:仅提供一种预设模式
static int virt_connector_get_modes(struct drm_connector *connector)
{
struct drm_display_mode *mode;
mode = drm_mode_create(connector->dev);
if (!mode)
return 0;
mode->clock = 25175; // 25.175 MHz
mode->hdisplay = 640;
mode->hsync_start = 656;
mode->hsync_end = 752;
mode->htotal = 800;
mode->vdisplay = 480;
mode->vsync_start = 490;
mode->vsync_end = 492;
mode->vtotal = 525;
mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
return 1; // 返回添加的模式数量
}
static const struct drm_connector_funcs virt_connector_funcs = {
.detect = virt_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_connector_helper_funcs virt_connector_helper_funcs = {
.get_modes = virt_connector_get_modes,
};
static int virt_bind_encoder_connector(void)
{
int ret;
// 1. 创建encoder
virt_encoder = kzalloc(sizeof(*virt_encoder), GFP_KERNEL);
if (!virt_encoder)
return -ENOMEM;
ret = drm_encoder_init(virt_dev, virt_encoder, NULL,
DRM_MODE_ENCODER_VIRTUAL, "virt-encoder");
if (ret < 0) {
kfree(virt_encoder);
return ret;
}
// 2. 创建connector
virt_connector = kzalloc(sizeof(*virt_connector), GFP_KERNEL);
if (!virt_connector) {
drm_encoder_cleanup(virt_encoder);
kfree(virt_encoder);
return -ENOMEM;
}
ret = drm_connector_init(virt_dev, virt_connector,
&virt_connector_funcs,
DRM_MODE_CONNECTOR_VIRTUAL);
if (ret < 0) {
drm_encoder_cleanup(virt_encoder);
kfree(virt_encoder);
kfree(virt_connector);
return ret;
}
drm_connector_helper_add(virt_connector, &virt_connector_helper_funcs);
// 3. 绑定:将connector关联到encoder
ret = drm_connector_attach_encoder(virt_connector, virt_encoder);
if (ret < 0) {
drm_connector_cleanup(virt_connector);
drm_encoder_cleanup(virt_encoder);
kfree(virt_connector);
kfree(virt_encoder);
return ret;
}
// 4. 注册connector(触发检测与模式探测)
ret = drm_connector_register(virt_connector);
if (ret < 0) {
drm_connector_unregister(virt_connector);
drm_connector_cleanup(virt_connector);
drm_encoder_cleanup(virt_encoder);
kfree(virt_connector);
kfree(virt_encoder);
return ret;
}
return 0;
}
static int virt_probe(struct platform_device *pdev)
{
int ret;
virt_dev = drm_dev_alloc(NULL, &pdev->dev);
if (IS_ERR(virt_dev))
return PTR_ERR(virt_dev);
ret = drm_dev_register(virt_dev, 0);
if (ret < 0) {
drm_dev_put(virt_dev);
return ret;
}
ret = virt_bind_encoder_connector();
if (ret < 0) {
drm_dev_unregister(virt_dev);
drm_dev_put(virt_dev);
return ret;
}
return 0;
}
static int virt_remove(struct platform_device *pdev)
{
if (virt_connector) {
drm_connector_unregister(virt_connector);
drm_connector_cleanup(virt_connector);
kfree(virt_connector);
}
if (virt_encoder) {
drm_encoder_cleanup(virt_encoder);
kfree(virt_encoder);
}
drm_dev_unregister(virt_dev);
drm_dev_put(virt_dev);
return 0;
}
static struct platform_driver virt_driver = {
.probe = virt_probe,
.remove = virt_remove,
.driver = {
.name = "virt_enc_conn",
},
};
module_platform_driver(virt_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal Encoder-Connector binding example");
编译与加载
在drivers/gpu/drm/Makefile中添加:
obj-$(CONFIG_DRM) += enc_conn_bind.o
编译并加载模块:
make drivers/gpu/drm/enc_conn_bind.ko
sudo insmod enc_conn_bind.ko
验证方法
加载模块后,通过以下步骤确认绑定成功且功能正确:
- 检查内核日志:
dmesg | grep virt-encoder,应能看到encoder注册信息。 - 查看DRM设备节点:
ls /sys/class/drm/,应出现card0-<connector-id>对应的虚拟connector目录。 - 读取connector状态:
cat /sys/class/drm/card0-*/status,预期输出为connected。 - 检查模式列表:
cat /sys/class/drm/card0-*/modes,预期输出包含640x480。
通过条件:上述第3、4步输出与预期一致,且dmesg无错误(如drm_connector_attach_encoder失败或内存分配失败),即判定案例实现正确。
关键说明
本案例使用DRM_MODE_ENCODER_VIRTUAL和DRM_MODE_CONNECTOR_VIRTUAL类型,避免依赖具体硬件。绑定通过drm_connector_attach_encoder完成,该函数会建立connector到encoder的反向指针,并确保encoder的possible_clones等掩码正确。模式列表由virt_connector_get_modes回调提供,检测状态由virt_connector_detect固定返回已连接,从而验证完整链路。