《DRM 专栏》| 彻底入门 DRM 驱动
来源:一口Linux 发布时间:2022-12-29 分享至微信

本篇我们一起来学习如何在 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 驱动之间的耦合度。

耦合的产生:

  1. connector 的主要作用就是获取显示参数,所以会在 LCD 驱动中去构造 connector object。但是 connector 初始化时需要 attach 上一个 encoder object,而这个 encoder object 往往是在另一个硬件驱动中生成的,为了访问该 encoder object,势必会产生一部分耦合的代码。
  2. encoder 除了扮演信号转换的角色,还担任着通知显示设备休眠唤醒的角色。因此,当 encoder 通知 LCD 驱动执行相应的 enable/disable 操作时,就一定会调用 LCD 驱动导出的全局函数,这也必然会产生一部分的耦合代码。

为了解决该耦合的问题,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 接口

下图为一个典型的 MIPI DSI 接口屏的硬件连接框图:

它在软件架构上与 DRM object 的对应关系如下图:

多余的细节不做介绍,这里只说明为何如此分配 drm object:

  • MIPI DPI 接口

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 驱动已经集成了如下功能:

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

下面就跟着我一起来学习,如何从0到1实现一个 VKMS 驱动吧!

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

#include<drm/drmP.h>

staticstructdrm_devicedrm;

staticstructdrm_drivervkms_driver={
.name="vkms",
.desc="VirtualKernelModeSetting",
.date="20180514",
.major=1,
.minor=0,
};

staticint__initvkms_init(void)
{
drm_dev_init(drm,vkms_driver,NULL);
drm_dev_register(drm,0);

return0;
}

module_init(vkms_init);

DRM 框架还为我们做了下面这些事情:

  1. 创建设备节点:/dev/dri/card0
  2. 创建 sysfs 节点:/sys/class/drm/card0
  3. 创建 debugfs 节点:/sys/kernel/debug/dri/0

不过该驱动目前什么事情也做不了,你唯一能做的就是查看该驱动的名字:

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

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

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

#include<drm/drmP.h>

staticstructdrm_devicedrm;

staticconststructfile_operationsvkms_fops={
.owner=THIS_MODULE,
.open=drm_open,
.release=drm_release,
.unlocked_ioctl=drm_ioctl,
.poll=drm_poll,
.read=drm_read,
};

staticstructdrm_drivervkms_driver={
.fops=vkms_fops,

.name="vkms",
.desc="VirtualKernelModeSetting",
.date="20180514",
.major=1,
.minor=0,
};

staticint__initvkms_init(void)
{
drm_dev_init(drm,vkms_driver,NULL);
drm_dev_register(drm,0);

return0;
}

module_init(vkms_init);

有了 fops,我们就可以对 card0 进行 open,read,ioctl操作了。让我们看看现在可以执行哪些 IOCTL:

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

添加 drm mode objects:

#include<drm/drmP.h>
#include<drm/drm_encoder.h>

staticstructdrm_devicedrm;
staticstructdrm_planeprimary;
staticstructdrm_crtccrtc;
staticstructdrm_encoderencoder;
staticstructdrm_connectorconnector;

staticconststructdrm_plane_funcsvkms_plane_funcs;
staticconststructdrm_crtc_funcsvkms_crtc_funcs;
staticconststructdrm_encoder_funcsvkms_encoder_funcs;
staticconststructdrm_connector_funcsvkms_connector_funcs;

staticconstu32vkms_formats[]={
DRM_FORMAT_XRGB8888,
};

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

staticconststructfile_operationsvkms_fops={
.owner=THIS_MODULE,
.open=drm_open,
.release=drm_release,
.unlocked_ioctl=drm_ioctl,
.poll=drm_poll,
.read=drm_read,
};

staticstructdrm_drivervkms_driver={
.driver_features=DRIVER_MODESET,
.fops=vkms_fops,

.name="vkms",
.desc="VirtualKernelModeSetting",
.date="20180514",
.major=1,
.minor=0,
};

staticint__initvkms_init(void)
{
drm_dev_init(drm,vkms_driver,NULL);

vkms_modeset_init();

drm_dev_register(drm,0);

return0;
}

module_init(vkms_init);

重点:

  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 了:

添加 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>

staticstructdrm_devicedrm;
staticstructdrm_planeprimary;
staticstructdrm_crtccrtc;
staticstructdrm_encoderencoder;
staticstructdrm_connectorconnector;

staticconststructdrm_plane_funcsvkms_plane_funcs;
staticconststructdrm_crtc_funcsvkms_crtc_funcs;
staticconststructdrm_encoder_funcsvkms_encoder_funcs;
staticconststructdrm_connector_funcsvkms_connector_funcs;

/*addhere*/
staticconststructdrm_mode_config_funcsvkms_mode_funcs={
.fb_create=drm_fb_cma_create,
};

staticconstu32vkms_formats[]={
DRM_FORMAT_XRGB8888,
};

staticvoidvkms_modeset_init(void)
{
drm_mode_config_init(drm);
drm.mode_config.max_width=8192;
drm.mode_config.max_height=8192;
/*addhere*/
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);
}

staticconststructfile_operationsvkms_fops={
.owner=THIS_MODULE,
.open=drm_open,
.release=drm_release,
.unlocked_ioctl=drm_ioctl,
.poll=drm_poll,
.read=drm_read,
/*addhere*/
.mmap=drm_gem_cma_mmap,
};

staticstructdrm_drivervkms_driver={
.driver_features=DRIVER_MODESET|DRIVER_GEM,
.fops=vkms_fops,

/*addhere*/
.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="VirtualKernelModeSetting",
.date="20180514",
.major=1,
.minor=0,
};

staticint__initvkms_init(void)
{
drm_dev_init(drm,vkms_driver,NULL);

vkms_modeset_init();

drm_dev_register(drm,0);

return0;
}

module_init(vkms_init);

重点:

  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 操作了!

实现 callback funcs,添加 Legacy Modeset 支持:

#include<drm/drm_crtc_helper.h>
#include<drm/drm_plane_helper.h>
#include<drm/drm_fb_cma_helper.h>
#include<drm/drm_gem_cma_helper.h>

staticstructdrm_devicedrm;
staticstructdrm_planeprimary;
staticstructdrm_crtccrtc;
staticstructdrm_encoderencoder;
staticstructdrm_connectorconnector;

staticvoidvkms_crtc_dpms(structdrm_crtc*crtc,intmode)
{
}

staticintvkms_crtc_mode_set(structdrm_crtc*crtc,
structdrm_display_mode*mode,
structdrm_display_mode*adjusted_mode,
intx,inty,structdrm_framebuffer*old_fb)
{
return0;
}

staticvoidvkms_crtc_prepare(structdrm_crtc*crtc)
{
}

staticvoidvkms_crtc_commit(structdrm_crtc*crtc)
{
}

staticintvkms_crtc_page_flip(structdrm_crtc*crtc,
structdrm_framebuffer*fb,
structdrm_pending_vblank_event*event,
uint32_tpage_flip_flags,
structdrm_modeset_acquire_ctx*ctx)
{
unsignedlongflags;

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

staticconststructdrm_crtc_helper_funcsvkms_crtc_helper_funcs={
.dpms=vkms_crtc_dpms,
.mode_set=vkms_crtc_mode_set,
.prepare=vkms_crtc_prepare,
.commit=vkms_crtc_commit,
};

staticconststructdrm_crtc_funcsvkms_crtc_funcs={
.set_config=drm_crtc_helper_set_config,
.page_flip=vkms_crtc_page_flip,
.destroy=drm_crtc_cleanup,
};

staticconststructdrm_plane_funcsvkms_plane_funcs={
.update_plane=drm_primary_helper_update,
.disable_plane=drm_primary_helper_disable,
.destroy=drm_plane_cleanup,
};

staticintvkms_connector_get_modes(structdrm_connector*connector)
{
intcount;

count=drm_add_modes_noedid(connector,8192,8192);
drm_set_preferred_mode(connector,1024,768);

returncount;
}

staticstructdrm_encoder*vkms_connector_best_encoder(structdrm_connector*connector)
{
returnencoder;
}

staticconststructdrm_connector_helper_funcsvkms_conn_helper_funcs={
.get_modes=vkms_connector_get_modes,
.best_encoder=vkms_connector_best_encoder,
};


staticconststructdrm_connector_funcsvkms_connector_funcs={
.dpms=drm_helper_connector_dpms,
.fill_modes=drm_helper_probe_single_connector_modes,
.destroy=drm_connector_cleanup,
};

staticconststructdrm_encoder_funcsvkms_encoder_funcs={
.destroy=drm_encoder_cleanup,
};

staticconststructdrm_mode_config_funcsvkms_mode_funcs={
.fb_create=drm_fb_cma_create,
};

staticconstu32vkms_formats[]={
DRM_FORMAT_XRGB8888,
};

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

staticconststructfile_operationsvkms_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,
};

staticstructdrm_drivervkms_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="VirtualKernelModeSetting",
.date="20180514",
.major=1,
.minor=0,
};

staticint__initvkms_init(void)
{
drm_dev_init(drm,vkms_driver,NULL);

vkms_modeset_init();

drm_dev_register(drm,0);

return0;
}

module_init(vkms_init);

重点:

  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 操作了。当前支持的 modeset IOCTL:

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

#include<drm/drm_atomic_helper.h>
#include<drm/drm_crtc_helper.h>
#include<drm/drm_fb_cma_helper.h>
#include<drm/drm_gem_cma_helper.h>
#include<linux/hrtimer.h>

staticstructdrm_devicedrm;
staticstructdrm_planeprimary;
staticstructdrm_crtccrtc;
staticstructdrm_encoderencoder;
staticstructdrm_connectorconnector;
staticstructhrtimervblank_hrtimer;

staticenumhrtimer_restartvkms_vblank_simulate(structhrtimer*timer)
{
drm_crtc_handle_vblank(crtc);

hrtimer_forward_now(vblank_hrtimer,16666667);

returnHRTIMER_RESTART;
}

staticvoidvkms_crtc_atomic_enable(structdrm_crtc*crtc,
structdrm_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);
}

staticvoidvkms_crtc_atomic_disable(structdrm_crtc*crtc,
structdrm_crtc_state*old_state)
{
hrtimer_cancel(vblank_hrtimer);
}

staticvoidvkms_crtc_atomic_flush(structdrm_crtc*crtc,
structdrm_crtc_state*old_crtc_state)
{
unsignedlongflags;

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

staticconststructdrm_crtc_helper_funcsvkms_crtc_helper_funcs={
.atomic_enable=vkms_crtc_atomic_enable,
.atomic_disable=vkms_crtc_atomic_disable,
.atomic_flush=vkms_crtc_atomic_flush,
};

staticconststructdrm_crtc_funcsvkms_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,
};

staticvoidvkms_plane_atomic_update(structdrm_plane*plane,
structdrm_plane_state*old_state)
{
}

staticconststructdrm_plane_helper_funcsvkms_plane_helper_funcs={
.atomic_update=vkms_plane_atomic_update,
};

staticconststructdrm_plane_funcsvkms_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,
};

staticintvkms_conn_get_modes(structdrm_connector*connector)
{
intcount;

count=drm_add_modes_noedid(connector,8192,8192);
drm_set_preferred_mode(connector,1024,768);

returncount;
}

staticconststructdrm_connector_helper_funcsvkms_conn_helper_funcs={
.get_modes=vkms_conn_get_modes,
};

staticconststructdrm_connector_funcsvkms_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,
};

staticconststructdrm_encoder_funcsvkms_encoder_funcs={
.destroy=drm_encoder_cleanup,
};

staticconststructdrm_mode_config_funcsvkms_mode_funcs={
.fb_create=drm_fb_cma_create,
.atomic_check=drm_atomic_helper_check,
.atomic_commit=drm_atomic_helper_commit,
};

staticconstu32vkms_formats[]={
DRM_FORMAT_XRGB8888,
};

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

staticconststructfile_operationsvkms_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,
};

staticstructdrm_drivervkms_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="VirtualKernelModeSetting",
.date="20180514",
.major=1,
.minor=0,
};

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

return0;
}

module_init(vkms_init);

重点:

  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 KMS 驱动,通常需要实现如下代码:

  1. fops、drm_driver
  2. dumb_create、fb_create、atomic_commit
  3. drm_xxx_funcs、drm_xxx_helper_funcs
  4. drm_xxx_init()、drm_xxx_helper_add()
  5. drm_dev_init()、drm_dev_register()

但这都只是表象,核心仍然是上面介绍的7个 objects,一切都围绕着这几个 objects 展开:

  1. 为了创建 crtc/plane/encoder/connector objects,需要调用 drm_xxx_init()。

  2. 为了创建 framebuffer object,需要实现 fb_create() callback。

  3. 为了创建 gem object,需要实现 dumb_create() callback。

  4. 为了创建 property objects,需要调用 drm_mode_config_init()。

  5. 为了让这些 objects 动起来,需要实现各种 funcs 和 helper funcs。

  6. 为了支持 atomic 操作,需要实现 atomic_commit() callback。

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


一口Linux

关注,回复【1024】海量Linux资料赠送


[ 新闻来源:一口Linux,更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj]
存入云盘 收藏
举报
全部评论

暂无评论哦,快来评论一下吧!