Rockchip RK3399 - DRM子系统
----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4
开发板
eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏
u-boot
:2023.04
linux
:6.3
----------------------------------------------------------------------------------------------------------------------------
从开始接触音频子系统到如今已经两个多月,说实话花费的时间的确有点长了。从今天起我们开始接触DRM
,网上已经有很多优秀的关于DRM
的文章了,因此我们学习直接去学习一些优秀的文章即可。后面有关DRM
相关的文章我们会大量参考DRM (Direct Rendering Manager)
。
一、DRM
介绍
1.1 DRM
概述
linux
内核中包含两类图形显示设备驱动框架:
FB
设备:Framebuffer
图形显示框架;DRM
:直接渲染管理器(Direct Rendering Manager
),是linux
目前主流的图形显示框架;
在实际场景中,具体选择哪一种图形设备驱动框架取决于我们自己的业务需求。
1.1.1 Frambebuffer
驱动
Frambebuffer
驱动具有以下特征:
-
直接控制显卡的帧缓冲区,提供基本的显卡输出功能;
-
使用一些内核数据结构和
API
来管理图形界面,并提供一组接口与用户空间的应用程序进行通信; -
相对简单,适合于嵌入式系统或者不需要高性能图形的应用场景。
1.1.2 DRM
驱动
相比FB
(Framebuffer
)架构,DRM
更能适应当前日益更新的显示硬件;
- 提供一种分离的图形驱动架构,将硬件驱动程序、内核模块和用户空间驱动程序进行分离;
- 支持多个应用程序同时访问显卡,并提供了更丰富的图形功能,例如硬件加速和
3D
加速; - 提供了一些内核接口,可以让用户空间应用程序与驱动程序进行交互;
- 支持多显示器(
Display
)和多GPU
的配置;
总之,一句话,DRM
是Linux
目前主流的图形显示框架,相比FB
架构,DRM
更能适应当前日益更新的显示硬。尽管FB
退出历史舞台,但是并未将其遗弃,而是集合到DRM
中,供部分嵌入式设备使用。
有关DRM
的发展历史可以参考这篇博客:DRM (Direct Rendering Manager)
的发展历史。
1.2 DRM
框架
我们来看一下DRM
子系统的软件架构:
DRM
框架从上到下依次为应用程序、libdrm
、DRM driver
、HW
;
(1) 应用程序:上图中并没有画出;应用程序可以直接操纵DRM
的ioctl
进行显示相关操作,后来封装成了libdrm
库,让用户可以更加方便的进行显示控制;
(2) libdrm
:lbdrm
是DRM
框架提供的位于用户空间操作DRM
的库,提供了DRM
驱动的用户空间接口;对底层接口进行封装,向上层应用程序提供通用的API
接口,本质上是对各种ioctl
接口进行封装;
(3) DRM core
:DRM
核心层,由GEM
和KMS
组成;
KMS
:Kernel Mode Setting
,所谓内核显示模式设置,其实说白了就两件事:更新画面和设置显示参数;- 更新画面:显示
buffer
的切换,多图层的合成方式,以及每个图层的显示位置; - 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等;
- 更新画面:显示
GEM
:Graphic Execution Manager
(图形执行管理器),它提供了一种抽象的显存管理方式,使用户空间应用程序可以更方便地管理显存,而不需要了解底层硬件的细节;- 实际上,在DRM中包含两个内存管理器,
TTM
(Translation Table Manager
)和GEM
(Graphic Execution Manager
),TTM
是第一个开发的DRM
内存管理器,关于TTM
我们就不做过多的介绍了,知道有这么一个东西就好了。
- 实际上,在DRM中包含两个内存管理器,
(4) HW
:硬件设备;
1.2.1 KMS
KMS
主要负责显示相关功能,在DRM
中将其进行抽象,包括:CRTC
、ENCODER
、CONNECTOR
、PLANE
、Framebuffer
、VBLANK
、property
;它们之间的关系如下图所示:
以HDMI
接口为例说明,Soc
内部一般包含一个Display
模块,通过总线连接到HDMI
接口上;
Display
模块对应CRTC
;HDMI
接口对应Connector
;Framebuffer
对应的是显存部分;Plane
是对Framebuffer
进行描述的部分;Encoder
是将像素转化为HDMI
接口所需要的信号,一般Encoder
和Connector
放到一块初始化。
1.2.2 GEM
GEM
主要负责显示buffer
的分配和释放,在DRM
中将其进行抽象,包括:DUMP
、PRIME
、fence
;
1.2.3 元素介绍
学习DRM
驱动其实就是学习上面各个元素的实现及用法,如果你能掌握这些知识点,那么在编写DRM
驱动的时候就能游刃有余。
元素 | 说明 |
---|---|
CRTC |
从Framebuffer 中读取待显示的图像,并按照响应的格式输出给encoder ,其主要承担的作用为(1)配置适合显示的显示模式、分辨率、刷新率等参数,并输出相应的时序; (2)扫描 Framebuffer 发送到一个或多个显示器;(3)更新 Framebuffer ;概括下就是,对显示器进行扫描,产生时序信号的模块、负责帧切换、电源控制、色彩调整等等。 |
Encoder |
编码器。它的作用就是将内存的pixel 像素编码(转换)为显示器所需要的信号。简单理解就是,如果需要将画面显示到不同的设备( Display Device )上,需要将画面转化为不同的电信号,例如DVID 、VGA 、YPbPr 、CVBS 、MIPI 、eDP 等。Encoder 和CRTC 之间的交互就是我们所说的Mode Setting ,其中包含了前面提到的色彩模式、还有时序(Timing )等 |
Connector |
连接器。它常常对应于物理连接器 (例如VGA ,DVI , FPD-Link , HDMI , DisplayPort , S-Video 等) ,它不是指物理线。在 DRM 中,Connector 是一个抽象的数据结构,代表连接的显示设备,从Connector 中可以得到当前物理连接的输出设备相关的信息 ;例如连接状态,EDID 数据,DPMS 状态、支持的视频模式等 |
Plane |
图层,实际输出的图像是多个图层叠加而成的,比如主图层、光标图层。其中有些图层由硬件加速模块生成,每个CRTC 至少一个plane ;plane 一共有三种,分别是:DRM_PLANE_TYPE_PRIMARY 、DRM_PLANE_TYPE_OVERLAY 、DRM_PLANE_TYPE_CURSOR 。这是配置plane 的三个枚举,标注主图层、覆盖图层、光标图层; |
Framebuffer |
Framebuffer ,用于存储单个图层(Plane )要实现的内容它是一块内存区域,可以理解为一块画布,驱动程序和应用都能访问它。绘画前需要将它格式化,设定绘制的色彩模式(例如 RGB888 ,YUV 等)和画布的大小(分辨率) |
Vblank |
软件和硬件的同步机制,RGB 时序中的垂直消影区,软件通常使用硬件VSYNC 来实现 |
property |
任何你想设置的参数都可以做成property ,是DRM 驱动中最灵活、最方便的Mode setting 机制 |
Dumb |
只支持连续物理内存,基于kernel 中通用CMA API 实现,多用于小分辨率简单场景 |
Prime |
连续、非连续物理内存都支持,基于DMA-BUF 机制,可以实现buffer 共享,多用于大内存复杂场景 |
fence |
buffer 同步机制,基于内核dma_fence 机制实现,用于防止显示内容出现异步问题 |
1.3 目录结构
linux
内核将DRM
驱动相关的代码都放在drivers/gpu/drm
目录下,这下面的文件还是比较多的,我们大概了解一下即可;
root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/ -I "*.o"
amd drm_fbdev_generic.c drm_print.c logicvc
arm drm_fb_dma_helper.c drm_privacy_screen.c Makefile
armada drm_fb_helper.c drm_privacy_screen_x86.c mcde
aspeed drm_file.c drm_probe_helper.c mediatek
ast drm_flip_work.c drm_property.c meson
atmel-hlcdc drm_format_helper.c drm_rect.c mgag200
bridge drm_fourcc.c drm_scatter.c modules.order
built-in.a drm_framebuffer.c drm_self_refresh_helper.c msm
display drm_gem_atomic_helper.c drm_shmem_helper.ko mxsfb
drm_agpsupport.c drm_gem.c drm_shmem_helper.mod nouveau
drm_aperture.c drm_gem_dma_helper.c drm_shmem_helper.mod.c omapdrm
drm_atomic.c drm_gem_framebuffer_helper.c drm_simple_kms_helper.c panel
drm_atomic_helper.c drm_gem_shmem_helper.c drm_syncobj.c panfrost
drm_atomic_state_helper.c drm_gem_ttm_helper.c drm_sysfs.c pl111
drm_atomic_uapi.c drm_gem_vram_helper.c drm_trace.h qxl
drm_auth.c drm_hashtab.c drm_trace_points.c radeon
drm_blend.c drm_internal.h drm_ttm_helper.ko rcar-du
drm_bridge.c drm_ioc32.c drm_ttm_helper.mod rockchip
drm_bridge_connector.c drm_ioctl.c drm_ttm_helper.mod.c scheduler
drm_buddy.c drm_irq.c drm_vblank.c shmobile
drm_bufs.c drm_kms_helper_common.c drm_vblank_work.c solomon
drm_cache.c drm_lease.c drm_vma_manager.c sprd
drm_client.c drm_legacy.h drm_vm.c sti
drm_client_modeset.c drm_legacy_misc.c drm_vram_helper.ko stm
drm_color_mgmt.c drm_lock.c drm_vram_helper.mod sun4i
drm_connector.c drm_managed.c drm_vram_helper.mod.c tegra
drm_context.c drm_memory.c drm_writeback.c tests
drm_crtc.c drm_mipi_dbi.c etnaviv tidss
drm_crtc_helper.c drm_mipi_dsi.c exynos tilcdc
drm_crtc_helper_internal.h drm_mm.c fsl-dcu tiny
drm_crtc_internal.h drm_mode_config.c gma500 ttm
drm_damage_helper.c drm_mode_object.c gud tve200
drm_debugfs.c drm_modes.c hisilicon udl
drm_debugfs_crc.c drm_modeset_helper.c hyperv v3d
drm_displayid.c drm_modeset_lock.c i2c vboxvideo
drm_dma.c drm_of.c i915 vc4
drm_drv.c drm_panel.c imx vgem
drm_dumb_buffers.c drm_panel_orientation_quirks.c ingenic virtio
drm_edid.c drm_pci.c Kconfig vkms
drm_edid_load.c drm_plane.c kmb vmwgfx
drm_encoder.c drm_plane_helper.c lib xen
drm_encoder_slave.c drm_prime.c lima xlnx
其中:
-
drm_drv.c
:DRM core
核心实现; -
drm_gem.c
:提供了GEM
相关的API
;
其中rockchip
为Rockchip
官方的实现代码:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/rockchip/ -I "*.o"
analogix_dp-rockchip.c inno_hdmi.c rockchip_drm_drv.h rockchip_drm_vop.h
built-in.a inno_hdmi.h rockchip_drm_fb.c rockchip_lvds.c
cdn-dp-core.c Kconfig rockchip_drm_fb.h rockchip_lvds.h
cdn-dp-core.h Makefile rockchip_drm_gem.c rockchip_rgb.c
cdn-dp-reg.c modules.order rockchip_drm_gem.h rockchip_rgb.h
cdn-dp-reg.h rk3066_hdmi.c rockchip_drm_vop2.c rockchip_vop2_reg.c
dw_hdmi-rockchip.c rk3066_hdmi.h rockchip_drm_vop2.h rockchip_vop_reg.c
dw-mipi-dsi-rockchip.c rockchip_drm_drv.c rockchip_drm_vop.c rockchip_vop_reg.h
二、硬件抽象
对于初学者来说,往往让人迷惑的不是DRM
中objects
的概念,而是如何去建立这些objects
与实际硬件的对应关系。因为并不是所有的Display
硬件都能很好的对应上plane/crtc/encoder/connector
这些objects
。
在学如何去抽象显示硬件到具体的DRM object
之前,我们先普及一下MIPI
相关的知识。
MIPI(Mobile Industry Processor Interface)
是2003年由ARM, Nokia, ST ,TI
等公司成立的一个联盟,目的是把手机内部的接口如摄像头、显示屏接口、射频/基带接口等标准化,从而减少手机设计的复杂程度和增加设计灵活性。
MIPI
联盟下面有不同的WorkGroup
,分别定义了一系列的手机内部接口标准,比如:
- 摄像头接口
CSI(Camera Serial Interface)
; - 显示接口
DSI(Display Serial Interface)
; - 射频接口
DigRF
; - 麦克风/喇叭接口
SLIMbus
等。
2.1 MIPI DSI
接口
下图为一个典型的MIPI DSI
接口屏的硬件连接框图:
它在软件架构上与DRM object
的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配drm object
:
object | 说明 |
---|---|
crtc |
RGB timing 的产生,以及显示数据的更新,都需要访问Dislay Controller 硬件寄存器,因此放在Display Controller 驱动中 |
plane |
对Overlay 硬件的抽象,同样需要访问Display Controller 寄存器,因此也放在Display Controller 驱动中 |
encoder |
将RGB 并行信号转换为DSI 行信号,需要配置DSI 硬件寄存器,因此放在DSI Controller 驱动中 |
connector |
可以通过drm_panel 来获取LCD 的mode 信息,但是encoder 在哪,connector 就在哪,因此放在DSI Controller 驱动中 |
drm_panel |
用于获取LCD mode 参数,并提供LCD 休眠唤醒的回调接口,供encoder 调用,因此放在LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
。
有关MIPI DSI
可以参考MIPI 系列之 DSI
。
2.2 MIPI DPI
接口
DPI
接口也就是我们常说的RGB
并行接口,Video
数据通过RGB
并行总线传输,控制命令(如初始化、休眠、唤醒等)则通过SPI/I2C
总线传输,比如早期的S3C2440 SoC
平台。下图为一个典型的MIPI DPI
接口屏的硬件连接框图:
该硬件连接在软件架构上与DRM object
的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配drm object
:
object | 说明 |
---|---|
crtc |
RGB timing 的产生,以及显示数据的更新,都需要访问LCD Controller 硬件寄存器,因此放在LCD Controller 驱动中 |
plane |
LCD Controller 没有Overlay 硬件,它只有一个数据源通道,被抽象为Primary Plane ,同样需要访问 LCD Controller 硬件寄存器,因此放在LCD Controller 驱动中 |
encoder |
由于DPI 接口本身不需要对RGB 信号做任何转换,因此没有哪个硬件与之对应。但是drm objects 又缺一不可,因此实现了一个虚拟的encoder object 。至于为什么要放在LCD 驱动中实现,纯粹只是为了省事而已,你也可以放在一个虚拟的平台驱动中去实现该encoder object |
connector |
encoder 在哪,connector 就在哪,没什么好说的了 |
drm_panel |
用于获取LCD mode 参数,并提供LCD 休眠唤醒的回调接口,供encoder 调用,因此放在LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/v5.0/source/drivers/gpu/drm/panel/panel-sitronix-st7789v.c
。
2.3 MIPI DBI
接口
DBI
接口也就是我们平时常说的MCU
或SPI
接口屏,这类屏的VIDEO
数据和控制命令都是通过同一总线接口(I80、SPI
接口)进行传输,而且这类屏幕必须内置GRAM
显存,否则屏幕无法维持正常显示。
下图为一个典型的DBI
接口屏的硬件连接框图:
该硬件连接在软件架构上与DRM object
的对应关系如下:
上图参考kernel4.19 tinydrm
软件架构。
object | 说明 |
---|---|
crtc |
这类硬件本身不需要任何RGB timing 信号,因此也没有实际的硬件与之对应。但是drm objects 缺一不可,需要实现一个虚拟的crtc object 。由于更新图像数据的动作需要通过SPI 总线发送命令才能完成,因此放在了LCD 驱动中 |
plane |
没有实际的硬件与之对应,但crtc 初始化时需要一个plane object 作为参数传递,因此和crtc 放在一起 |
encoder |
没有实际的硬件与之对应,使用虚拟的encoder object 。因为这类硬件并不是将RGB 信号转换为SPI 信号,而是根本就没有RGB 信号源,也就无从谈起encoder 设备。但是为了通知LCD 休眠唤醒,需要调用LCD 驱动的相应接口,因此放在LCD 驱动中 |
connector |
由于没有了drm_panel ,需要调用LCD 接口来获取mode 参数,因此放在LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/tinydrm/ili9341.c
。
三、DRM Objects
在编写DRM
驱动程序之前,我们先对DRM
内部的objects
进行一番介绍,因为这些objects
是DRM
框架的核心,它们缺一不可。
上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为drm_mode_object
(或者说是modset object
),虚线以下为drm_gem_object
(或者说是gem objec
)。
这些objects
之间的关系:
通过上图可以看到,plane
是连接framebuffer
和crtc
的纽带,而encoder
则是连接crtc
和connector
的纽带。与物理buffer
直接打交道的是gem
而不是framebuffer
。
个人理解:
buffer
是硬件存储设备, 由gem
分配和释放;framebuffer
用于描述分配的显存的信息(如format
、pitch
、size
等);plane
用于描述图层信息,同一个crtc
可以由多个plane
组成;crtc
控制显卡输出图像信号;encoder
将crtc
输出的图像信号转换成一定格式的数字信号,如HDMI
、DisplayPort
、MIPI
等;connector
用于将encoder
输出的信号传递给显示器,并与显示器建立连接;
需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些objects
,否则DRM
子系统无法正常运行。
3.1 drm_pane
encoder
驱动程序负责将图形数据转换为LCD
显示器所需的视频信号,而connector
驱动程序则负责将这些信号发送到正确的显示设备上。LCD
驱动程序需要和encoder
、connector
这两个驱动程序进行交互,以完成图形输出的控制。
耦合的产生:
connector
的主要作用就是获取显示参数,所以会在LCD
驱动中去构造connector object
。但是connector
初始化时需要attach
上一个encoder object
,而这个encoder object
往往是在另一个硬件驱动中生成的,为了访问该encoder object
,势必会产生一部分耦合的代码;encoder
除了扮演信号转换的角色,还担任着通知显示设备休眠唤醒的角色。因此,当encoder
通知LCD
驱动执行相应的enable/disable
操作时,就一定会调用LCD
驱动导出的全局函数,这也必然会产生一部分的耦合代码;
为了解决该耦合的问题,DRM
子系统为开发人员提供了drm_panel
结构体,该结构体封装了connector
& encoder
对LCD
访问的常用接口;
于是,原来的encoder
驱动和LCD
驱动之间的耦合,就转变成了上图中encoder
驱动与drm_panel
、drm_panel
与LCD
驱动之间的“耦合”,从而实现了encoder
驱动与LCD
驱动之间的解耦合。
drm_panel
不属于objects
的范畴,它只是一堆回调函数的集合。但它的存在降低了LCD
驱动与encoder
驱动之间的耦合度。
3.2 modeset object
对于plane
、crtc
、encoder
、connector
几个对象,它们有一个公共基类struct drm_mode_object
,这几个对象都由此基类扩展而来(该类作为crtc
等结构体的成员)。事实上这个基类扩展出来的子类并不是只有上面提到的几种,只不过这四种比较常见。其定义在include/drm/drm_mode_object.h
:
/**
* struct drm_mode_object - base structure for modeset objects
* @id: userspace visible identifier
* @type: type of the object, one of DRM_MODE_OBJECT\_\*
* @properties: properties attached to this object, including values
* @refcount: reference count for objects which with dynamic lifetime
* @free_cb: free function callback, only set for objects with dynamic lifetime
*
* Base structure for modeset objects visible to userspace. Objects can be
* looked up using drm_mode_object_find(). Besides basic uapi interface
* properties like @id and @type it provides two services:
*
* - It tracks attached properties and their values. This is used by &drm_crtc,
* &drm_plane and &drm_connector. Properties are attached by calling
* drm_object_attach_property() before the object is visible to userspace.
*
* - For objects with dynamic lifetimes (as indicated by a non-NULL @free_cb) it
* provides reference counting through drm_mode_object_get() and
* drm_mode_object_put(). This is used by &drm_framebuffer, &drm_connector
* and &drm_property_blob. These objects provide specialized reference
* counting wrappers.
*/
struct drm_mode_object {
uint32_t id;
uint32_t type;
struct drm_object_properties *properties;
struct kref refcount;
void (*free_cb)(struct kref *kref);
};
包括以下成员:
-
id
:用户空间可见的唯一标识标识符,基于idr
算法分配得到的; -
type
:对象的类型,可以是DRM_MODE_OBJECT_*
中的一个; -
properties
:附加到该对象的属性,包括属性的值;在DRM
驱动中,每个对象都可以拥有一组属性(例如分辨率、刷新率等),并且可以动态地增加、删除或修改属性。这些属性可以被用户空间的应用程序或者其他驱动程序获取或者设置; -
refcount
:具有动态生命周期的对象的引用计数;指drm_mode_object
对象在内核中的生命周期的管理,每个drm_mode_object
对象都有一个引用计数;- 当一个对象被创建时,它的引用计数被初始化为1;
- 每当一个新的引用指向该对象时,它的引用计数就会增加1;
- 每当一个引用被释放时,它的引用计数就会减少1;
- 当对象的引用计数降为0时,内核会自动释放该对象。
- 这种方式确保了内核中不会存在不再使用的对象,从而避免了内存泄漏。
-
free_cb
:释放函数回调,仅对具有动态生命周期的对象设置;
该结构体提供了用户空间可见的modeset objects
的基本结构,可以通过drm_mode_object_find
函数查找对象。
为了更加清晰的了解struct drm_mode_object
、struct drm_object_properties
、struct snd_jack_kctl
数据结构的关系,我们绘制了如下关系图:
3.2.1 对象类型
type
主要包含以下几种类型,定义在include/uapi/drm/drm_mode.h
:
#define DRM_MODE_OBJECT_CRTC 0xcccccccc
#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0
#define DRM_MODE_OBJECT_MODE 0xdededede
#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0
#define DRM_MODE_OBJECT_FB 0xfbfbfbfb
#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
#define DRM_MODE_OBJECT_ANY 0
3.2.2 对象属性
struct drm_object_properties
用于描述对象的属性,定义在include/drm/drm_mode_object.h
:
/**
* struct drm_object_properties - property tracking for &drm_mode_object
*/
struct drm_object_properties {
/**
* @count: number of valid properties, must be less than or equal to
* DRM_OBJECT_MAX_PROPERTY.
*/
int count;
/**
* @properties: Array of pointers to &drm_property.
*
* NOTE: if we ever start dynamically destroying properties (ie.
* not at drm_mode_config_cleanup() time), then we'd have to do
* a better job of detaching property from mode objects to avoid
* dangling property pointers:
*/
struct drm_property *properties[DRM_OBJECT_MAX_PROPERTY];
/**
* @values: Array to store the property values, matching @properties. Do
* not read/write values directly, but use
* drm_object_property_get_value() and drm_object_property_set_value().
*
* Note that atomic drivers do not store mutable properties in this
* array, but only the decoded values in the corresponding state
* structure. The decoding is done using the &drm_crtc.atomic_get_property and
* &drm_crtc.atomic_set_property hooks for &struct drm_crtc. For
* &struct drm_plane the hooks are &drm_plane_funcs.atomic_get_property and
* &drm_plane_funcs.atomic_set_property. And for &struct drm_connector
* the hooks are &drm_connector_funcs.atomic_get_property and
* &drm_connector_funcs.atomic_set_property .
*
* Hence atomic drivers should not use drm_object_property_set_value()
* and drm_object_property_get_value() on mutable objects, i.e. those
* without the DRM_MODE_PROP_IMMUTABLE flag set.
*
* For atomic drivers the default value of properties is stored in this
* array, so drm_object_property_get_default_value can be used to
* retrieve it.
*/
uint64_t values[DRM_OBJECT_MAX_PROPERTY];
};
该结构体包含以下字段:
count
:properties
数组长度,必须小于或等于DRM_OBJECT_MAX_PROPERTY
(值为24);properties
:指向drm_property
的指针数组;values
:用于存储属性值的数组,与properties
匹配;
其中struct drm_property
定义在include/drm/drm_property.h
:
点击查看代码
/**
* struct drm_property - modeset object property
*
* This structure represent a modeset object property. It combines both the name
* of the property with the set of permissible values. This means that when a
* driver wants to use a property with the same name on different objects, but
* with different value ranges, then it must create property for each one. An
* example would be rotation of &drm_plane, when e.g. the primary plane cannot
* be rotated. But if both the name and the value range match, then the same
* property structure can be instantiated multiple times for the same object.
* Userspace must be able to cope with this and cannot assume that the same
* symbolic property will have the same modeset object ID on all modeset
* objects.
*
* Properties are created by one of the special functions, as explained in
* detail in the @flags structure member.
*
* To actually expose a property it must be attached to each object using
* drm_object_attach_property(). Currently properties can only be attached to
* &drm_connector, &drm_crtc and &drm_plane.
*
* Properties are also used as the generic metadatatransport for the atomic
* IOCTL. Everything that was set directly in structures in the legacy modeset
* IOCTLs (like the plane source or destination windows, or e.g. the links to
* the CRTC) is exposed as a property with the DRM_MODE_PROP_ATOMIC flag set.
*/
struct drm_property {
/**
* @head: per-device list of properties, for cleanup.
*/
struct list_head head;
/**
* @base: base KMS object
*/
struct drm_mode_object base;
/**
* @flags:
*
* Property flags and type. A property needs to be one of the following
* types:
*
* DRM_MODE_PROP_RANGE
* Range properties report their minimum and maximum admissible unsigned values.
* The KMS core verifies that values set by application fit in that
* range. The range is unsigned. Range properties are created using
* drm_property_create_range().
*
* DRM_MODE_PROP_SIGNED_RANGE
* Range properties report their minimum and maximum admissible unsigned values.
* The KMS core verifies that values set by application fit in that
* range. The range is signed. Range properties are created using
* drm_property_create_signed_range().
*
* DRM_MODE_PROP_ENUM
* Enumerated properties take a numerical value that ranges from 0 to
* the number of enumerated values defined by the property minus one,
* and associate a free-formed string name to each value. Applications
* can retrieve the list of defined value-name pairs and use the
* numerical value to get and set property instance values. Enum
* properties are created using drm_property_create_enum().
*
* DRM_MODE_PROP_BITMASK
* Bitmask properties are enumeration properties that additionally
* restrict all enumerated values to the 0..63 range. Bitmask property
* instance values combine one or more of the enumerated bits defined
* by the property. Bitmask properties are created using
* drm_property_create_bitmask().
*
* DRM_MODE_PROP_OBJECT
* Object properties are used to link modeset objects. This is used
* extensively in the atomic support to create the display pipeline,
* by linking &drm_framebuffer to &drm_plane, &drm_plane to
* &drm_crtc and &drm_connector to &drm_crtc. An object property can
* only link to a specific type of &drm_mode_object, this limit is
* enforced by the core. Object properties are created using
* drm_property_create_object().
*
* Object properties work like blob properties, but in a more
* general fashion. They are limited to atomic drivers and must have
* the DRM_MODE_PROP_ATOMIC flag set.
* DRM_MODE_PROP_BLOB
* Blob properties store a binary blob without any format restriction.
* The binary blobs are created as KMS standalone objects, and blob
* property instance values store the ID of their associated blob
* object. Blob properties are created by calling
* drm_property_create() with DRM_MODE_PROP_BLOB as the type.
*
* Actual blob objects to contain blob data are created using
* drm_property_create_blob(), or through the corresponding IOCTL.
*
* Besides the built-in limit to only accept blob objects blob
* properties work exactly like object properties. The only reasons
* blob properties exist is backwards compatibility with existing
* userspace.
*
* In addition a property can have any combination of the below flags:
*
* DRM_MODE_PROP_ATOMIC
* Set for properties which encode atomic modeset state. Such
* properties are not exposed to legacy userspace.
*
* DRM_MODE_PROP_IMMUTABLE
* Set for properties whose values cannot be changed by
* userspace. The kernel is allowed to update the value of these
* properties. This is generally used to expose probe state to
* userspace, e.g. the EDID, or the connector path property on DP
* MST sinks. Kernel can update the value of an immutable property
* by calling drm_object_property_set_value().
*/
uint32_t flags;
/**
* @name: symbolic name of the properties
*/
char name[DRM_PROP_NAME_LEN];
/**
* @num_values: size of the @values array.
*/
uint32_t num_values;
/**
* @values:
*
* Array with limits and values for the property. The
* interpretation of these limits is dependent upon the type per @flags.
*/
uint64_t *values;
/**
* @dev: DRM device
*/
struct drm_device *dev;
/**
* @enum_list:
*
* List of &drm_prop_enum_list structures with the symbolic names for
* enum and bitmask values.
*/
struct list_head enum_list;
};
四、DRM
核心数据结构
学习DRM
驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。
4.1 struct drm_device
linux
内核使用struct drm_device
数据结构来描述一个drm
设备,定义在include/drm/drm_device.h
:
点击查看代码
/**
* struct drm_device - DRM device structure
*
* This structure represent a complete card that
* may contain multiple heads.
*/
struct drm_device {
/** @if_version: Highest interface version set */
int if_version;
/** @ref: Object ref-count */
struct kref ref;
/** @dev: Device structure of bus-device */
struct device *dev;
/**
* @managed:
*
* Managed resources linked to the lifetime of this &drm_device as
* tracked by @ref.
*/
struct {
/** @managed.resources: managed resources list */
struct list_head resources;
/** @managed.final_kfree: pointer for final kfree() call */
void *final_kfree;
/** @managed.lock: protects @managed.resources */
spinlock_t lock;
} managed;
/** @driver: DRM driver managing the device */
const struct drm_driver *driver;
/**
* @dev_private:
*
* DRM driver private data. This is deprecated and should be left set to
* NULL.
*
* Instead of using this pointer it is recommended that drivers use
* devm_drm_dev_alloc() and embed struct &drm_device in their larger
* per-device structure.
*/
void *dev_private;
/**
* @primary:
*
* Primary node. Drivers should not interact with this
* directly. debugfs interfaces can be registered with
* drm_debugfs_add_file(), and sysfs should be directly added on the
* hardware (and not character device node) struct device @dev.
*/
struct drm_minor *primary;
/**
* @render:
*
* Render node. Drivers should not interact with this directly ever.
* Drivers should not expose any additional interfaces in debugfs or
* sysfs on this node.
*/
struct drm_minor *render;
/** @accel: Compute Acceleration node */
struct drm_minor *accel;
/**
* @registered:
*
* Internally used by drm_dev_register() and drm_connector_register().
*/
bool registered;
/**
* @master:
*
* Currently active master for this device.
* Protected by &master_mutex
*/
struct drm_master *master;
/**
* @driver_features: per-device driver features
*
* Drivers can clear specific flags here to disallow
* certain features on a per-device basis while still
* sharing a single &struct drm_driver instance across
* all devices.
*/
u32 driver_features;
/**
* @unplugged:
*
* Flag to tell if the device has been unplugged.
* See drm_dev_enter() and drm_dev_is_unplugged().
*/
bool unplugged;
/** @anon_inode: inode for private address-space */
struct inode *anon_inode;
/** @unique: Unique name of the device */
char *unique;
/**
* @struct_mutex:
*
* Lock for others (not &drm_minor.master and &drm_file.is_master)
*
* WARNING:
* Only drivers annotated with DRIVER_LEGACY should be using this.
*/
struct mutex struct_mutex;
/**
* @master_mutex:
*
* Lock for &drm_minor.master and &drm_file.is_master
*/
struct mutex master_mutex;
/**
* @open_count:
*
* Usage counter for outstanding files open,
* protected by drm_global_mutex
*/
atomic_t open_count;
/** @filelist_mutex: Protects @filelist. */
struct mutex filelist_mutex;
/**
* @filelist:
*
* List of userspace clients, linked through &drm_file.lhead.
*/
struct list_head filelist;
/**
* @filelist_internal:
*
* List of open DRM files for in-kernel clients.
* Protected by &filelist_mutex.
*/
struct list_head filelist_internal;
/**
* @clientlist_mutex:
*
* Protects &clientlist access.
*/
struct mutex clientlist_mutex;
/**
* @clientlist:
*
* List of in-kernel clients. Protected by &clientlist_mutex.
*/
struct list_head clientlist;
/**
* @vblank_disable_immediate:
*
* If true, vblank interrupt will be disabled immediately when the
* refcount drops to zero, as opposed to via the vblank disable
* timer.
*
* This can be set to true it the hardware has a working vblank counter
* with high-precision timestamping (otherwise there are races) and the
* driver uses drm_crtc_vblank_on() and drm_crtc_vblank_off()
* appropriately. See also @max_vblank_count and
* &drm_crtc_funcs.get_vblank_counter.
*/
bool vblank_disable_immediate;
/**
* @vblank:
*
* Array of vblank tracking structures, one per &struct drm_crtc. For
* historical reasons (vblank support predates kernel modesetting) this
* is free-standing and not part of &struct drm_crtc itself. It must be
* initialized explicitly by calling drm_vblank_init().
*/
struct drm_vblank_crtc *vblank;
/**
* @vblank_time_lock:
*
* Protects vblank count and time updates during vblank enable/disable
*/
spinlock_t vblank_time_lock;
/**
* @vbl_lock: Top-level vblank references lock, wraps the low-level
* @vblank_time_lock.
*/
spinlock_t vbl_lock;
/**
* @max_vblank_count:
*
* Maximum value of the vblank registers. This value +1 will result in a
* wrap-around of the vblank register. It is used by the vblank core to
* handle wrap-arounds.
*
* If set to zero the vblank core will try to guess the elapsed vblanks
* between times when the vblank interrupt is disabled through
* high-precision timestamps. That approach is suffering from small
* races and imprecision over longer time periods, hence exposing a
* hardware vblank counter is always recommended.
*
* This is the statically configured device wide maximum. The driver
* can instead choose to use a runtime configurable per-crtc value
* &drm_vblank_crtc.max_vblank_count, in which case @max_vblank_count
* must be left at zero. See drm_crtc_set_max_vblank_count() on how
* to use the per-crtc value.
*
* If non-zero, &drm_crtc_funcs.get_vblank_counter must be set.
*/
u32 max_vblank_count;
/** @vblank_event_list: List of vblank events */
struct list_head vblank_event_list;
/**
* @event_lock:
*
* Protects @vblank_event_list and event delivery in
* general. See drm_send_event() and drm_send_event_locked().
*/
spinlock_t event_lock;
/** @num_crtcs: Number of CRTCs on this device */
unsigned int num_crtcs;
/** @mode_config: Current mode config */
struct drm_mode_config mode_config;
/** @object_name_lock: GEM information */
struct mutex object_name_lock;
/** @object_name_idr: GEM information */
struct idr object_name_idr;
/** @vma_offset_manager: GEM information */
struct drm_vma_offset_manager *vma_offset_manager;
/** @vram_mm: VRAM MM memory manager */
struct drm_vram_mm *vram_mm;
/**
* @switch_power_state:
*
* Power state of the client.
* Used by drivers supporting the switcheroo driver.
* The state is maintained in the
* &vga_switcheroo_client_ops.set_gpu_state callback
*/
enum switch_power_state switch_power_state;
/**
* @fb_helper:
*
* Pointer to the fbdev emulation structure.
* Set by drm_fb_helper_init() and cleared by drm_fb_helper_fini().
*/
struct drm_fb_helper *fb_helper;
/**
* @debugfs_mutex:
*
* Protects &debugfs_list access.
*/
struct mutex debugfs_mutex;
/**
* @debugfs_list:
*
* List of debugfs files to be created by the DRM device. The files
* must be added during drm_dev_register().
*/
struct list_head debugfs_list;
/* Everything below here is for legacy driver, never use! */
/* private: */
#if IS_ENABLED(CONFIG_DRM_LEGACY)
/* List of devices per driver for stealth attach cleanup */
struct list_head legacy_dev_list;
#ifdef __alpha__
/** @hose: PCI hose, only used on ALPHA platforms. */
struct pci_controller *hose;
#endif
/* AGP data */
struct drm_agp_head *agp;
/* Context handle management - linked list of context handles */
struct list_head ctxlist;
/* Context handle management - mutex for &ctxlist */
struct mutex ctxlist_mutex;
/* Context handle management */
struct idr ctx_idr;
/* Memory management - linked list of regions */
struct list_head maplist;
/* Memory management - user token hash table for maps */
struct drm_open_hash map_hash;
/* Context handle management - list of vmas (for debugging) */
struct list_head vmalist;
/* Optional pointer for DMA support */
struct drm_device_dma *dma;
/* Context swapping flag */
__volatile__ long context_flag;
/* Last current context */
int last_context;
/* Lock for &buf_use and a few other things. */
spinlock_t buf_lock;
/* Usage counter for buffers in use -- cannot alloc */
int buf_use;
/* Buffer allocation in progress */
atomic_t buf_alloc;
struct {
int context;
struct drm_hw_lock *lock;
} sigdata;
struct drm_local_map *agp_buffer_map;
unsigned int agp_buffer_token;
/* Scatter gather memory */
struct drm_sg_mem *sg;
/* IRQs */
bool irq_enabled;
int irq;
#endif
};
初识这个数据结构,我们发现这个数据结构包含的字段属实有点多,如果要将每个字段的含义都搞清楚,定然不是一件容易的事情,因此我们只关注如下字段即可:
ref
:具有动态生命周期的对象的引用计数,对象的初始引用计数为1,使用drm_dev_get
和drm_dev_put
获取和释放进一步的引用计数;dev
:设备驱动模型中的device
,可以将drm_device
看做其子类;driver
:drm
驱动;registered
:设备是否已注册;unique
:设备的唯一名称;vblank_event_list
:vblank
事件链表;num_crtcs
:CRTC
的数量;debugfs_list
:保存struct drm_debugfs_entry
的链表;mode_config
:当前的显示模式配置,struct drm_mode_config
类型。
4.1.1 struct drm_minor
在struct drm_device
数据结构中,primary
、render
、accel
字段都是struct drm_minor
类型。
这个数据结构和我们在ALSA
中介绍的 struct snd_minor
是非常相似的,其创建和注册分别通过drm_minor_alloc
和drm_minor_register
实现。
struct drm_minor
定义在include/drm/drm_file.h
,DRM core
会根据driver_features
来决定是否为drm_device
中的primary
、render
、accel
注册字符设备(同时在/dev/dri
目录下创建相应的设备节点),比如/dev/dri/card0
、/dev/dri/renderD128
等;
/*
* FIXME: Not sure we want to have drm_minor here in the end, but to avoid
* header include loops we need it here for now.
*/
/* Note that the order of this enum is ABI (it determines
* /dev/dri/renderD* numbers).
*
* Setting DRM_MINOR_ACCEL to 32 gives enough space for more drm minors to
* be implemented before we hit any future
*/
enum drm_minor_type {
DRM_MINOR_PRIMARY,
DRM_MINOR_CONTROL,
DRM_MINOR_RENDER,
DRM_MINOR_ACCEL = 32,
};
/**
* struct drm_minor - DRM device minor structure
*
* This structure represents a DRM minor number for device nodes in /dev.
* Entirely opaque to drivers and should never be inspected directly by drivers.
* Drivers instead should only interact with &struct drm_file and of course
* &struct drm_device, which is also where driver-private data and resources can
* be attached to.
*/
struct drm_minor {
/* private: */
int index; /* Minor device number */
int type; /* Control or render or accel */
struct device *kdev; /* Linux device */
struct drm_device *dev;
struct dentry *debugfs_root;
struct list_head debugfs_list;
struct mutex debugfs_lock; /* Protects debugfs_list. */
};
其中:
index
:次设备号;type
:drm
设备类型;kdev
:设备驱动模型中的device
;dev
:drm
设备;debugfs_root
:deugfs
目录项;debugfs_list
:与debugfs
有关的链表,链表中存放的都是struct drm_debugfs_entry
;
struct drm_debugfs_entry
定义在include/drm/drm_debugfs.h
;
/**
* struct drm_debugfs_info - debugfs info list entry
*
* This structure represents a debugfs file to be created by the drm
* core. 描述debugfs文件信息
*/
struct drm_debugfs_info {
/** @name: File name */
const char *name;
/**
* @show:
*
* Show callback. &seq_file->private will be set to the &struct
* drm_debugfs_entry corresponding to the instance of this info
* on a given &struct drm_device.
*/
int (*show)(struct seq_file*, void*);
/** @driver_features: Required driver features for this entry. */
u32 driver_features;
/** @data: Driver-private data, should not be device-specific. */
void *data;
};
/**
* struct drm_debugfs_entry - Per-device debugfs node structure
*
* This structure represents a debugfs file, as an instantiation of a &struct
* drm_debugfs_info on a &struct drm_device.
*/
struct drm_debugfs_entry {
/** @dev: &struct drm_device for this node. */
struct drm_device *dev;
/** @file: Template for this node. */
struct drm_debugfs_info file;
/** @list: Linked list of all device nodes. */
struct list_head list; // 链表节点,用于将当节点添加到drm设备的debugfs_list链表中
};
4.1.2 struct drm_mode_config
linux
内核使用struct drm_mode_config
来描述显示模式配置信息,drm_mode_config
的主要功能之一是提供对显示器模式的管理和配置。这包括添加、删除、修改和查询显示器模式的能力。此外,drm_mode_config
还提供了与模式相关的配置选项,例如色彩空间、刷新率、分辨率等等。
struct drm_mode_config
定义在include/drm/drm_mode_config.h
:
点击查看代码
/**
* struct drm_mode_config - Mode configuration control structure
* @min_width: minimum fb pixel width on this device
* @min_height: minimum fb pixel height on this device
* @max_width: maximum fb pixel width on this device
* @max_height: maximum fb pixel height on this device
* @funcs: core driver provided mode setting functions
* @poll_enabled: track polling support for this device
* @poll_running: track polling status for this device
* @delayed_event: track delayed poll uevent deliver for this device
* @output_poll_work: delayed work for polling in process context
* @preferred_depth: preferred RBG pixel depth, used by fb helpers
* @prefer_shadow: hint to userspace to prefer shadow-fb rendering
* @cursor_width: hint to userspace for max cursor width
* @cursor_height: hint to userspace for max cursor height
* @helper_private: mid-layer private data
*
* Core mode resource tracking structure. All CRTC, encoders, and connectors
* enumerated by the driver are added here, as are global properties. Some
* global restrictions are also here, e.g. dimension restrictions.
*
* Framebuffer sizes refer to the virtual screen that can be displayed by
* the CRTC. This can be different from the physical resolution programmed.
* The minimum width and height, stored in @min_width and @min_height,
* describe the smallest size of the framebuffer. It correlates to the
* minimum programmable resolution.
* The maximum width, stored in @max_width, is typically limited by the
* maximum pitch between two adjacent scanlines. The maximum height, stored
* in @max_height, is usually only limited by the amount of addressable video
* memory. For hardware that has no real maximum, drivers should pick a
* reasonable default.
*
* See also @DRM_SHADOW_PLANE_MAX_WIDTH and @DRM_SHADOW_PLANE_MAX_HEIGHT.
*/
struct drm_mode_config {
/**
* @mutex:
*
* This is the big scary modeset BKL which protects everything that
* isn't protect otherwise. Scope is unclear and fuzzy, try to remove
* anything from under its protection and move it into more well-scoped
* locks.
*
* The one important thing this protects is the use of @acquire_ctx.
*/
struct mutex mutex;
/**
* @connection_mutex:
*
* This protects connector state and the connector to encoder to CRTC
* routing chain.
*
* For atomic drivers specifically this protects &drm_connector.state.
*/
struct drm_modeset_lock connection_mutex;
/**
* @acquire_ctx:
*
* Global implicit acquire context used by atomic drivers for legacy
* IOCTLs. Deprecated, since implicit locking contexts make it
* impossible to use driver-private &struct drm_modeset_lock. Users of
* this must hold @mutex.
*/
struct drm_modeset_acquire_ctx *acquire_ctx;
/**
* @idr_mutex:
*
* Mutex for KMS ID allocation and management. Protects both @object_idr
* and @tile_idr.
*/
struct mutex idr_mutex;
/**
* @object_idr:
*
* Main KMS ID tracking object. Use this idr for all IDs, fb, crtc,
* connector, modes - just makes life easier to have only one.
*/
struct idr object_idr;
/**
* @tile_idr:
*
* Use this idr for allocating new IDs for tiled sinks like use in some
* high-res DP MST screens.
*/
struct idr tile_idr;
/** @fb_lock: Mutex to protect fb the global @fb_list and @num_fb. */
struct mutex fb_lock;
/** @num_fb: Number of entries on @fb_list. */
int num_fb;
/** @fb_list: List of all &struct drm_framebuffer. */
struct list_head fb_list;
/**
* @connector_list_lock: Protects @num_connector and
* @connector_list and @connector_free_list.
*/
spinlock_t connector_list_lock;
/**
* @num_connector: Number of connectors on this device. Protected by
* @connector_list_lock.
*/
int num_connector;
/**
* @connector_ida: ID allocator for connector indices.
*/
struct ida connector_ida;
/**
* @connector_list:
*
* List of connector objects linked with &drm_connector.head. Protected
* by @connector_list_lock. Only use drm_for_each_connector_iter() and
* &struct drm_connector_list_iter to walk this list.
*/
struct list_head connector_list;
/**
* @connector_free_list:
*
* List of connector objects linked with &drm_connector.free_head.
* Protected by @connector_list_lock. Used by
* drm_for_each_connector_iter() and
* &struct drm_connector_list_iter to savely free connectors using
* @connector_free_work.
*/
struct llist_head connector_free_list;
/**
* @connector_free_work: Work to clean up @connector_free_list.
*/
struct work_struct connector_free_work;
/**
* @num_encoder:
*
* Number of encoders on this device. This is invariant over the
* lifetime of a device and hence doesn't need any locks.
*/
int num_encoder;
/**
* @encoder_list:
*
* List of encoder objects linked with &drm_encoder.head. This is
* invariant over the lifetime of a device and hence doesn't need any
* locks.
*/
struct list_head encoder_list;
/**
* @num_total_plane:
*
* Number of universal (i.e. with primary/curso) planes on this device.
* This is invariant over the lifetime of a device and hence doesn't
* need any locks.
*/
int num_total_plane;
/**
* @plane_list:
*
* List of plane objects linked with &drm_plane.head. This is invariant
* over the lifetime of a device and hence doesn't need any locks.
*/
struct list_head plane_list;
/**
* @num_crtc:
*
* Number of CRTCs on this device linked with &drm_crtc.head. This is invariant over the lifetime
* of a device and hence doesn't need any locks.
*/
int num_crtc;
/**
* @crtc_list:
*
* List of CRTC objects linked with &drm_crtc.head. This is invariant
* over the lifetime of a device and hence doesn't need any locks.
*/
struct list_head crtc_list;
/**
* @property_list:
*
* List of property type objects linked with &drm_property.head. This is
* invariant over the lifetime of a device and hence doesn't need any
* locks.
*/
struct list_head property_list;
/**
* @privobj_list:
*
* List of private objects linked with &drm_private_obj.head. This is
* invariant over the lifetime of a device and hence doesn't need any
* locks.
*/
struct list_head privobj_list;
int min_width, min_height;
int max_width, max_height;
const struct drm_mode_config_funcs *funcs;
/* output poll support */
bool poll_enabled;
bool poll_running;
bool delayed_event;
struct delayed_work output_poll_work;
/**
* @blob_lock:
*
* Mutex for blob property allocation and management, protects
* @property_blob_list and &drm_file.blobs.
*/
struct mutex blob_lock;
/**
* @property_blob_list:
*
* List of all the blob property objects linked with
* &drm_property_blob.head. Protected by @blob_lock.
*/
struct list_head property_blob_list;
/* pointers to standard properties */
/**
* @edid_property: Default connector property to hold the EDID of the
* currently connected sink, if any.
*/
struct drm_property *edid_property;
/**
* @dpms_property: Default connector property to control the
* connector's DPMS state.
*/
struct drm_property *dpms_property;
/**
* @path_property: Default connector property to hold the DP MST path
* for the port.
*/
struct drm_property *path_property;
.... 大量的struct drm_property
/**
* @hdcp_content_type_property: DRM ENUM property for type of
* Protected Content.
*/
struct drm_property *hdcp_content_type_property;
/* dumb ioctl parameters */
uint32_t preferred_depth, prefer_shadow;
/**
* @prefer_shadow_fbdev:
*
* Hint to framebuffer emulation to prefer shadow-fb rendering.
*/
bool prefer_shadow_fbdev;
/**
* @quirk_addfb_prefer_xbgr_30bpp:
*
* Special hack for legacy ADDFB to keep nouveau userspace happy. Should
* only ever be set by the nouveau kernel driver.
*/
bool quirk_addfb_prefer_xbgr_30bpp;
/**
* @quirk_addfb_prefer_host_byte_order:
*
* When set to true drm_mode_addfb() will pick host byte order
* pixel_format when calling drm_mode_addfb2(). This is how
* drm_mode_addfb() should have worked from day one. It
* didn't though, so we ended up with quirks in both kernel
* and userspace drivers to deal with the broken behavior.
* Simply fixing drm_mode_addfb() unconditionally would break
* these drivers, so add a quirk bit here to allow drivers
* opt-in.
*/
bool quirk_addfb_prefer_host_byte_order;
/**
* @async_page_flip: Does this device support async flips on the primary
* plane?
*/
bool async_page_flip;
/**
* @fb_modifiers_not_supported:
*
* When this flag is set, the DRM device will not expose modifier
* support to userspace. This is only used by legacy drivers that infer
* the buffer layout through heuristics without using modifiers. New
* drivers shall not set fhis flag.
*/
bool fb_modifiers_not_supported;
/**
* @normalize_zpos:
*
* If true the drm core will call drm_atomic_normalize_zpos() as part of
* atomic mode checking from drm_atomic_helper_check()
*/
bool normalize_zpos;
/**
* @modifiers_property: Plane property to list support modifier/format
* combination.
*/
struct drm_property *modifiers_property;
/* cursor size */
uint32_t cursor_width, cursor_height;
/**
* @suspend_state:
*
* Atomic state when suspended.
* Set by drm_mode_config_helper_suspend() and cleared by
* drm_mode_config_helper_resume().
*/
struct drm_atomic_state *suspend_state;
const struct drm_mode_config_helper_funcs *helper_private;
};
同样,我们只关系核心字段:
object_idr
:struct idr
数据数据结构,基于idr
(redix
树),为framebuffer
,crtc
、plane
等分配唯一id
;fb_list
:链表,用于存放所有的struct drm_framebuffer
;num_fb
:fb_list
链表的元素个数;connector_list
:链表,用于存放所有的struct drm_connector
;encoder_list
:链表,用于存放所有的struct drm_encoder
;num_encoder
:encoder_list
链表的元素个数;plane_list
:链表,用于存放所有的struct drm_plane
;num_total_plane
:plane_list
链表的元素个数;crtc_list
:链表,用于存放所有的struct drm_crtc
;num_crtc
:crtc_list
链表的元素个数;min_width
和min_height
:设备上支持的最小帧缓冲区像素宽度和高度;max_width
和max_height
:设备上支持的最大帧缓冲区像素宽度和高度;funcs
:由核心驱动程序提供的模式设置回调函数,struct drm_mode_config_funcs *
类型;cursor_width
和cursor_height
:向用户空间提供关于光标最大宽度和高度的提示;helper_private
:中间层私有数据,struct drm_mode_config_helper_funcs *
类型;
4.1.3 struct drm_mode_config_funcs
在struct drm_mode_config
结构体中存在一个类型为struct drm_mode_config_funcs
的回调函数funcs
.
drm_mode_config_func
是一个函数指针结构体,用于驱动程序向内核注册显示器模式配置(Mode Setting
)的回调函数。这些函数指针包括添加和删除连接器CRTC
和编解码器,以及更新显示模式等功能。
当内核需要对显示器模式进行配置或管理时,它将调用这些回调函数以执行相应操作。
struct drm_mode_config_funcs
定义在include/drm/drm_mode_config.h
:
点击查看代码
/**
* struct drm_mode_config_funcs - basic driver provided mode setting functions
*
* Some global (i.e. not per-CRTC, connector, etc) mode setting functions that
* involve drivers.
*/
struct drm_mode_config_funcs {
/**
* @fb_create:
*
* Create a new framebuffer object. The core does basic checks on the
* requested metadata, but most of that is left to the driver. See
* &struct drm_mode_fb_cmd2 for details.
*
* To validate the pixel format and modifier drivers can use
* drm_any_plane_has_format() to make sure at least one plane supports
* the requested values. Note that the driver must first determine the
* actual modifier used if the request doesn't have it specified,
* ie. when (@mode_cmd->flags & DRM_MODE_FB_MODIFIERS) == 0.
*
* IMPORTANT: These implied modifiers for legacy userspace must be
* stored in struct &drm_framebuffer, including all relevant metadata
* like &drm_framebuffer.pitches and &drm_framebuffer.offsets if the
* modifier enables additional planes beyond the fourcc pixel format
* code. This is required by the GETFB2 ioctl.
*
* If the parameters are deemed valid and the backing storage objects in
* the underlying memory manager all exist, then the driver allocates
* a new &drm_framebuffer structure, subclassed to contain
* driver-specific information (like the internal native buffer object
* references). It also needs to fill out all relevant metadata, which
* should be done by calling drm_helper_mode_fill_fb_struct().
*
* The initialization is finalized by calling drm_framebuffer_init(),
* which registers the framebuffer and makes it accessible to other
* threads.
*
* RETURNS:
*
* A new framebuffer with an initial reference count of 1 or a negative
* error code encoded with ERR_PTR().
*/
struct drm_framebuffer *(*fb_create)(struct drm_device *dev,
struct drm_file *file_priv,
const struct drm_mode_fb_cmd2 *mode_cmd);
/**
* @get_format_info:
*
* Allows a driver to return custom format information for special
* fb layouts (eg. ones with auxiliary compression control planes).
*
* RETURNS:
*
* The format information specific to the given fb metadata, or
* NULL if none is found.
*/
const struct drm_format_info *(*get_format_info)(const struct drm_mode_fb_cmd2 *mode_cmd);
/**
* @output_poll_changed:
*
* Callback used by helpers to inform the driver of output configuration
* changes.
*
* Drivers implementing fbdev emulation use drm_kms_helper_hotplug_event()
* to call this hook to inform the fbdev helper of output changes.
*
* This hook is deprecated, drivers should instead use
* drm_fbdev_generic_setup() which takes care of any necessary
* hotplug event forwarding already without further involvement by
* the driver.
*/
void (*output_poll_changed)(struct drm_device *dev);
/**
* @mode_valid:
*
* Device specific validation of display modes. Can be used to reject
* modes that can never be supported. Only device wide constraints can
* be checked here. crtc/encoder/bridge/connector specific constraints
* should be checked in the .mode_valid() hook for each specific object.
*/
enum drm_mode_status (*mode_valid)(struct drm_device *dev,
const struct drm_display_mode *mode);
/**
* @atomic_check:
*
* This is the only hook to validate an atomic modeset update. This
* function must reject any modeset and state changes which the hardware
* or driver doesn't support. This includes but is of course not limited
* to:
*
* - Checking that the modes, framebuffers, scaling and placement
* requirements and so on are within the limits of the hardware.
*
* - Checking that any hidden shared resources are not oversubscribed.
* This can be shared PLLs, shared lanes, overall memory bandwidth,
* display fifo space (where shared between planes or maybe even
* CRTCs).
*
* - Checking that virtualized resources exported to userspace are not
* oversubscribed. For various reasons it can make sense to expose
* more planes, crtcs or encoders than which are physically there. One
* example is dual-pipe operations (which generally should be hidden
* from userspace if when lockstepped in hardware, exposed otherwise),
* where a plane might need 1 hardware plane (if it's just on one
* pipe), 2 hardware planes (when it spans both pipes) or maybe even
* shared a hardware plane with a 2nd plane (if there's a compatible
* plane requested on the area handled by the other pipe).
*
* - Check that any transitional state is possible and that if
* requested, the update can indeed be done in the vblank period
* without temporarily disabling some functions.
*
* - Check any other constraints the driver or hardware might have.
*
* - This callback also needs to correctly fill out the &drm_crtc_state
* in this update to make sure that drm_atomic_crtc_needs_modeset()
* reflects the nature of the possible update and returns true if and
* only if the update cannot be applied without tearing within one
* vblank on that CRTC. The core uses that information to reject
* updates which require a full modeset (i.e. blanking the screen, or
* at least pausing updates for a substantial amount of time) if
* userspace has disallowed that in its request.
*
* - The driver also does not need to repeat basic input validation
* like done for the corresponding legacy entry points. The core does
* that before calling this hook.
*
* See the documentation of @atomic_commit for an exhaustive list of
* error conditions which don't have to be checked at the in this
* callback.
*
* See the documentation for &struct drm_atomic_state for how exactly
* an atomic modeset update is described.
*
* Drivers using the atomic helpers can implement this hook using
* drm_atomic_helper_check(), or one of the exported sub-functions of
* it.
*
* RETURNS:
*
* 0 on success or one of the below negative error codes:
*
* - -EINVAL, if any of the above constraints are violated.
*
* - -EDEADLK, when returned from an attempt to acquire an additional
* &drm_modeset_lock through drm_modeset_lock().
*
* - -ENOMEM, if allocating additional state sub-structures failed due
* to lack of memory.
*
* - -EINTR, -EAGAIN or -ERESTARTSYS, if the IOCTL should be restarted.
* This can either be due to a pending signal, or because the driver
* needs to completely bail out to recover from an exceptional
* situation like a GPU hang. From a userspace point all errors are
* treated equally.
*/
int (*atomic_check)(struct drm_device *dev,
struct drm_atomic_state *state);
/**
* @atomic_commit:
*
* This is the only hook to commit an atomic modeset update. The core
* guarantees that @atomic_check has been called successfully before
* calling this function, and that nothing has been changed in the
* interim.
*
* See the documentation for &struct drm_atomic_state for how exactly
* an atomic modeset update is described.
*
* Drivers using the atomic helpers can implement this hook using
* drm_atomic_helper_commit(), or one of the exported sub-functions of
* it.
*
* Nonblocking commits (as indicated with the nonblock parameter) must
* do any preparatory work which might result in an unsuccessful commit
* in the context of this callback. The only exceptions are hardware
* errors resulting in -EIO. But even in that case the driver must
* ensure that the display pipe is at least running, to avoid
* compositors crashing when pageflips don't work. Anything else,
* specifically committing the update to the hardware, should be done
* without blocking the caller. For updates which do not require a
* modeset this must be guaranteed.
*
* The driver must wait for any pending rendering to the new
* framebuffers to complete before executing the flip. It should also
* wait for any pending rendering from other drivers if the underlying
* buffer is a shared dma-buf. Nonblocking commits must not wait for
* rendering in the context of this callback.
*
* An application can request to be notified when the atomic commit has
* completed. These events are per-CRTC and can be distinguished by the
* CRTC index supplied in &drm_event to userspace.
*
* The drm core will supply a &struct drm_event in each CRTC's
* &drm_crtc_state.event. See the documentation for
* &drm_crtc_state.event for more details about the precise semantics of
* this event.
*
* NOTE:
*
* Drivers are not allowed to shut down any display pipe successfully
* enabled through an atomic commit on their own. Doing so can result in
* compositors crashing if a page flip is suddenly rejected because the
* pipe is off.
*
* RETURNS:
*
* 0 on success or one of the below negative error codes:
*
* - -EBUSY, if a nonblocking updated is requested and there is
* an earlier updated pending. Drivers are allowed to support a queue
* of outstanding updates, but currently no driver supports that.
* Note that drivers must wait for preceding updates to complete if a
* synchronous update is requested, they are not allowed to fail the
* commit in that case.
*
* - -ENOMEM, if the driver failed to allocate memory. Specifically
* this can happen when trying to pin framebuffers, which must only
* be done when committing the state.
*
* - -ENOSPC, as a refinement of the more generic -ENOMEM to indicate
* that the driver has run out of vram, iommu space or similar GPU
* address space needed for framebuffer.
*
* - -EIO, if the hardware completely died.
*
* - -EINTR, -EAGAIN or -ERESTARTSYS, if the IOCTL should be restarted.
* This can either be due to a pending signal, or because the driver
* needs to completely bail out to recover from an exceptional
* situation like a GPU hang. From a userspace point of view all errors are
* treated equally.
*
* This list is exhaustive. Specifically this hook is not allowed to
* return -EINVAL (any invalid requests should be caught in
* @atomic_check) or -EDEADLK (this function must not acquire
* additional modeset locks).
*/
int (*atomic_commit)(struct drm_device *dev,
struct drm_atomic_state *state,
bool nonblock);
/**
* @atomic_state_alloc:
*
* This optional hook can be used by drivers that want to subclass struct
* &drm_atomic_state to be able to track their own driver-private global
* state easily. If this hook is implemented, drivers must also
* implement @atomic_state_clear and @atomic_state_free.
*
* Subclassing of &drm_atomic_state is deprecated in favour of using
* &drm_private_state and &drm_private_obj.
*
* RETURNS:
*
* A new &drm_atomic_state on success or NULL on failure.
*/
struct drm_atomic_state *(*atomic_state_alloc)(struct drm_device *dev);
/**
* @atomic_state_clear:
*
* This hook must clear any driver private state duplicated into the
* passed-in &drm_atomic_state. This hook is called when the caller
* encountered a &drm_modeset_lock deadlock and needs to drop all
* already acquired locks as part of the deadlock avoidance dance
* implemented in drm_modeset_backoff().
*
* Any duplicated state must be invalidated since a concurrent atomic
* update might change it, and the drm atomic interfaces always apply
* updates as relative changes to the current state.
*
* Drivers that implement this must call drm_atomic_state_default_clear()
* to clear common state.
*
* Subclassing of &drm_atomic_state is deprecated in favour of using
* &drm_private_state and &drm_private_obj.
*/
void (*atomic_state_clear)(struct drm_atomic_state *state);
/**
* @atomic_state_free:
*
* This hook needs driver private resources and the &drm_atomic_state
* itself. Note that the core first calls drm_atomic_state_clear() to
* avoid code duplicate between the clear and free hooks.
*
* Drivers that implement this must call
* drm_atomic_state_default_release() to release common resources.
*
* Subclassing of &drm_atomic_state is deprecated in favour of using
* &drm_private_state and &drm_private_obj.
*/
void (*atomic_state_free)(struct drm_atomic_state *state);
};
其中:
fb_create
:根据给定的framebuffer
参数,创建一个新的framebuffer object
(并不是分配内存,只是创建framebuffer object
,因为framebuffer
不涉及内存的分配与释放),并返回其句柄;get_format_info
:获取DRM
格式信息,返回的数据类型为struct drm_format_info
;
4.2 struct drm_driver
linux
内核使用struct drm_driver
数据结构来描述drm
驱动,该数据结构是drm
驱动的核心,drm
驱动程序通常会静态初始化一个drm_driver
结构体,其定义在include/drm/drm_drv.h
:
点击查看代码
/**
* struct drm_driver - DRM driver structure
*
* This structure represent the common code for a family of cards. There will be
* one &struct drm_device for each card present in this family. It contains lots
* of vfunc entries, and a pile of those probably should be moved to more
* appropriate places like &drm_mode_config_funcs or into a new operations
* structure for GEM drivers.
*/
struct drm_driver {
/**
* @load:
*
* Backward-compatible driver callback to complete initialization steps
* after the driver is registered. For this reason, may suffer from
* race conditions and its use is deprecated for new drivers. It is
* therefore only supported for existing drivers not yet converted to
* the new scheme. See devm_drm_dev_alloc() and drm_dev_register() for
* proper and race-free way to set up a &struct drm_device.
*
* This is deprecated, do not use!
*
* Returns:
*
* Zero on success, non-zero value on failure.
*/
int (*load) (struct drm_device *, unsigned long flags);
/**
* @open:
*
* Driver callback when a new &struct drm_file is opened. Useful for
* setting up driver-private data structures like buffer allocators,
* execution contexts or similar things. Such driver-private resources
* must be released again in @postclose.
*
* Since the display/modeset side of DRM can only be owned by exactly
* one &struct drm_file (see &drm_file.is_master and &drm_device.master)
* there should never be a need to set up any modeset related resources
* in this callback. Doing so would be a driver design bug.
*
* Returns:
*
* 0 on success, a negative error code on failure, which will be
* promoted to userspace as the result of the open() system call.
*/
int (*open) (struct drm_device *, struct drm_file *);
/**
* @postclose:
*
* One of the driver callbacks when a new &struct drm_file is closed.
* Useful for tearing down driver-private data structures allocated in
* @open like buffer allocators, execution contexts or similar things.
*
* Since the display/modeset side of DRM can only be owned by exactly
* one &struct drm_file (see &drm_file.is_master and &drm_device.master)
* there should never be a need to tear down any modeset related
* resources in this callback. Doing so would be a driver design bug.
*/
void (*postclose) (struct drm_device *, struct drm_file *);
/**
* @lastclose:
*
* Called when the last &struct drm_file has been closed and there's
* currently no userspace client for the &struct drm_device.
*
* Modern drivers should only use this to force-restore the fbdev
* framebuffer using drm_fb_helper_restore_fbdev_mode_unlocked().
* Anything else would indicate there's something seriously wrong.
* Modern drivers can also use this to execute delayed power switching
* state changes, e.g. in conjunction with the :ref:`vga_switcheroo`
* infrastructure.
*
* This is called after @postclose hook has been called.
*
* NOTE:
*
* All legacy drivers use this callback to de-initialize the hardware.
* This is purely because of the shadow-attach model, where the DRM
* kernel driver does not really own the hardware. Instead ownershipe is
* handled with the help of userspace through an inheritedly racy dance
* to set/unset the VT into raw mode.
*
* Legacy drivers initialize the hardware in the @firstopen callback,
* which isn't even called for modern drivers.
*/
void (*lastclose) (struct drm_device *);
/**
* @unload:
*
* Reverse the effects of the driver load callback. Ideally,
* the clean up performed by the driver should happen in the
* reverse order of the initialization. Similarly to the load
* hook, this handler is deprecated and its usage should be
* dropped in favor of an open-coded teardown function at the
* driver layer. See drm_dev_unregister() and drm_dev_put()
* for the proper way to remove a &struct drm_device.
*
* The unload() hook is called right after unregistering
* the device.
*
*/
void (*unload) (struct drm_device *);
/**
* @release:
*
* Optional callback for destroying device data after the final
* reference is released, i.e. the device is being destroyed.
*
* This is deprecated, clean up all memory allocations associated with a
* &drm_device using drmm_add_action(), drmm_kmalloc() and related
* managed resources functions.
*/
void (*release) (struct drm_device *);
/**
* @master_set:
*
* Called whenever the minor master is set. Only used by vmwgfx.
*/
void (*master_set)(struct drm_device *dev, struct drm_file *file_priv,
bool from_open);
/**
* @master_drop:
*
* Called whenever the minor master is dropped. Only used by vmwgfx.
*/
void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv);
/**
* @debugfs_init:
*
* Allows drivers to create driver-specific debugfs files.
*/
void (*debugfs_init)(struct drm_minor *minor);
/**
* @gem_create_object: constructor for gem objects
*
* Hook for allocating the GEM object struct, for use by the CMA
* and SHMEM GEM helpers. Returns a GEM object on success, or an
* ERR_PTR()-encoded error code otherwise.
*/
struct drm_gem_object *(*gem_create_object)(struct drm_device *dev,
size_t size);
/**
* @prime_handle_to_fd:
*
* Main PRIME export function. Should be implemented with
* drm_gem_prime_handle_to_fd() for GEM based drivers.
*
* For an in-depth discussion see :ref:`PRIME buffer sharing
* documentation <prime_buffer_sharing>`.
*/
int (*prime_handle_to_fd)(struct drm_device *dev, struct drm_file *file_priv,
uint32_t handle, uint32_t flags, int *prime_fd);
/**
* @prime_fd_to_handle:
*
* Main PRIME import function. Should be implemented with
* drm_gem_prime_fd_to_handle() for GEM based drivers.
*
* For an in-depth discussion see :ref:`PRIME buffer sharing
* documentation <prime_buffer_sharing>`.
*/
int (*prime_fd_to_handle)(struct drm_device *dev, struct drm_file *file_priv,
int prime_fd, uint32_t *handle);
/**
* @gem_prime_import:
*
* Import hook for GEM drivers.
*
* This defaults to drm_gem_prime_import() if not set.
*/
struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev,
struct dma_buf *dma_buf);
/**
* @gem_prime_import_sg_table:
*
* Optional hook used by the PRIME helper functions
* drm_gem_prime_import() respectively drm_gem_prime_import_dev().
*/
struct drm_gem_object *(*gem_prime_import_sg_table)(
struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sgt);
/**
* @gem_prime_mmap:
*
* mmap hook for GEM drivers, used to implement dma-buf mmap in the
* PRIME helpers.
*
* This hook only exists for historical reasons. Drivers must use
* drm_gem_prime_mmap() to implement it.
*
* FIXME: Convert all drivers to implement mmap in struct
* &drm_gem_object_funcs and inline drm_gem_prime_mmap() into
* its callers. This hook should be removed afterwards.
*/
int (*gem_prime_mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
/**
* @dumb_create:
*
* This creates a new dumb buffer in the driver's backing storage manager (GEM,
* TTM or something else entirely) and returns the resulting buffer handle. This
* handle can then be wrapped up into a framebuffer modeset object.
*
* Note that userspace is not allowed to use such objects for render
* acceleration - drivers must create their own private ioctls for such a use
* case.
*
* Width, height and depth are specified in the &drm_mode_create_dumb
* argument. The callback needs to fill the handle, pitch and size for
* the created buffer.
*
* Called by the user via ioctl.
*
* Returns:
*
* Zero on success, negative errno on failure.
*/
int (*dumb_create)(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);
/**
* @dumb_map_offset:
*
* Allocate an offset in the drm device node's address space to be able to
* memory map a dumb buffer.
*
* The default implementation is drm_gem_create_mmap_offset(). GEM based
* drivers must not overwrite this.
*
* Called by the user via ioctl.
*
* Returns:
*
* Zero on success, negative errno on failure.
*/
int (*dumb_map_offset)(struct drm_file *file_priv,
struct drm_device *dev, uint32_t handle,
uint64_t *offset);
/**
* @dumb_destroy:
*
* This destroys the userspace handle for the given dumb backing storage buffer.
* Since buffer objects must be reference counted in the kernel a buffer object
* won't be immediately freed if a framebuffer modeset object still uses it.
*
* Called by the user via ioctl.
*
* The default implementation is drm_gem_dumb_destroy(). GEM based drivers
* must not overwrite this.
*
* Returns:
*
* Zero on success, negative errno on failure.
*/
int (*dumb_destroy)(struct drm_file *file_priv,
struct drm_device *dev,
uint32_t handle);
/** @major: driver major number */
int major;
/** @minor: driver minor number */
int minor;
/** @patchlevel: driver patch level */
int patchlevel;
/** @name: driver name */
char *name;
/** @desc: driver description */
char *desc;
/** @date: driver date */
char *date;
/**
* @driver_features:
* Driver features, see &enum drm_driver_feature. Drivers can disable
* some features on a per-instance basis using
* &drm_device.driver_features.
*/
u32 driver_features;
/**
* @ioctls:
*
* Array of driver-private IOCTL description entries. See the chapter on
* :ref:`IOCTL support in the userland interfaces
* chapter<drm_driver_ioctl>` for the full details.
*/
const struct drm_ioctl_desc *ioctls;
/** @num_ioctls: Number of entries in @ioctls. */
int num_ioctls;
/**
* @fops:
*
* File operations for the DRM device node. See the discussion in
* :ref:`file operations<drm_driver_fops>` for in-depth coverage and
* some examples.
*/
const struct file_operations *fops;
#ifdef CONFIG_DRM_LEGACY
/* Everything below here is for legacy driver, never use! */
/* private: */
int (*firstopen) (struct drm_device *);
void (*preclose) (struct drm_device *, struct drm_file *file_priv);
int (*dma_ioctl) (struct drm_device *dev, void *data, struct drm_file *file_priv);
int (*dma_quiescent) (struct drm_device *);
int (*context_dtor) (struct drm_device *dev, int context);
irqreturn_t (*irq_handler)(int irq, void *arg);
void (*irq_preinstall)(struct drm_device *dev);
int (*irq_postinstall)(struct drm_device *dev);
void (*irq_uninstall)(struct drm_device *dev);
u32 (*get_vblank_counter)(struct drm_device *dev, unsigned int pipe);
int (*enable_vblank)(struct drm_device *dev, unsigned int pipe);
void (*disable_vblank)(struct drm_device *dev, unsigned int pipe);
int dev_priv_size;
#endif
};
同样,该数据结构也包含了大量的成员,其中:
prime_handle_to_fd
:主要的PRIME
导出函数,对于基于GEM
的驱动程序,应使用drm_gem_prime_handle_to_fd
实现它,有关详细讨论,请参阅PRIME buffer sharing documentation
;prime_fd_to_handle
:主要的PRIME
导入函数,对于基于GEM的驱动程序,应使用drm_gem_prime_fd_to_handle
实现它,,有关详细讨论,请参阅PRIME buffer sharing documentation
;gem_prime_import_sg_table
:是PRIME helpers
函数drm_gem_prime_import
和drm_gem_prime_import_dev
使用的可选钩子;gem_prime_mmap
:用于在GEM
驱动程序中实现PRIME helpers
的dma-buf mmap
的mmap
钩子,这个钩子只是出于历史原因而存在,驱动程序必须使用drm_gem_prime_mmap
来实现它;name
:驱动名称;desc
:驱动的描述信息;date
:驱动的日期以YYYYMMDD
的格式表示,用于标识驱动程序的最新修改日期。然而,由于大多数驱动程序未及时更新它,所以它的值大多没有实际意义;major
:主版本号;minor
:次版本号;patchlevel
:补丁版本号;data
:驱动数据;driver_features
:一个标志位集合,用于指定在该驱动程序实例中允许的特定功能;比如:- 添加上
DRIVER_MODESET
标志位,告诉DRM Core
当前驱动支持kernel Mode Setting
操作; - 添加上
DRIVER_GEM
标志位,告诉DRM Core
该驱动支持GEM
操作; - 添加上
DRIVER_ATOMIC
标志位,告诉DRM Core
该驱动支持Atomic
操作。
- 添加上
fops
:DRM
设备节点文件操作集,比如我们对设备节点/dev/dri/card0
进行读写,就会调用相应的操作方法;dumb_create
:该函数用于在驱动程序的后备存储管理器(如GEM、TTM
或其他管理器)中创建一个新的dumb buffer
,并返回相应的缓冲区句柄;这个句柄可以用来创建一个framebuffer modeset
对象;
4.2.1 driver_feature
drm
驱动特征使用可以使用如下标识位:
DRIVER_GEM
:驱动程序使用GEM
内存管理器,这对于所有现代驱动程序都应该设置;DRIVER_MODESET
:驱动程序支持模式设置接口(KMS
);DRIVER_RENDER
:Driver supports dedicated render nodes. See also the section on render nodes for details.
DRIVER_ATOMIC
:Driver supports the full atomic modesetting userspace API. Drivers which only use atomic internally, but do not support the full userspace API (e.g. not all properties converted to atomic, or multi-plane updates are not guaranteed to be tear-free) should not set this flag.
DRIVER_SYNCOB
:Driver supports drm_syncobj for explicit synchronization of command submission.
DRIVER_SYNCOBJ_TIMELINE
:Driver supports the timeline flavor of drm_syncobj for explicit synchronization of command submission.
DRIVER_COMPUTE_ACCEL
:Driver supports compute acceleration devices. This flag is mutually exclusive with DRIVER_RENDER and DRIVER_MODESET. Devices that support both graphics and compute acceleration should be handled by two drivers that are connected using auxiliary bus.
DRIVER_USE_AGP
:Set up DRM AGP support, see drm_agp_init(), the DRM core will manage AGP resources. New drivers don't need this.
DRIVER_LEGACY
:Denote a legacy driver using shadow attach. Do not use.
DRIVER_PCI_DMA
:Driver is capable of PCI DMA, mapping of PCI DMA buffers to userspace will be enabled. Only for legacy drivers. Do not use.
DRIVER_SG
:Driver can perform scatter/gather DMA, allocation and mapping of scatter/gather buffers will be enabled. Only for legacy drivers. Do not use.
DRIVER_HAVE_DMA
:Driver supports DMA, the userspace DMA API will be supported. Only for legacy drivers. Do not use.
DRIVER_HAVE_IRQ
:Legacy irq support. Only for legacy drivers. Do not use.
4.2.2 dumb_create
dumb_create
是分配物理内存dumb buffer
的回调接口;那什么是dumb buffer
呢?
dumb buffer
代表了所有的绘图操作都由CPU
来完成的framebuffer
,更多细节可以参考文章《关于DRM
中DUMB
和PRIME
名字的由来》。
int (*dumb_create)(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);
在drm_mode_create_dumb
参数中指定了width
、height
和bpp
,dumb_create
函数需要为创建的dumb buffer
填充handle
、pitch
和size
,该回调函数是通过ioctl
由用户调用的。
主要完成三件事:
- 创建
gem objec
; - 创建
gem handle
; - 分配物理内存
dumb buffer
(可选的) ;
第1、第2 步是所有dumb_create
都必须实现的操作,而第3步则是可选的,对于一次性映射需要事先分配好所有的物理内存。
分配完物理内存后,就可以通过mmap
将分配好的物理内存映射到用户空间。
4.2.3 struct drm_file
DRM
文件私有数据是驱动程序在处理文件操作时使用的附加数据,在DRM
中每个打开的文件都与一个struct drm_file
实例关联:
struct drm_file {
bool authenticated;
bool stereo_allowed;
bool universal_planes;
bool atomic;
bool aspect_ratio_allowed;
bool writeback_connectors;
bool was_master;
bool is_master;
struct drm_master *master;
spinlock_t master_lookup_lock;
struct pid *pid;
u64 client_id;
drm_magic_t magic;
struct list_head lhead;
struct drm_minor *minor;
struct idr object_idr;
spinlock_t table_lock;
struct idr syncobj_idr;
spinlock_t syncobj_table_lock;
struct file *filp;
void *driver_priv;
struct list_head fbs;
struct mutex fbs_lock;
struct list_head blobs;
wait_queue_head_t event_wait;
struct list_head pending_event_list;
struct list_head event_list;
int event_space;
struct mutex event_read_lock;
struct drm_prime_file_private prime;
};
五、DRM core
模块入口
DRM core
模块入口函数为drm_core_init
,位于drivers/gpu/drm/drm_drv.c
;
static int __init drm_core_init(void)
{
int ret;
drm_connector_ida_init();
// IDR初始化
idr_init(&drm_minors_idr);
drm_memcpy_init_early();
// 创建class类drm_class,同时会在/sys/class/目录下创建一个新的文件夹drm
ret = drm_sysfs_init();
if (ret < 0) {
DRM_ERROR("Cannot create DRM class: %d\n", ret);
goto error;
}
// 在/sys/kernel/debug下创建dri目录
drm_debugfs_root = debugfs_create_dir("dri", NULL);
// 申请主设备号,同时初始化以及注册字符设备cdev(这里注册的字符设备数量为256),并将字符设备的ops和drm_stub_fops绑定在一起
ret = register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops);
if (ret < 0)
goto error;
ret = accel_core_init();
if (ret < 0)
goto error;
drm_privacy_screen_lookup_init();
// 设置标志位
drm_core_init_complete = true;
DRM_DEBUG("Initialized\n");
return 0;
error:
drm_core_exit();
return ret;
}
module_init(drm_core_init);
在DRM core
初始化函数中,主要进行了如下操作:
- 调用
drm_sysfs_init
创建class
类drm_class
,在/sys/class
目录下一个名称为drm
的文件夹; - 调用
debugfs_create_dir
在/sys/kernel/debug
下创建dri
目录; - 调用
register_chrdev
申请主设备号为DRM_MAJOR
(值为226),同时注册256个字符设备,并将字符设备的ops
和drm_stub_fops
绑定在一起;
5.1 drm_sysfs_init
drm_sysfs_init
定义在drivers/gpu/drm/drm_sysfs.c
,用于创建一个DRM class
类;
/**
* drm_sysfs_init - initialize sysfs helpers
*
* This is used to create the DRM class, which is the implicit parent of any
* other top-level DRM sysfs objects.
*
* You must call drm_sysfs_destroy() to release the allocated resources.
*
* Return: 0 on success, negative error code on failure.
*/
int drm_sysfs_init(void)
{
int err;
// 创建设备类,此函数的执行会在/sys/class/目录下创建一个新的文件夹drm
drm_class = class_create(THIS_MODULE, "drm");
if (IS_ERR(drm_class))
return PTR_ERR(drm_class);
err = class_create_file(drm_class, &class_attr_version.attr);
if (err) {
class_destroy(drm_class);
drm_class = NULL;
return err;
}
// 设置设备节点
drm_class->devnode = drm_devnode;
drm_sysfs_acpi_register();
return 0;
}
可以看到在drm_sysfs_init
函数中创建了class
类drm_class
,名称为drm
,并设置devnode
指向了drm_devnode
;
static char *drm_devnode(const struct device *dev, umode_t *mode)
{
// 设置dev下设备节点名称 /dev/dri/xxx
return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
}
那么udev
就会根据devnode
的返回值来决定创建的设备节点文件的相对路径。同时,udev
还会为这些设备节点文件设置相应的权限、所属用户和组等信息,以确保用户可以正确访问这些设备节点文件。
比如,在NanoPC-T4
开发板运行如下命令:
root@rk3399:~# ll /sys/class/drm
lrwxrwxrwx 1 root root 0 Mar 15 23:04 card0 -> ../../devices/platform/display-subsystem/drm/card0/
lrwxrwxrwx 1 root root 0 Mar 15 23:04 card0-HDMI-A-1 -> ../../devices/platform/display-subsystem/drm/card0/card0-HDMI-A-1/
lrwxrwxrwx 1 root root 0 Mar 15 23:04 card1 -> ../../devices/platform/ff9a0000.gpu/drm/card1/
lrwxrwxrwx 1 root root 0 Mar 15 23:04 renderD128 -> ../../devices/platform/ff9a0000.gpu/drm/renderD128/
-r--r--r-- 1 root root 4096 Mar 15 23:04 version
5.2 drm_stub_fops
字符设备文件操作集被设置为了drm_stub_fops
,其定义在drivers/gpu/drm/drm_drv.c
;
/*
* DRM Core
* The DRM core module initializes all global DRM objects and makes them
* available to drivers. Once setup, drivers can probe their respective
* devices.
* Currently, core management includes:
* - The "DRM-Global" key/value database
* - Global ID management for connectors
* - DRM major number allocation
* - DRM minor management
* - DRM sysfs class
* - DRM debugfs root
*
* Furthermore, the DRM core provides dynamic char-dev lookups. For each
* interface registered on a DRM device, you can request minor numbers from DRM
* core. DRM core takes care of major-number management and char-dev
* registration. A stub ->open() callback forwards any open() requests to the
* registered minor.
*/
static int drm_stub_open(struct inode *inode, struct file *filp)
{
const struct file_operations *new_fops;
struct drm_minor *minor;
int err;
DRM_DEBUG("\n");
// 根据设备节点获取struct drm_minor
minor = drm_minor_acquire(iminor(inode));
if (IS_ERR(minor))
return PTR_ERR(minor);
// 获取drm driver的文件操作集
new_fops = fops_get(minor->dev->driver->fops);
if (!new_fops) {
err = -ENODEV;
goto out;
}
// 用new_fops替换file->f_op
replace_fops(filp, new_fops);
// 执行设备的文件open函数
if (filp->f_op->open)
err = filp->f_op->open(inode, filp);
else
err = 0;
out:
drm_minor_release(minor);
return err;
}
static const struct file_operations drm_stub_fops = {
.owner = THIS_MODULE,
.open = drm_stub_open,
.llseek = noop_llseek,
};
当上层应用打开drm
设备时,通过drm
设备节点获取到drm_minor
。通过对文件指针进行重定向,打开真正的drm
设备的open
函数。
六、初始化drm
设备
drm_dev_init
函数用于初始化struct drm_device
实例,函数定义在drivers/gpu/drm/drm_drv.c
;
static int drm_dev_init(struct drm_device *dev,
const struct drm_driver *driver,
struct device *parent)
{
struct inode *inode;
int ret;
if (!drm_core_init_complete) {
DRM_ERROR("DRM core is not initialized\n");
return -ENODEV;
}
if (WARN_ON(!parent))
return -EINVAL;
// 初始化drm_device对象引用计数为1
kref_init(&dev->ref);
dev->dev = get_device(parent);
dev->driver = driver;
INIT_LIST_HEAD(&dev->managed.resources);
spin_lock_init(&dev->managed.lock);
/* no per-device feature limits by default */
dev->driver_features = ~0u;
if (drm_core_check_feature(dev, DRIVER_COMPUTE_ACCEL) &&
(drm_core_check_feature(dev, DRIVER_RENDER) ||
drm_core_check_feature(dev, DRIVER_MODESET))) {
DRM_ERROR("DRM driver can't be both a compute acceleration and graphics driver\n");
return -EINVAL;
}
drm_legacy_init_members(dev);
// 初始化各种链表头节点
INIT_LIST_HEAD(&dev->filelist);
INIT_LIST_HEAD(&dev->filelist_internal);
INIT_LIST_HEAD(&dev->clientlist);
INIT_LIST_HEAD(&dev->vblank_event_list);
INIT_LIST_HEAD(&dev->debugfs_list);
// 初始化各种锁
spin_lock_init(&dev->event_lock);
mutex_init(&dev->struct_mutex);
mutex_init(&dev->filelist_mutex);
mutex_init(&dev->clientlist_mutex);
mutex_init(&dev->master_mutex);
mutex_init(&dev->debugfs_mutex);
ret = drmm_add_action_or_reset(dev, drm_dev_init_release, NULL);
if (ret)
return ret;
inode = drm_fs_inode_new();
if (IS_ERR(inode)) {
ret = PTR_ERR(inode);
DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);
goto err;
}
dev->anon_inode = inode;
// 如果driver_feature设置了DRIVER_COMPUTE_ACCEL,不会进入
if (drm_core_check_feature(dev, DRIVER_COMPUTE_ACCEL)) {
ret = drm_minor_alloc(dev, DRM_MINOR_ACCEL);
if (ret)
goto err;
} else { // 如果driver_feature设置了DRIVER_RENDER,不会进入
if (drm_core_check_feature(dev, DRIVER_RENDER)) {
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
if (ret)
goto err;
}
// 正常走这里
ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err;
}
ret = drm_legacy_create_map_hash(dev);
if (ret)
goto err;
drm_legacy_ctxbitmap_init(dev);
// 如果driver_feature设置了DRIVER_GEM,会进入
if (drm_core_check_feature(dev, DRIVER_GEM)) {
ret = drm_gem_init(dev);
if (ret) {
DRM_ERROR("Cannot initialize graphics execution manager (GEM)\n");
goto err;
}
}
// 初始化dev->unique
ret = drm_dev_set_unique(dev, dev_name(parent));
if (ret)
goto err;
return 0;
err:
drm_managed_release(dev);
return ret;
}
这里咱们以RK3399
DRM
驱动为例,
static const struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
....
}
由于driver_features
并没有设定DRIVER_COMPUTE_ACCEL
、DRM_MINOR_RENDER
,因此并不会执行如下代码:
ret = drm_minor_alloc(dev, DRM_MINOR_ACCEL);
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
故而不会初始化drm_device
成员render
、accel
。
drm_dev_init
函数中会执行以下两个比较重要的步骤,下面我们依次介绍。
// 初始化dev->primary
ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);
...
ret = drm_gem_init(dev);
6.1 drm_minor_alloc
drm_minor_alloc
定义在drivers/gpu/drm/drm_drv.c
,用以动态分配一个struct drm_minor
,然后动态分配和初始化drm_minor
->kdev
,其最终目的是为了注册字符设备并创建设备节点/dev/dri/card%d
;
static int drm_minor_alloc(struct drm_device *dev, unsigned int type) // type传入DRM_MINOR_PRIMARY=0
{
struct drm_minor *minor;
unsigned long flags;
int r;
// 动态分配struct drm_minor
minor = drmm_kzalloc(dev, sizeof(*minor), GFP_KERNEL);
if (!minor)
return -ENOMEM;
minor->type = type;
minor->dev = dev;
idr_preload(GFP_KERNEL);
if (type == DRM_MINOR_ACCEL) { // 不会进入
r = accel_minor_alloc();
} else {
// 获取自旋锁+关中断
spin_lock_irqsave(&drm_minor_lock, flags);
// 基于基数树分配唯一id 区间位于64 * type~64 * (type + 1);由于type=0,所以次设备编号为0~1;
// 如果type=2,区间位于128~129
r = idr_alloc(&drm_minors_idr,
NULL,
64 * type,
64 * (type + 1),
GFP_NOWAIT);
// 释放自旋锁+开中断
spin_unlock_irqrestore(&drm_minor_lock, flags);
}
idr_preload_end();
if (r < 0)
return r;
// 设置次设备编号
minor->index = r;
r = drmm_add_action_or_reset(dev, drm_minor_alloc_release, minor);
if (r)
return r;
// 为minor分配并初始化一个struct device
minor->kdev = drm_sysfs_minor_alloc(minor);
if (IS_ERR(minor->kdev))
return PTR_ERR(minor->kdev);
// 初始化dev->primary=minor
*drm_minor_get_slot(dev, type) = minor;
return 0;
}
6.1.1 drm_sysfs_minor_alloc
drm_sysfs_minor_alloc
函数实际上主要就是为minor
分配并初始化一个struct device
,定义在drivers/gpu/drm/drm_sysfs.c
;
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
{
const char *minor_str;
struct device *kdev;
int r;
// 动态初始化struct device
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
if (!kdev)
return ERR_PTR(-ENOMEM);
// 初始化设备,这个函数是device_register函数的前半部分的实现,主要用于设备的初始化
device_initialize(kdev);
if (minor->type == DRM_MINOR_ACCEL) { // 不会进入
minor_str = "accel%d";
accel_set_device_instance_params(kdev, minor->index);
} else {
if (minor->type == DRM_MINOR_RENDER)
minor_str = "renderD%d";
else
minor_str = "card%d"; // 走这里
// 设置设备号 主设备号为226,次设备号为minor->index
kdev->devt = MKDEV(DRM_MAJOR, minor->index);
// 设置设备class
kdev->class = drm_class;
// 设备类型
kdev->type = &drm_sysfs_device_minor;
}
// 设置父设备
kdev->parent = minor->dev->dev;
kdev->release = drm_sysfs_release;
// 设置设备驱动数据为minor
dev_set_drvdata(kdev, minor);
// 设置设备名称为 card%d
r = dev_set_name(kdev, minor_str, minor->index);
if (r < 0)
goto err_free;
return kdev;
err_free:
put_device(kdev);
return ERR_PTR(r);
}
具体流程如下:
-
动态分配一个
struct device
; -
调用
device_initialize
初始化设备,这个函数是device_register
函数的前半部分的实现,主要用于设备的初始化;- 而
device_add
是在drm_minor_register
函数中调用,该函数执行完会在/sys/class/drm
创建card%d
文件,同时创建设备节点/dev/drm/card%d
;
- 而
-
初始化设备号、
class
、设备类型、父设备、设备名称等;
那drm_class
是在哪里创建的呢?drm_class
是在drm
驱动模块注册函数drm_core_init
中创建的。
6.1.2 drm_minor_get_slot
函数drm_minor_get_slot
定义在drivers/gpu/drm/drm_drv.c
;
/*
* DRM Minors
* A DRM device can provide several char-dev interfaces on the DRM-Major. Each
* of them is represented by a drm_minor object. Depending on the capabilities
* of the device-driver, different interfaces are registered.
*
* Minors can be accessed via dev->$minor_name. This pointer is either
* NULL or a valid drm_minor pointer and stays valid as long as the device is
* valid. This means, DRM minors have the same life-time as the underlying
* device. However, this doesn't mean that the minor is active. Minors are
* registered and unregistered dynamically according to device-state.
*/
static struct drm_minor **drm_minor_get_slot(struct drm_device *dev,
unsigned int type)
{
switch (type) {
case DRM_MINOR_PRIMARY:
return &dev->primary;
case DRM_MINOR_RENDER:
return &dev->render;
case DRM_MINOR_ACCEL:
return &dev->accel;
default:
BUG();
}
}
6.2 drm_gem_init
drm_gem_init
定义在drivers/gpu/drm/drm_gem.c
;
/**
* drm_gem_init - Initialize the GEM device fields
* @dev: drm_devic structure to initialize
*/
int
drm_gem_init(struct drm_device *dev)
{
struct drm_vma_offset_manager *vma_offset_manager;
mutex_init(&dev->object_name_lock);
idr_init_base(&dev->object_name_idr, 1);
vma_offset_manager = drmm_kzalloc(dev, sizeof(*vma_offset_manager),
GFP_KERNEL);
if (!vma_offset_manager) {
DRM_ERROR("out of memory\n");
return -ENOMEM;
}
dev->vma_offset_manager = vma_offset_manager;
drm_vma_offset_manager_init(vma_offset_manager,
DRM_FILE_PAGE_OFFSET_START,
DRM_FILE_PAGE_OFFSET_SIZE);
return drmm_add_action(dev, drm_gem_init_release, NULL);
}
七、注册drm
设备
drm_dev_register
函数向内核注册一个drm
设备,同时在用户空间创建drm
设备节点/dev/dri/card%d
,函数定义在drivers/gpu/drm/drm_drv.c
;
/**
* drm_dev_register - Register DRM device
* @dev: Device to register
* @flags: Flags passed to the driver's .load() function
*
* Register the DRM device @dev with the system, advertise device to user-space
* and start normal device operation. @dev must be initialized via drm_dev_init()
* previously.
*
* Never call this twice on any device!
*
* NOTE: To ensure backward compatibility with existing drivers method this
* function calls the &drm_driver.load method after registering the device
* nodes, creating race conditions. Usage of the &drm_driver.load methods is
* therefore deprecated, drivers must perform all initialization before calling
* drm_dev_register().
*
* RETURNS:
* 0 on success, negative error code on failure.
*/
int drm_dev_register(struct drm_device *dev, unsigned long flags)
{
const struct drm_driver *driver = dev->driver;
int ret;
// 如果没有配置load,则校验drm模式配置
if (!driver->load)
drm_mode_config_validate(dev);
WARN_ON(!dev->managed.final_kfree);
// 如果需要全局互斥锁,则获取互斥锁
if (drm_dev_needs_global_mutex(dev))
mutex_lock(&drm_global_mutex);
// 直接返回
ret = drm_minor_register(dev, DRM_MINOR_RENDER);
if (ret)
goto err_minors;
// 重点 注册dev->primary这个minor
ret = drm_minor_register(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err_minors;
// 直接返回
ret = drm_minor_register(dev, DRM_MINOR_ACCEL);
if (ret)
goto err_minors;
ret = create_compat_control_link(dev);
if (ret)
goto err_minors;
// 设备注册标志设置为true
dev->registered = true;
// 如果定义了load,会先执行load
if (driver->load) {
ret = driver->load(dev, flags);
if (ret)
goto err_minors;
}
// 如果设置了DRIVER_MODESET标志位,这里正常会执行
if (drm_core_check_feature(dev, DRIVER_MODESET))
// 注册plane、crtc、encoder、connector这4个drm_mode_object
drm_modeset_register_all(dev);
DRM_INFO("Initialized %s %d.%d.%d %s for %s on minor %d\n",
driver->name, driver->major, driver->minor,
driver->patchlevel, driver->date,
dev->dev ? dev_name(dev->dev) : "virtual device",
dev->primary ? dev->primary->index : dev->accel->index);
goto out_unlock;
err_minors:
remove_compat_control_link(dev);
drm_minor_unregister(dev, DRM_MINOR_ACCEL);
drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
drm_minor_unregister(dev, DRM_MINOR_RENDER);
out_unlock:
// 同理,释放互斥锁
if (drm_dev_needs_global_mutex(dev))
mutex_unlock(&drm_global_mutex);
return ret;
}
7.1 drm_mode_config_validate
drm_mode_config_validate
函数用于校验drm
模式配置,定义在drivers/gpu/drm/drm_mode_config.c
;
点击查看代码
void drm_mode_config_validate(struct drm_device *dev)
{
struct drm_encoder *encoder;
struct drm_crtc *crtc;
struct drm_plane *plane;
u32 primary_with_crtc = 0, cursor_with_crtc = 0;
unsigned int num_primary = 0;
if (!drm_core_check_feature(dev, DRIVER_MODESET))
return;
drm_for_each_encoder(encoder, dev)
fixup_encoder_possible_clones(encoder);
drm_for_each_encoder(encoder, dev) {
validate_encoder_possible_clones(encoder);
validate_encoder_possible_crtcs(encoder);
}
drm_for_each_crtc(crtc, dev) {
WARN(!crtc->primary, "Missing primary plane on [CRTC:%d:%s]\n",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_set,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_set func",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_set2,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_set2 func",
crtc->base.id, crtc->name);
WARN(crtc->cursor && crtc->funcs->cursor_move,
"[CRTC:%d:%s] must not have both a cursor plane and a cursor_move func",
crtc->base.id, crtc->name);
if (crtc->primary) {
WARN(!(crtc->primary->possible_crtcs & drm_crtc_mask(crtc)),
"Bogus primary plane possible_crtcs: [PLANE:%d:%s] must be compatible with [CRTC:%d:%s]\n",
crtc->primary->base.id, crtc->primary->name,
crtc->base.id, crtc->name);
WARN(primary_with_crtc & drm_plane_mask(crtc->primary),
"Primary plane [PLANE:%d:%s] used for multiple CRTCs",
crtc->primary->base.id, crtc->primary->name);
primary_with_crtc |= drm_plane_mask(crtc->primary);
}
if (crtc->cursor) {
WARN(!(crtc->cursor->possible_crtcs & drm_crtc_mask(crtc)),
"Bogus cursor plane possible_crtcs: [PLANE:%d:%s] must be compatible with [CRTC:%d:%s]\n",
crtc->cursor->base.id, crtc->cursor->name,
crtc->base.id, crtc->name);
WARN(cursor_with_crtc & drm_plane_mask(crtc->cursor),
"Cursor plane [PLANE:%d:%s] used for multiple CRTCs",
crtc->cursor->base.id, crtc->cursor->name);
cursor_with_crtc |= drm_plane_mask(crtc->cursor);
}
}
drm_for_each_plane(plane, dev) {
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
num_primary++;
}
WARN(num_primary != dev->mode_config.num_crtc,
"Must have as many primary planes as there are CRTCs, but have %u primary planes and %u CRTCs",
num_primary, dev->mode_config.num_crtc);
}
7.2 drm_minor_register
在drm_dev_register
函数中多次调用了drm_minor_register
,那drm_minor_register
函数是干嘛用的呢?
该函数的目的主要是用来注册drm_minor
的,会在/dev/dri
目录下创建card%d
设备节点;函数定义在drivers/gpu/drm/drm_drv.c
;
static int drm_minor_register(struct drm_device *dev, unsigned int type) // type以DRM_MINOR_PRIMAR为例
{
struct drm_minor *minor;
unsigned long flags;
int ret;
DRM_DEBUG("\n");
// dev->primary
minor = *drm_minor_get_slot(dev, type);
if (!minor) // 针对于DRM_MINOR_RENDER、DRM_MINOR_ACCEL会直接返回的
return 0;
if (minor->type == DRM_MINOR_ACCEL) { // 不会进入
accel_debugfs_init(minor, minor->index);
} else {
// 初始化debugfs
ret = drm_debugfs_init(minor, minor->index, drm_debugfs_root);
if (ret) {
DRM_ERROR("DRM: Failed to initialize /sys/kernel/debug/dri.\n");
goto err_debugfs;
}
}
// 注册minor->kdev设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/dri/card%d
ret = device_add(minor->kdev);
if (ret)
goto err_debugfs;
/* replace NULL with @minor so lookups will succeed from now on */
if (minor->type == DRM_MINOR_ACCEL) { // 不会进入
accel_minor_replace(minor, minor->index);
} else
// 获取自旋锁+关中断
spin_lock_irqsave(&drm_minor_lock, flags);
// 将minor与minor->index这个id关联起来,这样就可以通过id查找到minor
idr_replace(&drm_minors_idr, minor, minor->index);
// 释放自旋锁+开中断
spin_unlock_irqrestore(&drm_minor_lock, flags);
}
DRM_DEBUG("new minor registered %d\n", minor->index);
return 0;
err_debugfs:
drm_debugfs_cleanup(minor);
return ret;
}
主要进行了如下操作:
- 调用
drm_minor_get_slot
获取dev
->primary
这个minor
; - 调用
drm_debugfs_init
进行debugfs
的初始化工作; - 调用
device_add
注册minor->kdev
设备,这样在模块加载的时候,udev daemon
就会自动为我们创建设备节点文件/dev/dri/card%d
; - 调用
idr_replace
将minor
与minor
->index
这个id
关联起来,这样就可以在基数树中通过id
查找到minor
;
7.2.1 drm_minor_get_slot
通过drm_minor_get_slot
获取drm
->primary
这个minor
。
7.2.2 drm_debugfs_init
调用drm_debugfs_init
进行debugfs
的初始化工作,函数定义在drivers/gpu/drm/drm_debugfs.c
;
int drm_debugfs_init(struct drm_minor *minor, int minor_id,
struct dentry *root)
{
struct drm_device *dev = minor->dev;
struct drm_debugfs_entry *entry, *tmp;
char name[64];
// 初始化debugfs_list链表
INIT_LIST_HEAD(&minor->debugfs_list);
// 初始化互斥锁
mutex_init(&minor->debugfs_lock);
// 初始化name
sprintf(name, "%d", minor_id);
// 在debugfs以name命名的目录,父目录为drm_debugfs_root
minor->debugfs_root = debugfs_create_dir(name, root);
// 遍历drm_debugfs_list数组,为每个元素(struct drm_debugfs_info)创建struct drm_debugfs_entry,并添加到dev->debugfs_list
drm_debugfs_add_files(minor->dev, drm_debugfs_list, DRM_DEBUGFS_ENTRIES);
if (drm_drv_uses_atomic_modeset(dev)) {
drm_atomic_debugfs_init(minor);
}
if (drm_core_check_feature(dev, DRIVER_MODESET)) {
drm_framebuffer_debugfs_init(minor);
drm_client_debugfs_init(minor);
}
// 如果指定了debugfs_init回调函数,则执行
if (dev->driver->debugfs_init)
dev->driver->debugfs_init(minor);
// 遍历dev->debugfs_list链表
list_for_each_entry_safe(entry, tmp, &dev->debugfs_list, list) {
// 在debugfs创建以entry->file.name命名的文件,位于/sys/kernel/debug/dri/${name}目录下
debugfs_create_file(entry->file.name, 0444,
minor->debugfs_root, entry, &drm_debugfs_entry_fops);
list_del(&entry->list);
}
return 0;
}
drm_debugfs_root
在drm_core_init
函数中被初始化,定义在drivers/gpu/drm/drm_drv.c
,该函数会在/sys/kernel/debug
下创建dri
目录;
static int __init drm_core_init(void)
{
......
// 在/sys/kernel/debug下创建dri目录
drm_debugfs_root = debugfs_create_dir("dri", NULL);
......
}
drm_debugfs_list
是一个全局数组,定义在drivers/gpu/drm/drm_debugfs.c
,内容如下:
/***************************************************
* Initialization, etc.
**************************************************/
static int drm_name_info(struct seq_file *m, void *data)
{
struct drm_debugfs_entry *entry = m->private;
struct drm_device *dev = entry->dev;
struct drm_master *master;
mutex_lock(&dev->master_mutex);
master = dev->master;
seq_printf(m, "%s", dev->driver->name);
if (dev->dev)
seq_printf(m, " dev=%s", dev_name(dev->dev));
if (master && master->unique)
seq_printf(m, " master=%s", master->unique);
if (dev->unique)
seq_printf(m, " unique=%s", dev->unique);
seq_printf(m, "\n");
mutex_unlock(&dev->master_mutex);
return 0;
}
static int drm_clients_info(struct seq_file *m, void *data)
{
struct drm_debugfs_entry *entry = m->private;
struct drm_device *dev = entry->dev;
struct drm_file *priv;
kuid_t uid;
seq_printf(m,
"%20s %5s %3s master a %5s %10s\n",
"command",
"pid",
"dev",
"uid",
"magic");
/* dev->filelist is sorted youngest first, but we want to present
* oldest first (i.e. kernel, servers, clients), so walk backwardss.
*/
mutex_lock(&dev->filelist_mutex);
list_for_each_entry_reverse(priv, &dev->filelist, lhead) {
struct task_struct *task;
bool is_current_master = drm_is_current_master(priv);
rcu_read_lock(); /* locks pid_task()->comm */
task = pid_task(priv->pid, PIDTYPE_PID);
uid = task ? __task_cred(task)->euid : GLOBAL_ROOT_UID;
seq_printf(m, "%20s %5d %3d %c %c %5d %10u\n",
task ? task->comm : "<unknown>",
pid_vnr(priv->pid),
priv->minor->index,
is_current_master ? 'y' : 'n',
priv->authenticated ? 'y' : 'n',
from_kuid_munged(seq_user_ns(m), uid),
priv->magic);
rcu_read_unlock();
}
mutex_unlock(&dev->filelist_mutex);
return 0;
}
static int drm_gem_one_name_info(int id, void *ptr, void *data)
{
struct drm_gem_object *obj = ptr;
struct seq_file *m = data;
seq_printf(m, "%6d %8zd %7d %8d\n",
obj->name, obj->size,
obj->handle_count,
kref_read(&obj->refcount));
return 0;
}
static int drm_gem_name_info(struct seq_file *m, void *data)
{
struct drm_debugfs_entry *entry = m->private;
struct drm_device *dev = entry->dev;
seq_printf(m, " name size handles refcount\n");
mutex_lock(&dev->object_name_lock);
idr_for_each(&dev->object_name_idr, drm_gem_one_name_info, m);
mutex_unlock(&dev->object_name_lock);
return 0;
}
static const struct drm_debugfs_info drm_debugfs_list[] = {
{"name", drm_name_info, 0}, // 依次为name、show、data
{"clients", drm_clients_info, 0},
{"gem_names", drm_gem_name_info, DRIVER_GEM},
};
drm_minor_register
函数会遍历drm_debugfs_list
数组,依次为每个成员在/sys/kernel/debug/dri/${name}
目录下创建以name
为名称的文件,在对文件进行打开操作时会执行相应的show
方法;
比如,在NanoPC-T4
开发板运行如下命令:
root@rk3399:~# ll /sys/kernel/debug/dri/
drwxr-xr-x 5 root root 0 Jan 1 1970 0/
drwxr-xr-x 2 root root 0 Jan 1 1970 1/
drwxr-xr-x 2 root root 0 Jan 1 1970 128/
root@rk3399:~# ll /sys/kernel/debug/dri/0/
drwxr-xr-x 2 root root 0 Jan 1 1970 HDMI-A-1/
-r--r--r-- 1 root root 0 Jan 1 1970 clients
drwxr-xr-x 3 root root 0 Jan 1 1970 crtc-0/
drwxr-xr-x 3 root root 0 Jan 1 1970 crtc-1/
-r--r--r-- 1 root root 0 Jan 1 1970 framebuffer
-r--r--r-- 1 root root 0 Jan 1 1970 gem_names
-r--r--r-- 1 root root 0 Jan 1 1970 internal_clients
-r--r--r-- 1 root root 0 Jan 1 1970 name
-r--r--r-- 1 root root 0 Jan 1 1970 state
root@rk3399:~# ll /sys/kernel/debug/dri/1
-r--r--r-- 1 root root 0 Jan 1 1970 clients
-r--r--r-- 1 root root 0 Jan 1 1970 gem_names
-r--r--r-- 1 root root 0 Jan 1 1970 name
root@rk3399:~# ll /sys/kernel/debug/dri/128/
-r--r--r-- 1 root root 0 Jan 1 1970 clients
-r--r--r-- 1 root root 0 Jan 1 1970 gem_names
-r--r--r-- 1 root root 0 Jan 1 1970 name
当调用drm_minor_register(dev,xxx)
时,如果minor
->index
=0,在会创建/sys/kernel/debug/dri/0
目录以及文件clients
、gem_names
、name
;
root@rk3399:~# cat /sys/kernel/debug/dri/0/name
rockchip dev=display-subsystem master=display-subsystem unique=display-subsystem
root@rk3399:~# cat /sys/kernel/debug/dri/0/clients
command pid dev master a uid magic
systemd-logind 3657 0 y y 0 0
Xorg 4148 0 n y 0 1
root@rk3399:~# cat /sys/kernel/debug/dri/0/gem_names
name size handles refcount
7.2.3 device_add
调用device_add(minor->kdev)
注册minor
->kdev
设备,这样在模块加载的时候,udev daemon
就会自动为我们创建设备节点文件/dev/dri/card%d
,其中minor
->kdev
设备的class
被设置为了drm_class
;
root@rk3399:~# ll /dev/dri/card0
crw-rw---- 1 root video 226, 0 Sep 9 22:10 /dev/dri/card0
root@rk3399:~# ll /sys/class/drm/card0
lrwxrwxrwx 1 root root 0 Mar 15 23:04 /sys/class/drm/card0 -> ../../devices/platform/display-subsystem/drm/card0/
root@rk3399:~# ll /sys/class/drm/card0/
card0-HDMI-A-1/ device/ subsystem/
dev power/ uevent
7.3 drm_modeset_register_all
drm_modeset_register_all
用于注册plane
、crtc
、encoder
、connector
这4个drm_mode_object
,函数定义在drivers/gpu/drm/drm_mode_config.c
;
int drm_modeset_register_all(struct drm_device *dev)
{
int ret;
ret = drm_plane_register_all(dev);
if (ret)
goto err_plane;
ret = drm_crtc_register_all(dev);
if (ret)
goto err_crtc;
ret = drm_encoder_register_all(dev);
if (ret)
goto err_encoder;
ret = drm_connector_register_all(dev);
if (ret)
goto err_connector;
drm_debugfs_late_register(dev);
return 0;
err_connector:
drm_encoder_unregister_all(dev);
err_encoder:
drm_crtc_unregister_all(dev);
err_crtc:
drm_plane_unregister_all(dev);
err_plane:
return ret;
}
有关drm_xxx_register_all
函数的具体实现我们在后面章节一一介绍。
八、模式配置初始化
drm_mode_config_init
用于初始化drm_device
的mode_config
结构体,函数定义在include/drm/drm_mode_config.h
;
/**
* drm_mode_config_init - DRM mode_configuration structure initialization
* @dev: DRM device
*
* This is the unmanaged version of drmm_mode_config_init() for drivers which
* still explicitly call drm_mode_config_cleanup().
*
* FIXME: This function is deprecated and drivers should be converted over to
* drmm_mode_config_init().
*/
static inline int drm_mode_config_init(struct drm_device *dev)
{
return drmm_mode_config_init(dev);
}
drmm_mode_config_init
定义在drivers/gpu/drm/drm_mode_config.c
;
/**
* drmm_mode_config_init - managed DRM mode_configuration structure
* initialization
* @dev: DRM device
*
* Initialize @dev's mode_config structure, used for tracking the graphics
* configuration of @dev.
*
* Since this initializes the modeset locks, no locking is possible. Which is no
* problem, since this should happen single threaded at init time. It is the
* driver's problem to ensure this guarantee.
*
* Cleanup is automatically handled through registering drm_mode_config_cleanup
* with drmm_add_action().
*
* Returns: 0 on success, negative error value on failure.
*/
int drmm_mode_config_init(struct drm_device *dev)
{
int ret;
mutex_init(&dev->mode_config.mutex);
drm_modeset_lock_init(&dev->mode_config.connection_mutex);
mutex_init(&dev->mode_config.idr_mutex);
mutex_init(&dev->mode_config.fb_lock);
mutex_init(&dev->mode_config.blob_lock);
INIT_LIST_HEAD(&dev->mode_config.fb_list);
INIT_LIST_HEAD(&dev->mode_config.crtc_list);
INIT_LIST_HEAD(&dev->mode_config.connector_list);
INIT_LIST_HEAD(&dev->mode_config.encoder_list);
INIT_LIST_HEAD(&dev->mode_config.property_list);
INIT_LIST_HEAD(&dev->mode_config.property_blob_list);
INIT_LIST_HEAD(&dev->mode_config.plane_list);
INIT_LIST_HEAD(&dev->mode_config.privobj_list);
idr_init_base(&dev->mode_config.object_idr, 1);
idr_init_base(&dev->mode_config.tile_idr, 1);
ida_init(&dev->mode_config.connector_ida);
spin_lock_init(&dev->mode_config.connector_list_lock);
init_llist_head(&dev->mode_config.connector_free_list);
INIT_WORK(&dev->mode_config.connector_free_work, drm_connector_free_work_fn);
ret = drm_mode_create_standard_properties(dev);
if (ret) {
drm_mode_config_cleanup(dev);
return ret;
}
/* Just to be sure */
dev->mode_config.num_fb = 0;
dev->mode_config.num_connector = 0;
dev->mode_config.num_crtc = 0;
dev->mode_config.num_encoder = 0;
dev->mode_config.num_total_plane = 0;
if (IS_ENABLED(CONFIG_LOCKDEP)) {
struct drm_modeset_acquire_ctx modeset_ctx;
struct ww_acquire_ctx resv_ctx;
struct dma_resv resv;
int ret;
dma_resv_init(&resv);
drm_modeset_acquire_init(&modeset_ctx, 0);
ret = drm_modeset_lock(&dev->mode_config.connection_mutex,
&modeset_ctx);
if (ret == -EDEADLK)
ret = drm_modeset_backoff(&modeset_ctx);
ww_acquire_init(&resv_ctx, &reservation_ww_class);
ret = dma_resv_lock(&resv, &resv_ctx);
if (ret == -EDEADLK)
dma_resv_lock_slow(&resv, &resv_ctx);
dma_resv_unlock(&resv);
ww_acquire_fini(&resv_ctx);
drm_modeset_drop_locks(&modeset_ctx);
drm_modeset_acquire_fini(&modeset_ctx);
dma_resv_fini(&resv);
}
return drmm_add_action_or_reset(dev, drm_mode_config_init_release,
NULL);
}
九、drm_xxx_init
drm_xxx_init
则分别用于初始化framebuffer
、plane
、crtc
、encoder
、connector
这5个 drm_mode_object
。
具体实现函数drm_framebuffer_init
、drm_universal_plane_init
、drm_crtc_init_with_planes
、drm_encoder_init
、drm_connector_init
我们在后面章节单独介绍。
十、总结
下面我们使用一张图来将DRM core
模块入口、drm
设备初始化、drm
设备注册等流程整合起来;
参考文章
[1] DRM (Direct Rendering Manager)
[2] Wiki: Direct Rendering Manager
[3] Linux graphic subsystem(2)_DRI
介绍
[4] The DRM/KMS subsystem from a newbie’s point of view
[5] Linux
环境下的图形系统和AMD R600
显卡编程(1)
[7] The DRM/KMS subsystem from a newbie’s point of view
[10] DRM
驱动程序开发(开篇)
[11] DRM
驱动程序开发(VKMS
)
[12] MIPI
自学笔记
[13] DRM
的GEM
[14] DRM
驱动mmap
详解:(二)CMA Helper