常识 | drm kms 详解

DRM --- direct rendering manager

以下来自维基 general description:

常识 | drm kms 详解_第1张图片常识 | drm kms 详解_第2张图片

Linux内核已经有了一个名为fbdev的API,用于管理图形适配器的帧缓冲区,但它不能用于处理现代基于gpu的3d加速视频硬件的需求。这些设备通常需要在它们自己的内存中设置和管理一个命令队列,以便将命令分发给GPU,还需要管理该内存中的缓冲区和空闲空间。最初,用户空间程序(如X Server)直接管理这些资源,但它们通常表现得好像它们是唯一能够访问这些资源的程序。当两个或多个程序试图同时控制相同的硬件,并以自己的方式设置其资源时,大多数情况下它们会灾难性地结束

DRM能够允许多个程序共同使用视频硬件资源。DRM获得对GPU的独占访问权,并负责初始化和维护命令队列、内存和任何其他硬件资源。希望使用GPU的程序将请求发送给DRM,后者充当仲裁人并注意避免可能的冲突。

DRM的范围在过去几年里得到了扩展,涵盖了以前由用户空间程序处理的更多功能,如帧缓冲区管理和模式设置、内存共享对象和内存同步。其中一些扩展被赋予了特定的名称,如图形执行管理器(GEM)或内核模式设置(KMS),但它们实际上是整个内核DRM子系统的一部分。

目前计算机通常包含两个GPU,一个是离散的GPU,一个是集成的GPU,导致了新的问题,如GPU切换,这些问题也需要在DRM层解决。为了匹配英伟达Optimus技术,DRM提供了GPU卸载能力,称为PRIME.

软件架构:

常识 | drm kms 详解_第3张图片

DRM驻留在内核空间中,因此用户空间程序必须使用内核系统调用来请求它的服务。但是,DRM没有定义它自己的系统调用。相反,它遵循Unix“一切都是文件”的原则,通过文件系统名称空间公开gpu,使用/dev层次结构下的设备文件。每一个被DRM检测到的GPU都被称为DRM设备,并创建一个设备文件/dev/driver/cardx(其中X是一个连续的数字)来与它连接。希望与GPU通信的用户空间程序必须打开这个文件并使用ioctl调用与DRM通信。不同的ioctl对应于DRM API的不同功能。

为了方便用户空间程序与DRM子系统的接口,创建了一个名为libdrm的库。这个库只是一个包装器,它为DRM API的每个ioctl以及常量、结构和其他辅助元素提供了用C编写的函数。使用libdrm不仅避免了将内核接口直接暴露给应用程序,而且提供了在程序之间重用和共享代码的通常优点。

常识 | drm kms 详解_第4张图片

DRM由两部分组成:通用的“DRM core”和针对每种支持的硬件类型的特定的“DRM driver”。
DRM core提供了基本框架,不同的DRM驱动程序可以在其中注册,还为用户空间提供了最小的ioctl集,具有通用的、硬件独立的功能。
DRM driver实现API中与硬件相关的部分,具体到它所支持的GPU类型;并提供DRM core未覆盖的剩余ioctl的实现,也可以扩展API,提供附加的ioctl,具有仅在此类硬件上可用的额外功能。当特定的DRM driver提供了扩展API时,libdrm也通过一个额外的库libdrm-driver进行扩展

kms


以下来自开发者手册

大纲:

  1. driver 初始化
  2. 内存管理
  3. mode setting
  4. kms初始化
  5. helper function
  6. kms属性
  7. 文件操作
  8. ioctl
  9. 命令提交/隔离
  10. 暂停/恢复
  11. DMA

1 -- driver 初始化

典型的初始化包括:设置cmd buffer,初始化输出配置,初始化core service

每一个DRM driver都有drm_driver结构体,首先初始化drm_driver,然后传入drm_*_init()进行注册

drm_driver结构包括:
1. 用于描述driver的static信息
2. 一些指针,drm core通过调用这些指针实现drm api

driver info

driver_features中设置flag来体现driver的特性,在注册时就要设置这些flag

  • DRIVER_USE_AGP / DRIVER_REQUIRE_AGP
  • DRIVER_PCI_DMA   能否映射 PCI DMA buffers 至 userspace 
  • DRIVER_SG 是否支持离散/聚集DMA内存映射
  • DRIVER_HAVE_DMA   是否支持DMA
  • DRIVER_HAVE_IRQ 是否含中断处理
  • DRIVER_IRQ_SHARED 是否支持共享中断
  • DRIVER_GEM 是都使用GEM进行内存管理
  • DRIVER_MODESET 是否支持KMS
  • DRIVER_PRIME 实现 DRM PRIME buffer sharing.
  • DRIVER_RENDER 是否支持渲染节点

driver versions

int major;
int minor;
int patchlevel;

driver info

char *name;
char *desc;
char *date;

driver load加载
是驱动和设备的entry 点,负责分配和初始化驱动私有数据和指定支持的性能计数器;执行资源分配和映射(例如获取时钟,映射寄存器或分配命令缓冲区);初始化内存管理器(“内存管理”part),安装IRQ处理程序(“IRQ注册”part);设置垂直空白处理(“垂直空白”的部分);模式设置(“模式设置”part)和初始输出配置(“KMS初始化和清理”part)。

int (*load) (struct drm_device *, unsigned long flags);

其中flag是通过drm_*_init()注册返回的id,只有pci设备使用这一项,usb之类的不用

driver private

driver private可以将drm_device挂起(什么意思),然后可跟踪某些信息bit,比如寄存器偏移量、cmd buffer状态、寄存器状态。加载时可设置drm_device.dev_priv,卸载时设为null

irq注册

DRM core 提供drm_irq_install进行irq注册(适用于单中断处理器)

drm_irq_install:
调用drm_dev_to_irq,去总线上检索一个irq号
调用irq_preinstall,可选的
调用request_irq,中断处理函数必须传入request_irq
调用irq_postinstall,启用中断

drm_irq_uninstall
它首先唤醒所有等待vblank中断的进程,以确保它们不会挂起,然后调用可选的irq_uninstall驱动程序操作。该操作必须禁用所有硬件中断(关中断)。最后调用free_irq释放IRQ。

多中断处理器的driver只能通过手动注册的方式(不能调用drm_irq_install)

初始化内存管理器

选择TTM/GEM进行初始化,后续有详述

其他配置

在配置过程中,PCI设备可能需要的另一个任务是映射视频BIOS。在许多设备上,VBIOS描述设备配置、LCD面板计时(如果有的话),并包含指示设备状态的标志。可以使用pci_map_rom()调用来映射BIOS,这是一个方便的函数,它负责映射实际的ROM,不管它是否已经被隐藏到内存中(通常在地址0xc0000),还是存在于ROM BAR中的PCI设备上。注意,在ROM被映射并且提取了任何必要的信息之后,它应该被解除映射;在许多设备上,ROM地址解码器与其他bar共享,因此将其映射可能导致不希望的行为,如挂起或内存损坏。

2 -- 内存管理

DRM core 包括两个内存管理器,即转换表映射(TTM)和图形执行管理器(GEM)。TTM是第一个被开发的DRM内存管理器,它试图成为一个一刀切的解决方案。它提供了一个单一的用户空间API,以适应所有硬件的需求,支持统一内存架构(UMA)设备和具有专用视频RAM的设备(即大多数离散视频卡)。这导致了一段庞大而复杂的代码很难用于驱动程序开发。

GEM最初是英特尔赞助的一个项目,以应对TTM的复杂性。GEM不是为每个与图形内存相关的问题提供解决方案,而是识别驱动程序之间的公共代码,并创建一个支持库来共享它。GEM的初始化和执行要求比TTM更简单,但没有显存管理功能,因此只能用于UMA设备。

GEM

GEM向用户空间公开了一组与内存相关的标准操作,并向驱动程序公开了一组helper func,并允许驱动程序使用自己的私有API实现特定于硬件的操作。

GEM管理抽象缓冲区对象,而不知道每个缓冲区包含什么。因此,需要了解缓冲区内容或用途(比如缓冲区分配或同步原语)的api必须使用特定于驱动程序的ioctl实现。

