上图有误,wayland-compositor下面是通过libdrm调用的kms接口,未给出。
总览
该框架以基于Wayland的Windowing system为例,描述了linux graphic系统在DRI框架下,通过两条路径(DRM和KMS),分别实现Rendering和送显两个显示步骤。
1)Application(如3D game)根据用户动作,需要重绘界面,此时它会通过OpenGL|ES、EGL等接口,将一系列的绘图请求,提交给GPU。
a)OpenGL|ES、EGL的实现,可以有多种形式,这里以Mesa 3D为例,所有的3D rendering请求,都会经过该软件库,它会根据实际情况,通过硬件或者软件的方式,响应Application的rendering请求。
b)当系统存在基于DRI的硬件rendering机制时,Mesa 3D会通过libGL-meas-DRI,调用DRI提供的rendering功能。
c)libGL-meas-DRI会调用libdrm,libdrm会通过ioctl调用kernel态的DRI驱动,这里称作DRM(Direct Rendering Module)。
d)kernel的DRM模块,最终通过GPU完成rendering动作。
2)GPU绘制完成后,将rendering的结果返回给Application。
rendering的结果是以image buffer的形式返回给应用程序。 //听起来不靠谱,因为如果是以buffer返还,那么这里已经有了一次copy动作
3)Application将这些绘制完成的图像buffer(可能不止一个)送给Wayland compositor,Wayland compositor会控制硬件,将buffer显示到屏幕上。
Wayland compositor会搜集系统Applications送来的所有image buffers,并处理buffer在屏幕上的坐标、叠加方式后,直接通过ioctl,交给kernel KMS(kernel mode setting)模块,该模块会控制显示控制器将图像显示到具体的显示设备上。
DRM和KMS
DRM是Direct Rendering Module的缩写,是DRI框架在kernel中的实现,负责管理GPU(或显卡,graphics card)及相应的graphics memory,主要功能有二:
1)统一管理、调度多个应用程序向显卡发送的命令请求,可以类比为管理CPU资源的进程管理(process management)模块。
2)统一管理显示有关的memory(memory可以是GPU专用的,也可以是system ram划给GPU的,后一种方法在嵌入式系统比较常用),该功能由GEM(Graphics Execution Manager)模块实现,主要包括:
a) 允许用户空间程序创建、管理、销毁video memory对象(称作“"GEM objects”,以handle为句柄)。
b)允许不同用户空间程序共享同一个"GEM objects”(需要将不唯一的handle转换为同一个driver唯一的GEM name,后续使用dma buf的共享功能)。
c)处理CPU和GPU之间内存一致性的问题。(本质是dma buf的功能 fence)
d)video memory都在kernel管理,便于给到display controller进行送显(Application只需要把句柄通过Wayland Compositor递给kernel即可,kernel会自行获取memory及其内容)。
KMS是Kernel Mode Setting的缩写,也称作Atomic KMS,它是一个在linux 4.2版本的kernel上,才最终定性的技术。从字面意义上理解,它要实现的功能比较简单,即:显示模式(display mode)的设置,包括屏幕分辨率(resolution)、颜色深的(color depth)、屏幕刷新率(refresh rate)等等。一般来说,是通过控制display controller的来实现上述功能的。
目前的kernel版本(如4.2之后),KMS和DRM基本上没有什么逻辑耦合(除了代码位于相同目录,以及通过相同的设备节点提供ioctl之外),可以当做独立模块看待。
http://www.wowotech.net/linux_kenrel/dri_overview.html
DRM
DRM层为图形驱动程序提供了多种服务,其中许多服务由libdrm提供应用程序接口,libdrm是包装大多数DRM ioctl的库,包括vblank事件处理,内存管理,输出管理,帧缓冲管理,命令提交和防护,睡眠/唤醒支持和DMA服务。
现代Linux系统需要大量图形内存来存储帧缓冲区,纹理,顶点和其他与图形相关的数据。鉴于许多数据非常动态,有效管理图形内存这件事对于图形堆栈至关重要,在DRM基础架构中发挥核心作用。
DRM核心包括两个存储器管理器,即转换表映射(TTM)[已经过时]和图形执行管理器(GEM)。 TTM是第一个开发的DRM内存管理器,并试图成为一个适合所有人的解决方案。它提供单个用户空间API以满足所有硬件的需求,支持统一存储器架构(UMA)设备和具有专用视频RAM(即大多数分立视频卡)的设备。这导致了大量复杂的代码,结果很难用于驱动程序开发。
GEM起初是英特尔赞助的项目,以应对TTM的复杂性。它的设计理念完全不同:GEM不是为每个与图形内存相关的问题提供解决方案,而是在驱动程序之间识别出通用代码,并创建了一个支持库来共享它。 GEM比TTM具有更简单的初始化和执行要求,但没有视频RAM管理功能,因此仅限于UMA设备。
图形执行管理器(GEM)
GEM设计方法导致内存管理器无法完全覆盖其用户空间或内核API中的所有(甚至所有常见)用例。 GEM向用户空间公开了一组与标准内存相关的操作,并向驱动程序公开了一组辅助函数,另外让驱动程序使用自己的私有API实现特定于硬件的操作。
GEM --LWN上的图形执行管理器文章中描述了GEM用户空间API。虽然略微过时,但该文档提供了GEM API原则的良好概述。作为通用GEM API的一部分描述的缓冲区分配和读写操作,目前最新的实现使用特定于驱动程序的ioctl。
GEM与数据无关。它管理抽象缓冲区对象,而不知道单个缓冲区包含什么。因此,需要了解缓冲区内容或目的的API(例如缓冲区分配或同步原语)不属于GEM的范围,必须使用特定于驱动程序的ioctl来实现。
从根本上讲,GEM涉及多项任务:
- 内存分配和释放
- 命令执行
- 命令执行时的间隙管理[Aperture management at command execution time]
缓冲区对象分配相对简单,主要由Linux的shmem层提供,提供内存来支持每个对象。
特定于设备的操作(例如命令执行,固定,缓冲区读写,映射和域所有权传输)留给特定于驱动程序的ioctl。
GEM初始化
使用GEM的驱动程序必须在结构体 struct drm_driver driver_features字段中设置DRIVER_GEM位。然后,DRM core将在调用load操作之前自动初始化GEM core。在这之后,这将创建一个DRM内存管理器对象,该对象为对象分配提供地址空间池。
在KMS配置中,如果硬件需要,驱动程序需要在核心GEM初始化之后分配和初始化命令环缓冲区。 UMA设备通常具有所谓的“被盗”存储器区域,其为初始帧缓冲器和设备所需的大的连续存储器区域提供空间。此空间通常不由GEM管理,必须单独初始化为其自己的DRM MM对象。
KMS初始化
驱动程序必须通过调用DRM设备上的drm_mode_config_init()来初始化模式设置核心。该函数初始化struct drm_device mode_config字段,永远不会失败。完成后,必须通过初始化以下字段来设置模式配置。
- int min_width, min_height; int max_width, max_height; Minimum and maximum width and height of the frame buffers in pixel units.
- struct drm_mode_config_funcs *funcs; Mode setting functions.
KMS Display Pipeline Overview
KMS向用户空间呈现的基本对象结构非常简单。帧缓冲区(由struct drm_framebuffer表示,请参阅帧缓冲区抽象)提供给平面。平面由struct drm_plane表示,有关详细信息,请参见Plane Abstraction。一个或多个(甚至没有)平面将其像素数据馈送到CRTC(由struct drm_crtc表示,参见CRTC抽象)以进行混合。平面组成属性和相关章节中更详细地解释了混合步骤。
对于输出路由,第一步是编码器(由struct drm_encoder表示,参见编码器抽象)。这些只是用于实现KMS驱动程序的辅助库的内部工件。除此之外,它们使用户空间变得更加复杂,无法确定CRTC和连接器之间的连接是什么,以及支持哪种克隆,它们在用户空间API中没有用处。不幸的是编码器已暴露给用户空间,因此目前无法删除它们。此外,暴露的限制通常由驱动工程师错误地设定,并且在许多情况下不足以表达真正的限制。 CRTC可以连接到多个编码器,对于有源CRTC,必须至少有一个编码器。
显示链中的最终和实际端点是连接器(由struct drm_connector表示,请参阅Connector Abstraction)。连接器可以有不同的编码器,但内核驱动程序选择用于每个连接器的编码器。用例是DVI,可以在模拟和数字编码器之间切换。编码器还可以驱动多个不同的连接器。每个活动编码器只有一个活动连接器。
在内部,输出管道有点复杂,并且更紧密地匹配如今的硬件:
KMS Output Pipeline
帧缓冲抽象[framebuffer]
帧缓冲区抽象存储器对象,提供扫描到CRTC的像素源。应用程序通过DRM_IOCTL_MODE_ADDFB(2)ioctls显式请求创建帧缓冲区,并返回一个可以传递给KMS CRTC控件,平面配置和页面翻转函数的不透明句柄fd。
帧缓冲区依赖底层内存管理器来分配后备存储。创建帧缓冲区时,应用程序通过struct drm_mode_fb_cmd2参数传递内存句柄(或多平面格式的内存句柄列表)。对于使用GEM作为其用户空间缓冲区管理界面的驱动程序,这将是一个GEM句柄。然而,驱动程序可以自由地使用它们自己的后备存储对象句柄。 vmwgfx直接向用户空间公开特殊的TTM句柄,因此需要创建ioctl而不是GEM句柄中的TTM句柄。
使用struct drm_framebuffer跟踪帧缓冲区。它们使用drm_framebuffer_init()发布 - 在调用该函数后,用户空间可以使用并访问framebuffer对象。辅助函数drm_helper_mode_fill_fb_struct()可用于预填充所需的元数据字段。
drm帧缓冲区的生命周期由引用计数控制,驱动程序可以使用drm_framebuffer_get()获取其他引用,并使用drm_framebuffer_put()再次删除它们。对于永远不会丢弃最后一个引用的驱动程序专用帧缓冲区(例如,当struct struct drm_framebuffer嵌入到fbdev辅助结构中时,对于fbdev帧缓冲区)驱动程序可以使用drm_framebuffer_unregister_private()在模块卸载时手动清理帧缓冲区。但不建议这样做,最好有一个普通的独立式结构drm_framebuffer。
note:
dumb-buffer : https://www.systutorials.com/docs/linux/man/7-drm-memory/
几乎所有的内核DRM硬件驱动程序都支持一个称为Dumb-Buffers的API。该API允许创建可用于扫描的任意大小的缓冲区。这些缓冲区可以通过mmap(2)进行内存映射,这样就可以在CPU上渲染它们。然而,GPU访问这些缓冲区通常是不可能的。因此,它们适合简单的任务,但不适合复杂的合成和渲染。该 buffer 的操作不依赖于硬件或相关加速器[GPU],多用于简单 buffer 的应用场合,适合做 framebuffer 使用。它的目的是降低应用程序操作 DRM 的复杂度,比如 Plymouth 这种应用程序,本身使用场景就比较简单,无需使用 driver 所提供的特定 API 来完成 buffer 的操作。
DRM_IOCTL_MODE_CREATE_DUMB ioctl可以用来创建dumb缓冲区。内核将返回一个32位handle,可以使用此句柄用DRM API来管理缓冲区。您可以使用drmModeAddFB(3)创建framebuffer,并将其用于模式设置和扫描。要访问缓冲区,首先需要检索缓冲区的偏移量。DRM_IOCTL_MODE_MAP_DUMB ioctl请求DRM子系统为内存映射准备缓冲区,并返回一个可以与mmap(2)一起使用的伪偏移量。
创建dumb缓冲区,返回handle,在create_arg保存
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg);
准备映射的缓冲区内存,返回偏移量给map_arg保存
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg);
完成最终映射,用到了dumb的handle和offset
mmap(0, create_arg.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map_arg.offset);
綁定在framebuffer上面
drmIoctl(fd, DRM_IOCTL_MODE_ADDFB2, &cmd2);
平面抽象[plane]
Plane是连接FB与CRTC的纽带,是内存的搬运工。
Plane表示在扫描输出过程期间可以与CRTC混合或重叠在CRTC顶部的图像源。 Planes从drm_framebuffer对象获取输入数据。平面本身指定该图像的裁剪和缩放,它放置在显示管道的可见区域则由drm_crtc表示。平面还可以具有指定像素如何定位和混合的附加属性,例如旋转或Z-order。所有这些属性都存储在drm_plane_state中。
要创建一个平面,KMS驱动程序会分配和清零struct drm_plane的实例(可能作为更大结构的一部分),并通过调用drm_universal_plane_init()来注册它。
Cursor和Overlay平面是可选的。所有驱动程序应为每个CRTC提供一个Primary平面(GPU送下来的那个平面)。有关这些特殊的与uapi相关的平面类型的更深入讨论,请参见enum drm_plane_type。通过调用drm_crtc_init_with_planes()将特殊平面与其CRTC相关联。
平面的类型在不可变“类型”枚举属性中公开,该属性具有以下值之一:“Overlay”,“Primary”,“Cursor”
---------------------
一个高级的Plane,通常具有如下功能:
功能 说明
Crop 裁剪,如上图
Scaling 缩放,放大或缩小
Rotation 旋转,90° 180° 270° X/Y镜像
Z-Order Z-顺序,调整当前层在总图层中的Z轴顺序
Blending 合成,pixel alpha / global alpha
Format 颜色格式,ARGB888 XRGB888 YUV420 等
再次强调,以上这些功能都是由硬件直接完成的,而非软件实现。
在DRM框架中,Plane又分为如下3种类型:
类型 说明
Cursor 光标图层,一般用于PC系统,用于显示鼠标
Overlay 叠加图层,通常用于YUV格式的视频图层
Primary 主要图层,通常用于仅支持RGB格式的简单图层
---------------------
作者:何小龙
来源:CSDN
原文:
版权声明:本文为博主原创文章,转载请附上博文链接!
CRTC抽象
CRTC代表整个显示管道。它从(多个)drm_plane接收像素数据并将它们混合在一起。 drm_display_mode也附加到CRTC,指定显示时间。在输出端,数据被馈送到一个或多个drm_encoder,然后每个drm_encoder连接到一个drm_connector。
要创建CRTC,KMS驱动程序会分配和清零struct drm_crtc的实例(可能作为更大结构的一部分),并通过调用drm_crtc_init_with_planes()来注册它。
CRTC也是传统模式集操作的入口点,请参阅drm_crtc_funcs.set_config,传统平面操作,请参阅drm_crtc_funcs.page_flip和drm_crtc_funcs.cursor_set2以及其他遗留操作,如drm_crtc_funcs.gamma_set。对于原子驱动程序,所有这些功能都通过drm_property和drm_mode_config_funcs.atomic_check和drm_mode_config_funcs.atomic_check来控制。
连接器抽象Connector Abstraction
在DRM中,连接器是显示接收器的一般抽象,包括固定面板或可以某种形式显示像素的任何其他东西。与代表硬件的所有其他KMS对象(如CRTC,encodeer或平plane抽象)相反,连接器可以在运行时进行热插拔。因此,使用drm_connector_get()和drm_connector_put()对它们进行引用计数。
KMS驱动程序必须在struct drm_connector中为每个这样的接收器进行创建,初始化,注册和添加操作。该实例作为其他KMS对象创建,并通过设置以下字段进行初始化。通过调用drm_connector_init()并使用指向struct drm_connector_funcs和connector类型的指针初始化连接器,然后通过调用drm_connector_register()将其暴露给用户空间。
connector必须连接到要使用的encoder。对于将connector映射到encoder的1对1的设备,应在初始化时通过调用drm_connector_attach_encoder()连接connector。驱动程序还必须将drm_connector.encoder字段设置为指向附加的encoder。
对于未固定的连接器(如内置面板),驱动程序需要支持热插拔通知。最简单的方法是使用探针助手,请参阅drm_kms_helper_poll_init(),了解没有热插拔中断硬件支持的连接器。具有硬件热插拔支持的连接器可以改为使用例如drm_helper_hpd_irq_event()。
编码器抽象Encoder Abstraction
编码器表示CRTC(作为整体像素管道,由struct drm_crtc表示)和连接器(作为通用接收器实体,由struct drm_connector表示)之间的连接元素。编码器从CRTC获取像素数据并将其转换为适合任何连接的连接器的格式。编码器是暴露给用户空间的对象,最初允许用户空间推断克隆和对连接器/ CRTC的限制[不对]。不幸的是,几乎所有的驱动都错了,使得uabi几乎没用。最重要的是,暴露的限制对于今天的硬件来说太简单了,推测限制的推荐方法是使用原子IOCTL的DRM_MODE_ATOMIC_TEST_ONLY标志。
否则编码器根本不在uapi中使用(来自用户空间的任何模式集请求直接将连接器与CRTC连接),因此驱动程序可以随意使用它们。 Modeset辅助库大量地使用编码器来促进代码共享。但是对于更复杂的设置,通常最好将共享代码移动到单独的drm_bridge中。与编码器相比,桥接器还具有纯粹的内部抽象的优点,因为它们根本不暴露于用户空间。
使用drm_encoder_init()初始化编码器,并使用drm_encoder_cleanup()进行清理。
参考:
http://www.wowotech.net/graphic_subsystem/graphic_subsystem_overview.html
http://www.wowotech.net/linux_kenrel/dri_overview.html