DRM 驱动程序开发(VKMS)

前言

距离上一篇《DRM 驱动程序开发(开篇)》已经过去快整整1年了,如果再不更新的话,这个 DRM 系列教程很可能就夭折了。之所以现在才写本文,主要有两个原因:1. 本人工作内容发生变动。2. 始终没找到合适的硬件平台来做示例讲解。其中第2点是我一直拖延的主要原因,因为作为示例教程,我希望它是硬件无关的,这样即使大家没有硬件环境也可以学习 DRM 驱动开发。可惜,最终找来找去也就 QEMU 比较可行,但需要自己开发模拟硬件,还在摸索中。

与其等到 QEMU 硬件开发完成(猴年马月),不如先找个最简单的 DRM 驱动程序讲讲,也不至于让那些关注本教程的人苦苦等待。好了,那就让我们直接上 VKMS 吧!

VKMS 简介

VKMS 是 “Virtual Kernel Mode Setting” 的缩写,它于2018年7月5日被合入到 linux-4.19 主线版本中,并存放在 drivers/gpu/drm/vkms 目录下。之所以称它为 Virtual KMS,是因为该驱动不需要真实的硬件,它完全是一个软件虚拟的“显示”设备,甚至连显示都算不上,因为当它运行时,你看不到任何显示内容。它唯一能提供的,就是一个由高精度 timer 模拟的 VSYNC 中断信号!该驱动存在的目的,主要是为了 DRM 框架自测试,以及方便那些无头显示器设备的调试应用。虽然我们看不到 VKMS 的显示效果,但是在驱动流程上,它实现了 modesetting 该有的基本操作。因其逻辑简单,代码量少,拿来做学习案例讲解再好不过。

DRM 驱动程序开发(VKMS)_第1张图片

随着内核版本的不断升级,添加到 VKMS 的功能也越来越多,截止到目前最新的内核版本 kernel 5.7-rc2,该 VKMS 驱动已经集成了如下功能:

  • Atomic Modeset
  • VBlank
  • Dumb Buffer
  • Cursor & Primary Plane
  • Framebuffer CRC 校验
  • Plane Composition
  • GEM Prime Import

linux-4.19-rc1 是 VKMS 的第一个版本,也最为简单,因此本文就以该版本为例,并将其反向移植到 linux-4.14.143 上,同时对代码做了进一步精简,尽量避免细枝末节。下面就跟着我一起来学习,如何从0到1实现一个 VKMS 驱动吧!

示例 1

这是一个最简单的 DRM 驱动代码:

#include 

static struct drm_device drm;

static struct drm_driver vkms_driver = {
.name = “vkms”,
.desc = “Virtual Kernel Mode Setting”,
.date = “20180514”,
.major = 1,
.minor = 0,
};

static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
drm_dev_register(&drm, 0);

return 0;

}

module_init(vkms_init);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

你没看错,就这么几行代码!是不是颠覆了你对 DRM Driver 的认识?
我知道你在怀疑什么,让我们来看看这点代码能给我们带来些什么。我们将该驱动以 build-in 方式编译进内核,然后启动内核,如果你在 kernel log 中仔细查找,会发现有如下 drm log:

[drm] Initialized vkms 1.0.0 20180514 for virtual device on minor 0

 
   
   
   
   
  • 1

这些信息正是从上面的 namedatemajorminor 字段中获取的。除此之外,DRM 框架还为我们做了下面这些事情:

  • 创建设备节点:/dev/dri/card0
  • 创建 sysfs 节点:/sys/class/drm/card0
  • 创建 debugfs 节点:/sys/kernel/debug/dri/0

当然,简单是以牺牲功能为代价的。该驱动目前什么事情也做不了,你唯一能做的就是查看该驱动的名字:

$ cat /sys/kernel/debug/dri/0/name
vkms unique=vkms

 
   
   
   
   
  • 1
  • 2

你甚至都无法对 /dev/dri/card0 进行 open 操作,因为该 vkms driver 都还没有实现 fops 接口!

即使不添加任何 DRM 驱动,仅仅只是让 DRM Core 代码参与编译,你会发现 DRM 框架已经为我们创建好了如下调试节点:

  • /sys/class/drm/
  • /sys/kernel/debug/dri/
  • /sys/module/drm/parameters/
  • /sys/module/drm_kms_helper/parameters/

