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

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

sinye56 2024-11-14 17:42 6 浏览 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接口,确保数据提交到了硬件上。

相关推荐

RHEL8和CentOS8怎么重启网络

本文主要讲解如何重启RHEL8或者CentOS8网络以及如何解决RHEL8和CentOS8系统的网络管理服务报错,当我们安装好RHEL8或者CentOS8,重启启动网络时,会出现以下报错:...

Linux 内、外网双网卡路由配置

1.路由信息的影响Linux系统中如果有多张网卡的情况下,如果路由信息配置不正确,...

Linux——centos7修改网卡名

修改网卡名这个操作可能平时用不太上,可作为了解。修改网卡默认名从ens33改成eth01.首先修改网卡配置文件名(建议将原配置文件进行备份)...

CentOS7下修改网卡名称为ethX的操作方法

?Linux操作系统的网卡设备的传统命名方式是eth0、eth1、eth2等,而CentOS7提供了不同的命名规则,默认是基于固件、拓扑、位置信息来分配。这样做的优点是命名全自动的、可预知的...

Linux 网卡名称enss33修改为eth0

一、CentOS修改/etc/sysconfig/grub文件(修改前先备份)为GRUB_CMDLINE_LINUX变量增加2个参数(net.ifnames=0biosdevname=0),修改完成...

CentOS下双网卡绑定,实现带宽飞速

方式一1.新建/etc/sysconfig/network-scripts/ifcfg-bond0文件DEVICE=bond0IPADDR=191.3.60.1NETMASK=255.255.2...

linux 双网卡双网段设置路由转发

背景网络情况linux双网卡:网卡A(ens3)和网卡B(...

Linux-VMware设置网卡保持激活

Linux系统只有在激活网卡的状态下才能去连接网络,进行网络通讯。修改配置文件(永久激活网卡)...

VMware虚拟机三种网络模式

01.VMware虚拟机三种网络模式由于linux目前很热门,越来越多的人在学习linux,但是买一台服务放家里来学习,实在是很浪费。那么如何解决这个问题?虚拟机软件是很好的选择,常用的虚拟机软件有v...

Rocky Linux 9/CentOS Stream 9修改网卡配置/自动修改主机名(实操)

推荐...

2023年最新版 linux克隆虚拟机 解决网卡uuid重复问题

问题描述1、克隆了虚拟机,两台虚拟机里面的ip以及网卡的uuid都是一样的2、ip好改,但是uuid如何改呢?解决问题1、每台主机应该保证网卡的UUID是唯一的,避免后面网络通信有问题...

Linux网卡的Vlan配置,你可能不了解的玩法

如果服务器上连的交换机端口已经预先设置了TRUNK,并允许特定的VLAN可以通过,那么服务器的网卡在配置时就必须指定所属的VLAN,否则就不通了,这种情形在虚拟化部署时较常见。例如在一个办公环境中,办...

Centos7 网卡绑定

1、切换到指定目录#备份网卡数据cd/etc/sysconfig/network-scriptscpifcfg-enp5s0f0ifcfg-enp5s0f0.bak...

Linux搭建nginx+keepalived 高可用(主备+双主模式)

一:keepalived简介反向代理及负载均衡参考:...

Linux下Route 路由指令使用详解

linuxroute命令用于显示和操作IP路由表。要实现两个不同子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该...

取消回复欢迎 发表评论: