Linux DRM介绍:Direct Rendering Manager

DRM (Direct Rendering Manager)是Linux负责与现代GPU视频卡交互的内核子系统。用户空间程序可以使用DRM的API发送数据和命令给GPU,并且可以执行类似于设置显示模式(mode setting)的操作。最初开发DRM是作为X Server的DRI(Direct Rendering Infrastructure)在内核空间的组件。但是从那以后,它也用于其他图形堆栈中(如Wayland)。用户空间程序也可以使用DRM API来控制GPU硬件加速的3D渲染、视频解码、以及GPGPU计算。

1 总览

在Linux Kernel中已经存在一个fbdev的API接口用来管理显卡的framebuffer,但是它不能被用来处理基于视频卡的现代3D图形加速功能。现代的GPU通常需要设置和管理在其显存中的命令队列(下发命令到GPU),并且需要管理GPU显存中的缓冲区和空闲的存储空间。在一开始的时候,用户空间程序(如X server)直接管理这些资源,但X server通常只是独自访问这些资源,当两个或者更多的程序试图在同一时刻控制相同的硬件时,并且每个程序都在按照自己的方式设置自己的资源的时候,就会出现竞争问题。

DRM的出现就是为了让多个程序配合使用显卡硬件资源。DRM会获取独占访问GPU的权利,并负责初始化和维护命令队列,内存以及其他的硬件资源。程序需要访问GPU的时候需要给DRM发送请求,DRM将充当仲裁程序,并注意避免可能发生的冲突。

DRM的功能范围在多年的发展中已经扩大了很多,并且覆盖了很多以前在用户空间程序处理的功能,例如framebuffer管理、mode setting、内存共享对象、内存同步。其中的一些功能有特定的命名:如GEM(Graphics Execution Manager)、KMS(kernel mode-setting)。当明确提及其提供的功能时,以术语为准。但是它们的确是整个内核DRM子系统的一部分。

在一台计算机中有包含两块显卡的趋势(一个独立显卡、一个集成显卡),使得GPU的切换成为DRM中要解决的新的问题,为了匹配Nvidia Optimus技术,DRM提供了GPU卸载(GPU offloading)功能,称为PRIME。

未使用DRM

使用DRM

2 软件架构

DRM是内核空间程序,所以用户空间程序必须使用内核的系统调用来发送自己的请求。然而DRM没有定义自有定制的系统调用,而是遵循Unix的“everything is a file”的原则,将GPU设备以文件系统命名空间的方式(使用/dev/文件)暴露给用户。DRM检测到的GPU都被引用为DRM设备,并且创建/dev/dri/cardX(X是序列号)文件。用户空间程序将通过打开设备文件并且调用ioctl来与DRM驱动交互。不同的ioctl命令表示DRM API的不同功能。

为了方便用户空间程序调用DRM子系统,将系统调用封装了一个libdrm的库。该库只是对系统调用接口的一个封装,它为DRM API的每个ioctl提供用C函数、常量、结构和其他辅助件。使用libdrm不仅可以避免内核接口直接暴露给用户,而且可以方便在程序之间重用和共享代码。

对于每一个支持DRM的硬件,DRM包含两部分:通用的DRM core部分和设备相关的DRM device部分。DRM core提供基本的框架,不同的驱动都可以注册为DRM device。DRM core还为用户空间提供一组通用的硬件无关的功能接口。另一方面,DRM驱动程序实现接口的硬件相关部分,具体取决于它所支持的GPU类型。这些驱动应提供DRM核心未涵盖的其余ioctl的实现,也可以扩展API来提供仅具有此类硬件上可用功能的附加ioctl。当一个特定的DRM驱动程序提供额外的API时,用于空间的libdrm也由额外的库libdrm-driver扩展,用户空间可以使用该库与其他ioctl进行接口。

使用DRM访问GPU

DRM core和DRM driver

2.1 API

DRM core将多个接口暴露给用户空间程序,通常是通过相应的libdrm包装的函数使用。此外,驱动程序还通过ioctl和sysfs文件导出设备专用的接口,供用户空间驱动程序和支持设备的应用程序使用。外部的接口一般包括:存储映射、上下文管理、DMA操作、AGP管理、vblank控制、同步管理、存储管理、输出管理等。

