百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

Linux显卡驱动,DRM Atomic Commit流程介绍

sinye56 2024-11-14 17:42 57 浏览 0 评论

上一篇文章介绍了DRM显示一帧图像的Atomic接口,从系统调用到DRM内部的实现流程。Atomic接口把用户空间中的参数设置好后,分配一个drm_atomic_state对象,然后commit此state即完成图像的更新。这篇文章继续介绍Commit一个state的大体流程。 Atomic接口在内核层面从drm_ioctl开始,然后调用drm_mode_atomic_ioctl分配state对象,设置相关属性,最后调用drm_atomic_commit提交一个state。可以这么说,一帧图像的刷新,就是一个state commit的过程。state commit的过程如下图所示:

1、atomic_commit接口

atomic_commit接口位于drm_mode_config成员drm_mode_config_funcs结构体中,是commit的顶层接口,每个驱动程序可以实现自己的commit行为,也可以调用内核统一实现的函数。大部分驱动都会定义自己的drm_mode_config_funcs对象,但把atomic_commit接口指定为内核的默认实现函数drm_atomic_helper_commit。drm_atomic_commit函数封装了调用atomic_commit接口的逻辑,在调用之前先执行drm_atomic_check_only检查state是否符合要求,如果state参数不正确将返回并报错。

int drm_atomic_helper_commit(struct drm_device *dev,
        struct drm_atomic_state *state,
        bool nonblock)
{
 int ret;

 if (state->async_update) {
  ret = drm_atomic_helper_prepare_planes(dev, state);
  if (ret)
   return ret;

  drm_atomic_helper_async_commit(dev, state);
  drm_atomic_helper_cleanup_planes(dev, state);

  return 0;
 }

 ret = drm_atomic_helper_setup_commit(state, nonblock);
 if (ret)
  return ret;

 INIT_WORK(&state->commit_work, commit_work);

 ret = drm_atomic_helper_prepare_planes(dev, state);
 if (ret)
  return ret;

 if (!nonblock) {
  ret = drm_atomic_helper_wait_for_fences(dev, state, true);
  if (ret)
   goto err;
 }

 /*
  * This is the point of no return - everything below never fails except
  * when the hw goes bonghits. Which means we can commit the new state on
  * the software side now.
  */

 ret = drm_atomic_helper_swap_state(state, true);
 if (ret)
  goto err;

 /*
  * Everything below can be run asynchronously without the need to grab
  * any modeset locks at all under one condition: It must be guaranteed
  * that the asynchronous work has either been cancelled (if the driver
  * supports it, which at least requires that the framebuffers get
  * cleaned up with drm_atomic_helper_cleanup_planes()) or completed
  * before the new state gets committed on the software side with
  * drm_atomic_helper_swap_state().
  *
  * This scheme allows new atomic state updates to be prepared and
  * checked in parallel to the asynchronous completion of the previous
  * update. Which is important since compositors need to figure out the
  * composition of the next frame right after having submitted the
  * current layout.
  *
  * NOTE: Commit work has multiple phases, first hardware commit, then
  * cleanup. We want them to overlap, hence need system_unbound_wq to
  * make sure work items don't artificially stall on each another.
  */

 drm_atomic_state_get(state);
 if (nonblock)
  queue_work(system_unbound_wq, &state->commit_work);
 else
  commit_tail(state);

 return 0;

err:
 drm_atomic_helper_cleanup_planes(dev, state);
 return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_commit);

drm_atomic_helper_commit是内核实现的标准commit流程,驱动程序只要在drm_mode_config_funcs的接口中改成自己的函数,并在函数中包含对此函数的调用即可实现自己的流程,例如,追踪接口调用或调试程序等。函数主要工作是setup_commit环境,初始化一个工作队列,prepare_planes的内容,最后由工作函数(commit_work)提交到硬件中。 Commit需要异步执行,以便能够在不同的CRTC上并行地Commit Atomic,最简单的方法就是把工作函数加入到在system_unbound_wq工作队列上。驱动程序来不需要拆分Atomic并行地执行每个commit,这对于modesets来说是十分有帮助的,因为这些必须作为一个全局操作来完成,而有时启用或禁用CRTC可能需要很长时间。 如果一个drm_atomic_state对应多个CRTC的update,DRM会对CRTC进行排序。在更新plane状态时,驱动程序不能在其atomic_check过程中获取不相关的CRTC的drm_crtc_state,因为这样会阻碍多个CRTC的并行提交。而且,我们应该尽可能避免添加额外的状态结构,因为在排序和锁定时对这个有要求。 state随drm_atomic_helper_swap_state同步更新。意味着在所有modeset锁的保护下,调用者永远不会看到不一致的状态。 接下来就会按步骤的执行 a) pre-plane commit b) plane commit c) post-plane commit 然后,在旧的帧缓冲区不再显示后清理它。 至于后续的同步工作由drm_crtc_commit对象负责。

setup_commit

创建drm_crtc_commit对象,代码如下:

int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
       bool nonblock)
{
 struct drm_crtc *crtc;
 struct drm_crtc_state *old_crtc_state, *new_crtc_state;
 struct drm_connector *conn;
 struct drm_connector_state *old_conn_state, *new_conn_state;
 struct drm_plane *plane;
 struct drm_plane_state *old_plane_state, *new_plane_state;
 struct drm_crtc_commit *commit;
 int i, ret;

 for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
  commit = kzalloc(sizeof(*commit), GFP_KERNEL);
  if (!commit)
   return -ENOMEM;

  init_commit(commit, crtc);

  new_crtc_state->commit = commit;

  ret = stall_checks(crtc, nonblock);
  if (ret)
   return ret;

  /* Drivers only send out events when at least either current or
   * new CRTC state is active. Complete right away if everything
   * stays off. */
  if (!old_crtc_state->active && !new_crtc_state->active) {
   complete_all(&commit->flip_done);
   continue;
  }

  /* Legacy cursor updates are fully unsynced. */
  if (state->legacy_cursor_update) {
   complete_all(&commit->flip_done);
   continue;
  }

  if (!new_crtc_state->event) {
   commit->event = kzalloc(sizeof(*commit->event),
      GFP_KERNEL);
   if (!commit->event)
    return -ENOMEM;

   new_crtc_state->event = commit->event;
  }

  new_crtc_state->event->base.completion = &commit->flip_done;
  new_crtc_state->event->base.completion_release = release_crtc_commit;
  drm_crtc_commit_get(commit);

  commit->abort_completion = true;

  state->crtcs[i].commit = commit;
  drm_crtc_commit_get(commit);
 }

 for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) {
  /* Userspace is not allowed to get ahead of the previous
   * commit with nonblocking ones. */
  if (nonblock && old_conn_state->commit &&
      !try_wait_for_completion(&old_conn_state->commit->flip_done))
   return -EBUSY;

  /* Always track connectors explicitly for e.g. link retraining. */
  commit = crtc_or_fake_commit(state, new_conn_state->crtc ?: old_conn_state->crtc);
  if (!commit)
   return -ENOMEM;

  new_conn_state->commit = drm_crtc_commit_get(commit);
 }

 for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
  /* Userspace is not allowed to get ahead of the previous
   * commit with nonblocking ones. */
  if (nonblock && old_plane_state->commit &&
      !try_wait_for_completion(&old_plane_state->commit->flip_done))
   return -EBUSY;

  /* Always track planes explicitly for async pageflip support. */
  commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc);
  if (!commit)
   return -ENOMEM;

  new_plane_state->commit = drm_crtc_commit_get(commit);
 }

 return 0;
}
EXPORT_SYMBOL(drm_atomic_helper_setup_commit);

drm_atomic_helper_setup_commit函数分配drm_crtc_commit对象并初始化它。drm_crtc_commit对象控制整个commit过程的过程,可以这么说,整个commit其实就是围绕drm_crtc_commit的几个事件来处理的。state在实际提交给硬件之前,必须调用drm_atomic_helper_wait_for_dependencies()等待上一次的完成,对于非阻塞提交,还必须将此调用置于异步工作线程中。 硬件提交完成后必须使用drm_atomic_helper_commit_hw_done()发出信号。在此步骤之后,不允许驱动程序读取或更改任何永久性软件或硬件modeset状态。唯一的例外是通过除drm_modeset_lock锁以外的其他方式进行状态保护的对象,只能检查带有指向旧状态结构指针的独立state,例如使用drm_atomic_helper_cleanup_planes()清理旧缓冲区。 最后,在清理之前,驱动程序必须调用drm_atomic_helper_commit_cleanup_done()。 默认情况下,不需要显式地清理drm_atomic_helper_setup_commit函数分配的资源drm_atomic_state_default_clear()会自动地完成它。

prepare_planes

在commit之前,需要准备plane的各种资源

int drm_atomic_helper_prepare_planes(struct drm_device *dev,
         struct drm_atomic_state *state)
{
 struct drm_connector *connector;
 struct drm_connector_state *new_conn_state;
 struct drm_plane *plane;
 struct drm_plane_state *new_plane_state;
 int ret, i, j;

 for_each_new_connector_in_state(state, connector, new_conn_state, i) {
  if (!new_conn_state->writeback_job)
   continue;

  ret = drm_writeback_prepare_job(new_conn_state->writeback_job);
  if (ret < 0)
   return ret;
 }

