4. Encoder与Connector的绑定

任务说明

本案例在Linux内核显示子系统中,实现一个最小可复现的Encoder与Connector创建及绑定流程。核心目标是:创建encoder和connector对象,完成两者的绑定关系,并验证connector的检测状态(`connector_status_connected`)以及模式列表(`modes`)能够正确生成。案例基于DRM框架的软件模拟路径,不涉及具体硬件驱动,使用虚拟设备模型完成闭环。

环境与依赖

需要Linux内核源码树,并已启用以下Kconfig选项:

  • CONFIG_DRM=y 或 =m
  • CONFIG_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

验证方法

加载模块后,通过以下步骤确认绑定成功且功能正确:

  1. 检查内核日志:dmesg | grep virt-encoder,应能看到encoder注册信息。
  2. 查看DRM设备节点:ls /sys/class/drm/,应出现card0-<connector-id>对应的虚拟connector目录。
  3. 读取connector状态:cat /sys/class/drm/card0-*/status,预期输出为connected。
  4. 检查模式列表: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固定返回已连接,从而验证完整链路。