2.1.1 DRM-Master 和 DRM-Auth

在考虑安全目的或并发问题时,DRM中的数个操作(ioctl)必须限制为每个设备只能由一个用户空间进程使用。为了实现这个限制因素,DRM限制这些ioctl只能被DRM设备视为Master的程序调用,在打开了设备节点/dev/dri/cardX的所有进程中,只有一个进程将其文件句柄标记为master,其就是第一个调用SET_MASTER ioctl的进程。其他非DRM-Master的进程想要调用这些受限制ioctl的时候,将会返回错误。拥有DRM-Master的进程也可以通过调用DROP_MASRTER放弃其Master属性。

X server(或其他显示server)通常是拥有其管理的DRM设备的DRM-Master权限的进程。X server在启动期间打开相应的设备节点,并在整个图形会话中保留这些特权,直到完成或结束。

对于其他用户空间进程,还有另一种获取特权以在DRM设备上调用某些受限操作的方式称为DRM-Auth。这是DRM设备身份验证的一种基本方法,目的是证明该过程已获得DRM主设备的批准才能获得此类特权。该过程包括:

  • 客户端通过使用GET_MAGIC ioctl命令从DRM设备处获取一个唯一的Token(32位整数),并且通过任意方式将其传送给DRM-Master(例如IPC,在DRI2中有一个DRI2Authenticate请求,任何X客户端都可以将其发送到X Server)。

  • DRM-Master进程依次通过调用AUTH_MAGIC ioctl将Token发送回DRM设备。

  • 设备将赋予auth token与DRM-Master接收到相同的进程特殊权限。

2.2 Graphics Execution Manager

随着显存容量的增加以及图形API(如OpenGL)复杂度的增加,在每个上下文切换的时候去重新初始化图形卡状态性能代价比较大。此外,现代的Linux桌面还需要一种最佳方式与合成管理器共享off-screen的缓冲区。这些需求导致了开发新方法来管理内核内部的图形缓冲区。GEM就是其中一种方法。

GEM提供了一套显式内存管理的API,通过GEM,用户空间程序可以创建、操作、释放在GPU显存上的内存对象。这些内存对象称之为GEM object,这些对象从用户空间程序的角度来看是固定的,并且不需要在程序每次重新获得对GPU控制的时候重新加载。当用户空间程序需要一块显存空间时(用来存储framebuffer、纹理、或其他GPU需要的数据缓存),就可以通过GEM API发送申请请求给DRM驱动。DRM驱动程序会跟踪已使用的显存空间,如果有满足请求的可用内存,则将“handle”返回给用户空间,在以后的操作中进一步引用分配的内存。GEM API还提供了一些其他操作,如填充缓冲区并在不再需要时释放缓冲区。当用户空间进程关闭DRM设备文件时,没有被释放的GEM句柄将被恢复。

GEM还允许两个或者更多的用户空间进程使用相同的DRM设备(操作相同的DRM驱动)并在进程间分享GEM object。GEM 的handle是每个进程本地唯一的32位整数,并且数值在其他进程中可重复使用,因此这个handle不适合共享。因为共享操作需要一个全局的命名空间,GEM通过使用GEM name全局句柄来提供一个命名空间。GEM name是由相同的DRM驱动在同一个DRM设备上创建的唯一的32位整数,并且在同一个DRM驱动中仅仅只有一个。GEM提供了一个用于从GEM handle获取GEM name的操作。用户态进程可以使用有效的IPC机制将GEM name(32位整数)传送给另一个进程。接收方的进程可以使用GEM name获取一个本地的GEM handle 用老操作原始的GEM object。

不幸的是,使用GEM name的方式并不是很安全。恶意的第三方程序可以通过穷举32位数用来访问同一DRM设备上相互共享缓冲区的GEM name。一旦找到了可用的GEM name,其对应缓冲区里面的内容就可以被访问或者修改。这就使得缓冲区中的数据完整性和安全性收到威胁。后来通过DRM中引入DMA-BUF的支持解决了这一缺陷。