 for_each_new_plane_in_state(state, plane, new_plane_state, i) {
  const struct drm_plane_helper_funcs *funcs;

  funcs = plane->helper_private;

  if (funcs->prepare_fb) {
   ret = funcs->prepare_fb(plane, new_plane_state);
   if (ret)
    goto fail;
  }
 }

 return 0;

fail:
 for_each_new_plane_in_state(state, plane, new_plane_state, j) {
  const struct drm_plane_helper_funcs *funcs;

  if (j >= i)
   continue;

  funcs = plane->helper_private;

  if (funcs->cleanup_fb)
   funcs->cleanup_fb(plane, new_plane_state);
 }

 return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_prepare_planes);

drm_atomic_helper_prepare_planes函数通过调用drm_plane_helper_funcs.prepare_fb().为plane准备新的状态,特别是帧缓冲区。如果遇到任何失败,此函数将在任何已成功准备的帧缓冲区上调用drm_plane_helper_funcs.cleanup_fb()。 prepare_fb函数是几乎每个驱动都会实现的接口,为的是准备好一个可用帧缓冲区以便输出,例如通过固定它的后备存储器,或将它重新定位到一个连续的VRAM块中。 此函数并不能中断未完成的渲染,即使异步提交能够将任何错误返回给用户空间,也不应该这样做,更好的方法是填写传入的drm_plane_state.fence成员,由它来完成工作。如果驱动程序不支持fence,那么应该通过plane结构中的私有成员实现等效功能。如果是始终固定缓冲区的驱动程序应该使用drm_gem_fb_prepare_fb()来代替此功能。prepare_fb返回成功后,系统将会调用cleanup_fb()清理fb。

2、commit_work

commit_work函数是工作队列执行例程,负责把state提交到硬件的全部工作,函数进一步调用commit_tail,但最终在drm_atomic_helper_commit_tail得以实现具体功能。

int drm_atomic_helper_prepare_planes(struct drm_device *dev,
         struct drm_atomic_state *state)
{
 struct drm_connector *connector;
 struct drm_connector_state *new_conn_state;
 struct drm_plane *plane;
 struct drm_plane_state *new_plane_state;
 int ret, i, j;

 for_each_new_connector_in_state(state, connector, new_conn_state, i) {
  if (!new_conn_state->writeback_job)
   continue;

  ret = drm_writeback_prepare_job(new_conn_state->writeback_job);
  if (ret < 0)
   return ret;
 }

 for_each_new_plane_in_state(state, plane, new_plane_state, i) {
  const struct drm_plane_helper_funcs *funcs;

  funcs = plane->helper_private;

  if (funcs->prepare_fb) {
   ret = funcs->prepare_fb(plane, new_plane_state);
   if (ret)
    goto fail;
  }
 }

 return 0;

fail:
 for_each_new_plane_in_state(state, plane, new_plane_state, j) {
  const struct drm_plane_helper_funcs *funcs;

  if (j >= i)
   continue;

  funcs = plane->helper_private;

  if (funcs->cleanup_fb)
   funcs->cleanup_fb(plane, new_plane_state);
 }

 return ret;
}
EXPORT_SYMBOL(drm_atomic_helper_prepare_planes);

drm_atomic_helper_commit_tail函数按标准流程执行commit的流程,以兼容各种显卡,每种显卡的驱动都可以在标准流程实现相应的接口以处理自己的业务

modeset_disables

drm_atomic_helper_commit_modeset_disables函数关闭所有需要关闭的输出,并用新模式准备它们(如果需要)。为了与旧的CRTC 帮助函数兼容,应该在commit plane之前调用它,这是默认的。不同的驱动程序可以将modeset分组,并在最后进行commit plane。这对于电源管理的驱动程序很有用,因为只有在实际启用了CRTC时才会发生plane更新。

commit_planes

具体的commit行为,下一节讲到。

modeset_enables

drm_atomic_helper_commit_modeset_enables函数启用所有具有更新时必须关闭的新配置的输出。有disable就有enable,此为配套函数,不同的驱动可以选择实现不同的功能。

fake_vblank

drm_atomic_helper_fake_vblank函数用于遍历所有CRTC并为drm_crtc_state. no_vblank等于true和drm_crtc_state.event != NULL的Commit制造一个VBLANK事件。主要用于写回连接器以一次性模式工作。此操作仅对已入队列的作业有效,对任何没有接触连接器的管线无效,会导致在调用drm_atomic_helper_wait_for_vblank()或drm_atomic_helper_wait_for_flip_done()时超时。

hw_done

drm_atomic_helper_commit_hw_done函数用于发出硬件提交步骤完成的信号。在此步骤之后,不允许驱动程序读取或更改任何永久性软件或硬件模式集状态。调用此功能后,驱动程序应尝试推迟任何开销或延迟较大的清理工作。

