4.13.6. VKMS示例
VKMS是”Virtual Kernel Mode Setting”的缩写,之所以称他为Virtual KMS, 是因为该驱动不需要真实的硬件,他完全是一个软件 虚拟的”显示设备”,甚至连显示都算不上,因为当它运行时,你看不到任何显示内容。它唯一能提供的就是一个由高精度timer模拟 的VSYNC中断信号。
该驱动存在的真实目的,主要是为了DRM框架自测试。虽然看不到VKMS的显示效果,但是在驱动流程上,它实现了modesetting该有 的基本操作(Modesetting是指在显示器上渲染图像的过程中,调整显示器的分辨率、刷新率、像素格式等参数)
代码存放在drivers/gpu/drm/vkms/目录下
目前VKMS驱动已经集成了如下功能
Atomic Modeset
VBlank
Dumb Buffer
Cursor & Primary Plane
Framebuffer CRC校验
Plane Composition
GEM Prime Import
4.13.6.1. 最简单的VKMS驱动
#include <drm/drmP.h>
static struct drm_device drm; //用于抽象一个完整的DRM设备
static struct drm_driver vkms_driver = {
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022-8-9",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
drm_register(&drm, 0);
return 0;
}
module_init(vkms_init);
当次驱动注册进内核后,内核会做一下事情
创建设备节点: /dev/dri/card0
创建sysfs节点: /sys/class/drm/card0
创建debugfs节点: /sys/kernel/debug/dri/0
但该驱动目前什么事情也做不了,唯一能做的就是查看该驱动的名字
cat /sys/kernel/debug/dri/0/name
vkms unique=vkms
4.13.6.2. VKMS驱动添加fops操作接口
#include <drm/drmP.h>
static struct drm_device drm; //用于抽象一个完整的DRM设备
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};
static struct drm_driver vkms_driver = {
.fops = &vkms_fops, //增加fops操作接口
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022-8-9",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
drm_register(&drm, 0);
return 0;
}
module_init(vkms_init);
有了fops,我们就可以对card0进行open/read操作了。同时可以进行一些ioctl操作了,但是只限于drm相关的只读操作且不需要调用 相关回调函数的操作。
到目前为止,凡是和Modesetting相关的操作,还是操作不了
4.13.6.3. 添加drm mode objects
#include <drm/drmP.h>
static struct drm_device drm; //用于抽象一个完整的DRM设备
//增加drm mode object
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
//增加drm mode object对应的回调函数结构体
static const struct drm_plane_funcs vkms_plane_funcs;
static const struct drm_crtc_funcs vkms_crtc_funcs;
static const struct drm_encoder_funcs vkms_encoder_funcs;
static const struct drm_connector_funcs vkms_connector_funs;
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGS8888,
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm); //初始化KMS框架,本质上是初始化drm_device中mode_config结构体
//最后初始化drm_device中包含的drm_connector, drm_crtc等对象
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs, vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET, //添加DRIVER_MODESET标志位,告诉DRM Core当前驱动支持modesetting操作
.fops = &vkms_fops, //增加fops操作接口
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022-8-9",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_register(&drm, 0);
return 0;
}
module_init(vkms_init);
由于上面4个object在创建时,他们的callback funcs没有赋初值,所以真正的modeset操作目前还无法正常运行,不过至少可以使用一些 只读的modeset ioctl了(不需要调用DRM相关回调函数的操作)
4.13.6.4. 添加FB和GEM支持
#include <drm/drmP.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
static struct drm_device drm; //用于抽象一个完整的DRM设备
//增加drm mode object
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
//增加drm mode object对应的回调函数结构体
static const struct drm_plane_funcs vkms_plane_funcs;
static const struct drm_crtc_funcs vkms_crtc_funcs;
static const struct drm_encoder_funcs vkms_encoder_funcs;
static const struct drm_connector_funcs vkms_connector_funs;
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGS8888,
};
//定义了一些函数指针,用于管理驱动程序中的显示模式配置
//这些函数指针包括添加和删除连接器、CRTC和编解码器,以及更新显示模式等功能
//这些函数在驱动程序中被调用以进行显示模式的管理和配置
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create, //根据给定的帧缓冲参数,创建一个新的帧缓冲设备,并返回其句柄
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm); //初始化KMS框架,本质上是初始化drm_device中mode_config结构体
//填充mode_config中min_width, max_width, max_height,这些值是framebuffer大小限制
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
//设置mode_config->func指针,本质上是一组由驱动程序实现的回调函数,涵盖KMS中一些相当基本的操作
drm.mode_config.funcs = &vkms_mode_funcs;
//最后初始化drm_device中包含的drm_connector, drm_crtc等对象
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs, vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
//DRM的GEM对象映射到用户空间,以便用于空间使用
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET, //添加DRIVER_MODESET标志位,告诉DRM Core当前驱动支持modesetting操作
.fops = &vkms_fops, //增加fops操作接口
//增加fb相关操作
.dumb_create = drm_gem_cma_dumb_create, //用于创建dumb buffer,也就是一个简单的、未映射的内存区域。
//通常用于测试或者临时存储
.gem_vm_ops = &drm_gem_cma_vm_ops, //用于实现GEM内存管理机制的各种功能
//用户空间程序请求显存时被调用,用于控制显存的访问权限、分配显存
.gem_free_object_unlocked = drm_gem_cma_free_object,
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022-8-9",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_register(&drm, 0);
return 0;
}
module_init(vkms_init);
现在可以使用ioctl进行一些标准的GEM和FB操作了
在drm_mode_config结构体中存在一个类型为drm_mode_config_func的回调函数指针结构体,用于驱动程序向内核注册显示器模式 配置。这些函数指针包含添加和删除连接器,CRTC和编码器,以及更新显示模式等功能。
注解
drm_mode_config_funcs结构体中fb_create创建一个新的帧缓冲对象,但并不是分配内存,frambuffer不涉及内存的分配与 释放。framebuffer通过访问GEM对象来获取内存区域的物理地址信息。
4.13.6.5. 实现callback funcs并添加Legacy Modeset支持
#include <drm/drmP.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
static struct drm_device drm; //用于抽象一个完整的DRM设备
//增加drm mode object
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
static void vkms_crtc_dpms(struct drm_crtc *crtc, int mode)
{
}
static int vkms_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
int x, int y, struct drm_framebuffer *old_fb)
{
return 0;
}
static void vkms_crtc_prepare(struct drm_crtc *crtc)
{
}
static void vkms_crtc_commit(struct drm_crtc *crtc)
{
}
static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.dpms = vkms_crtc_dpms,
.mode_set = vkms_crtc_mode_set,
.prepare = vkms_crtc_prepare,
.commit = vkms_crtc_commit,
};
static int vkms_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags,
struct drm_modeset_acquire_ctx *ctx)
{
unsigned long flags;
crtc->primary->fb = fb;
if(event) {
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irqsave(&crtc->dev->event_lock, flags);
}
return 0;
}
//增加drm mode object对应的回调函数结构体
static const struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_primary_helper_update,
.disable_plane = drm_primary_helper_disable,
.destroy = drm_plane_cleanup,
};
static const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.page_flip = vkms_cetc_page_flip,
.destroy = drm_crtc_cleanup,
};
static const struct drm_encoder_funcs vkms_encoder_funcs;
static const struct drm_connector_funcs vkms_connector_funs;
static const u32 vkms_formats[] = {
DRM_FORMAT_XRGS8888,
};
static int vkms_connector_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector, 8192, 8192);
drm_set_preferred_mode(connector, 1024, 768);
return count;
}
static struct drm_encode *vkms_connector_best_encoder(struct drm_connector *connector)
{
return &encoder;
}
static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.get_modes = vkms_connector_get_modes,
.best_encoder = vkms_connector_best_encoder,
};
static const struct drm_connector_funcs vkms_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
};
static const struct encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
//定义了一些函数指针,用于管理驱动程序中的显示模式配置
//这些函数指针包括添加和删除连接器、CRTC和编解码器,以及更新显示模式等功能
//这些函数在驱动程序中被调用以进行显示模式的管理和配置
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create, //根据给定的帧缓冲参数,创建一个新的帧缓冲设备,并返回其句柄
};
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm); //初始化KMS框架,本质上是初始化drm_device中mode_config结构体
//填充mode_config中min_width, max_width, max_height,这些值是framebuffer大小限制
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
//设置mode_config->func指针,本质上是一组由驱动程序实现的回调函数,涵盖KMS中一些相当基本的操作
drm.mode_config.funcs = &vkms_mode_funcs;
//最后初始化drm_device中包含的drm_connector, drm_crtc等对象
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs, vkms_formats, ARRAY_SIZE(vkms_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_crtc_helper_add(&crtc, &vkms_crtc_helper_funcs);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(&connector, &vkms_conn_helper_funcs);
drm_mode_connector_attach_encoder(&connector, &encoder);
}
static const struct file_operations vkms_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.poll = drm_poll,
.read = drm_read,
//DRM的GEM对象映射到用户空间,以便用于空间使用
.mmap = drm_gem_cma_mmap,
};
static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET, //添加DRIVER_MODESET标志位,告诉DRM Core当前驱动支持modesetting操作
.fops = &vkms_fops, //增加fops操作接口
//增加fb相关操作
.dumb_create = drm_gem_cma_dumb_create, //用于创建dumb buffer,也就是一个简单的、未映射的内存区域。
//通常用于测试或者临时存储
.gem_vm_ops = &drm_gem_cma_vm_ops, //用于实现GEM内存管理机制的各种功能
//用户空间程序请求显存时被调用,用于控制显存的访问权限、分配显存
.gem_free_object_unlocked = drm_gem_cma_free_object,
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022-8-9",
.major = 1,
.minor = 0,
};
static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
vkms_modeset_init();
drm_register(&drm, 0);
return 0;
}
module_init(vkms_init);