除了管理显存空间以外,另外一个在显存管理系统中比较重要的事情是处理CPU和GPU之间数据的同步。现在的内存架构是非常复杂的并且拥有多个级别的cache,不仅是系统存储中在显存的架构中也存在多级cache。因此显存管理必须处理cache一致性问题来保证CPU和GPU之间共享数据的一致性。这就意味着显存管理内部实现上高度依赖于GPU和存储架构的硬件细节,因此必须是硬件相关的。

GEM最初是由Intel的工程师为给i915的驱动提供显存管理的,Intel的GMA 9xx系列是集成显卡,并且是UMA(Uniform Memory Archiecture)结构,在UMA结构下GPU和CPU共享系统的物理内存,并没有专用的VRAM。GEM定义了存储域来实现内存同步,尽管这些存储域是不依赖于具体GPU的,但其在设计上主要考虑的是UMD的内存架构,所以GEM并不适用于其他存储架构的GPU(如独立显存)。基于这个原因,其他的DRM驱动依旧提供给用户空间程序GEM API接口,但是在内部实现了一套不同的存储管理系统,以更适合自己特定的硬件以及存储架构。

GEM API也提供控制执行流(命令缓冲区)的功能,但仅限定于特定的Intel i915或者之后的GPU。并没有其他DRM驱动程序尝试实现GEM API的任何部分。

2.2.1 Translation Table Maps

TTM(Translation Table Maps)是在GEM之前开发的用于通用GPU显存管理的的项目,它专门用于管理GPU可以访问的不同类型的存储,包括独立显存(通常在显卡中)和通过I/O内存管理单元(通常称之为GART,Graphics Address Remapping Table)访问的系统内存。考虑到用户空间图形应用程序通常可以处理大量视频数据,TTM还应处理CPU无法直接寻址的VRAM部分,并且实现最好的性能。另一个重要的事情是保持所涉及的不同存储和cache之间的一致性。

TTM的主要概念是buffer object,VRAM上的空间必须是GPU可编址的。当用户空间的图形程序访问一段确定的buffer object的时候(通常填充其内容),TTM可能需要将其重新定位为CPU可以寻址的内存。随后当GPU需要访问buffer object但其不在GPU地址空间中的时候将触发重定位操作(或者GART 映射操作)。这些重定位操作中的每一个都必须处理任何相关的数据和Cache一致性问题。

TTM另外的一个重要的概念是fences。Fence实质上是管理GPU和CPU之间并发的机制。通常情况下Fence会跟踪GPU不再使用的buffer object,然后通知用户空间进程访问这个buffer object。

事实上,TTM试图管理所有类型的存储系统,其中包括无独立VRAM的情况,其以适当的方式在内存管理器中提供所有可能想到的功能,以用于任何类型的硬件,所以导致实现了一个相当负责的方案,以至于其API比起需要的更大的多。当GEM实现的更简单的内存管理器出现时,一些DRM的开发人员认为,TTM已不适用于任何特定的驱动程序,尤其是API部分。但是一部分开发者认为TTM所采用的方法更适合于独立显卡,这些显卡拥有独立的显存以及独立的IOMMU,所以他们决定在内部使用TTM实现,但是将其buffer object以GEM object的方式暴露给应用程序,因此支持GEM 的API。

但是一些驱动程序开发人员认为TTM所采用的方法更适合于具有专用视频内存和IOMMU的离散视频卡,因此他们决定在内部使用TTM,同时将其缓冲区对象公开为GEM对象,从而支持GEM API。当前使用TTM作为内部内存管理器但提供GEM API的驱动程序的例子包括AMD的radeon驱动和NVIDIA的nouveau驱动。

2.2.2 DMA Buffer Sharing和PRIME

DMA Buffer Sharing API(通常缩写为DMA-BUF)是一种Linux内核内部的API,旨在提供一种通用机制来在多个设备之间共享DMA缓冲区,并可能由不同类型的设备驱动程序进行管理。例如一个Video4Linux设备和图形适配器设备可以通过DMA-BUF共享缓冲区,以实现前者产生并由后者消耗的视频流数据的零拷贝。任何Linux设备驱动都可以将此API实现位导出者或用户(消费者)或两者都实现。

在DRM中首次利用此功能来实现PRIME,这是一种用于GPU offloading的解决方案,它使用DMA-BUF在离散GPU和集成GPU的DRM驱动程序之间共享生成的framebuffer。DMA-BUF的重要功能是将共享缓冲区作为文件描述符提供给用户空间。为了开发PRIME,在DRM API中添加了两个新的ioctl,一个用于将本地GEM handle转换为DMA-BUF文件描述符,另一个用于完全相反的操作。

后来,这两个新的ioctl被重新使用,以解决GEM缓冲区共享固有的不安全性。与GEM name不同,文件描述符不能被穷举(因为它不是一个全局的命名空间),并且Unix操作系统提供了一种安全的方式,使用SCM_RIGHTS将它们通过Unix域套接字传递。想要与另一个进程共享GEM object的进程可以将其本地GEM handle转换为DMA-BUF文件描述符,并将其传递给接收者,而接收者又可以从接收的文件描述符中获得自己的GEM handle。DRI3中使用此方法在客户端和X Server之间以及Wayland中共享缓冲区。

2.3 Kernel Mode Setting

为了正常工作,视频卡或图形适配器必须将其模式(屏幕分辨率,色彩深度和刷新率的组合)设置在其本身和所连接的显示屏所支持的值的范围内。这个操作被称之为模式设置,并且其通常需要原始访问图形硬件,例如写入视频卡某些寄存器的功能。模式设置操作必须在开始使用framebuffer之前,以及在应用程序或用户要求更改模式时,都必须执行模式设置操作。

在早期的时候,用户空间程序需要用到图形framebuffer的时候还要提供模式设置操作,因此其需要访问视频卡硬件的特权。在类Unix操作系统中(X server最为常用),其模式设置实现实在特定硬件的DDX驱动中。 其模式设置实现包含在每种特定类型的视频卡的DDX驱动程序中。这种方法,后来称为用户空间模式设置或UMS(User space Mode-Setting),带来了几个问题。它不仅打破了操作系统应在程序和硬件之间提供的隔离,增加了稳定性和安全性问题,并且将使图形硬件在两个或多个用户程序同时处理模式设置的时候处于一个不一致的状态。为了避免这些问题,X Server 成为唯一的一个可以处理模式设置操作的用户空间程序,其余的用户空间程序依靠X Server来设置适当的模式并处理涉及模式设置的任何其他操作。最初模式设置操作只在X Server启动的时候执行,后面X Server也实现了在运行中执行模式设置。XFree86中的XFree86-VidModeExtension扩展可以让任何X客户端请求对X Server进行Modeline(分辨率)更改。VidMode扩展后来又被XRandR扩展取代。

然而,X11并不是Linux系统中唯一一个做模式设置的模块。在系统启动阶段,Linux内核必须为虚拟控制台设置最小文本模式(基于VESA BIOS扩展定义的标准模式)。并且在Linux内核的framebuffer 驱动中也包含模式设置代码来配置framebuffer设备。为了避免模式设置的冲突,XFree86 Server(以及后来的X.org)通过保存用户的模式设置的状态并在用户切换回X时还原状态的方式来处理用户从图形环境切换到文本虚拟控制台的情况。这个过程在过渡时会引起闪烁问题,并且还可能失败,从而导致输出显示无法使用。

用户空间模式设置的方法还会引起其他问题:

  • 挂起/恢复过程必须依靠用户空间工具来恢复以前的模式。某单个程序出现故障或崩溃可能使系统由于模式设置错误而无法正常显示,从而无法使用。

  • 当屏幕处于图形模式时(例如X 正在运行),内核也不可能显示错误或调试信息,因为内核知道的唯一模式是VESA BIOS标准文本模式。

  • 一个更为紧迫的问题是绕过X server的应用程序以及X的其他图形堆栈替代方案的增涨,导致模式设置代码在整个系统中的重复。