wait_vblank

drm_atomic_helper_wait_for_vblank函数在commit后,应该等待所有受影响的CRTC上的VBLANK。但它只会等待framebuffers已实际更改的CRTC,用以优化cursor和plane的更新。

3、commit planes

drm_atomic_helper_commit_planes函数是commit的核心函数,每一个CRTC的更新,都可以看作它内部每一个plane更新叠加后的成果。

void drm_atomic_helper_commit_planes(struct drm_device *dev,
         struct drm_atomic_state *old_state,
         uint32_t flags)
{
 struct drm_crtc *crtc;
 struct drm_crtc_state *old_crtc_state, *new_crtc_state;
 struct drm_plane *plane;
 struct drm_plane_state *old_plane_state, *new_plane_state;
 int i;
 bool active_only = flags & DRM_PLANE_COMMIT_ACTIVE_ONLY;
 bool no_disable = flags & DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET;

 for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
  const struct drm_crtc_helper_funcs *funcs;

  funcs = crtc->helper_private;

  if (!funcs || !funcs->atomic_begin)
   continue;

  if (active_only && !new_crtc_state->active)
   continue;

  funcs->atomic_begin(crtc, old_crtc_state);
 }

 for_each_oldnew_plane_in_state(old_state, plane, old_plane_state, new_plane_state, i) {
  const struct drm_plane_helper_funcs *funcs;
  bool disabling;

  funcs = plane->helper_private;

  if (!funcs)
   continue;

  disabling = drm_atomic_plane_disabling(old_plane_state,
             new_plane_state);

  if (active_only) {
   /*
    * Skip planes related to inactive CRTCs. If the plane
    * is enabled use the state of the current CRTC. If the
    * plane is being disabled use the state of the old
    * CRTC to avoid skipping planes being disabled on an
    * active CRTC.
    */
   if (!disabling && !plane_crtc_active(new_plane_state))
    continue;
   if (disabling && !plane_crtc_active(old_plane_state))
    continue;
  }

  /*
   * Special-case disabling the plane if drivers support it.
   */
  if (disabling && funcs->atomic_disable) {
   struct drm_crtc_state *crtc_state;

   crtc_state = old_plane_state->crtc->state;

   if (drm_atomic_crtc_needs_modeset(crtc_state) &&
       no_disable)
    continue;

   funcs->atomic_disable(plane, old_plane_state);
  } else if (new_plane_state->crtc || disabling) {
   funcs->atomic_update(plane, old_plane_state);
  }
 }

 for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
  const struct drm_crtc_helper_funcs *funcs;

  funcs = crtc->helper_private;

  if (!funcs || !funcs->atomic_flush)
   continue;

  if (active_only && !new_crtc_state->active)
   continue;

  funcs->atomic_flush(crtc, old_crtc_state);
 }
}
EXPORT_SYMBOL(drm_atomic_helper_commit_planes);

drm_atomic_helper_commit_planes函数提交新的plane state,前提条件是atomic state必须保存有相关的对象指针,因为这一步不许失败。old_state参数表示哪些平面和CRTC需要更新。请注意,此功能一步完成所有CRTC的所有plane的更新。如果硬件不支持这种方法,可以查看drm_atomic_helper_commit_planes_on_crtc函数。当CRTC被disable时,应用程序可以更新plane的参数。DRM/KMS核心将参数存储在plane state中,当CRTC再次enable时,驱动就可以使用它们。因此,大多数驱动不需要通知plane为disabled CRTC进行更新。 驱动应该在flags中设置ACTIVE_ONLY标志,以免收到与disabled CRTC的相关的更新通知。这避免了当驱动程序或硬件不能或不需要处理disabled CRTC上的更新时,需要添加手动忽略的代码。 如果相关显示控制器要求在禁用CRTC时禁用CRTC的plane,则驱动可以在flags中设置NO_DISABLE_AFTER_MODESET标志,如果旧的plane state的CRTC需要modesetting操作,此标志将跳过对plane的drm_plane_helper_funcs.atomic_disable的调用。当然,驱动程序需要在CRTC disable回调中disable plane,因为这个只能自己去完成。 为了与遗留代码保持最大的兼容,drm_atomic_helper_commit默认并没有设置ACTIVE_ONLY标志,这点需要注意。

开始,函数遍历state中的每一个CRTC,执行与它相关的atomic_begin接口,各驱动可以实现这些接口,处理自己需要处理的逻辑。 接着,遍历state中的每一个plane,调用与plane相关的atomic_update接口,这个接口几乎每个驱动都会实现,这就是把数据提交的硬件的具体执行的地方。 最后,又遍历state中的每一个CRTC,执行与它相关的atomic_flush接口,确保数据提交到了硬件上。

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&amp;配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: