《DRM专栏》|彻底入门DRM驱动

一口Linux 2022-12-29 21:40

本篇我们一起来学习如何在 kernel 空间编写 DRM 驱动程序。

DRM 驱动相关的概念

Objects

在开始编写 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

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 学习

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 驱动吧!

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

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
vkms unique=vkms

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

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

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

但是到目前为止,凡是和 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. 给 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 了:

示例 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. 给 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 操作了!

示例 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, 81928192);
 drm_set_preferred_mode(connector, 1024768);

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

示例 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, 81928192);
 drm_set_preferred_mode(connector, 1024768);

 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. 给 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 相关的知识,敬请期待! 

end


一口Linux 


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

精彩文章合集


文章推荐

【专辑】ARM
【专辑】粉丝问答
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图
一口Linux 写点代码,写点人生!
评论
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 198浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 101浏览
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 588浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 267浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 88浏览
  • 电竞鼠标应用环境与客户需求电竞行业近年来发展迅速,「鼠标延迟」已成为决定游戏体验与比赛结果的关键因素。从技术角度来看,传统鼠标的延迟大约为20毫秒,入门级电竞鼠标通常为5毫秒,而高阶电竞鼠标的延迟可降低至仅2毫秒。这些差异看似微小,但在竞技激烈的游戏中,尤其在对反应和速度要求极高的场景中,每一毫秒的优化都可能带来致胜的优势。电竞比赛的普及促使玩家更加渴望降低鼠标延迟以提升竞技表现。他们希望通过精确的测试,了解不同操作系统与设定对延迟的具体影响,并寻求最佳配置方案来获得竞技优势。这样的需求推动市场
    百佳泰测试实验室 2025-01-16 15:45 340浏览
  • 随着消费者对汽车驾乘体验的要求不断攀升,汽车照明系统作为确保道路安全、提升驾驶体验以及实现车辆与环境交互的重要组成,日益受到业界的高度重视。近日,2024 DVN(上海)国际汽车照明研讨会圆满落幕。作为照明与传感创新的全球领导者,艾迈斯欧司朗受邀参与主题演讲,并现场展示了其多项前沿技术。本届研讨会汇聚来自全球各地400余名汽车、照明、光源及Tier 2供应商的专业人士及专家共聚一堂。在研讨会第一环节中,艾迈斯欧司朗系统解决方案工程副总裁 Joachim Reill以深厚的专业素养,主持该环节多位
    艾迈斯欧司朗 2025-01-16 20:51 244浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 124浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 176浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 173浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 235浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 131浏览
  • 80,000人到访的国际大展上,艾迈斯欧司朗有哪些亮点?感未来,光无限。近日,在慕尼黑electronica 2024现场,ams OSRAM通过多款创新DEMO展示,以及数场前瞻洞察分享,全面展示自身融合传感器、发射器及集成电路技术,精准捕捉并呈现环境信息的卓越能力。同时,ams OSRAM通过展会期间与客户、用户等行业人士,以及媒体朋友的深度交流,向业界传达其以光电技术为笔、以创新为墨,书写智能未来的深度思考。electronica 2024electronica 2024构建了一个高度国际
    艾迈斯欧司朗 2025-01-16 20:45 613浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 118浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 225浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