为了解决这一些列问题,模式设置代码被移到内核空间的单个位置,特别是现有的DRM模块。之后所有的进程(包括X Server)都可以命令内核执行模式设置操作。并且内核将确保并发操作不会导致状态不一致。添加到DRM模块以执行这些模式设置操作的新的内核API和代码称为内核模式设置(KMS,Kernel Mode-Setting)。

KMD有很多优势,当然最直接的是从内核态(Linux控制台,fbdev)和用户空间(X Server DDX驱动程序)中删除重复的模式设置的代码。KMS还使得开发图形系统更加容易,这些图形系统现在无需实现自己的模式设置代码。通过提供集中的模式管理,KMS解决了在控制台和X之间以及X的不同实例之间进行切换(快速用户切换)时的闪烁问题。由于它在内核中实现,因此也可以在引导过程开始时使用它,从而避免了由于这些早期阶段的模式更改而导致的闪烁问题。

事实上KMS是内核的一部分,也就是说它可以使用一些只能在内核态使用的资源(如中断)。例如,挂起/恢复进程之后的模式恢复通过由内核本身进行管理,大大简化了操作,并附带地提高了安全性(不再需要具有root权限的用户空间工具)。内核还允许轻松地热插新显示设备,从而解决了一个长期存在的问题。模式设置也与内存管理密切相关,因为帧缓冲区基本上是内存缓冲区,因此会强烈建议其与图形内存管理器紧密集成。这就是将内核模式设置代码合并到DRM中而不是作为一个单独的子系统的主要原因。

为了避免破坏DRM API的向后兼容性,内核模式设置作为DRM驱动的附加驱动提供。任何DRM驱动在向DRM Core注册时都可以选择提供DRIVER_MODESET标志位,以表明它将支持KMS API。那些实现内核模式设置的驱动程序通常被称为KMS驱动程序,用来区别于传统的(无KMS)DRM驱动。

采用KMS的方式使得某些缺乏3D加速的驱动程序仍可在没有DRM API其余部分的情况下实现KMS API。(或者硬件供应商不希望公开或实现3D加速部分)

KMS

2.3.1 KMS device model

KMS将输出设备建模和管理为一系列抽象硬件模块,这些模块通常在显示控制器的显示输出管道上找到。这些模块包含如下:

  • CRTCs:每一个CRTC代表一个显示控制器的扫描引擎,其指向扫描缓冲区(framebuffer)。CRTC的目的是读取当前在扫描缓冲区中的像素数据,并借助PLL电路并生成视频模式时序信号。可用的CRTC数量决定了硬件可以同时处理多少个独立的输出设备,因此,为了使用多头配置,每个显示设备至少需要一个CRTC。如果两个或更多个CRTC从相同的帧缓冲区中扫描出并将相同的图像发送到多个输出设备,它们也可以在克隆模式下工作。

  • Connectors(连接器):连接器代表显示控制器从扫描操作将视频信号发送到要显示的地方。通常,KMS中的连接器概念对应于硬件中的物理连接器(VGA, DVI, FPD-Link, HDMI, DisplayPort, S-Video ...),而输出设备(显示器、笔记本电脑面板)可以是永久的也可以是临时的。与当前物理连接的输出设备有关的信息(例如连接状态,EDID数据,DPMS状态或支持的视频模式)也存储在连接器内。

  • Encoders(编码器):显示控制器必须使用适用于预期连接器的格式对来自CRTC的视频模式时序信号进行编码。编码器代表能够执行这些编码之一的硬件模块。编码器代表能够执行这些编码的硬件模块。例如用于数字输出的编码为TMDS和LVDS、用于VGA或TV输出必须使用特定的DAC模块。一个连接器一次只能接收一个编码器的信号,并且每种类型的连接器仅支持某些编码。可能还会存在其他物理限制,即并非每个CRTC都连接到每个可用的编码器,从而限制了CRTC-encoder-connector的可能组合。

  • Planes(面板):面板不是硬件模块,而是包含可传送到扫描引擎(CRTC)的缓存区的内存对象。保持framebuffer的面板称为主面板(primary Plane),每个CRTC必须关联一个面板。因为CRTC通过面板确定视频模式(显示分辨率,宽度和高度、像素大小、像素格式、刷新率等)的来源。如果显示控制器支持硬件光标叠加,则CRTC还需要与它关联的光标面板。如果显示控制能够从其他硬件覆盖中扫描并且能够即时合成或混合发送到输出设备的最终图像或辅助平面(如果它能够从其他硬件覆盖中扫描出)并“即时”合成或混合发送到输出设备的最终图像,则还需要关联辅助面板。

