http://landley.net/kdocs/htmldocs/drm.html
Initial version Intel Corporation
Driver internals Ideas on board SPRL
Copyright © 2008-2009, 2012 Intel Corporation, Laurent Pinchart
The contents of this file may be used under the terms of the GNU General Public License version 2 (the "GPL") as distributed in the kernel source COPYING file.
Revision History | ||
---|---|---|
Revision 1.0 | 2012-07-13 | LP |
Added extensive documentation about driver internals. |
目录
1.简介
2. DRM内部
驱动程序初始化
内存管理
模式设定
KMS初始化和清理
模式设置帮助函数
KMS属性
V(垂直) blank
打开/关闭,文件操作和IOCTL
命令提交和防护
睡眠/唤醒
DMA服务
3.用户接口
渲染节点
VBlank事件处理
A.DRM驱动程序API
Linux DRM层包含旨在满足复杂图形设备需求的代码,通常包含非常适合3D图形加速的可编程管线[pipeline]。内核中的图形驱动程序可以利用DRM功能来简化诸如内存管理,中断处理和DMA之类的任务,并为应用程序提供统一的接口。
版本说明:本指南涵盖了DRM树中的功能,包括TTM内存管理器,输出配置和模式设置以及新的vblank内部,以及当前内核中的所有常规功能。
[在此处插入典型DRM堆栈的图]
本章介绍了与驱动程序作者和开发人员相关的DRM内部,这些工作人员和开发人员致力于为现有驱动程序添加对最新功能的支持。
首先,我们讨论一些典型的驱动程序初始化要求,例如设置命令缓冲区,创建并初始化输出配置以及初始化核心服务。后续部分将更详细地介绍核心内部结构,并提供实施说明和示例。
DRM层为图形驱动程序提供了多种服务,其中许多服务是由它通过libdrm提供的应用程序接口驱动的,libdrm是包装大多数DRM ioctl的库。这些包括vblank事件处理,内存管理,输出管理,帧缓冲区管理,命令提交和防护,挂起/恢复支持以及DMA服务。
每个DRM驱动程序的核心都是drm_driver 结构。驱动程序通常会静态初始化drm_driver结构,然后将其传递给drm_*_init()
函数之一以在DRM子系统中注册它。
该drm_driver结构包含了描述驱动程序和功能支持,以及指向方法的DRM核心将调用来实现DRM API静态信息。我们将首先浏览drm_driver静态信息字段,然后在以后的部分中详细描述各个操作。
驱动功能
驱动程序通过在driver_features
字段中设置适当的标志来告知DRM内核其要求和支持的功能 。由于这些标志自注册就会影响DRM核心行为,因此必须将大多数标志在注册drm_driver 实例时便设置。高通对应msm_drv.c里面的msm_driver结构体
u32 driver_features;
驱动程序功能标志
DRIVER_USE_AGP
驱动程序使用AGP接口,DRM核心将管理AGP资源。
DRIVER_REQUIRE_AGP
驱动程序需要AGP接口才能运行。AGP初始化失败将成为致命错误。
DRIVER_PCI_DMA
驱动程序具有PCI DMA的功能,将启用PCI DMA缓冲区到用户空间的映射。不推荐使用。
DRIVER_SG
驱动程序可以执行分散/收集DMA,将启用分散/收集缓冲区的分配和映射。不推荐使用。
DRIVER_HAVE_DMA
驱动程序支持DMA,将支持用户空间DMA API。不推荐使用。
DRIVER_HAVE_IRQ,DRIVER_IRQ_SHARED
DRIVER_HAVE_IRQ指示驱动程序是否具有由DRM Core管理的IRQ处理程序。设置该标志后,内核将支持简单的IRQ处理程序安装。安装过程在“ IRQ注册”一节中介绍。
DRIVER_IRQ_SHARED指示设备和处理程序是否支持共享的IRQ(请注意,这是PCI驱动程序所必需的)。
DRIVER_GEM
驱动程序使用GEM内存管理器。
DRIVER_MODESET
驱动程序支持模式设置界面(KMS)。
DRIVER_PRIME
驱动程序实现DRM PRIME缓冲区共享。
DRIVER_RENDER
驱动程序支持专用渲染节点。
主要,次要和补丁级别
int major;
int minor;
int patchlevel;
DRM核心通过主要,次要和补丁程序级别的三元组来标识驱动程序版本。该信息在初始化时被打印到内核日志中,并通过DRM_IOCTL_VERSION ioctl传递到用户空间。
主编号和次编号也用于验证[应用程序]通过DRM_IOCTL_SET_VERSION的请求的驱动程序API版本。当驱动程序API在次要版本之间更改时,应用程序可以调用DRM_IOCTL_SET_VERSION以选择特定版本的API。如果请求的主版本不等于驱动程序主版本,或者所请求的次版本大于驱动程序次版本,则DRM_IOCTL_SET_VERSION调用将返回错误。否则,驱动程序的set_version()方法将被调用来设置请求版本。
名称,说明和日期
char *name;
char *desc;
char *date;
驱动程序名称在初始化时被打印到内核日志中,用于IRQ注册,并通过DRM_IOCTL_VERSION传递给用户空间。
驱动程序描述是一个纯信息性的字符串,它通过DRM_IOCTL_VERSION ioctl传递给用户空间,否则不会被内核使用。
驱动程序日期,格式为YYYYMMDD,用于标识对驱动程序的最新修改日期。但是,由于大多数驱动程序无法更新它,因此它的值几乎没有用。DRM内核在初始化时将其打印到内核日志,并通过DRM_IOCTL_VERSION ioctl将其传递到用户空间。
该load
方法是驱动程序和设备初始化的入口点。该方法负责分配和初始化驱动程序私有数据,指定支持的性能计数器,执行资源分配和映射(例如,获取时钟,映射寄存器或分配命令缓冲区),初始化内存管理器(称为“内存管理”的部分),安装IRQ处理程序(称为“ IRQ注册”的部分),设置垂直消隐处理(称为“垂直消隐” 的部分),模式设置(称为“ Mode Setting”的部分)和初始输出配置(称为“ KMS初始化和清理”部分)。
如果需要考虑兼容性(例如,将驱动程序从“用户模式设置”转换为“内核模式设置”),则必须注意防止设备初始化和控制与当前活动的用户空间驱动程序不兼容。例如,如果正在使用用户级别模式设置驱动程序,则在加载时执行输出发现和配置会很成问题。同样,如果使用了不了解内存管理的用户级驱动程序,则可能需要省略内存管理和命令缓冲区设置。这些要求是特定于驱动程序的,因此必须注意使新旧应用程序和库均能正常工作。
int (*load) (struct drm_device *, unsigned long flags);
该方法有两个参数,一个指向新创建的drm_device的指针 和标志。这些标志用于传递设备id的driver_data字段,该设备id对应于传递给drm_*_init()的设备。当前只有PCI设备使用此方法,USB和平台DRM驱动程序将其load
方法,参数标志为0。
驱动程序私有和性能计数器
驱动程序私有挂起(隐藏?)了主要的 drm_device结构,可用于跟踪设备特定的各种信息位,例如寄存器偏移,命令缓冲区状态,用于挂起/恢复的寄存器状态等。在加载时,驱动程序可以简单地分配并适当地设置一个drm_device.dev_priv;它应该被释放和drm_device.dev_priv设置为空当驱动程序被卸载。
DRM支持几个用于粗略性能描述的计数器。该统计计数器系统已弃用,不应使用。如果需要性能监视,则开发人员应调查并可能增强内核性能和跟踪基础结构,以导出GPU相关的性能信息,以供性能监视工具和应用程序使用。
IRQ注册
DRM核心试图通过提供drm_irq_install
和 drm_irq_uninstall
功能来促进IRQ处理程序的注册和注销。这些功能每个设备仅支持单个中断,使用多个IRQ的设备需要手动处理。
管理IRQ注册
drm_irq_install和
drm_irq_uninstall
都是通过调用函数drm_dev_to_irq
获取设备的IRQ 。此内联函数将调用特定于总线的操作以检索IRQ号。对于平台设备,platform_get_irq
(...,0)用于检索IRQ号。
drm_irq_install
通过调用 irq_preinstall
驱动程序操作启动。该操作是可选的,必须确保在通过清除所有挂起的中断标志或禁用中断时不会触发中断。
然后,通过调用request_irq
来请求IRQ。如果设置了DRIVER_IRQ_SHARED驱动功能标志,则将请求共享(IRQF_SHARED)IRQ。
IRQ处理函数必须作为必需的irq_handler驱动程序操作提供。它会直接传递给 request_irq
,所有IRQ处理程序相同的原型。他将作为第二个参数被调用,一个指向DRM设备的指针。
最后,该函数调用可选的 irq_postinstall
驱动程序操作。该操作通常启用中断(vblank中断除外,vblank中断是单独启用的),但是驱动程序可以选择在其他时间启用/禁用中断。
drm_irq_uninstall与drm_irq_install
相似,用于卸载IRQ处理程序。首先唤醒所有等待vblank中断的进程,以确保它们不会挂起,然后调用可选的 irq_uninstall
驱动程序操作。该操作必须禁用所有硬件中断。最后,该函数通过调用 free_irq
释放IRQ。
手动IRQ注册
需要多个中断处理程序的驱动程序不能使用托管的IRQ注册功能。在这种情况下,必须手动注册和注销IRQ(通常使用request_irq
和free_irq
函数,或者它们的devm_ *等效项)。
手动注册IRQ时,驱动程序不得设置DRIVER_HAVE_IRQ驱动程序功能标志,并且不得提供 irq_handler
驱动程序操作。他们必须在注册IRQ 时将drm_device irq_enabled
字段设置为1,并在取消注册IRQ之后将其清除为0。
内存管理器初始化
每个DRM驱动程序都需要一个内存管理器,必须在加载时对其进行初始化。DRM当前包含两个内存管理器,即转换表管理器(TTM)和图形执行管理器(GEM)。本文档仅描述GEM内存管理器的用法。有关详细信息,请参见 “内存管理”部分。
杂项设备配置
配置期间PCI设备可能需要执行的另一项任务是映射视频BIOS。在许多设备上,VBIOS描述设备配置,LCD面板时钟(timing)(如果有),并包含指示设备状态的标志。可以使用pci_map_rom()调用完成BIOS映射,pci_map_rom()是一个便捷函数,负责映射实际的ROM,无论该ROM已被隐藏到内存中(通常位于地址0xc0000),还是存在于ROM BAR中的PCI设备上。请注意,在映射ROM并提取了所有必要的信息之后,应取消映射。在许多设备上,ROM地址解码器与其他BAR共享,因此,将其保留为映射状态可能会导致不良行为,如挂起或内存损坏。
现代Linux系统需要大量的图形内存来存储帧缓冲区,纹理,顶点和其他与图形相关的数据。考虑到许多数据的动态特性,因此有效地管理图形内存对于图形堆栈至关重要,并且在DRM基础架构中发挥着核心作用。
DRM核心包括两个内存管理器,即转换表映射(TTM)和图形执行管理器(GEM)。TTM是第一个开发的DRM内存管理器,并试图成为一种适用于所有人的解决方案。它提供了一个单一的用户空间API,可以满足所有硬件的需求,同时支持统一内存体系结构(UMA)设备和具有专用视频RAM的设备(即大多数离散视频卡)。这导致了一个庞大,复杂的代码片段,结果证明这些代码难以用于驱动程序开发。
GEM最初是由英特尔赞助的项目,以应对TTM的复杂性。它的设计理念是完全不同的:GEM没有为每个与图形内存相关的问题提供解决方案,而是确定了驱动程序之间的通用代码,并创建了一个共享它的支持库。与TTM相比,GEM的初始化和执行要求更简单,但没有视频RAM管理功能,因此仅限于UMA设备。
TTM设计背景和信息属于此处。
TTM初始化
本节已过时。
希望支持TTM的驱动程序必须填写drm_bo_driver结构。该结构包含几个带有函数指针的字段,这些函数指针用于初始化TTM,分配和释放内存,等待命令完成和栅栏同步以及内存迁移。有关用法的示例,请参见radeon_ttm.c文件。
ttm_global_reference结构由几个字段组成:
struct ttm_global_reference {
enum ttm_global_types global_type;
size_t size;
void *object;
int (*init) (struct ttm_global_reference *);
void (*release) (struct ttm_global_reference *);
};
整个内存管理器应该有一个全局引用结构,而内存管理器在运行时为每个对象创建的其他引用结构也应有。您的全局TTM应该具有TTM_GLOBAL_TTM_MEM类型。全局对象的大小字段应为sizeof(struct ttm_mem_global),并且init和release钩子应指向特定于驱动程序的init和release例程,这可能最终分别调用ttm_mem_global_init和ttm_mem_global_release。
通过调用ttm_global_item_ref()设置并初始化全局TTM记帐结构后,您需要创建一个缓冲区对象TTM,以提供一个供客户端和内核本身分配缓冲区池对象。该对象的类型应为TTM_GLOBAL_TTM_BO,其大小应为sizeof(struct ttm_bo_global)。同样,可以提供特定于驱动程序的初始化和释放功能,可能最终分别调用ttm_bo_global_init()和ttm_bo_global_release()。同样,与前一个对象一样,ttm_global_item_ref()用于为TTM创建初始引用计数,该计数将调用您的初始化函数。
GEM设计方法导致了内存管理器无法在其用户空间或内核API中提供所有(甚至所有常见)用例的完整覆盖。GEM向用户空间公开了一组与内存相关的标准操作,并向驱动程序提供了一组帮助程序功能,并允许驱动程序使用自己的私有API来实现特定于硬件的操作。[承上启下的中间层]
GEM-“图形执行管理器”文章中 介绍了GEM用户空间API 。尽管有些过时,但该文档很好地概述了GEM API原则。目前,使用特定于驱动程序的ioctl来实现缓冲区分配以及读写操作(作为通用GEM API的一部分进行描述)。
GEM与数据无关。它管理抽象缓冲区对象,而无需知道各个缓冲区包含哪些内容。因此,需要了解缓冲区内容或用途(例如缓冲区分配或同步原语)的API不在GEM的范围内,必须使用特定于驱动程序的ioctl来实现。
从根本上讲,GEM涉及以下几种操作:
缓冲区对象分配相对简单,并且主要由Linux的shmem层提供,后者提供了支持每个对象的内存。
特定于设备的操作(例如命令执行,固定,缓冲区读和写,映射和域所有权转移)留给特定于驱动程序的ioctl。
GEM初始化
使用GEM的驱动程序必须在struct drm_driver driver_features
字段中设置DRIVER_GEM位 。然后,DRM内核将在调用load
操作之前自动初始化GEM内核 。在后台,这将创建DRM内存管理器对象,该对象提供用于对象分配的地址空间池。
在KMS配置中,如果硬件需要,驱动程序需要在核心GEM初始化之后分配和初始化命令环缓冲区。UMA设备通常具有所谓的“被盗”存储区,该存储区为初始帧缓冲区和设备所需的大而连续的存储区提供了空间。该空间通常不由GEM管理,必须单独初始化为它自己的DRM MM对象。
GEM对象创建
GEM将GEM对象的创建和支持它们的内存分配分为两个不同的操作。
GEM对象由struct drm_gem_object的实例表示 。驱动程序通常需要使用私有信息来扩展GEM对象,从而创建特定于驱动程序的GEM对象结构类型,以嵌入struct drm_gem_object的实例 。
要创建GEM对象,驱动程序会为其特定GEM对象类型的实例分配内存,并通过调用drm_gem_object_init来初始化嵌入的结构drm_gem_object。该函数需要指向DRM设备的指针,指向GEM对象的指针以及缓冲区对象的大小(以字节为单位)。
GEM使用shmem分配匿名可分页内存。 drm_gem_object_init
将创建所需大小的shmfs文件,并将其存储到struct drm_gem_object filp
字段中。当图形硬件直接使用系统内存时,该内存可用作对象的主要存储,否则用作后备存储。
驱动程序通过调用shmem_read_mapping_page_gfp
来负责每个页面实际的物理页面分配。请注意,它们可以在初始化GEM对象时决定分配页面,或者将分配延迟到需要内存之前(例如,由于用户空间内存访问而导致页面错误时,或者驱动程序需要启动涉及内存内容的DMA传输时)。
例如,当硬件需要物理上连续的系统内存时(例如嵌入式设备中的常见情况),并不总是需要匿名的可分页内存分配。驱动程序可以通过调用drm_gem_private_object_init
代替drm_gem_object_init
来初始化没有shmfs支持的GEM对象(称为私有GEM对象)。专用GEM对象的存储必须由驱动程序管理。
不需要使用私有信息扩展GEM对象的驱动程序可以调用drm_gem_object_alloc
函数来分配和初始化struct drm_gem_object 实例。用drm_gem_object_init
初始化GEM对象后,GEM内核将调用可选的驱动程序操作gem_init_object
。
int (*gem_init_object) (struct drm_gem_object *obj);
私有GEM对象不存在alloc-and-init函数。
GEM对象生命周期
所有GEM对象均由GEM内核引用计数。引用可以分别通过calling drm_gem_object_reference
和drm_gem_object_unreference
申请和释放。调用者必须持有drm_device struct_mutex
锁。为方便起见,GEM提供了drm_gem_object_reference_unlocked
和 drm_gem_object_unreference_unlocked
功能,无需锁即可调用它们。
当GEM对象的最后一个引用被释放时,GEM内核将调用drm_driver gem_free_object
操作。对于启用GEM的驱动程序,该操作是必需的,并且必须释放GEM对象和所有相关资源。
void (*gem_free_object) (struct drm_gem_object *obj);
驱动程序负责释放所有GEM对象资源,包括GEM核心创建的资源。如果已为对象创建了mmap偏移量(在这种情况下 drm_gem_object :: map_list
:: map
不为NULL),则必须通过调用drm_gem_free_mmap_offset
释放它。必须通过调用drm_gem_object_release
释放shmfs后备存储 (如果尚未创建shmfs后备存储,也可以安全地调用该函数)。
GEM对象命名
用户空间和内核之间的通信使用本地句柄,全局名称或更近期使用文件描述符来引用GEM对象。所有这些都是32位整数值。通常的Linux内核限制适用于文件描述符。
GEM句柄是DRM文件。应用程序通过特定于驱动程序的ioctl获取GEM对象的句柄,并且可以使用该句柄引用其他标准或特定于驱动程序的ioctl中的GEM对象。关闭DRM文件句柄将释放其所有GEM句柄并取消引用关联的GEM对象。
要为GEM对象驱动程序创建句柄,请调用 drm_gem_handle_create
。该函数获取指向DRM文件和GEM对象的指针,并返回本地唯一的句柄。当不再需要该句柄时,驱动程序通过调用drm_gem_handle_delete
来删除它 。最后,可以通过调用 drm_gem_object_lookup
来检索与句柄关联的GEM对象。
句柄不拥有GEM对象的所有权,它们仅引用在句柄销毁时将被删除的对象上。为避免泄漏GEM对象,驱动程序必须确保适当地删除其拥有的引用(例如在对象创建时获取的初始引用),而无需对句柄进行任何特殊考虑。例如,在dumb_create
操作的实现中结合了GEM对象和句柄创建的特定情况下 ,驱动程序必须在返回句柄之前删除对GEM对象的初始引用。
GEM名称在用途上类似于句柄,但不是DRM文件的本地名称。它们可以在进程之间传递,以全局引用GEM对象。名称不能直接用于引用DRM API中的对象,应用程序必须分别使用DRM_IOCTL_GEM_FLINK和DRM_IOCTL_GEM_OPEN ioctls将句柄转换为名称,并将名称转换为句柄。转换是由DRM核心处理的,不需要任何特定于驱动程序的支持。
与全局名称相似,GEM文件描述符也用于跨进程共享GEM对象。它们提供了额外的安全性:由于必须通过UNIX域套接字显式发送文件描述符以在应用程序之间共享,因此不能像全局唯一的GEM名称那样猜测它们。[GEM名称可以猜测并调用对应api]
支持GEM文件描述符(也称为DRM PRIME API)的驱动程序必须在struct drm_driver driver_features
字段中设置DRIVER_PRIME位 ,并实现 prime_handle_to_fd
和 prime_fd_to_handle
操作。
int (*prime_handle_to_fd)(struct drm_device *dev,
struct drm_file *file_priv, uint32_t handle,
uint32_t flags, int *prime_fd);
int (*prime_fd_to_handle)(struct drm_device *dev,
struct drm_file *file_priv, int prime_fd,
uint32_t *handle);
这两个操作将句柄转换为PRIME文件描述符,反之亦然。驱动程序必须使用内核dma-buf缓冲区共享框架来管理PRIME文件描述符。
非GEM驱动程序必须自己实现操作,而GEM驱动程序必须使用drm_gem_prime_handle_to_fd
和drm_gem_prime_fd_to_handle
帮助程序功能。这些帮助程序依靠驱动程序 gem_prime_export
和 gem_prime_import
操作从GEM对象(dma-buf exporter role)创建dma-buf实例,和从dma-buf实例(dma-buf importer role)创建GEM对象。
struct dma_buf * (*gem_prime_export)(struct drm_device *dev,
struct drm_gem_object *obj,
int flags);
struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev,
struct dma_buf *dma_buf);
对于支持DRM PRIME的GEM驱动程序,这两个操作是必需的。
DRM PRIME辅助功能参考
驱动程序可以使用辅助功能更简单的API的条款drm_gem_prime_export
和 drm_gem_prime_import
实现gem_prime_export
和gem_prime_import
。这些函数通过五个较低级别的驱动程序回调实现dma-buf支持:
导出回调:
- gem_prime_pin
(可选):准备要导出的GEM对象
- gem_prime_get_sg_table
:提供固定页面的分散/聚集表
- gem_prime_vmap
:vmap驱动程序导出的缓冲区// vmap a buffer exported by your driver
- gem_prime_vunmap
:映射驱动程序导出的缓冲区// vunmap a buffer exported by your driver
导入回调:
- gem_prime_import_sg_table
(导入):从另一个驱动程序的散点图/聚集表生成GEM对象
GEM对象映射
由于映射操作相当繁重,因此与通过将缓冲区映射到用户空间相比,GEM更喜欢通过驱动程序特定的ioctl实现对缓冲区的类似于读/写的访问。但是,当需要随机访问缓冲区(例如执行软件渲染)时,直接访问对象可能会更有效率。
mmap系统调用不能直接用于映射GEM对象,因为它们没有自己的文件句柄。当前共存在两种将GEM对象映射到用户空间的替代方法。第一种方法使用特定于驱动程序的ioctl进行映射操作,调用do_mmap
在后台进行 。这通常被认为是可疑的,似乎不建议使用支持GEM的新驱动程序,因此在此不再赘述。
第二种方法在DRM文件句柄上使用mmap系统调用。
void *mmap(void *addr, size_t length, int prot, int flags, int fd,
off_t offset);
DRM通过mmap offset参数传递的伪偏移量标识要映射的GEM对象。因此,在映射之前,GEM对象必须与假偏移量关联。为此,驱动程序必须在该对象上调用 drm_gem_create_mmap_offset
。该函数从池中分配一个假偏移范围,并将偏移量除以PAGE_SIZE的值存储在 obj->map_list.hash.key
中。如果已经为该对象分配了伪偏移,则必须小心不要调用drm_gem_create_mmap_offset
。可以通过将obj->map_list.map
其设置为non-NULL进行测试 。
分配后,假偏移值(obj->map_list.hash.key << PAGE_SHIFT
)必须以特定于驱动程序的方式传递给应用程序,然后可以用作mmap offset参数。
GEM核心提供了一种辅助方法drm_gem_mmap
来处理对象映射。该方法可以直接设置为mmap文件操作处理程序。它将基于偏移值查找GEM对象,并将VMA操作设置为 drm_driver gem_vm_ops
字段。请注意,drm_gem_mmap
这不会将内存映射到用户空间,而是依赖于驱动程序提供的故障处理程序来单独映射页面。
要使用drm_gem_mmap
,驱动程序必须填充struct drm_driver gem_vm_ops
字段,一个指向VM操作的指针。
struct vm_operations_struct *gem_vm_ops
struct vm_operations_struct {
void (*open)(struct vm_area_struct * area);
void (*close)(struct vm_area_struct * area);
int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
};
在open
与close
操作中必须更新GEM对象的引用计数。驱动程序可以直接使用drm_gem_vm_open
和 drm_gem_vm_close
帮助函数作为打开和关闭处理程序。
fault处理程序负责在发生页面错误时将各个页面映射到用户空间。根据内存分配方案,驱动程序可以在故障时分配页面,或者可以在创建对象时决定为GEM对象分配内存。
想要预先映射GEM对象而不是处理页面错误的驱动程序可以实现自己的mmap文件操作处理程序。
Dumb GEM对象
GEM API并未标准化GEM对象的创建,而是将其留给特定于驱动程序的ioctl。虽然对于包含特定于设备的用户空间组件(例如在libdrm中)的成熟图形堆栈来说这不是问题,但此限制使基于DRM的early-boot graphics不必要地复杂。
Dumb GEM对象通过提供一个标准API来创建适合于scanout的Dumb缓冲区,从而在一定程度上缓解了这个问题,然后可以使用它来创建KMS帧缓冲区。
为了支持dumb GEM对象程序必须实现 dumb_create
, dumb_destroy
和 dumb_map_offset
操作。
int (*dumb_create)(struct drm_file *file_priv, struct drm_device *dev,
struct drm_mode_create_dumb *args);
该dumb_create
操作根据struct drm_mode_create_dumb 的宽度,高度和深度参数,创建适合于扫描的GEM对象。它用新创建的GEM对象的句柄及其行间距和大小(以字节为单位)填充参数的句柄、间距和大小字段。
int (*dumb_destroy)(struct drm_file *file_priv, struct drm_device *dev,
uint32_t handle);
dumb_destroy操作会销毁dumb_create创建的dumb GEM对象。
int (*dumb_map_offset)(struct drm_file *file_priv, struct drm_device *dev,
uint32_t handle, uint64_t *offset);
该dumb_map_offset
操作将mmap伪偏移与该句柄给定的GEM对象相关联并返回。驱动程序必须使用drm_gem_create_mmap_offset
函数关联虚假偏移量,如“ GEM对象映射”一节中 所述。
内存一致性
当映射到设备或在命令缓冲区中使用时,对象的备份页面将刷新到内存中并标记为写,以便与GPU保持一致。同样,如果在GPU完成渲染对象之后CPU访问对象,则必须使对象与CPU的内存视图保持一致,通常涉及各种GPU缓存刷新。该CPU <-> GPU一致性管理重点由特定于设备的ioctl提供,该ioctl评估对象的当前域并执行任何必要的刷新或同步操作以将对象放入所需的一致性域中(请注意,对象可能很忙,即有效的渲染目标;在这种情况下,在执行任何必要的刷新操作之前,需要设置域阻塞client端并等待渲染完成)。
命令执行
对于GPU设备来说,最重要的GEM功能可能是为客户端提供一个命令执行接口。客户端程序构造包含对先前分配的内存对象的引用的命令缓冲区,然后将其提交给GEM。那时,GEM会小心地将所有对象绑定到GTT中,执行缓冲区,并在访问相同缓冲区的客户端之间提供必要的同步。这通常涉及从GTT中清除某些对象并重新绑定其他对象(这是一项相当昂贵的操作),并提供重定位支持,从而向客户端隐藏了固定的GTT偏移量。客户端必须注意不要提交引用比GTT中可容纳的对象更多的命令缓冲区;否则GEM将拒绝它们,并且不会进行渲染。同样,如果缓冲区中的多个对象要求分配fence寄存器以进行正确渲染(例如965之前的芯片上的2D Blits),则必须注意不要要求比客户端可用的更多的fence寄存器。这种资源管理应该从libdrm中的客户端中抽象出来。
驱动程序必须通过drm_mode_config_init
在DRM设备上调用来初始化模式设置核心 。该函数将初始化drm_device mode_config
字段,并且永远不会失败。完成后,必须通过初始化以下字段来设置模式配置。
int min_width, min_height;
int max_width, max_height;
帧缓冲区的最小和最大宽度和高度,以像素为单位。
struct drm_mode_config_funcs *funcs;
模式设定函数。
struct drm_framebuffer *(*fb_create)(struct drm_device *dev,
struct drm_file *file_priv,
struct drm_mode_fb_cmd2 *mode_cmd);
帧缓冲区是抽象的内存对象,它提供像素源以扫描到CRTC[framebuffer -> crtc]。应用程序通过ioctl DRM_IOCTL_MODE_ADDFB(2)显式请求创建帧缓冲区,并接收不透明的句柄,该句柄可以传递给KMS CRTC控制器,平面配置和页面翻转功能。
帧缓冲区依靠底层内存管理器进行低级内存操作。在创建帧缓冲区时,应用程序通过参数传递内存句柄(或一系列内存句柄列表用于多平面模式)drm_mode_fb_cmd2
。本文档假定驱动程序使用GEM,因此这些句柄将引用GEM对象。
驱动程序必须首先验证通过mode_cmd传递的请求帧缓冲区参数。特别是在这里可以捕获无效的尺寸,像素格式或间距。
如果认为这些参数有效,则驱动程序将进行创建,初始化并返回struct drm_framebuffer的实例。如果需要,可以将实例嵌入更大的特定于驱动程序的结构中。驱动必须从drm_mode_fb_cmd2
参数传递的值中
填写的width
, height
,pitches
, offsets
,depth
, bits_per_pixel
和 pixel_format
字段。他们应该调用drm_helper_mode_fill_fb_struct
辅助函数来实现。
新的帧缓冲区实例的初始化通过调用drm_framebuffer_init
完成,该调用接收指向DRM帧缓冲区操作的指针(结构 drm_framebuffer_funcs)。请注意,此函数发布了帧缓冲区,因此从这一点出发,可以从其他线程并发访问它。因此,它必须是驱动程序的帧缓冲区初始化序列中的最后一步。帧缓冲区操作是
int (*create_handle)(struct drm_framebuffer *fb,
struct drm_file *file_priv, unsigned int *handle);
创建用于底层内存对象的帧缓冲区的句柄。如果帧缓冲区使用多平面模式,则句柄将引用与第一个平面关联的内存对象。
驱动程序调用drm_gem_handle_create
以创建句柄。
void (*destroy)(struct drm_framebuffer *framebuffer);
销毁帧缓冲区对象并释放所有关联的资源。驱动程序必须调用 drm_framebuffer_cleanup
释放由DRM核心分配给帧缓冲区对象的资源,并且必须确保取消与帧缓冲区关联的所有内存对象的引用。由create_handle
操作创建的句柄由 DRM核心释放。
int (*dirty)(struct drm_framebuffer *framebuffer,
struct drm_file *file_priv, unsigned flags, unsigned color,
struct drm_clip_rect *clips, unsigned num_clips);
此可选操作通知驱动程序,在响应DRM_IOCTL_MODE_DIRTYFB ioctl调用时,帧缓冲区的一个区域发生了变化。
drm帧缓冲区的生命周期由引用计数控制,驱动程序可以使用 drm_framebuffer_reference
获取额外的引用
drm_framebuffer_unreference
删除引用。对于未删除的最后一个引用的驱动程序专用帧缓冲区(例如,将结构drm_framebuffer嵌入fbdev helper结构中时的fbdev帧缓冲区 ),驱动程序可以在模块卸载时使用drm_framebuffer_unregister_private
手动清除帧缓冲区 。
void (*output_poll_changed)(struct drm_device *dev);
此操作通知驱动程序一个或多个连接器的状态已更改。使用fb助手的驱动程序可以调用drm_fb_helper_hotplug_event
函数来处理此操作。
除了一些具有自己的锁定的查找结构(隐藏在接口功能后面)之外,大多数模式集状态还受dev-
mode_config.lock
允许实时crtc上的并发屏幕更新),则他们还必须小心始终抓住相关的crtc锁。
KMS设备被抽象并公开为一组,平面(panel),CRTC,编码器(encoder)和连接器(connector)。因此,在初始化模式设置之后,KMS驱动程序必须在加载时创建和初始化所有这些对象。
CRTC是表示芯片一部分的抽象,其中包含指向扫描缓冲区的指针。因此,可用的CRTC数量决定了在任何给定时间可以激活多少个独立的扫描缓冲区。CRTC结构包含几个字段来支持此操作:指向某些视频内存的指针(抽象为帧缓冲区对象),显示模式,以及视频内存中的(x,y)偏移量以支持平移或配置,其中一个视频存储器跨越多个CRTC。
CRTC初始化
KMS设备必须创建并注册至少一个struct drm_crtc实例。该实例由驱动程序分配并归零(可能是较大结构的一部分),并通过指向CRTC函数的指针drm_crtc_init
调用进行注册。
CRTC操作
设置配置
int (*set_config)(struct drm_mode_set *set);
将新的CRTC配置应用于设备。该配置指定CRTC,要从中扫描的帧缓冲区,帧缓冲区中的(x,y)位置,显示模式以及与CRTC一起驱动的一组连接器。
如果配置中指定的帧缓冲区为NULL,则驱动程序必须分离所有连接到CRTC的编码器和所有连接到这些编码器的连接器,并禁用它们。
在保持模式配置锁定的情况下调用此操作。
FIXME:set_config应该如何与DPMS[显示器电源管理信号(Display POWER MANAGEMENT Signalling)]交互?如果CRTC被睡眠,是否应该唤醒?
页面翻转
int (*page_flip)(struct drm_crtc *crtc, struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event);
将页面翻转安排到CRTC的给定帧缓冲区。在保持模式配置互斥锁的情况下调用此操作。
页面翻转是一种同步机制,可以在垂直消隐(vbank,打无效像素点的时候)期间将CRTC扫描出的帧缓冲区替换为新的帧缓冲区,从而避免撕裂。当应用程序请求翻页时,DRM内核将验证新的帧缓冲区是否足够大,以供CRTC在当前配置的模式下进行扫描,然后使用指向新帧缓冲区的指针调用CRTC的page_flip
操作。
该page_flip
实现翻页操作。一旦任何针对新帧缓冲区的等待渲染目标完成,CRTC将重新编程为在下一次垂直刷新后显示该帧缓冲区。该操作必须立即返回,而不必等待渲染或页面翻转完成,并且必须阻止任何新的渲染到帧缓冲区,直到页面翻转完成。
如果可以成功调度页面翻转,则驱动程序必须将该drm_crtc-
fb
。这一点很重要,这样才能使基于帧缓冲区的引用计数保持平衡。
如果页面翻转已经挂起,则 page_flip
操作必须返回-EBUSY。
为了将页面翻转同步到垂直消隐,驱动程序可能需要启用垂直消隐中断。它应该为此目的而调用drm_vblank_get
,并在页面翻转完成后调用drm_vblank_put
。
如果应用程序在翻页完成时请求得到通知,则将使用指向drm_pending_vblank_event实例的非空事件参数调用page_flip操作。翻页完成后,驱动程序必须调用drm_send_vblank_event
填写事件并发送,以唤醒所有等待的进程。这可以用
spin_lock_irqsave(&dev->event_lock, flags);
...
drm_send_vblank_event(dev, pipe, event);
spin_unlock_irqrestore(&dev->event_lock, flags);
FIXME:不需要等待渲染完成的驱动程序是否可以将事件添加到dev->vblank_event_list
,并让DRM内核处理所有事情,例如“正常的”垂直消隐事件?
在等待页面翻转完成时,驱动程序可以自由使用列表头event->base.link
,以将pending事件存储在特定于驱动程序的列表中。
如果在发出事件信号之前关闭了文件句柄,则驱动程序必须注意在其preclose
操作中销毁该事件 (并在需要时调用 drm_vblank_put
)。
混杂操作
void (*set_property)(struct drm_crtc *crtc,
struct drm_property *property, uint64_t value);
将给定的CRTC属性的值设置为 value
。有关属性的更多信息,请参见“ KMS属性”一节。
void (*gamma_set)(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b,
uint32_t start, uint32_t size);
将伽玛表应用于设备上。该操作是可选的。
void (*destroy)(struct drm_crtc *crtc);
不再需要时销毁CRTC。请参阅“ KMS初始化和清理”部分。
平面表示可以在扫描过程中与CRTC混合或叠加在CRTC顶部的图像源。平面与帧缓冲区关联,以裁剪图像存储器(源)的一部分,并可以选择将其缩放到目标大小。然后将结果与CRTC混合或叠加在CRTC之上。
平面初始化
平面是可选的。要创建平面,KMS驱动程序会分配struct drm_plane实例 (可能是较大结构的一部分)的实例并将其清零,并通过调用drm_plane_init
进行注册。该函数采用可与平面关联的CRTC的位掩码,指向平面函数的指针以及格式支持的格式的列表。
平面操作
int (*update_plane)(struct drm_plane *plane, struct drm_crtc *crtc,
struct drm_framebuffer *fb, int crtc_x, int crtc_y,
unsigned int crtc_w, unsigned int crtc_h,
uint32_t src_x, uint32_t src_y,
uint32_t src_w, uint32_t src_h);
使用给定的CRTC和帧缓冲区启用并配置平面以。
帧缓冲存储器坐标源矩形由给定src_x
,src_y
, src_w
和src_h
参数(如16.16固定点值)。不支持亚像素平面坐标的设备可以忽略小数部分。
CRTC坐标目标矩形由给定 crtc_x
,crtc_y
, crtc_w
和crtc_h
参数(如整数的值)。设备将源矩形缩放为目标矩形。如果不支持缩放,并且源矩形大小与目标矩形大小不匹配,则驱动程序必须返回-EINVAL错误。
int (*disable_plane)(struct drm_plane *plane);
禁用plane。DRM内核调用此方法以响应将帧缓冲区ID设置为0的DRM_IOCTL_MODE_SETPLANE ioctl调用。CRTC不得处理禁用的平面。
void (*destroy)(struct drm_plane *plane);
不再需要时销毁Plane。请参阅“ KMS初始化和清理”部分。
编码器从CRTC获取像素数据,并将其转换为适合任何连接着的连接器的格式。在某些设备上,CRTC可能会向多个编码器发送数据。在那种情况下,多个个编码器都将从同一个扫描缓冲区接收数据,从而导致跨连接到每个编码器的连接器的“克隆”显示配置。
编码器初始化
对于CRTC,KMS驱动程序必须创建,初始化和注册至少一个struct drm_encoder实例。该实例由驱动程序分配并归零,可能是较大结构的一部分。
驱动程序必须在注册编码器之前初始化struct drm_encoder 的possible_crtcs
和 possible_clones
字段。这两个字段分别是:编码器可以连接到的CRTC的位掩码,用于克隆的同级编码器。
初始化后,必须调用drm_encoder_init
注册编码器 。该函数获取指向编码器功能的指针和编码器类型。支持的类型是
编码器必须连接到CRTC才能使用。DRM驱动程序在初始化时不附加编码器。应用程序(或实现时的fbdev兼容性层)负责将其要使用的编码器附加到CRTC。
编码器操作
void (*destroy)(struct drm_encoder *encoder);
在不再需要时调用销毁编码器。请参阅 “ KMS初始化和清理”部分。
void (*set_property)(struct drm_plane *plane,
struct drm_property *property, uint64_t value);
将给定平面属性的值设置为 value
。有关属性的更多信息,请参见“ KMS属性”一节。
连接器是设备上像素数据的最终目的地,通常直接连接到外部显示设备,例如显示器或笔记本电脑面板。一个连接器一次只能连接到一个编码器。连接器也是保留有关显示器信息的附加结构,因此它包含显示数据,EDID数据,DPMS和连接状态, 以及有关显示器支持的模式的信息的字段。
连接器初始化
最后,KMS驱动程序必须创建,初始化,注册并附加至少一个struct drm_connector实例。该实例将与其他KMS对象一起创建,并通过设置以下字段进行初始化。
interlace_allowed
连接器是否可以处理隔行模式。
doublescan_allowed
连接器是否可以处理双重扫描。
display_info
当检测到显示时,显示信息由EDID信息填充。对于非热插拔显示器(例如嵌入式系统中的平板),驱动程序应初始化 display_info.
width_mm
和 display_info.
height_mm
,带有显示器实际尺寸的字段。
polled
连接器轮询模式,组合以下属性
DRM_CONNECTOR_POLL_HPD
连接器会生成热插拔事件,不需要定期进行轮询。不能将CONNECT和DISCONNECT标志与HPD标志一起设置。
DRM_CONNECTOR_POLL_CONNECT
定期轮询连接器以进行连接。
DRM_CONNECTOR_POLL_DISCONNECT
定期轮询连接器是否断开连接。
将不支持连接状态发现的连接器设置为0。
然后,使用指向连接器功能和连接器类型的指针调用drm_connector_init
来注册该连接器,并通过调用drm_sysfs_connector_add
将其显示公开到sysfs 。
支持的连接器类型为
连接器必须连接到编码器上才能使用。对于将连接器1:1映射至编码器的设备,应在初始化时通过调用drm_mode_connector_attach_encoder
来连接该连接器 。驱动程序还必须设置drm_connector encoder
字段为指向附加的编码器。
最后,驱动程序必须通过调用drm_kms_helper_poll_init
来初始化连接器状态更改检测。如果至少一个连接器是可轮询的,但不能生成热插拔中断(由DRM_CONNECTOR_POLL_CONNECT和DRM_CONNECTOR_POLL_DISCONNECT连接器标志指示),则延迟的工作将自动排队以定期轮询更改。可以生成热插拔中断的连接器必须改用DRM_CONNECTOR_POLL_HPD标志进行标记,并且它们的中断处理程序必须调用drm_helper_hpd_irq_event
。该功能将延迟的工作进行排序,以检查所有连接器的状态,但不会进行定期轮询。
连接器操作
除非另有说明,否则所有操作都是强制性的。
DPMS
void (*dpms)(struct drm_connector *connector, int mode);
DPMS操作设置连接器的电源状态。模式参数是以下之一
DRM_MODE_DPMS_ON
DRM_MODE_DPMS_STANDBY
DRM_MODE_DPMS_SUSPEND
DRM_MODE_DPMS_OFF
在除DPMS_ON模式以外的所有模式下,连接器所连接的编码器均应通过适当地驱动其信号,将显示器置于低功耗模式。如果编码器上连接了多个连接器,则应注意不要改变其他显示器的电源状态。当所有相关的连接器都置于低功耗模式时,应将低功耗模式传播到编码器和CRTC。[逻辑类似于suspend/resume]
模式
int (*fill_modes)(struct drm_connector *connector, uint32_t max_width,
uint32_t max_height);
用连接器支持的所有模式填充模式列表。如果 max_width
和max_height
参数不为零,则实现必须忽略大于max_width
或大于max_height
的所有模式。
连接器还必须以连接的显示器物理尺寸(以毫米为单位)填写其display_info
width_mm
和height_mm
字段。如果该值未知或不适用(例如,对于投影仪设备),则应将字段设置为0。
连接状态
如果支持,则通过轮询或热插拔事件更新连接状态(请参阅参考资料polled
)。状态值通过ioctls报告给用户空间,并且不能在驱动程序内部使用,因为状态值只能从用户空间调用drm_mode_getconnector
来初始化 。
enum drm_connector_status (*detect)(struct drm_connector *connector,
bool force);
检查连接器上是否连接了任何东西。force参数在轮询时设置为false,在根据用户请求检查连接器时设置为true。在自动探测期间,驱动程序可以使用force来避免昂贵的、破坏性的操作。
如果连接器已连接东西,则返回connector_status_connected;如果未连接任何东西,则返回connector_status_disconnected;如果连接状态未知,则返回connector_status_unknown。
如果连接状态确实被探测为已连接,驱动程序应该只返回connector_status_connected。无法检测连接状态或连接状态探测失败的连接器应该返回connector_status_unknown。
混杂项
void (*set_property)(struct drm_connector *connector,
struct drm_property *property, uint64_t value);
将给定连接器属性的值设置为 value
。有关属性 的更多信息,请参见“ KMS属性”一节。
void (*destroy)(struct drm_connector *connector);
不再需要时销毁连接器。请参阅 “ KMS初始化和清理”。
DRM核心管理其对象的生存周期。当不再需要某个对象时,内核调用其destroy函数,该函数必须清除并释放为该对象分配的所有资源。每个 drm_*_init
调用必须与相应的drm_*_cleanup
调用相匹配,以清理CRTC(drm_crtc_cleanup
),平面(drm_plane_cleanup
),编码器(drm_encoder_cleanup
)和连接器(drm_connector_cleanup
)。此外,在调用drm_connector_cleanup之前,必须通过调用drm_sysfs_connector_remove来删除添加到sysfs中的连接器。
连接器状态更改检测必须通过调用 drm_kms_helper_poll_fini
进行清理。
void intel_crt_init(struct drm_device *dev)
{
struct drm_connector *connector;
struct intel_output *intel_output;
intel_output = kzalloc(sizeof(struct intel_output), GFP_KERNEL);
if (!intel_output)
return;
connector = &intel_output->base;
drm_connector_init(dev, &intel_output->base,
&intel_crt_connector_funcs, DRM_MODE_CONNECTOR_VGA);
drm_encoder_init(dev, &intel_output->enc, &intel_crt_enc_funcs,
DRM_MODE_ENCODER_DAC);
drm_mode_connector_attach_encoder(&intel_output->base,
&intel_output->enc);
/* Set up the DDC bus. */
intel_output->ddc_bus = intel_i2c_create(dev, GPIOA, "CRTDDC_A");
if (!intel_output->ddc_bus) {
dev_printk(KERN_ERR, &dev->pdev->dev, "DDC bus registration "
"failed.\n");
return;
}
intel_output->type = INTEL_OUTPUT_ANALOG;
connector->interlace_allowed = 0;
connector->doublescan_allowed = 0;
drm_encoder_helper_add(&intel_output->enc, &intel_crt_helper_funcs);
drm_connector_helper_add(connector, &intel_crt_connector_helper_funcs);
drm_sysfs_connector_add(connector);
}
在上面的示例(取自i915驱动程序)中,创建了CRTC,连接器和编码器组合。还创建了特定于设备的i2c总线,以获取EDID数据并执行监视器检测。完成该过程后,将向sysfs注册新连接器,使其属性对应用程序可用。
drm_modeset_lock_all —获取所有模式集锁
void fsfuncdrm_modeset_lock_all ( dev);
struct drm_device * dev;
dev
DRM设备
此函数具有所有模式集锁定,适用于尚未实现更细粒度的方案的情况。
drm_modeset_unlock_all —删除所有模式集锁
void fsfuncdrm_modeset_unlock_all ( dev);
struct drm_device * dev;
dev
设备
drm_warn_on_modeset_not_all_locked —检查是否已锁定所有模式设置锁
void fsfuncdrm_warn_on_modeset_not_all_locked ( dev);
struct drm_device * dev;
dev
设备
drm_mode_object_find —查找具有静态生存期的drm对象
struct drm_mode_object * fsfuncdrm_mode_object_find ( dev,
id,
type);
struct drm_device * dev;
uint32_t id;
uint32_t type;
dev
DRM设备
id
模式对象的ID
type
模式对象的类型
请注意,framebuffer不能用这个函数查找——因为它们是引用计数的,所以需要特殊处理。
drm_framebuffer_init —初始化帧缓冲区
int fsfuncdrm_framebuffer_init ( dev,
fb,
funcs);
struct drm_device * dev;
struct drm_framebuffer * fb;
const struct drm_framebuffer_funcs * funcs;
dev
DRM设备
fb
要初始化的帧缓冲区
funcs
...具有这些功能
为帧缓冲区的父模式对象分配一个ID,设置其模式函数和设备文件,并将其添加到主fd列表中。
此功能将发布fb,并使其可供其他用户并发访问。这意味着此时必须完全设置fb -由于所有fb属性在其生命周期内都是不变的,因此无需进一步锁定,而仅需要正确的引用计数即可。
成功为零,失败为错误代码。
drm_framebuffer_lookup —查找drm帧缓冲区并获取引用
struct drm_framebuffer * fsfuncdrm_framebuffer_lookup ( dev,
id);
struct drm_device * dev;
uint32_t id;
dev
DRM设备
id
fb对象的ID
如果成功,这将获取对帧缓冲区的附加引用-调用者需要确保最终再次取消对返回的帧缓冲区的引用。
drm_framebuffer_unreference —取消引用帧缓冲区
void fsfuncdrm_framebuffer_unreference ( fb);
struct drm_framebuffer * fb;
fb
帧缓冲区以取消引用
此功能将fb的refcount递减,并在其降至零时释放它。
drm_framebuffer_reference —增加fb引用
void fsfuncdrm_framebuffer_reference ( fb);
struct drm_framebuffer * fb;
fb
帧缓冲区
drm_framebuffer_unregister_private —查找IDR中注销私有fb
void fsfuncdrm_framebuffer_unregister_private ( fb);
struct drm_framebuffer * fb;
fb
要取消注册的fb
驱动程序需要在清理驱动程序私有的帧缓冲区时调用它,例如那些用于fbdev的帧缓冲区。请注意,调用者必须持有自己的引用,即对象不能通过这个调用被销毁(因为它会导致锁定反转)。
drm_framebuffer_cleanup —删除一个帧缓冲对象
void fsfuncdrm_framebuffer_cleanup ( fb);
struct drm_framebuffer * fb;
fb
要删除帧缓冲区
清理对用户创建的帧缓冲区的引用。该功能旨在从驱动程序-> destroy回调中使用。
请注意,这个函数并没有将fb从活动usuage中移除——如果它仍然在任何地方使用,则会出现hilarity,因为用户空间可以在id上调用getfb并返回-EINVAL。显然,不关心驱动程序卸载时间。
同样,不会从查找idr中删除帧缓冲区-对于用户创建的帧缓冲区,这将在rmfb ioctl中发生。对于驱动程序专用对象(例如,对于fbdev),驱动程序需要显式调用drm_framebuffer_unregister_private。
drm_framebuffer_remove —删除和取消引用帧缓冲区对象
void fsfuncdrm_framebuffer_remove (fb);
struct drm_framebuffer * fb;
fb
删除帧缓冲区
在dev
的mode_config中扫描所有CRTC和平面。如果他们使用fb
,将其删除,并将其设置为NULL。然后删除对传入的帧缓冲区的引用。可能会占用模式设置锁。
请注意,如果调用方持有对帧缓冲区的最后一个引用,则此函数可优化清除操作。在这种情况下,还可以确保不使用模式集锁定。
drm_crtc_init —初始化新的CRTC对象
int fsfuncdrm_crtc_init ( dev,
crtc,
funcs);
struct drm_device * dev;
struct drm_crtc * crtc;
const struct drm_crtc_funcs * funcs;
dev
DRM设备
crtc
需要初始化的CRTC对象
funcs
新CRTC的回调函数
创建一个新对象作为驱动程序crtc对象的基础部分。
成功为零,失败为错误代码。
drm_crtc_cleanup —清理核心crtc用法
void fsfuncdrm_crtc_cleanup ( crtc);
struct drm_crtc * crtc;
crtc
进行清理的CRTC
此功能清除crtc
并从DRM模式设置核心中将其删除。请注意,该函数“不会 ”释放crtc结构本身,这是调用者的责任。
drm_mode_probed_add —将模式添加到连接器的探测模式列表
void fsfuncdrm_mode_probed_add ( connector,
mode);
struct drm_connector * connector;
struct drm_display_mode * mode;
connector
连接器新模式
mode
模式数据
添加mode
到connector
的模式列表以供以后使用。
drm_connector_init —初始化预分配的连接器
int fsfuncdrm_connector_init ( dev,
connector,
funcs,
connector_type);
struct drm_device * dev;
struct drm_connector * connector;
const struct drm_connector_funcs * funcs;
int connector_type;
dev
DRM设备
connector
初始化连接器
funcs
此连接器的回调
connector_type
用户可见的连接器类型
初始化预分配的连接器。连接器应细分为驱动程序连接器对象的一部分。
成功为零,失败为错误代码。
drm_connector_cleanup —清理一个初始化的连接器
void fsfuncdrm_connector_cleanup ( connector);
struct drm_connector * connector;
connector
要清理连接器
清理连接器,但不释放对象。
drm_plane_init —初始化一个新的平面对象
int fsfuncdrm_plane_init ( dev,
plane,
possible_crtcs,
funcs,
formats,
format_count,
priv);
struct drm_device * dev;
struct drm_plane * plane;
unsigned long possible_crtcs;
const struct drm_plane_funcs * funcs;
const uint32_t * formats;
uint32_t format_count;
bool priv;
dev
DRM设备
plane
平面对象初始化
possible_crtcs
可能的CRTC的位掩码
funcs
新plane的回调
formats
支持的格式数组(DRM_FORMAT_
*)
format_count
格式中的元素数
priv
plane是私人的(从用户空间隐藏)?
初始化一个新对象,该对象创建为驱动程序平面对象的基础部分。
成功为零,失败为错误代码。
drm_plane_cleanup —清理核心平面的使用
void fsfuncdrm_plane_cleanup ( plane);
struct drm_plane * plane;
plane
准备清理的plane
此功能清除plane
并从DRM模式设置核心中将其删除。请注意,该函数*不会*释放平面结构本身,这是调用者的责任。
drm_plane_force_disable —强制禁用plane
void fsfuncdrm_plane_force_disable ( plane);
struct drm_plane * plane;
plane
准备禁用plane
强制禁用plane。
在销毁平面的当前帧缓冲区以及恢复fbdev模式时使用。
drm_mode_create —创建新的显示模式
struct drm_display_mode * fsfuncdrm_mode_create ( dev);
struct drm_device * dev;
dev
DRM设备
创建一个新的drm_display_mode,给它一个ID,然后返回它。
成功时指向新模式的指针,错误时指向NULL。
drm_mode_destroy —删除模式
void fsfuncdrm_mode_destroy ( dev,
mode);
struct drm_device * dev;
struct drm_display_mode * mode;
dev
DRM设备
mode
准备删除的模式
释放mode
的唯一标识符,然后释放它。
drm_mode_create_dvi_i_properties —创建特定于DVI-I的连接器属性
int fsfuncdrm_mode_create_dvi_i_properties ( dev);
struct drm_device * dev;
dev
DRM设备
第一次建立DVI-I连接器时由驱动程序调用。
drm_mode_create_tv_properties —创建TV特定的连接器属性
int fsfuncdrm_mode_create_tv_properties ( dev,
num_modes,
modes[]);
struct drm_device * dev;
int num_modes;
char * modes[];
dev
DRM设备
num_modes
支持的不同TV格式(模式)的数量
modes[]
包含每种格式名称的字符串的指针数组
由驱动的TV初始化例程调用,此函数为给定设备创建TV特定的连接器属性。调用者负责分配格式名称列表,并将其传递给此例程。
drm_mode_create_scaling_mode_property-创建缩放模式属性
int fsfuncdrm_mode_create_scaling_mode_property ( dev);
struct drm_device * dev;
dev
DRM设备
第一次需要时由驱动程序调用,必须将其连接到所需的连接器。
drm_mode_create_dirty_info_property-创建脏属性
int fsfuncdrm_mode_create_dirty_info_property ( dev);
struct drm_device * dev;
dev
DRM设备
第一次需要时由驱动程序调用,必须将其连接到所需的连接器。
drm_mode_set_config_internal —辅助调用-> set_config函数
int fsfuncdrm_mode_set_config_internal (set);
struct drm_mode_set * set;
set
模式设置配置
这是一个小助手包装内部调用到->set_config驱动程序接口。
drm_format_num_planes —获取指定格式的平面数
int fsfuncdrm_format_num_planes (format);
uint32_t format;
format
像素格式(DRM_FORMAT_ *)
指定的像素格式使用的平面数。
drm_format_plane_cpp —确定每个像素的字节值
int fsfuncdrm_format_plane_cpp (format,
plane);
uint32_t format;
int plane;
format
像素格式(DRM_FORMAT_ *)
plane
平面索引
指定平面的每个像素的字节值。
drm_format_horz_chroma_subsampling —获取水平色度子采样因子
int fsfuncdrm_format_horz_chroma_subsampling (format);
uint32_t format;
format
像素格式(DRM_FORMAT_ *)
指定像素格式的水平色度子采样因子。
drm_format_vert_chroma_subsampling —获取垂直色度子采样因子
int fsfuncdrm_format_vert_chroma_subsampling (format);
uint32_t format;
format
像素格式(DRM_FORMAT_ *)
指定像素格式的垂直色度子采样因子。
drm_mode_config_init —初始化DRM mode_configuration结构
void fsfuncdrm_mode_config_init (dev);
struct drm_device * dev;
dev
DRM设备
初始化dev
的mode_config结构,用于跟踪的图形配置dev
。
由于这会初始化模式集锁定,因此无法进行锁定。因为这应该在单线程时初始化,所以没有问题,确保安全是驱动的问题。
drm_mode_config_cleanup-释放DRM mode_config信息
void fsfuncdrm_mode_config_cleanup (dev);
struct drm_device * dev;
dev
DRM设备
释放与此DRM设备关联的所有连接器和CRTC,然后释放帧缓冲区和关联的缓冲区对象。
请注意,由于驱动/设备拆卸/应该/在单线程时操作,,因此不需要锁定。确保这项操作正确是驱动的工作。
tongshi 清理所有悬空的用户缓冲区对象
驱动程序提供DRM API实现CRTC,编码器和连接器的功能。它们(DRM_API)由DRM核心和ioctl处理程序调用以处理设备状态更改和配置请求。由于实现这些功能通常需要特定于驱动程序的逻辑,因此可以使用中间层帮助程序功能来避免重复样板代码。
DRM核心包含一个中间层实现。中间层提供了几个CRTC,编码器和连接器功能的实现(从中间层的顶部调用),这些功能可预处理请求并调用驱动程序提供的较低级功能(在中间层的底部) 。例如,该 drm_crtc_helper_set_config
函数可用于填充struct drm_crtc_funcs 的set_config
字段。调用时,它将把set_config
操作拆分为更小,更简单的操作,并调用驱动程序进行处理。
要使用中间层,驱动程序调用drm_crtc_helper_add
, drm_encoder_helper_add
以及 drm_connector_helper_add
功能安装他们中间层底部的操作处理,并填写 drm_crtc_funcs, drm_encoder_funcs和 drm_connector_funcs结构的指针到中间层顶API函数。最好在注册相应的KMS对象之后立即安装中间层底部操作处理程序。
中间层未在CRTC,编码器和连接器操作之间划分。要使用它,驱动程序必须为所有三个KMS实体提供底层功能。
int drm_crtc_helper_set_config(struct drm_mode_set *set);
该drm_crtc_helper_set_config
辅助函数是一个CRTC set_config
的实现。它首先尝试通过调用连接器best_encoder
帮助程序,来找到每个连接器的最佳编码器。
找到合适的编码器后,帮助函数将调用mode_fixup
编码器和CRTC帮助操作来调整请求的模式;或者完全拒绝它,在这种情况下,错误将返回给应用程序。如果模式调整后的新配置与当前配置相同,则辅助功能将返回而不执行任何其他操作。
如果调整后的模式与当前模式相同,但需要对帧缓冲区进行更改,则该 drm_crtc_helper_set_config
函数将调用CRTC mode_set_base
帮助操作。如果从当前模式的调整模式的不同,或者如果 mode_set_base
辅助操作未提供时,则辅助函数通过调用prepare、mode_set和commit CRTC和编码器帮助操作来执行完整的模式集序列。
void drm_helper_connector_dpms(struct drm_connector *connector, int mode);
该drm_helper_connector_dpms
辅助函数是一个连接器dpms实现,它跟踪连接器的电源状态。要使用该功能,驱动程序必须为CRTC和编码器提供dpms
辅助函数,以将DPMS状态应用于设备。
中间层不跟踪CRTC和编码器的电源状态。dpms
辅助操作因此可以被相同于当前活动的模式的模式调用。
int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
uint32_t maxX, uint32_t maxY);
该drm_helper_probe_single_connector_modes
辅助函数是一个连接器fill_modes
实现,它更新连接器的连接状态,然后通过调用连接器get_modes助手操作检索模式列表。
该功能会滤除大于max_width
和max_height
指定的模式 。然后,它调用连接器 帮助程序操作mode_valid
为所探测列表中的每种模式,以检查该模式是否对连接器有效。
bool (*mode_fixup)(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
让CRTC调整请求的模式或完全拒绝它。如果模式被接受(可能在调整之后),则此操作返回true;如果模式被拒绝,则返回false。
如果该mode_fixup
操作无法合理使用,则应拒绝该模式。在这种情况下,“合理”的定义目前是模糊的。一种可能的行为是,当将固定模式面板与能够缩放的硬件一起使用时,将调整后的模式设置为面板定时。另一行为是接受任何输入模式并将其调整为硬件支持的最接近模式(FIXME:这需要澄清)。
int (*mode_set_base)(struct drm_crtc *crtc, int x, int y,
struct drm_framebuffer *old_fb)
将当前帧缓冲区(存储在crtc->fb
中)上的CRTC 移到位置(x,y)。帧缓冲区,x位置或y位置中的任何一个都可能已被修改。
此辅助操作是可选的。如果未提供,该 drm_crtc_helper_set_config
功能将退回到mode_set
帮助程序操作。
FIXME:为什么x和y作为参数传递,因为可以通过crtc->x
和 访问它们crtc->y
?
void (*prepare)(struct drm_crtc *crtc);
准备CRTC以进行模式设置。验证请求的模式后,将调用此操作。驱动程序使用它来执行设置新模式之前所需的设备特定操作。
int (*mode_set)(struct drm_crtc *crtc, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode, int x, int y,
struct drm_framebuffer *old_fb);
设置新的模式,位置和帧缓冲区。根据设备要求,该模式可以由驱动程序在内部存储并在commit
操作中应用,或立即编程到硬件。
如果mode_set
成功,则操作返回0;如果发生错误,则返回负错误代码。
void (*commit)(struct drm_crtc *crtc);
提交模式。设置新模式后将调用此操作。返回时,设备必须使用新模式并可以完全操作。
bool (*mode_fixup)(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
让编码器调整请求的模式或完全拒绝它。如果模式被接受(可能在调整之后),则此操作返回true;如果模式被拒绝,则返回false。有关允许的调整的说明,请参见 mode_fixup CRTC辅助操作。
void (*prepare)(struct drm_encoder *encoder);
准备编码器以进行模式设置。验证请求的模式后,将调用此操作。驱动程序使用它来执行设置新模式之前所需的设备特定操作。
void (*mode_set)(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
设置新模式。根据设备要求,该模式可以由驱动程序在内部存储并在 commit
操作中应用,或立即编程到硬件。
void (*commit)(struct drm_encoder *encoder);
提交模式。设置新模式后将调用此操作。返回时,设备必须使用新模式并可以完全操作。
struct drm_encoder *(*best_encoder)(struct drm_connector *connector);
返回一个指向此连接器的最佳编码器的指针。连接器1:1映射到编码器的设备只需返回到关联的编码器指针即可。此操作是强制性的。
int (*get_modes)(struct drm_connector *connector);
使用drm_add_edid_modes解析EDID数据,或者直接为每个支持的模式调用drm_mode_probed_add,以填充连接器的probed_modes列表,并返回它已检测到的模式数目。此操作是强制性的。
手动添加模式时,驱动程序会通过调用 drm_mode_create
来创建每个模式,必须填写以下字段。
__u32 type;
模式类型位掩码,组合
DRM_MODE_TYPE_BUILTIN
不曾用过?
DRM_MODE_TYPE_CLOCK_C
不曾用过?
DRM_MODE_TYPE_CRTC_C
不曾用过?
DRM_MODE_TYPE_PREFERRED-连接器的首选模式
不曾用过?
DRM_MODE_TYPE_DEFAULT
不曾用过?
DRM_MODE_TYPE_USERDEF
不曾用过?
DRM_MODE_TYPE_DRIVER
该模式已由驱动程序创建(与用户创建的模式相反)。
驱动程序必须为他们创建的所有模式设置DRM_MODE_TYPE_DRIVER位,并为首选模式设置DRM_MODE_TYPE_PREFERRED位。
__u32 clock;
像素时钟频率(kHz)
__u16 hdisplay, hsync_start, hsync_end, htotal;
__u16 vdisplay, vsync_start, vsync_end, vtotal;
水平和垂直时序信息
Active Front Sync Back
Region Porch Porch
<-----------------------><----------------><-------------><-------------->
//|
// |
// |.................. ................
_______________
<----- [hv]display ----->
<------------- [hv]sync_start ------------>
<--------------------- [hv]sync_end --------------------->
<-------------------------------- [hv]total ----------------------------->
__u16 hskew;
__u16 vscan;
未知
__u32 flags;
模式标志,组合包含
DRM_MODE_FLAG_PHSYNC
水平同步为高电平有效
DRM_MODE_FLAG_NHSYNC
水平同步为低电平有效
DRM_MODE_FLAG_PVSYNC
垂直同步高电平有效
DRM_MODE_FLAG_NVSYNC
垂直同步为低电平有效
DRM_MODE_FLAG_INTERLACE
模式隔行
DRM_MODE_FLAG_DBLSCAN
模式使用双重扫描
DRM_MODE_FLAG_CSYNC
模式使用复合同步
DRM_MODE_FLAG_PCSYNC
复合同步高电平有效
DRM_MODE_FLAG_NCSYNC
复合同步低电平有效
DRM_MODE_FLAG_HSKEW
提供hskew(未使用?)
DRM_MODE_FLAG_BCAST
不曾用过?
DRM_MODE_FLAG_PIXMUX
不曾用过?
DRM_MODE_FLAG_DBLCLK
不曾用过?
DRM_MODE_FLAG_CLKDIV2
?
请注意,如果连接器的interlace_allowed
或 doublescan_allowed
字段设置为0 ,则标记为INTERLACE或DBLSCAN标志的模式将被drm_helper_probe_single_connector_modes
滤除 。
char name[DRM_DISPLAY_MODE_LEN];
模式名称。填充相应的字段后,驱动程序必须调用drm_mode_set_name来填充来自hdisplay、vdisplay和interlace标志的模式名,然后填充相应的字段。
该vrefresh
值由drm_helper_probe_single_connector_modes
计算 。
解析EDID数据时,drm_add_edid_modes
填写连接器display_info
width_mm
和height_mm
字段。手动创建模式时,如果尚未设置字段,则get_modes
辅助操作必须设置 display_info
width_mm
和height_mm
字段(例如,初始化时在将固定大小的面板连接到连接器)。模式width_mm
和height_mm
字段仅在EDID解析期间在内部使用,在手动创建模式时不应设置。
int (*mode_valid)(struct drm_connector *connector,
struct drm_display_mode *mode);
验证模式对于连接器是否有效。对于支持的模式,返回MODE_OK;对于不支持的模式,返回枚举drm_mode_status值(MODE_ *)之一。此操作是强制性的。
由于模式拒绝原因目前不用于立即删除不受支持的模式,因此实现可以返回MODE_BAD,而不管模式无效的确切原因是什么。
注意,mode_valid helper操作只对设备检测到的模式调用,而不会对用户通过CRTC set_config操作设置的模式调用。
drm_helper_move_panel_connectors_to_head —将面板移到连接器列表的最前面
void fsfuncdrm_helper_move_panel_connectors_to_head (dev);
struct drm_device * dev;
dev
进行操作的drm设备
某些用户空间假定第一个连接的连接器是主显示屏,应该在其中显示例如登录屏幕。对于笔记本电脑,这应该是主面板。使用此功能可将所有(eDP / LVDS)面板排序到连接器列表的最前面,而不必费力地尝试按正确的顺序对其进行初始化。
drm_helper_probe_single_connector_modes —获取完整的显示模式集
int fsfuncdrm_helper_probe_single_connector_modes (connector,
maxX,
maxY);
struct drm_connector * connector;
uint32_t maxX;
uint32_t maxY;
connector
需要探测的连接器
maxX
模式的最大宽度
maxY
模式的最大高度
调用者必须持有模式配置锁。
基于connector
实现的帮助程序回调,尝试检测所有有效模式。模式将首先被添加到连接器的probed_modes列表,然后剔除不符合的(基于有效性和maxX
,maxY
参数),其余投入正常模式列表。
打算用作->probe
connector
回调的通用实现,为用crtc辅助函数进行输出模式过滤和检测的驱动程序所使用 。
在connector
上找到的模式数量。
drm_helper_encoder_in_use —检查是否正在使用给定的编码器
bool fsfuncdrm_helper_encoder_in_use ( encoder);
struct drm_encoder * encoder;
encoder
需要检测的编码器
调用者必须持有模式配置锁。
遍历encoders
对应的
DRM设备的mode_config,看看它是否正在使用中。
如果encoder
属于mode_config,则为true,否则为false。
drm_helper_crtc_in_use —检查给定的CRTC是否在mode_config中
bool fsfuncdrm_helper_crtc_in_use ( crtc);
struct drm_crtc * crtc;
crtc
需要检查的CRTC
调用者必须持有模式配置锁。
遍历crtc对应的
DRM设备的mode_config,看看它是否正在使用中。
如果crtc
属于mode_config,则为true,否则为false。
drm_helper_disable_unused_functions —禁用未使用的对象
void fsfuncdrm_helper_disable_unused_functions (dev);
struct drm_device * dev;
dev
对应的DRM
调用者必须持有模式配置锁。
如果连接器或CRTC不属于dev的
mode_config的一部分,则可以通过调用其dpms函数将其关闭,以将其关闭。
drm_crtc_helper_set_mode —设置模式的内部辅助
bool fsfuncdrm_crtc_helper_set_mode ( crtc,
mode,
x,
y,
old_fb);
struct drm_crtc * crtc;
struct drm_display_mode * mode;
int x;
int y;
struct drm_framebuffer * old_fb;
crtc
需要处理的CRTC
mode
需要使用的mode
x
进入surface的水平偏移
y
进入surface的垂直偏移
old_fb
旧的帧缓冲区,用于清理
调用者必须持有模式配置锁。
尝试crtc
上设置mode
。crtc
在尝试设置模式之前,这是一个内部辅助功能,驱动程序可以用来更新属性,要求在新的配置中禁用和重新启用整个输出管道。例如,用于更改是否在hdmi链接上启用了音频,或用于更改面板fitter或抖动属性。drm_crtc_helper_set_config
辅助函数也调用它 来驱动模式设置序列。
如果成功设置了模式,则为true,否则为false。
drm_crtc_helper_set_config-从用户空间设置新配置
int fsfuncdrm_crtc_helper_set_config ( set);
struct drm_mode_set * set;
set
模式设置配置
调用者必须持有模式配置锁。
在set
中设置由上层提供的新配置(来自用户空间的ioctl调用或内部的fbdev支持代码,例如fbdev支持代码),并启用他。这是驱动程序的主要辅助函数,它使用crtc辅助函数实现内核模式设置,这些驱动程序使用crtc帮助程序功能以及各种-> prepare
,-> modeset
和-> commit
helper回调来实现内核模式设置。
成功返回0,失败返回-ERRNO。
drm_helper_connector_dpms —连接器dpms辅助实现
void fsfuncdrm_helper_connector_dpms ( connector,
mode);
struct drm_connector * connector;
int mode;
connector
受控制的连接器
mode
DPMS模式
这是crtc辅助程序框架提供的用于实现DPMS连接器属性的主要帮助程序功能。它为输出网格中的所有编码器和crtcs计算新的所需DPMS状态,并适当地调用驱动程序提供的->dpms
回调。
fb帮助程序功能可用于在drm内核模式设置驱动程序之上提供fbdev。它们可以独立于许多驱动程序用来实现内核模式设置接口的crtc helper函数来独立使用。
初始化完成需要有三个步骤drm_fb_helper_init
, drm_fb_helper_single_add_all_connectors
和drm_fb_helper_initial_config
。要求比默认行为更高的驱动程序可以使用自己的代码覆盖第二步。拆卸通过drm_fb_helper_fini
完成。
在运行时,驱动程序应通过从其-> lastclose回调中进行调用drm_fb_helper_restore_fbdev_mode
来还原fbdev控制台 。他们还应该通过调用drm_fb_helper_hotplug_event
来通知fb辅助程序有关输出配置更新的信息。为了更轻松地与drm_crtc_helper.c中的输出轮询代码集成,模式集代码被提供一个-> output_poll_changed回调。
fb帮助程序库导出的所有其他函数均可用于由驱动程序实现fbdev驱动程序接口。
drm_fb_helper_single_add_all_connectors —将所有连接器添加到fbdev仿真助手
int fsfuncdrm_fb_helper_single_add_all_connectors (fb_helper);
struct drm_fb_helper * fb_helper;
fb_helper
用drm_fb_helper_init初始化的fbdev
此函数将添加所有可用的连接器,以与给定的fb_helper一起使用。这是一个单独的步骤,允许驱动程序自由地将连接器分配给fbdev,例如,如果某些连接器保留用于特殊目的或不足以用于fbcon。
由于这是发布fbdev之前的初始设置的一部分,因此不需要锁定。
drm_fb_helper_debug_enter —-> fb_debug_enter的实现
int fsfunc drm_fb_helper_debug_enter (info);
struct fb_info * info;
info
fbdev由辅助函数注册
drm_fb_helper_debug_leave —-> fb_debug_leave的实现
int fsfunc drm_fb_helper_debug_leave (info);
struct fb_info * info;
info
fbdev由辅助函数注册
drm_fb_helper_restore_fbdev_mode —恢复fbdev配置
bool fsfunc drm_fb_helper_restore_fbdev_mode (fb_helper);
struct drm_fb_helper * fb_helper;
fb_helper
fbcon(fbconfig?)恢复
使用此帮助程序在kms顶部实现fbcon时,应从驱动程序的drm-> lastclose回调中调用此函数。这样可以确保当X(x11,显示的上层server)死了时,用户不会看到黑屏。
drm_fb_helper_blank —-> fb_blank的实现
int fsfunc drm_fb_helper_blank (blank,
info);
int blank;
struct fb_info * info;
blank
所需的消隐状态
info
fbdev由辅助函数注册
drm_fb_helper_init —初始化一个drm_fb_helper结构
int fsfunc drm_fb_helper_init ( dev,
fb_helper,
crtc_count,
max_conn_count);
struct drm_device * dev;
struct drm_fb_helper * fb_helper;
int crtc_count;
int max_conn_count;
dev
DRM设备
fb_helper
驱动程序分配的fbdev助手结构进行初始化
crtc_count
此fbdev仿真中支持的crtcs的最大数量
max_conn_count
最大连接器数
这将使用给定的限制为fbdev帮助程序分配结构。请注意,这还不会(通过驱动程序接口)接触硬件,也不会注册fbdev。 这仅在drm_fb_helper_initial_config完成,
是为了允许驱动程序对确切的初始化顺序进行更多控制。
驱动程序必须在调用drm_fb_helper_initial_config
之前设置fb_helper-> funcs 。
如果一切正常,则为零,否则为非零。
drm_fb_helper_setcmap —-> fb_setcmap的实现
int fsfuncdrm_fb_helper_setcmap ( cmap,
info);
struct fb_cmap * cmap;
struct fb_info * info;
cmap
要设置的cmap
info
fbdev由辅助函数注册
drm_fb_helper_check_var —-> fb_check_var的实现
int fsfuncdrm_fb_helper_check_var ( var,
info);
struct fb_var_screeninfo * var;
struct fb_info * info;
var
屏幕信息检查
info
fbdev由辅助函数注册
drm_fb_helper_set_par —-> fb_set_par的实现
int fsfuncdrm_fb_helper_set_par ( info);
struct fb_info * info;
info
fbdev由辅助函数注册
这将使fbcon进行模式初始化,并在初始化时由fbdev内核在注册驱动程序时进行调用,然后再通过hotplug回调进行调用。
drm_fb_helper_pan_display —-> fb_pan_display的实现
int fsfuncdrm_fb_helper_pan_display ( var,
info);
struct fb_var_screeninfo * var;
struct fb_info * info;
var
更新的屏幕信息
info
fbdev由助手注册
drm_fb_helper_fill_fix —初始化固定的fbdev信息
void fsfuncdrm_fb_helper_fill_fix ( info,
pitch,
depth);
struct fb_info * info;
uint32_t pitch;
uint32_t depth;
info
fbdev由助手注册
pitch
所需pitch
depth
所需深度
帮助程序可填充固定的fbdev信息,这些信息对于非加速的fbdev仿真非常有用。支持加有其他约束的加速方法的驱动程序需要设置自己的限制。
驱动程序应从其-> fb_probe回调中调用此代码(或等效的安装代码)。
drm_fb_helper_fill_var —初始化变量fbdev信息
void fsfuncdrm_fb_helper_fill_var ( info,
fb_helper,
fb_width,
fb_height);
struct fb_info * info;
struct drm_fb_helper * fb_helper;
uint32_t fb_width;
uint32_t fb_height;
info
fbdev实例设置
fb_helper
fb帮助程序实例用作模板
fb_width
所需的fb宽度
fb_height
所需的fb高度
根据给定的fb帮助程序实例和fb_helper-> fb中分配的drm帧缓冲区设置变量fbdev元信息。
在分配了fbdev后备存储帧缓冲区之后,驱动程序应从其-> fb_probe回调中调用此代码(或等效的安装代码)。
drm_fb_helper_initial_config —设置合理的初始连接器配置
bool fsfuncdrm_fb_helper_initial_config ( fb_helper,
bpp_sel);
struct drm_fb_helper * fb_helper;
int bpp_sel;
fb_helper
fb_helper设备结构
bpp_sel
用于帧缓冲区配置的bpp值
扫描CRTC和连接器,并尝试将初始设置放在一起。目前,这是所有源头的克隆配置,其中有一个新的framebuffer对象作为后备存储。
请注意,这还会注册fbdev,因此允许用户空间通过fbdev接口调用驱动程序。
该函数将调用-> fb_probe回调,以使驱动程序分配和初始化fbdev信息结构以及用于支持fbdev的drm帧缓冲区。drm_fb_helper_fill_var
和 drm_fb_helper_fill_fix
作为帮助程序来设置fbdev info结构的简单默认值。
如果一切正常,则为零,否则为非零。
drm_fb_helper_hotplug_event —通过探测附加到fb的所有输出来响应热插拔通知
int fsfuncdrm_fb_helper_hotplug_event ( fb_helper);
struct drm_fb_helper * fb_helper;
fb_helper
drm_fb_helper
通知输出配置的更改后,扫描连接到fb_helper的连接器,并尝试将设置汇总在一起。
在运行时调用,使用模式配置锁可以检查/更改模式集配置。必须从进程上下文运行(这通常意味着输出轮询工作或从驱动程序的热插拔中断启动的工作项)。
注意,驱动程序必须确保这仅是fb已完全设置后的唯一调用,即在调用drm_fb_helper_initial_config之后。
成功时返回0,否则返回非零错误代码。
struct drm_fb_helper_funcs — fbdev仿真库的驱动程序回调
struct drm_fb_helper_funcs {
void (* gamma_set) (struct drm_crtc *crtc, u16 red, u16 green,u16 blue, int regno);
void (* gamma_get) (struct drm_crtc *crtc, u16 *red, u16 *green,u16 *blue, int regno);
int (* fb_probe) (struct drm_fb_helper *helper,struct drm_fb_helper_surface_size *sizes);
bool (* initial_config) (struct drm_fb_helper *fb_helper,struct drm_fb_helper_crtc **crtcs,struct drm_display_mode **modes,bool *enabled, int width, int height);
};
gamma_set
在给定的crtc上设置给定的gamma lut寄存器。
gamma_get
读取给定crtc上给gamma lut寄存器,该寄存器用于在强制恢复fbdev(例如kdbg)时保存当前lut。
fb_probe
驱动程序回调以分配和初始化fbdev信息结构。此外,它还需要分配用于支持fbdev的drm帧缓冲区。
initial_config
设置初始fbdev显示配置
fbdev仿真帮助程序库使用的驱动程序回调。
这些功能包含各种抽象级别的一些通用逻辑和帮助程序,用于处理Display Port接收器设备和相关内容,例如DP辅助通道传输,在DP辅助通道上读取EDID,解码某些DPCD块,...
struct i2c_algo_dp_aux_data —基于dp aux算法的i2c的驱动程序接口结构
struct i2c_algo_dp_aux_data {
bool running;
u16 address;
int (* aux_ch) (struct i2c_adapter *adapter,int mode, uint8_t write_byte,uint8_t *read_byte);
};
running
由算法设置的值,指示i2c是否正在进行或i2c总线是否处于静态
address
当前正在进行传输的i2c目标地址
aux_ch
驱动程序回调以传输i2c有效负载的单个字节
i2c_dp_aux_add_bus —使用辅助通道帮助程序注册i2c适配器
int fsfunci2c_dp_aux_add_bus ( adapter);
struct i2c_adapter * adapter;
adapter
i2c适配器进行注册
这将注册一个使用dp aux通道作为传输基础的i2c适配器。驱动程序需要填写i2c_algo_dp_aux_data结构并将其存储在algo_data成员的adapter
参数中。i2c over dp aux算法将使用它来驱动硬件。
成功时为0,失败时为-ERRNO。
drm_edid_is_valid —完整性检查EDID数据
bool fsfuncdrm_edid_is_valid ( edid);
struct edid * edid;
edid
EDID数据
完整检查整个EDID记录(包括扩展名)
drm_probe_ddc —
bool fsfuncdrm_probe_ddc ( adapter);
struct i2c_adapter * adapter;
adapter
-未描述-
\ param适配器:i2c设备适配器\成功返回1
drm_get_edid —获取EDID数据(如果有)
struct edid * fsfuncdrm_get_edid ( connector,
adapter);
struct drm_connector * connector;
struct i2c_adapter * adapter;
connector
我们正在探索的连接器
adapter
用于DDC的i2c适配器
如有可能,拨入给定的i2c通道以获取EDID数据。如果找到,将其连接到连接器。
返回edid数据;如果找不到,则返回NULL。
drm_match_cea_mode —查找与给定模式匹配的CEA模式
u8 fsfuncdrm_match_cea_mode ( to_match);
const struct drm_display_mode * to_match;
to_match
显示模式
返回该模式的CEA视频ID(VIC);如果不是CEA-861模式,则返回0。
drm_edid_to_eld —从EDID生成ELD
void fsfuncdrm_edid_to_eld ( connector,
edid);
struct drm_connector * connector;
struct edid * edid;
connector
对应于HDMI / DP接收器的接口
edid
需要解析的EDID
填充ELD(类似于EDID的数据)缓冲区以传递给音频驱动程序。
-Conn_Type-HDCP-Port_ID
drm_edid_to_sad —从EDID中提取SAD
int fsfuncdrm_edid_to_sad ( edid,
sads);
struct edid * edid;
struct cea_sad ** sads;
edid
需要解析的EDID
sads
将设置为提取到的SAD的指针
查找CEA EDID块并从中提取SAD(短音频描述符)。
返回的指针需要kfreed
返回找到的SAD数或错误时返回负数。
drm_edid_to_speaker_allocation —从EDID中提取扬声器分配的数据块
int fsfuncdrm_edid_to_speaker_allocation ( edid,
sadb);
struct edid * edid;
u8 ** sadb;
edid
需要解析的EDID
sadb
指向扬声器模块的指针
查找CEA EDID块,并从中提取扬声器分配数据块。
返回的指针需要kfreed
返回找到的扬声器分配块数或错误时返回负数。
drm_av_sync_delay — HDMI / DP接收器音频-视频同步延迟(以毫秒为单位)
int fsfuncdrm_av_sync_delay ( connector,
mode);
struct drm_connector * connector;
struct drm_display_mode * mode;
connector
与HDMI / DP接收器关联的连接器
mode
显示模式
drm_select_eld —从多个HDMI / DP接收器中选择一个ELD
struct drm_connector * fsfuncdrm_select_eld ( encoder,
mode);
struct drm_encoder * encoder;
struct drm_display_mode * mode;
encoder
编码器刚刚更改了显示模式
mode
调整后的显示模式
一个编码器可能与多个HDMI / DP接收器关联。现在,已对该策略进行了硬编码,以仅使用第一个HDMI / DP接收器的ELD。
drm_detect_hdmi_monitor —检测显示器是否为hdmi。
bool fsfuncdrm_detect_hdmi_monitor ( edid);
struct edid * edid;
edid
显示器的EDID信息
根据CEA-861-B解析CEA扩展名。如果HDMI,则返回true,否则返回false,否则返回未知。
drm_detect_monitor_audio —检查监视器音频功能
bool fsfuncdrm_detect_monitor_audio ( edid);
struct edid * edid;
edid
显示器的EDID信息
显示器应具有CEA扩展块。如果显示器具有“基本音频”,但没有CEA音频块,则仅是“基本音频”。如果有任何音频扩展块和支持的音频格式,则即使在EDID中未定义“基本音频”,也至少要假定“基本音频”支持。
drm_rgb_quant_range_selectable — RGB量化范围可以选择吗?
bool fsfuncdrm_rgb_quant_range_selectable ( edid);
struct edid * edid;
edid
显示器的EDID信息
检查显示器是否报告支持的RGB量化范围选择。然后可以使用AVI信息帧通知监视器使用了哪个量化范围(完整或有限)。
drm_add_edid_modes —从EDID数据添加模式(如果有)
int fsfuncdrm_add_edid_modes ( connector,
edid);
struct drm_connector * connector;
struct edid * edid;
connector
我们正在探索的连接器
edid
显示器的EDID信息
将指定的模式添加到连接器的模式列表中。
返回添加的模式数量,如果找不到则返回0。
drm_add_modes_noedid —为没有EDID的连接器添加模式
int fsfuncdrm_add_modes_noedid ( connector,
hdisplay,
vdisplay);
struct drm_connector * connector;
int hdisplay;
int vdisplay;
connector
我们正在探索的连接器
hdisplay
水平显示极限
vdisplay
垂直显示限制
将指定的模式添加到连接器的模式列表中。仅当hdisplay / vdisplay不超过给定限制时,才会添加它。
返回添加的模式数量,如果找不到则返回0。
drm_hdmi_avi_infoframe_from_display_mode —用来自DRM显示模式的数据填充HDMI AVI信息帧
int fsfuncdrm_hdmi_avi_infoframe_from_display_mode ( frame,
mode);
struct hdmi_avi_infoframe * frame;
const struct drm_display_mode * mode;
frame
HDMI AVI信息框
mode
DRM显示模式
成功返回0,失败返回负错误代码。
drm_hdmi_vendor_infoframe_from_display_mode —用来自DRM显示模式的数据填充HDMI信息帧
int fsfuncdrm_hdmi_vendor_infoframe_from_display_mode ( frame,
mode);
struct hdmi_vendor_infoframe * frame;
const struct drm_display_mode * mode;
frame
HDMI供应商信息框
mode
DRM显示模式
请注意,仅在使用4k或立体3D模式时才需要发送HDMI供应商信息帧。因此,当提供任何其他模式作为输入时,此函数将返回-EINVAL,该错误可以安全地忽略。
成功返回0,失败返回负错误代码。
实用程序功能可帮助管理矩形区域以进行裁剪,缩放等计算。
struct drm_rect —二维矩形
struct drm_rect {
int x1;
int y1;
int x2;
int y2;
};
x1
水平起始坐标(含)
11
垂直起始坐标(含)
2倍
水平结束坐标(不包括)
22
垂直结束坐标(不包括)
drm_rect_adjust_size —调整矩形的大小
void fsfuncdrm_rect_adjust_size ( r,
dw,
dh);
struct drm_rect * r;
int dw;
int dh;
r
要调整的矩形
dw
水平调整
dh
垂直调整
改变矩形r
的大小由dw
在水平方向上,并且通过dh
在垂直方向上,同时保持r
的中心固定不动。
正dw
和dh
增加大小,负值减少它。
drm_rect_translate —平移矩形
void fsfuncdrm_rect_translate ( r,
dx,
dy);
struct drm_rect * r;
int dx;
int dy;
r
要翻译的矩形
dx
水平翻译
dy
垂直翻译
移动矩形r,dx
在水平方向上 ,dy
在垂直方向上移动矩形。
drm_rect_downscale —缩小矩形
void fsfuncdrm_rect_downscale ( r,
horz,
vert);
struct drm_rect * r;
int horz;
int vert;
r
要缩小的矩形
horz
水平缩小系数
vert
垂直缩小系数
矩形的坐标r
除以horz
和vert
。
drm_rect_width —确定矩形的宽度
int fsfuncdrm_rect_width ( r);
const struct drm_rect * r;
r
返回宽度的矩形
矩形的宽度。
drm_rect_height —确定矩形的高度
int fsfuncdrm_rect_width (r);
const struct drm_rect * r;
r
返回高度的矩形
矩形的高度。
drm_rect_visible —确定矩形是否可见
int fsfuncdrm_rect_height ( r);
const struct drm_rect * r;
r
返回可见性的矩形
true
如果矩形可见,false
否则。
drm_rect_equals —确定两个矩形是否相等
bool fsfuncdrm_rect_equals ( r1,
r2);
const struct drm_rect * r1;
const struct drm_rect * r2;
r1
第一个矩形
r2
第二个矩形
true
如果矩形相等,false
否则。
drm_rect_intersect —与两个矩形相交
bool fsfuncdrm_rect_intersect ( r1,
r2);
struct drm_rect * r1;
const struct drm_rect * r2;
r1
第一个矩形
r2
第二个矩形
计算矩形r1
和的交点r2
。 r1
将被相交处覆盖。
true
如果矩形r1
在操作后仍然可见, false
否则。
drm_rect_clip_scaled —执行缩放的剪辑操作
bool fsfuncdrm_rect_clip_scaled ( src,
dst,
clip,
hscale,
vscale);
struct drm_rect * src;
struct drm_rect * dst;
const struct drm_rect * clip;
int hscale;
int vscale;
src
源窗口矩形
dst
目标窗口矩形
clip
剪辑矩形
hscale
水平比例因子
vscale
垂直比例因子
用矩形clip
剪辑矩形dst
。裁剪矩形src
以相同数量乘以hscale
和vscale
。
true
如果矩形dst
在裁剪后仍然可见, false
否则
drm_rect_calc_hscale —计算水平缩放因子
int fsfuncdrm_rect_calc_hscale ( src,
dst,
min_hscale,
max_hscale);
const struct drm_rect * src;
const struct drm_rect * dst;
int min_hscale;
int max_hscale;
src
源窗口矩形
dst
目标窗口矩形
min_hscale
最小允许水平缩放比例
max_hscale
最大允许水平缩放比例
将水平缩放系数计算为(src
宽度)/(dst
宽度)。
水平缩放因子或超出范围的错误。
drm_rect_calc_vscale —计算垂直比例因子
int fsfuncdrm_rect_calc_vscale ( src,
dst,
min_vscale,
max_vscale);
const struct drm_rect * src;
const struct drm_rect * dst;
int min_vscale;
int max_vscale;
src
源窗口矩形
dst
目标窗口矩形
min_vscale
最小允许垂直缩放系数
max_vscale
最大允许垂直缩放比例
将垂直缩放系数计算为(src
高度)/(dst
高度)。
垂直比例因子或超出范围的错误值。
drm_rect_calc_hscale_relaxed —计算水平缩放因子
int fsfuncdrm_rect_calc_hscale_relaxed ( src,
dst,
min_hscale,
max_hscale);
struct drm_rect * src;
struct drm_rect * dst;
int min_hscale;
int max_hscale;
src
源窗口矩形
dst
目标窗口矩形
min_hscale
最小允许水平缩放比例
max_hscale
最大允许水平缩放比例
将水平缩放系数计算为(src
宽度)/(dst
宽度)。
如果计算出的比例因子小于min_vscale
,则减小矩形的高度以dst
进行补偿。
如果计算出的比例因子大于max_vscale
,则减小矩形的高度以src
进行补偿。
水平缩放因子。
drm_rect_calc_vscale_relaxed —计算垂直比例因子
int fsfuncdrm_rect_calc_vscale_relaxed ( src,
dst,
min_vscale,
max_vscale);
struct drm_rect * src;
struct drm_rect * dst;
int min_vscale;
int max_vscale;
src
源窗口矩形
dst
目标窗口矩形
min_vscale
最小允许垂直缩放系数
max_vscale
最大允许垂直缩放比例
将垂直缩放系数计算为(src
高度)/(dst
高度)。
如果计算出的比例因子小于min_vscale
,则减小矩形的高度以dst
进行补偿。
如果计算出的比例因子大于max_vscale
,则减小矩形的高度以src
进行补偿。
垂直比例因子。
drm_rect_debug_print —打印矩形信息
void fsfuncdrm_rect_debug_print ( r,
fixed_point);
const struct drm_rect * r;
bool fixed_point;
r
要打印的矩形
fixed_point
矩形为16.16定点格式
在flip / vblank之后,可以使工作排队以从工作队列上下文中运行。通常,这可以用于将帧缓冲区,鼠标bo等的取消引用推迟到vblank之后。这些API都是安全的(无锁),最多可同时有一个生产者和一个消费者。通过将排队的工作提交到单个工作队列来确保单用户方面。
struct drm_flip_work —翻转工作队列
struct drm_flip_work {
const char * name;
atomic_t pending;
atomic_t count;
drm_flip_func_t func;
struct work_struct worker;
};
name
调试名称
pending
排队但未提交的项目数
count
承诺项目数
func
为每个已提交项目调用回调函数
worker
调用func的工作队列对象
drm_flip_work_queue —工作队列
void fsfuncdrm_flip_work_queue ( work,
val);
struct drm_flip_work * work;
void * val;
work
翻转工作
val
要排队的值
队列工作,该工作随后将在drm_flip_work_commit
调用后在工作队列上运行(传递回drm_flip_func_t func)。
drm_flip_work_commit —提交排队的工作
void fsfuncdrm_flip_work_commit ( work,
wq);
struct drm_flip_work * work;
struct workqueue_struct * wq;
work
翻转工作
wq
在其上运行排队的工作的工作队列
触发先前排队的工作drm_flip_work_queue
以在工作队列上运行。典型的用法是(通过 drm_flip_work_queue
)在任何点(通过vblank irq和/或之前)对工作进行排队,然后从vblank irq提交排队的工作。
drm_flip_work_init —初始化翻转工作
int fsfuncdrm_flip_work_init ( work,
size,
name,
func);
struct drm_flip_work * work;
int size;
const char * name;
drm_flip_func_t func;
work
初始化的翻转工作
size
最大队列深度
name
调试名称
func
回调工作功能
初始化/分配用于翻转作业的资源
成功为零,失败为错误代码。
drm_flip_work_cleanup —清理翻转工作
void fsfuncdrm_flip_work_cleanup ( work);
struct drm_flip_work * work;
work
需要清理filp-work
销毁分配给filp-work的资源
vma-manager负责将依赖于驱动程序的任意内存区域映射到线性用户地址空间。它为调用方提供偏移量,然后可以在drm设备的address_space上使用它。注意不要重叠区域,适当调整它们的大小,并且不要因不一致的伪造vm_pgoff字段而混淆mm-core。驱动程序不应将其用于VMEM中的对象放置。该管理器仅应用于管理到线性用户空间VM的映射。
我们使用drm_mm作为后端来管理对象分配。但是它针对分配/释放调用(而非查找)进行了高度优化。因此,我们使用rb-tree[红黑树]来加速偏移量查找。
您不得在单个address_space上使用多个偏移量管理器。否则,mm-core将无法拆除内存映射,因为VM将不再是线性的。在这种情况下,请使用VM_NONLINEAR并实现您自己的偏移量管理器。
此偏移量管理器适用于基于页面的地址。也就是说,每个参数和返回码(除外drm_vma_node_offset_addr
)都以页数而不是字节数给出。这意味着对象大小和偏移量必须始终与页面对齐(通常)。如果要获取给定偏移量的有效的基于字节的用户空间地址,请参见drm_vma_node_offset_addr
。
除了偏移量管理,vma偏移量管理器还处理访问管理。对于每个允许访问给定节点的开放文件上下文,必须调用drm_vma_node_allow
。否则,mmap
对该节点偏移的开放文件的调用将失败并返回-EACCES。要再次撤消访问权限,请使用drm_vma_node_revoke
。但是,如果需要,调用者负责销毁已经存在的映射。
drm_vma_offset_manager_init —初始化新的偏移量管理器
void fsfuncdrm_vma_offset_manager_init ( mgr,
page_offset,
size);
struct drm_vma_offset_manager * mgr;
unsigned long page_offset;
unsigned long size;
mgr
管理对象
page_offset
可用存储区的偏移量(基于页面)
size
可用地址空间范围的大小(基于页面)
初始化一个新的偏移量管理器。可用于管理器的偏移量和区域大小以page_offset
和size
给出。两者单位都为页码,而不是字节。
从管理器添加/删除节点在内部被锁定,并且可以防止并发访问。但是,节点分配和销毁留给调用方。调用vma-manager时,必须始终保证引用了给定节点。
drm_vma_offset_manager_destroy —销毁偏移量管理器
void fsfuncdrm_vma_offset_manager_destroy ( mgr);
struct drm_vma_offset_manager * mgr;
mgr
管理对象
销毁先前通过drm_vma_offset_manager_init
创建的对象管理器 。在销毁管理器之前,调用者必须删除所有分配的节点。否则,drm_mm将拒绝释放请求的资源。
调用此函数后,不得再访问此管理器。
drm_vma_offset_lookup —在偏移空间中查找节点
struct drm_vma_offset_node * fsfuncdrm_vma_offset_lookup ( mgr,
start,
pages);
struct drm_vma_offset_manager * mgr;
unsigned long start;
unsigned long pages;
mgr
管理对象
start
对象的起始地址(基于页面)
pages
对象大小(基于页面)
查找具有给定起始地址和对象大小的节点。这将返回给定节点的_best_匹配项。也就是说,start
只要该节点指向整个有效区域(给定的页面数为pages
),就可以指向某个有效区域中的某个位置并返回给定的节点。
如果找不到合适的节点,则返回NULL。否则,将返回最佳匹配。调用者有责任确保在调用者访问节点之前不会破坏该节点。
drm_vma_offset_lookup_locked —在偏移空间中查找节点
struct drm_vma_offset_node * fsfuncdrm_vma_offset_lookup_locked ( mgr,
start,
pages);
struct drm_vma_offset_manager * mgr;
unsigned long start;
unsigned long pages;
mgr
管理对象
start
对象的起始地址(基于页面)
pages
对象大小(基于页面)
与drm_vma_offset_lookup
相同,但要求调用者手动锁定偏移量查找。请参阅drm_vma_offset_lock_lookup
示例。
如果找不到合适的节点,则返回NULL。否则,将返回最佳匹配。
drm_vma_offset_add —将偏移量节点添加到管理器
int fsfuncdrm_vma_offset_add ( mgr,
node,
pages);
struct drm_vma_offset_manager * mgr;
struct drm_vma_offset_node * node;
unsigned long pages;
mgr
管理对象
node
要添加的节点
pages
用户空间可见的分配大小(以页数为单位)
将节点添加到offset-manager。如果已添加节点,则不执行任何操作,并返回0。pages
是对象的大小(以页数为单位)。成功执行此调用后,您可以访问节点的偏移量,直到再次将其删除为止。
如果此调用失败,则可以安全地重试该操作或调用drm_vma_offset_remove
。但是,在这种情况下,不需要清理。
pages
不需要与要映射的基础内存对象大小相同。它仅限制了用户空间可以映射到其地址空间的大小。
成功时为0,失败时为负错误代码。
drm_vma_offset_remove —从管理器中删除偏移节点
void fsfuncdrm_vma_offset_remove ( mgr,
node);
struct drm_vma_offset_manager * mgr;
struct drm_vma_offset_node * node;
mgr
管理对象
node
要删除的节点
从偏移管理器中删除节点。如果之前未添加节点,则不会执行任何操作。此调用返回后,偏移量和大小将为0,直到drm_vma_offset_add
再次分配新的偏移量为止。辅助函数类似于 drm_vma_node_start
,drm_vma_node_offset_addr
如果没有分配偏移量,它将返回0。
drm_vma_node_allow-将打开文件添加到允许的用户列表中
int fsfuncdrm_vma_node_allow ( node,
filp);
struct drm_vma_offset_node * node;
struct file * filp;
node
修改节点
filp
开放文件以添加
添加filp
到此节点允许的打开文件列表。如果filp
已在此列表中,则引用计数将增加。
允许的用户列表保留在drm_vma_offset_add
和 drm_vma_offset_remove
调用之间。如果该节点当前未添加到任何偏移量管理器中,您甚至可以调用它。
在销毁节点之前,您必须删除所有打开文件的次数与添加它们的次数相同。否则,您将泄漏内存。
这是在内部禁止并发访问的。
成功时为0,内部失败时为负错误代码(内存不足)
drm_vma_node_revoke —从允许的用户列表中删除打开文件
void fsfuncdrm_vma_node_revoke ( node,
filp);
struct drm_vma_offset_node * node;
struct file * filp;
node
修改节点
filp
打开文件删除
减少filp
上允许的打开文件列表node
中的ref-count 。如果引用计数降至零,filp
则从列表中删除。你必须调用此一次,每drm_vma_node_allow
上filp
。
这是在内部禁止并发访问的。
如果filp
不在列表中,则什么也不做。
drm_vma_node_is_allowed —检查是否授予打开文件访问权限
bool fsfuncdrm_vma_node_is_allowed ( node,
filp);
struct drm_vma_offset_node * node;
struct file * filp;
node
要检查的节点
filp
打开文件进行检查
在当前node
搜索列表是否filp
在允许的打开文件列表中(请参阅参考资料drm_vma_node_allow
)。
这是在内部禁止并发访问的。
真的,如果filp
在清单上
drm_vma_offset_exact_lookup —按确切地址查找节点
struct drm_vma_offset_node * fsfuncdrm_vma_offset_exact_lookup ( mgr,
start,
pages);
struct drm_vma_offset_manager * mgr;
unsigned long start;
unsigned long pages;
mgr
管理对象
start
起始地址(基于页面,而非基于字节)
pages
对象大小(基于页面)
与drm_vma_offset_lookup
节点相同,但不允许向节点偏移。它仅返回具有给定起始地址的确切对象。
节点位于确切的起始地址start
。
drm_vma_offset_lock_lookup-锁定查找以供扩展私人使用
void fsfuncdrm_vma_offset_lock_lookup ( mgr);
struct drm_vma_offset_manager * mgr;
mgr
管理对象
锁定VMA Manager以进行扩展查找。_locked
按住此锁定仅允许* VMA函数调用。在通过drm_vma_offset_unlock_lookup
释放锁定之前,所有其他上下文都无法访问VMA 。
如果您需要在重新释放此锁之前引用drm_vma_offset_lookup_locked
返回的对象,请使用此方法 。
除扩展查找外,不得将此锁用于任何其他用途。按住此锁定时,不得调用任何其他VMA帮助器。
持有此锁,您处于原子上下文中!
drm_vma_offset_lock_lookup(mgr);
node = drm_vma_offset_lookup_locked(mgr);
if (node)
kref_get_unless_zero(container_of(node, sth, entr));
drm_vma_offset_unlock_lookup(mgr);
drm_vma_offset_unlock_lookup —解锁查找以供扩展私人使用
void fsfuncdrm_vma_offset_unlock_lookup ( mgr);
struct drm_vma_offset_manager * mgr;
mgr
管理对象
释放查找锁定。请参阅drm_vma_offset_lock_lookup
以获取更多信息。
drm_vma_node_reset —初始化或重置节点对象
void fsfuncdrm_vma_node_reset ( node);
struct drm_vma_offset_node * node;
node
要初始化或重置的节点
将节点重置为其初始状态。在与任何VMA偏移管理器一起使用之前,必须先调用它。
不得在已分配的节点上调用此方法,否则会泄漏内存。
drm_vma_node_start —返回基于页面的寻址的起始地址
unsigned long fsfuncdrm_vma_node_start ( node);
struct drm_vma_offset_node * node;
node
要检查的节点
返回给定节点的起始地址。可以用作VMA偏移管理器提供的线性VM空间中的偏移。请注意,这只能用于基于页面的寻址。如果您需要为用户空间映射提供适当的偏移量,则必须应用“ << PAGE_SHIFT ”或使用 drm_vma_node_offset_addr
辅助程序。
node
用于基于页面的寻址的 起始地址。如果节点未分配偏移量,则为0。
drm_vma_node_size —返回大小(基于页面)
unsigned long fsfuncdrm_vma_node_size ( node);
struct drm_vma_offset_node * node;
node
要检查的节点
以给定节点的页面数形式返回大小。这与传递给drm_vma_offset_add
的大小相同。如果没有为节点分配偏移量,则为0。
node
页数的 大小。如果节点未分配偏移量,则为0。
drm_vma_node_has_offset —检查是否将节点添加到偏移管理器
bool fsfuncdrm_vma_node_has_offset ( node);
struct drm_vma_offset_node * node;
node
要检查的节点
如果先前已为节点分配了偏移并将其添加到vma偏移管理器,则为true。
drm_vma_node_offset_addr —返回用户空间mmap的已清理偏移量
__u64 fsfuncdrm_vma_node_offset_addr ( node);
struct drm_vma_offset_node * node;
node
链接偏移节点
与相同,drm_vma_node_start
但返回地址作为有效偏移量,可在期间用于用户空间映射mmap
。不能在未链接的节点上调用它。
node
基于字节的寻址的 偏移量。如果节点未分配对象,则为0。
drm_vma_node_unmap —取消映射偏移量节点
void fsfuncdrm_vma_node_unmap ( node,
file_mapping);
struct drm_vma_offset_node * node;
struct address_space * file_mapping;
node
偏移节点
file_mapping
地址空间取消映射node
从
取消映射给定偏移节点的所有用户空间映射。映射必须与file_mapping
地址空间关联。如果不存在偏移量或地址空间无效,则不执行任何操作。
此呼叫已解锁。调用方必须保证drm_vma_offset_remove
不会在此节点上同时调用。
drm_vma_node_verify_access — TTM的访问验证帮助器
int fsfuncdrm_vma_node_verify_access ( node,
filp);
struct drm_vma_offset_node * node;
struct file * filp;
node
偏移节点
filp
打开文件
这将检查是否filp
授予对的访问权限node
。它与drm_vma_node_is_allowed
相同, 但很适合 TTM verify_access
回调的插入助手。
如果授予访问权限,则为0,否则为-EACCES。
驱动程序可能需要向应用程序公开除前几节所述之外的其他参数。KMS支持将属性附加到CRTC,连接器和平面,并提供用户空间API来列出,获取和设置属性值。
属性由唯一定义属性用途的名称标识,并存储关联的值。对于除blob属性以外的所有属性类型,该值为64位无符号整数。
KMS区分属性和属性实例。驱动程序首先创建属性,然后创建这些属性的各个实例并将其与对象关联。一个属性可以被实例化多次并与不同的对象关联。值存储在属性实例中,所有其他属性信息存储在属性中,并在属性的所有实例之间共享。
每个属性的创建类型都会影响KMS核心处理该属性的方式。支持的属性类型是:
DRM_MODE_PROP_RANGE
范围属性报告其最小和最大允许值。KMS核心将验证应用程序设置的值是否在该范围内。
DRM_MODE_PROP_ENUM
枚举属性采用从0到该属性定义的枚举值的数量的数字值减去1,并将自由格式的字符串名称与每个值相关联。应用程序可以检索已定义的值-名称对的列表,并使用数值来获取和设置属性实例值。
DRM_MODE_PROP_BITMASK
位掩码属性是枚举属性,它另外将所有枚举值限制在0..63范围内。位掩码属性实例值组合了该属性定义的一个或多个枚举位。
DRM_MODE_PROP_BLOB
Blob属性存储二进制Blob,没有任何格式限制。二进制Blob被创建为KMS独立对象,并且Blob属性实例值存储其关联的Blob对象的ID。
Blob属性仅用于连接器EDID属性,不能由驱动程序创建。
要创建属性驱动程序,请根据属性类型调用以下函数之一。所有属性创建函数均采用属性标志和名称以及特定于类型的参数。
struct drm_property *drm_property_create_range(struct drm_device *dev, int flags,
const char *name,
uint64_t min, uint64_t max);
用给定的最小值和最大值创建一个range属性。
struct drm_property *drm_property_create_enum(struct drm_device *dev, int flags,
const char *name,
const struct drm_prop_enum_list *props,
int num_values);
创建一个枚举的属性。该props
参数指向num_values
值-名称对的数组。
struct drm_property *drm_property_create_bitmask(struct drm_device *dev,
int flags, const char *name,
const struct drm_prop_enum_list *props,
int num_values);
创建一个位掩码属性。该props
参数指向num_values
值-名称对的数组。
可以另外将属性创建为不可变的,在这种情况下,它们对于应用程序将是只读的,但可以由驱动程序进行修改。要创建不可变属性,驱动程序必须在属性创建时设置DRM_MODE_PROP_IMMUTABLE标志。
如果在属性创建时没有值名称对数组可用于枚举或范围属性,驱动程序可以使用该drm_property_create
函数创建属性,并通过调用该drm_property_add_enum
函数手动添加枚举值名称对 。必须注意通过flags
参数正确指定属性类型。
创建属性后,驱动程序可以通过调用drm_object_attach_property
将属性实例附加到CRTC,连接器和平面对象 。该函数获取指向目标对象的指针,指向先前创建的属性的指针和初始实例值。
垂直消隐在图形渲染中起主要作用。为了实现无撕裂显示,用户必须将页面翻转和/或渲染与垂直消隐同步。DRM API提供了ioctl来执行与垂直消隐同步的页面翻转,并等待垂直消隐。
DRM内核处理大多数垂直消隐管理逻辑,其中包括滤除虚假中断,保留无竞争的消隐计数器,应对计数器环绕和复位以及保持使用计数。它依靠驱动程序生成垂直消隐中断,并可选地提供硬件垂直消隐计数器。驱动程序必须执行以下操作。
int (*enable_vblank) (struct drm_device *dev, int crtc);
void (*disable_vblank) (struct drm_device *dev, int crtc);
启用或禁用给定CRTC的垂直消隐中断。
u32 (*get_vblank_counter) (struct drm_device *dev, int crtc);
检索给定CRTC的垂直消隐计数器的值。如果硬件保持垂直消隐计数器,则应返回其值。否则,驱动程序可以使用 drm_vblank_count
助手功能来处理此操作。
驱动程序必须在load
操作中通过drm_vblank_init
调用来初始化垂直消隐处理核心 。该函数会将struct drm_device vblank_disable_allowed
字段设置 为0。这将使垂直消隐中断永久启用,直到第一个模式设置操作(其中vblank_disable_allowed
设置为1)为止。其背后的原因尚不清楚。调用drm_vblank_init后,
驱动程序可以将字段设置为1,以便从头开始动态管理垂直消隐中断。
垂直消隐中断可以由DRM内核或驱动程序本身启用(例如,处理页面翻转操作)。DRM内核维护垂直消隐使用计数,以确保在用户仍需要中断时不禁用这些中断。要增加使用次数,驱动请调用drm_vblank_get
。返回时,保证将使能垂直消隐中断。
要减少使用计数驱动程序,请调用 drm_vblank_put
。仅当使用计数降至零时,DRM内核才会通过调度计时器在延迟后禁用垂直消隐中断。可通过vblankoffdelay模块参数或drm_vblank_offdelay
全局变量访问该延迟,并以毫秒为单位表示。其默认值为5000毫秒。
当发生垂直消隐中断时,驱动程序只需要调用该 drm_handle_vblank
函数即可解决该中断。
必须通过drm_vblank_cleanup
在驱动程序 unload
操作处理程序中调用来释放 由drm_vblank_init
分配的资源。
int (*firstopen) (struct drm_device *);
void (*lastclose) (struct drm_device *);
int (*open) (struct drm_device *, struct drm_file *);
void (*preclose) (struct drm_device *, struct drm_file *);
void (*postclose) (struct drm_device *, struct drm_file *);
firstopen
仅当应用程序打开没有其他打开的文件句柄的设备时,DRM内核才会为旧版UMS(用户模式设置)驱动程序调用 该方法。UMS驱动程序可以实现它以获取设备资源。KMS驱动程序不能使用该方法,而必须在该load
方法中获取资源。
同样lastclose
,对于UMS和KMS驱动程序,当最后一个在设备上打开了打开文件句柄的应用程序关闭时,将调用该方法。此外,在模块卸载时或对于可热插拔的设备,在拔出设备时也调用该方法。在firstopen
和 lastclose
这样的调用是不平衡的。
每次由应用程序打开设备时都会调用open
方法。驱动程序可以使用此方法分配每个文件的私有数据,并将其存储在struct drm_file driver_priv
字段中。请注意,该open
方法是在firstopen
之前调用的。
关闭操作分为preclose
和 postclose
方法。preclose
方法中的,驱动程序必须停止并清除该所有per-file操作。例如,必须取消待处理的垂直消隐和页面翻转事件。从preclose
方法返回后,不允许对文件句柄进行按文件的操作。
最后,该postclose
方法被称为关闭操作的最后一步,如果设备不存在其他打开文件句柄,则在调用lastclose
方法之前 调用。在open
方法中分配了每个per-file私有数据的驱动程序应在此处释放它。
该lastclose
方法应将CRTC和平面属性恢复为默认值,以便设备的后续打开不会继承先前用户的状态。它还可以用于执行延迟的电源开关状态更改,例如,与vga-switcheroo基础架构结合使用。除此之外,KMS驱动程序不应进行任何进一步的清理。只有旧版UMS驱动程序可能需要清理设备状态,以便vga控制台或独立的fbdev驱动程序可以接管。
const struct file_operations *fops
驱动程序必须定义构成DRM用户空间API入口点的文件操作结构,即使其中大多数操作是在DRM核心中实现的也是如此。的open
, release
和ioctl
操作由处理
.owner = THIS_MODULE,
.open = drm_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = drm_compat_ioctl,
#endif
实现需要32/64位兼容性支持的私有ioctl的驱动程序必须提供自己的 compat_ioctl
处理程序,该处理器处理私有ioctl并调用drm_compat_ioctl
核心ioctl。
在read
与poll
操作读取DRM事件和轮询他们提供支持。它们由
.poll = drm_poll,
.read = drm_read,
.llseek = no_llseek
内存映射的实现方式取决于驱动程序如何管理内存。将使用Pre-GEM驱动程序drm_mmap
,而将使用支持GEM的驱动程序drm_gem_mmap
。请参阅 “图形执行管理器(GEM)”部分。
.mmap = drm_gem_mmap,
DRM API不支持其他文件操作。
struct drm_ioctl_desc *ioctls;
int num_ioctls
特定于驱动程序的ioctl数字从DRM_COMMAND_BASE开始。ioctl描述符表由距基准值的ioctl编号索引。驱动程序可以使用DRM_IOCTL_DEF_DRV()宏来初始化表条目。
DRM_IOCTL_DEF_DRV(ioctl, func, flags)
ioctl
是ioctl名称。驱动程序必须将DRM _ ## ioctl和DRM_IOCTL _ ## ioctl宏定义为分别偏离DRM_COMMAND_BASE和ioctl编号的ioctl编号。第一个宏是设备专用的,而第二个宏必须在公共头文件中公开给用户空间。
func
是指向与drm_ioctl_t类型兼容的ioctl处理函数的指针。
typedef int drm_ioctl_t(struct drm_device *dev, void *data,
struct drm_file *file_priv);
flags
是以下值的位掩码组合。它限制了如何调用ioctl。
DRM_AUTH-仅允许通过身份验证的呼叫者
DRM_MASTER-ioctl只能在主文件句柄上调用
DRM_ROOT_ONLY-仅允许具有SYSADMIN功能的呼叫者
DRM_CONTROL_ALLOW-ioctl只能在控制设备上调用
DRM_UNLOCKED-将在不锁定DRM全局互斥锁的情况下调用ioctl处理程序
这应该涵盖一些特定于设备的命令提交实现。
DRM核心提供了一些挂起/恢复代码,但是想要完全挂起/恢复支持的驱动程序应提供save()和restore()函数。这些在挂起,休眠或恢复时间被调用,并且应在挂起或休眠状态之间执行设备所需的任何状态保存或还原。
int (*suspend) (struct drm_device *, pm_message_t state);
int (*resume) (struct drm_device *);
这些是传统的挂起和恢复方法。新的驱动程序应该使用自己的总线类型提供的电源管理接口(通常是通过结构的device_driver dev_pm_ops),并设置这些方法为NULL。
这应该涵盖内核如何支持DMA映射等。这些功能已被弃用,不应使用。
目录
渲染节点
VBlank事件处理
DRM核心将几个接口导出到应用程序,这些接口通常旨在通过相应的libdrm包装函数使用。另外,驱动程序通过ioctl和sysfs文件导出设备专用的接口,以供用户空间驱动程序和支持设备的应用程序使用。
外部接口包括:内存映射,上下文管理,DMA操作,AGP管理,vblank控制,fence管理,内存管理和输出管理。
在这里介绍通用的ioctl和sysfs布局。我们只需要高级信息,因为手册页应涵盖其余内容。
DRM内核提供了多个字符设备供用户空间使用。根据打开哪个设备,用户空间可以执行一组不同的操作(主要是ioctl)。主节点始终被创建并称为
随着屏幕外渲染器和GPGPU应用程序使用的增加,客户端不再需要运行合成器或图形服务器来使用GPU。但是DRM API要求非特权客户端在访问GPU之前必须先向DRM-Master进行身份验证。为了避免此步骤并在不进行身份验证的情况下授予客户端GPU访问权限,引入了渲染节点。渲染节点仅服务于渲染客户端,也就是说,无法在渲染节点上发布模式设置或特权ioctl。仅允许使用非全局渲染命令。如果驱动程序支持渲染节点,则它必须通过
如果驱动程序宣告支持渲染节点,则DRM核心将创建一个名为
使用渲染节点,用户空间现在可以通过基本文件系统访问模式来控制对渲染节点的访问。不再需要用于在特权的主/旧版节点上对客户端进行身份验证的图形服务器。相反,客户端可以打开渲染节点并立即被授予GPU访问权限。客户端(或服务器)之间的通信是通过PRIME完成的。不支持从渲染节点到旧版节点的FLINK。新客户端不得使用不安全的FLINK接口。
除了删除所有模式集/全局图标外,渲染节点还删除DRM-Master概念。没有理由将渲染客户端与DRM-Master关联,因为它们独立于任何图形服务器。此外,无论如何,它们必须在没有任何运行主机的情况下工作。如果驱动程序支持渲染节点,则它们必须能够在没有主对象的情况下运行。另一方面,如果驱动程序要求客户端之间的共享状态对用户空间可见并且可以在打开文件边界之外访问,则它们不支持渲染节点。
DRM核心公开了两个垂直的空白相关的ioctl:
DRM_IOCTL_WAIT_VBLANK
这以struct drm_wait_vblank结构作为其参数,并在发生指定的vblank事件时用于阻止或请求信号。
DRM_IOCTL_MODESET_CTL
在模式设置之前和之后,应由应用程序级别的驱动程序调用此方法,因为在许多设备上,垂直空白计数器当时会被重置。在内部,当使用_DRM_PRE_MODESET命令调用ioctl时,DRM会对最后的vblank计数进行快照,以使计数器不会向后移动(使用_DRM_POST_MODESET时将进行处理)。
在此处包括自动生成的API参考(也需要在以上段落中进行参考)。