GEM功能包括:

  • 内存分配和释放
  • 命令执行
  • 命令执行时的光圈管理(?

特定于设备的操作,如命令执行、固定、缓冲区读写、映射和域所有权转移都留给特定于驱动程序的ioctls。

gem初始化

在drm driver进行加载load时,创建一个DRM Memory Manager对象,该对象为对象分配提供地址空间池。在KMS配置中,如果硬件需要,驱动程序需要在核心GEM初始化之后分配并初始化一个命令环缓冲区。

gem创建对象

GEM对象由结构体drm_gem_object的实例表示。驱动程序需要用私有信息扩展GEM对象,从而创建一个特定于驱动程序的GEM对象结构类型,然后嵌入drm_gem_object结构体的实例中。

创建对象时首先分配内存,然后调用drm_gem_object_init初始化上述嵌入的gem对象。该函数接受一个指向DRM设备的指针、一个指向GEM对象的指针和缓冲区对象大小(以字节为单位)

GEM使用shmem分配匿名可分页内存。Drm_gem_object_init将创建一个请求大小的SHMFS文件,并将其存储到结构drm_gem_object filp字段中。、

驱动程序通过为每个页面调用shmem_read_mapping_page_gfp来负责实际的物理页面分配。注意,他们可以在初始化GEM对象时决定分配页面,或者在需要内存时决定延迟分配(例如,当用户空间内存访问导致页面错误时,或者当驱动程序需要启动涉及内存的DMA传输时)。

驱动程序可以调用drm_gem_object_alloc函数来分配和初始化结构drm_gem_object实例。(但是私有的gem对象就不行)。在使用drm_gem_object_init初始化GEM对象之后,GEM core将调用可选的驱动程序gem_init_object操作。

int (*gem_init_object) (struct drm_gem_object *obj);

gem对象的生命周期

采用引用计数的方式,引用可以通过分别调用drm_gem_object_reference和drm_gem_object_unreference来获取和释放。调用者必须持有drm_device结构互斥锁。当最后一个引用被释放时,GEM核心调用drm_driver gem_free_object操作。

gem对象naming

用户空间和内核之间的通信引用使用本地句柄、全局名称或文件描述符

应用程序通过驱动程序特定的ioctl获得GEM对象的句柄,并可以使用该句柄引用其他标准或驱动程序特定的ioctl中的GEM对象。

GEM名称在目的上与句柄相似,但不是DRM文件的本地名称。它们可以在进程之间传递,以全局引用GEM对象。名称不能直接用于引用DRM API中的对象,应用程序必须可使用ioctls将句柄转换为名称和名称转换为句柄。

与全局名称类似,GEM文件描述符也用于跨进程共享GEM对象。它们提供了额外的安全性。支持GEM文件描述符的驱动,也称为DRM PRIME API

helper func

通过使用辅助函数drm_gem_prime_export和drm_gem_prime_import,驱动程序可以在更简单的api中实现gem_prime_export和gem_prime_import。这些函数通过5个较低级的驱动程序回调实现了对dma-buf的支持

gem 对象映射

GEM更倾向于通过特定于驱动程序的ioctl对缓冲区进行类似于读/写的访问,而不是将缓冲区映射到用户空间。

mmap系统调用不能直接用于映射GEM对象,因此方法是在DRM文件句柄上使用mmap系统调用。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

DRM通过通过ff_t offset假偏移来标识要映射的GEM对象。假偏移以特定于驱动程序的方式传递给应用程序,然后可以用作mmap偏移量参数。GEM核心提供了一个帮助方法drm_gem_mmap来处理对象映射。将根据偏移值查找GEM对象,并将VMA操作设置为drm_driver gem_vm_ops字段。

内存一致性

cpu与gpu之间的内存一致性大概是说一边用完了就刷新到另一边的缓存/内存中,由ioctl管理,必要时阻塞用户然后去做刷新

cmd执行

对于GPU设备来说,最重要的GEM功能可能是为客户端提供命令执行接口。客户端程序构造命令缓冲区,其中包含对以前分配的内存对象的引用,然后将它们提交给GEM。此时,GEM会小心地将所有对象绑定到GTT中,执行缓冲区,并在访问相同缓冲区的客户机之间提供必要的同步。

3 -- mode setting -- 帧缓冲区

驱动程序必须通过调用DRM设备上的drm_mode_config_init来初始化模式设置核心。包括:

int min_width, min_height;
int max_width, max_height;
struct drm_mode_config_funcs *funcs;

帧缓冲区的最小和最大宽度和高度。

创建帧缓冲区

帧缓冲区是抽象的内存对象,提供扫描到CRTC的像素源。应用程序显式地请求通过DRM_IOCTL_MODE_ADDFB(2) ioctls创建帧缓冲区,并接收一个不透明句柄,可以传递给KMS CRTC控制,平面配置和页面翻转函数。

1 driver首先验证通过mode_cmd所请求的帧缓冲区参数。(不能过大不能非法
2 然后将创建、初始化并返回结构体drm_framebuffer的实例。如果需要,可以将实例嵌入到更大的驱动程序特定结构中。驱动程序必须从通过drm_mode_fb_cmd2参数传递的值中填充它的宽度、高度、间距、偏移量、深度、bits_per_pixel和pixel_format字段。
3 drm_framebuffer_init完成实例化

对帧缓冲区的操作有:

create_handle 创建帧缓冲区底层内存对象的句柄。
destroy销毁帧缓冲区对象并释放所有相关资源。
dirty帧缓冲区的一个区域在响应DRM_IOCTL_MODE_DIRTYFB ioctl调用时发生了变化。

4 -- kms初始化

A KMS device is abstracted and exposed as a set of planes, CRTCs, encoders and connectors.

常识 | drm kms 详解_第5张图片

crtc

CRTC是一个抽象,表示芯片的一部分,其中包含一个指向扫描缓冲区的指针。因此,可用的crtc数量决定了在任何给定时间可以激活的独立扫描缓冲区的数量。CRTC结构包含几个字段来支持这一点:指向某些显存的指针(抽象为帧缓冲区对象)、显示模式和显存的(x, y)偏移量,以支持平移或在一块显存跨越多个CRTC时进行配置。

crtc初始化

一个kms服务必须注册一个以上的drm_crtc实例,并通过drm_crtc_init初始化

crtc操作

int (*set_config)(struct drm_mode_set *set);

应用一个新的CRTC配置到设备。该配置指定一个CRTC、要从其中扫描出的帧缓冲区、帧缓冲区中的(x,y)位置、显示模式和要使用CRTC驱动的连接器数组(如果可能的话)。

int (*page_flip)(struct drm_crtc *crtc, struct drm_framebuffer *fb,
                   struct drm_pending_vblank_event *event);

为CRTC安排一个页面翻转到给定的帧缓冲区。是一种同步机制。

page_flip操作安排页面翻转。一旦任何以新帧缓冲区为目标的暂挂渲染完成,CRTC将被重新编程,在下一次垂直刷新后显示该帧缓冲区。操作必须立即返回,而不是等待呈现或页面翻转完成,并且必须阻塞任何新的呈现到帧缓冲区,直到页面翻转完成。

plane

平面表示可以在扫描过程中与CRTC混合或叠加在其上的图像源。平面与帧缓冲区相关联,以裁剪图像内存(源)的一部分,并可选地将其缩放到目标大小。然后将结果与CRTC混合或叠加在其上。

plane是可选的 结构是drm_plane

encoder 编码

编码器从CRTC获取像素数据,并将其转换为适合任何附加连接器的格式。

connectors

连接器是设备上像素数据的最终目的地,通常直接连接到外部显示设备,如显示器或笔记本电脑面板。一个连接器一次只能连接到一个编码器上。连接器也是保存有关附加显示器的信息的结构,包含显示数据、EDID数据、DPMS和连接状态的字段,以及有关附加显示器支持的模式的信息。

kms api

太多了随便吧。。。

5 -- helper functions

驱动程序提供的CRTC、编码器和连接器功能实现了DRM API。DRM核心和ioctl处理程序调用它们来处理设备状态更改和配置请求。由于实现这些函数通常需要不特定于驱动程序的逻辑,因此可以使用中间层帮助函数来避免重复样板代码。

DRM核心包含一个中间层实现。中间层提供了几个CRTC、编码器和连接器函数(从中间层顶部调用)的实现,这些函数对请求进行预处理,并调用驱动程序(在中间层底部)提供的较低级函数。例如,可以使用drm_crtc_helper_set_config函数填充结构drm_crtc_funcs set_config字段。当调用时,它将把set_config操作拆分为更小、更简单的操作,并调用驱动程序来处理它们。

为了使用中间层,驱动程序调用drm_crtc_helper_add, drm_encoder_helper_add和drm_connector_helper_add函数来安装它们的中间层底层操作处理程序,并用指向中间层顶层API函数的指针填充drm_crtc_funcs, drm_encoder_funcs和drm_connector_funcs结构。安装中间层底层操作处理程序最好在注册相应的KMS对象之后立即完成。

中间层没有在CRTC、编码器和连接器操作之间分割。要使用它,驱动程序必须为所有三个KMS实体提供底层函数。

你可能感兴趣的:(什么都来点,DRM,KMS)