2.3.2 Atomic Display

近年来,一直在努力将原子操作引入与KMS API有关的某些常规操作中,特别是模式设置和翻页操作中。这种增强的KMS API被称为“Atomic Display”(以前称为“atomic mode-setting”和“atomic or nuclear pageflip”)。

原子模式设置的目的是通过避免可能导致视频状态不一致或无效的中间步骤确保在具有多个限制的复杂配置中能够正确的修改模式。 它还在必须撤销失败的模式设置操作时避免了危险的视频状态。通过提供模式测试功能,原子模式设置可以预先知道某些特定的模式配置是否正确。当原子模式被测试并验证有效之后,它可以与单个不可分割的提交操作一起应用。测试操作和提交操作均由具有不同标志的同一新ioctl提供。

另一方面,通过原子翻页,可以更新在同一VBLANK间隔内同步到同一输出上的多个面板,以确保正确的显示而不会出现撕裂。例如,主面板、光标面板、可能还有一些辅助面板。这个需求在移动和嵌入式显示控制器特别强,因为其需要使用多个面板/叠加以节省功耗。

新的原子API建立在旧的KMS API之上。它使用相同的模型和对象(CRTC,编码器,连接器,面板等),但是对象属性的数量却不断增加。原子程序是基于更改相关属性来构建我们要测试或提交的状态。我们要修改的属性取决于我们是否要进行模式设置(主要是CRTC,编码器和连接器属性)或翻页(通常是面板属性)。在两种情况下,ioctl都是相同的,不同之处在于每种参数传递的属性列表。

2.4 Render nodes

在原始的DRM API中,DRM设备/dev/dri/cardX用于特权和非特权操作(特权操作如模式设置、显示控制,非特权操作如渲染、GPGPU计算)。为了安全起见,打开关联的DRM设备文件需要特权模式“等同于root特权”。这就导致了一种架构,其中只有某些可靠的用户空间程序(X Server,图形合成器等)可以完全访问DRM API,其中包括特权部分,例如模式设置API。想要渲染或进行GPGPU计算的其余用户空间应用程序应由DRM设备的所有者(“ DRM Master”)通过使用特殊的身份验证接口来授予。经过身份验证的应用程序可以使用DRM API的受限版本来进行渲染或进行计算,而无需特权操作。这个设计有一个严重的约束:就是必须有一个始终运行的图形服务器(如X server、Wayland 合成器等)作为DRM设备的DRM Master存在,用来授予其他用户空间程序使用设备的权限,而即使在不涉及任何图形显示的情况下也是如此,例如GPGPU计算。

渲染节点(Render Node)的概念出现试图解决这些场景,其通过将DRM用户空间的API氛围两部分接口:特权和非特权,并且分别使用分开的设备文件。对于每个发现的GPU,其相应的DRM驱动程序会创建一个设备文件/dev/dri/renderDX(前提是其支持渲染节点),就称之为渲染节点。并且主节点/dev/dri/cardX依旧存在。使用直接渲染模型的客户端和想要利用GPU计算功能的应用程序,只需打开任何现有的渲染节点即可完成而无需其他特权,之后使用这些节点支持的DRM API的有限子集调度GPU操作,这个操作只需要要他们具有打开设备文件的文件系统权限。显示服务器,合成器和任何其他需要模式设置API或任何其他特权操作的程序,都必须打开允许访问完整DRM API的标准主节点,并像往常一样使用它。渲染节点明确指出禁止GEM Flink操作,目的是为了防止使用不安全的GEM全局名称的共享缓冲区。仅PRIME(DMA-BUF)文件描述符可用于与包括图形服务器在内的另一个客户端共享缓冲区。

AMD图形硬件加速

参考资料

猜你喜欢

转载自blog.csdn.net/u014756627/article/details/129101601