示例 2

接下来我们给 vkms 添加上 fops 操作接口。

#include 

static struct drm_device 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,

.name			= "vkms",
.desc			= "Virtual Kernel Mode Setting",
.date			= "20180514",
.major			= 1,
.minor			= 0,

};

static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);
drm_dev_register(&drm, 0);

return 0;

}

module_init(vkms_init);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

有了 fops,我们就可以对 card0 进行 open / read 操作了。更重要的是,我们现在可以进行一些简单的 ioctl 操作了!让我们看看现在可以执行哪些 IOCTL:

IOCTL Userspace API 描述
DRM_IOCTL_VERSION drmGetVersion() 获取 Driver 版本信息,即上面的 name、desc、date、major、minor 字段
DRM_IOCTL_GET_UNIQUE drmGetBusid() 获取 Bus ID
DRM_IOCTL_GET_MAGIC drmGetMagic() 获取 Magic Number,用于 GEM ioctl 权限检查
DRM_IOCTL_AUTH_MAGIC drmAuthMagic() 通过当前 Magic Number 获取 GEM ioctl 的权限认证
DRM_IOCTL_GET_CLIENT drmGetClient() 获取当前 DRM 设备上的所有 client 进程
DRM_IOCTL_GET_CAP drmGetCap() 获取当前 DRM 设备所支持的能力
DRM_IOCTL_SET_CLIENT_CAP drmSetClientCap() 告诉 DRM 驱动当前用户进程所支持的能力
DRM_IOCTL_SET_MASTER drmSetMaster() 获取 DRM-Master 访问权限
DRM_IOCTL_DROP_MASTER drmDropMaster() 放弃 DRM-Master 访问权限

到目前为止,凡是和 modesetting 相关的操作,我们还是操作不了。

示例 3

添加 drm mode objects:

#include 
#include 

static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;

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_funcs;

static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};

static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);

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,
.fops = &vkms_fops,

.name			= "vkms",
.desc			= "Virtual Kernel Mode Setting",
.date			= "20180514",
.major			= 1,
.minor			= 0,

};

static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);

vkms_modeset_init();

drm_dev_register(&drm, 0);

return 0;

}

module_init(vkms_init);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

重点:

  1. 给 driver_features 添加上 DRIVER_MODESET 标志位,告诉 DRM Core 当前驱动支持 modesetting 操作;
  2. drm_mode_config_init() 初始化一些全局的数据结构。注意,那些 Standard Properties 就是在这里创建的。
  3. drm_xxx_init() 则分别用于创建 plane、crtc、encoder、connector 这4个 drm_mode_object

由于上面4个 objects 在创建时,它们的 callback funcs 没有赋初值,所以真正的 modeset 操作目前还无法正常执行,不过我们至少可以使用下面这些只读的 modeset IOCTL 了:

IOCTL Userspace API 描述
DRM_IOCTL_MODE_GETRESOURCES drmModeGetResources() 获取 fb、crtc、connector、encoder 资源列表,
DRM_IOCTL_MODE_GETPLANERESOURCES drmModeGetPlaneResources() 获取 Plane 资源列表
DRM_IOCTL_MODE_GETCRTC drmModeGetCrtc() 获取 CRTC 当前的状态信息
DRM_IOCTL_MODE_GETPLANE drmModeGetPlane() 获取 Plane 当前的状态信息
DRM_IOCTL_MODE_GETENCODER drmModeGetEncoder() 获取 Encoder 当前的状态信息
DRM_IOCTL_MODE_GETCONNECTOR drmModeGetConnector() 获取 Connector 的当前状态信息
DRM_IOCTL_MODE_GETPROPBLOB drmModeGetPropertyBlob() 获取1个 Property Blob 对象
DRM_IOCTL_MODE_CREATEPROPBLOB drmModeCreatePropertyBlob() 创建1个 Property Blob 对象
DRM_IOCTL_MODE_DESTROYPROPBLOB drmModeDestroyPropertyBlob() 销毁1个 Property Blob 对象

示例 4

添加 FB 和 GEM 支持:

#include 
#include 
#include 
#include 

static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;

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_funcs;

/* add here */
static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create,
};

static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};

