本篇我们一起来学习如何在 kernel 空间编写 DRM 驱动程序。
在开始编写 DRM 驱动程序之前,我有必要对 DRM 内部的 Objects 进行一番介绍。因为这些 Objects 是 DRM 框架的核心,它们缺一不可。
上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为 drm_mode_object,虚线以下为 drm_gem_object。
之前曾对这些 objects 做过简要介绍,这里有必要再强调一下这些 objects 的概念:
这些 objects 之间的关系:
通过上图可以看到,plane 是连接 framebuffer 和 crtc 的纽带,而 encoder 则是连接 crtc 和 connector 的纽带。与物理 buffer 直接打交道的是 gem 而不是 framebuffer。
需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些 objects,否则 DRM 子系统无法正常运行。
drm_panel 不属于 objects 的范畴,它只是一堆回调函数的集合。但它的存在降低了 LCD 驱动与 encoder 驱动之间的耦合度。
耦合的产生:
为了解决该耦合的问题,DRM 子系统为开发人员提供了 drm_panel 结构体,该结构体封装了 connector & encoder 对 LCD 访问的常用接口。
于是,原来的 Encoder 驱动和 LCD 驱动之间的耦合,就转变成了上图中 Encoder 驱动与 drm_panel、drm_panel 与 LCD 驱动之间的“耦合”,从而实现了 Encoder 驱动与 LCD 驱动之间的解耦合。
为了方便驱动程序设计,通常都将 encoder 与 connector 放在同一个驱动中初始化,即 encoder 在哪,connector 就在哪。
对于初学者来说,往往让他们迷惑的不是 DRM 中 objects 的概念,而是如何去建立这些 objects 与实际硬件的对应关系。因为并不是所有的 Display 硬件都能很好的对应上 plane/crtc/encoder/connector 这些 objects。下面我们就来一起学习,如何去抽象显示硬件到具体的 DRM object。
下图为一个典型的 MIPI DSI 接口屏的硬件连接框图:
它在软件架构上与 DRM object 的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配 drm object:
DPI 接口也就是我们常说的 RGB 并行接口,Video 数据通过 RGB 并行总线传输,控制命令(如初始化、休眠、唤醒等)则通过 SPI/I2C 总线传输,比如早期的 S3C2440 SoC 平台。下图为一个典型的 MIPI DPI 接口屏的硬件连接框图:
该硬件连接在软件架构上与 DRM object 的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配 drm object:
VKMS 是 “Virtual Kernel Mode Setting” 的缩写,它于2018年7月5日被合入到 linux-4.19 主线版本中,并存放在 drivers/gpu/drm/vkms 目录下。之所以称它为 Virtual KMS,是因为该驱动不需要真实的硬件,它完全是一个软件虚拟的“显示”设备,甚至连显示都算不上,因为当它运行时,你看不到任何显示内容。它唯一能提供的,就是一个由高精度 timer 模拟的 VSYNC 中断信号!该驱动存在的目的,主要是为了 DRM 框架自测试,以及方便那些无头显示器设备的调试应用。虽然我们看不到 VKMS 的显示效果,但是在驱动流程上,它实现了 modesetting 该有的基本操作。因其逻辑简单,代码量少,拿来做学习案例讲解再好不过。
随着内核版本的不断升级,添加到 VKMS 的功能也越来越多,截止到内核版本 kernel 5.7-rc2,该 VKMS 驱动已经集成了如下功能:
下面就跟着我一起来学习,如何从0到1实现一个 VKMS 驱动吧!
这是一个最简单的 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);
DRM 框架还为我们做了下面这些事情:
不过该驱动目前什么事情也做不了,你唯一能做的就是查看该驱动的名字:
$ cat /sys/kernel/debug/dri/0/name
vkms unique=vkms
你甚至都无法对 /dev/dri/card0 进行 open 操作,因为该驱动还没有实现 fops 接口。
接下来我们给 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);
有了 fops,我们就可以对 card0 进行 open,read,ioctl 操作了。让我们看看现在可以执行哪些 IOCTL:
但是到目前为止,凡是和 modesetting 相关的操作,还是操作不了。
添加 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);
重点:
由于上面4个 objects 在创建时,它们的 callback funcs 没有赋初值,所以真正的 modeset 操作目前还无法正常执行,不过我们至少可以使用下面这些只读的 modeset IOCTL 了:
添加 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);
重点:
现在,我们可以使用如下 IOCTL 来进行一些标准的 GEM 和 FB 操作了!
实现 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);
重点:
提示:本示例中的 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 操作了。当前支持的 modeset IOCTL:
将上面的 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);
重点:
终于,我们可以使用 drmModeAtomicCommit() 了。
要实现一个 DRM KMS 驱动,通常需要实现如下代码:
但这都只是表象,核心仍然是上面介绍的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 学习路上的小伙伴们提供帮助。下一篇,我将介绍 DRM GEM 相关的知识,敬请期待!
end
一口Linux
关注,回复【1024】海量Linux资料赠送
精彩文章合集
文章推荐