static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
/* add here */
drm.mode_config.funcs = &vkms_mode_funcs;

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,
/* add here */
.mmap = drm_gem_cma_mmap,
};

static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
.fops = &vkms_fops,

/* add here */
.dumb_create	= drm_gem_cma_dumb_create,
.gem_vm_ops		= &drm_gem_cma_vm_ops,
.gem_free_object_unlocked = drm_gem_cma_free_object,

.name			= "vkms",
.desc			= "Virtual Kernel Mode Setting",
.date			= "20180514",
.major			= 1,
.minor			= 0,

};

static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);

vkms_modeset_init();

drm_dev_register(&drm, 0);

return 0;

}

module_init(vkms_init);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

重点:

  1. 给 driver_features 添加上 DRIVER_GEM 标志位,告诉 DRM Core 该驱动支持 GEM 操作;
  2. dumb_create 回调接口用于创建 gem object,并分配物理 buffer。这里直接使用 CMA helper 函数来实现;
  3. fb_create 回调接口用于创建 framebuffer object,并绑定 gem objects。这里直接使用 CMA helper 函数实现。
  4. fops 中的 mmap 接口,用于将 dumb buffer 映射到 userspace,它依赖 drm driver 中的 gem_vm_ops 实现。这里也直接使用 CMA helper 函数来实现。

现在,我们可以使用如下 IOCTL 来进行一些标准的 GEM 和 FB 操作了!

IOCTL Userspace API 描述
DRM_IOCTL_MODE_CREATE_DUMB dumb_bo_create() 创建一个 dumb buffer 对象
DRM_IOCTL_MODE_MAP_DUMB dumb_bo_map() 将 dumb buffer 映射到用户空间
DRM_IOCTL_MODE_DESTROY_DUMB dumb_bo_destroy() 销毁一个 dumb buffer 对象
DRM_IOCTL_MODE_ADDFB drmModeAddFB() 向 DRM 驱动注册一个 framebuffer object
DRM_IOCTL_MODE_ADDFB2 drmModeAddFB2() drmModeAddFB() 的升级版
DRM_IOCTL_MODE_GETFB drmModeGetFB() 获取指定 ID 的 framebuffer object
DRM_IOCTL_MODE_RMFB drmModeRmFB() 销毁指定的 framebuffer object

示例 5

实现 callback funcs,添加 Legacy Modeset 支持(代码量较大):

#include 
#include 
#include 
#include 

static struct drm_device drm;
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 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_irqrestore(&crtc->dev->event_lock, flags);
}
return 0;

}

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 const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.page_flip = vkms_crtc_page_flip,
.destroy = drm_crtc_cleanup,
};

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 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_encoder 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 drm_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};

static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create,
};

static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};

static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
drm.mode_config.funcs = &vkms_mode_funcs;

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,
.mmap = drm_gem_cma_mmap,
};

static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM,
.fops = &vkms_fops,

.dumb_create	= drm_gem_cma_dumb_create,
.gem_vm_ops		= &drm_gem_cma_vm_ops,
.gem_free_object_unlocked = drm_gem_cma_free_object,

.name			= "vkms",
.desc			= "Virtual Kernel Mode Setting",
.date			= "20180514",
.major			= 1,
.minor			= 0,

};

static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);

vkms_modeset_init();

drm_dev_register(&drm, 0);

return 0;

}

module_init(vkms_init);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164

重点:

  1. xxx_funcs 必须有,xxx_helper_funcs 可以没有。
  2. drm_xxx_init() 必须有,drm_xxx_helper_add() 可以没有。
  3. 只有当 xxx_funcs 采用 DRM 标准的 helper 函数实现时,才有可能 需要定义 xxx_helper_funcs 接口。
  4. drmModeSetCrtc() ===> crtc_funcs.set_config()
    drmModePageFlip() ===> crtc_funcs.page_flip()
    drmModeSetPlane() ===> plane_funcs.update_plane()
    drmModeGetConnector() ===> connector_funcs.fill_modes()
  5. xxx_funcs.destroy() 接口必须实现。

提示:本示例中的 funcs 和 helper funcs 接口无法再精简,否则运行时将出现 kernel crash!

helper 函数的作用:
drm_xxx_funcs 是 drm ioctl 操作的最终入口,但是对于大多数 SoC 厂商来说,它们的 drm_xxx_funcs 操作流程基本相同(你抄我,我抄你),只是在寄存器配置上存在差异,因此开发者们将那些 common 的操作流程做成了 helper 函数,而将那些厂商差异化的代码放到了 drm_xxx_helper_funcs 中去,由 SoC 厂商自己实现。

有了各种 funcs 和 helper funcs,我们现在终于可以执行真正的 modeset 操作了,不过目前只支持 legacy modeset,赶紧使用《最简单的DRM应用程序 (single-buffer)》 来验证一下吧!

当前支持的 modeset IOCTL:

IOCTL Userspace API 描述
DRM_IOCTL_MODE_SETCRTC drmModeSetCrtc() 初始化整个硬件 pipeline 并显示图像
DRM_IOCTL_MODE_SETPLANE drmModeSetPlane() 设置单个 Plane 的显示参数
DRM_IOCTL_MODE_PAGE_FLIP drmModePageFlip() 基于 VSYNC 同步机制的显示刷新
DRM_IOCTL_MODE_GETPROPERTY drmModeGetProperty() 根据 Property ID 获取对应 Property 的值
DRM_IOCTL_MODE_SETPROPERTY drmModeConnectorSetProperty() 根据 Property ID 设置对应 Property 的值,目前仅用于 Connector 对象

示例 6

将上面的 Legacy code 转换为 Atomic 版本:

#include 
#include 
#include 
#include 
#include 

static struct drm_device drm;
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;
static struct hrtimer vblank_hrtimer;

static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
{
drm_crtc_handle_vblank(&crtc);

hrtimer_forward_now(&vblank_hrtimer, 16666667);

return HRTIMER_RESTART;

}

static void vkms_crtc_atomic_enable(struct drm_crtc crtc,
struct drm_crtc_state old_state)
{
hrtimer_init(&vblank_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
vblank_hrtimer.function = &vkms_vblank_simulate;
hrtimer_start(&vblank_hrtimer, 16666667, HRTIMER_MODE_REL);
}

static void vkms_crtc_atomic_disable(struct drm_crtc crtc,
struct drm_crtc_state old_state)
{
hrtimer_cancel(&vblank_hrtimer);
}

static void vkms_crtc_atomic_flush(struct drm_crtc crtc,
struct drm_crtc_state old_crtc_state)
{
unsigned long flags;

if (crtc->state->event) {
	spin_lock_irqsave(&crtc->dev->event_lock, flags);
	drm_crtc_send_vblank_event(crtc, crtc->state->event);
	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);

	crtc->state->event = NULL;
}

}

static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.atomic_enable = vkms_crtc_atomic_enable,
.atomic_disable = vkms_crtc_atomic_disable,
.atomic_flush = vkms_crtc_atomic_flush,
};

static const struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.destroy = drm_crtc_cleanup,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};

static void vkms_plane_atomic_update(struct drm_plane plane,
struct drm_plane_state old_state)
{
}

static const struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
.atomic_update = vkms_plane_atomic_update,
};

static const struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};

static int vkms_conn_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 const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
.get_modes = vkms_conn_get_modes,
};

static const struct drm_connector_funcs vkms_connector_funcs = {
.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_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};

static const struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_fb_cma_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};

static const u32 vkms_formats[] = {
DRM_FORMAT_XRGB8888,
};

static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm);
drm.mode_config.max_width = 8192;
drm.mode_config.max_height = 8192;
drm.mode_config.funcs = &vkms_mode_funcs;

drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs,
			 vkms_formats, ARRAY_SIZE(vkms_formats),
			 NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_plane_helper_add(&primary, &vkms_plane_helper_funcs);

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);

drm_mode_config_reset(&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,
.mmap = drm_gem_cma_mmap,
};

static struct drm_driver vkms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.fops = &vkms_fops,

.dumb_create	= drm_gem_cma_dumb_create,
.gem_vm_ops		= &drm_gem_cma_vm_ops,
.gem_free_object_unlocked = drm_gem_cma_free_object,

.name			= "vkms",
.desc			= "Virtual Kernel Mode Setting",
.date			= "20180514",
.major			= 1,
.minor			= 0,

};

static int __init vkms_init(void)
{
drm_dev_init(&drm, &vkms_driver, NULL);

vkms_modeset_init();

drm_vblank_init(&drm, 1);

drm.irq_enabled = true;

drm_dev_register(&drm, 0);

return 0;

}

module_init(vkms_init);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184

重点:

  1. 给 driver_features 添加上 DRIVER_ATOMIC 标志位,告诉 DRM Core 该驱动支持 Atomic 操作。
  2. drm_mode_config_funcs.atomic_commit() 接口是 atomic 操作的主要入口函数,必须实现。这里直接使用 drm_atomic_helper_commit() 函数实现。
  3. Atomic 操作依赖 VSYNC 中断(即 VBLANK 事件),因此需要使用 hrtimer 来提供软件中断信号。在驱动初始化时调用 drm_vblank_init(),在 VSYNC 中断处理函数中调用 drm_handle_vblank()
  4. 在 plane/crtc/encoder/connector objects 初始化完成之后,一定要调用 drm_mode_config_reset() 来动态创建各个 pipeline 的软件状态(即 drm_xxx_state)。
  5. 与 Legacy 相比,Atomic 的 xxx_funcs 必须 实现如下接口:
    reset()
    atomic_duplicate_state()
    atomic_destroy_state()
    它们主要用于维护 drm_xxx_state 数据结构,不能省略!
  6. drm_plane_helper_funcs.atomic_update() 必须实现!

终于,我们可以使用 drmModeAtomicCommit() 了,赶紧使用前面的《DRM应用程序进阶 (atomic-plane)》 示例程序测试一下吧!

IOCTL Userspace API 描述
DRM_IOCTL_MODE_ATOMIC drmModeAtomicCommit() 不解释
DRM_IOCTL_MODE_OBJ_GETPROPERTIES drmModeObjectGetProperties() 根据 drm mode object 的 ID,获取该 object 所拥有的所有 Property 对象
DRM_IOCTL_MODE_OBJ_SETPROPERTY drmModeObjectSetProperty() 根据 drm mode object 的 ID,设置该 object 某个具体的 Property 对象的值

到这里,一个 “最简单” 的 Atomic 驱动程序也就完成了,虽然最终看起来并不那么简单。。。


总结

要实现一个 DRM KMS 驱动,通常需要实现如下代码:

  1. fopsdrm_driver
  2. dumb_createfb_createatomic_commit
  3. drm_xxx_funcsdrm_xxx_helper_funcs
  4. drm_xxx_init()drm_xxx_helper_add()
  5. drm_dev_init()drm_dev_register()

但这都只是表象,核心仍然是上一篇《DRM 驱动程序开发(开篇)》中介绍的7个 objects,一切都围绕着这几个 objects 展开:

  • 为了创建 crtc/plane/encoder/connector objects,需要调用 drm_xxx_init()
  • 为了创建 framebuffer object,需要实现 fb_create() callback。
  • 为了创建 gem object,需要实现 dumb_create() callback。
  • 为了创建 property objects,需要调用 drm_mode_config_init()
  • 为了让这些 objects 动起来,需要实现各种 funcs 和 helper funcs。
  • 为了支持 atomic 操作,需要实现 atomic_commit() callback。

DRM 驱动程序开发(VKMS)_第2张图片

结语

DRM Driver 是内核中一个较为复杂的子系统,在内核社区的更新也十分活跃,但因其涉及到的知识点较多,教程写起来难度也比较大,我想这也是为什么国内熟悉 DRM 驱动的人不在少数,却很少有人能输出 系统性 的 DRM 学习文档,毕竟要写好真的很难,而且大牛们真的都很忙。

希望我的文章,能为那些还在 DRM 学习路上的小伙伴们提供帮助,解答他们心中的疑惑。下一篇,我将介绍 DRM GEM 相关的知识,敬请期待!

最后,送上 VKMS 社区简化版(相比本文的示例,更接近于 kernel 4.19 版本):vkms_drv.c

源码下载

我的码云:vkms

参考资料

  1. kernel doc: drm/vkms Virtual Kernel Modesetting
  2. drm/vkms: Introduce basic VKMS driver

文章汇总: DRM (Direct Rendering Manager) 学习简介

你可能感兴趣的:(DRM,驱动开发)