注:文章都是通过阅读各位前辈总结的资料 Android 11.0 && Linux(Kernel 4.19)Rockchip平台源码、加上自己的思考分析总结出来的,其中难免有理解不对的地方,欢迎大家批评指正。文章为个人学习、研究、欣赏之用,图文内容整理自互联网,如有侵权,请联系删除(◕‿◕),转载请注明出处(©Rockchip ©Android @Linux 版权所有),谢谢。

(==文章基于 Kernel-4.19==)&&(==文章基于 Android 11.0==)

【zhoujinjian.com博客原图链接】

【开发板 RockPi4bPlusV1.6】

【开发板 RockPi4bPlusV1.6 Android 11.0 && Linux(Kernel 4.19)源码链接】:(repo init -u https://github.com/radxa/manifests.git -b Android11_Radxa_rk11.1 -m rockchip-r-release.xml)

【开发板 RockPi4bPlusV1.6 Android 11.0 && Linux(Kernel 4.19)编译指南】

正是由于前人(各位大神)的分析和总结,帮助我节约了大量的时间和精力,特别感谢,由于不喜欢图片水印,去除了水印,敬请谅解!!!

本文转自Rockchip RK3399 - DRM子系统 ,如有侵权,请联系删除。


开发板 :ROCK Pi 4B+开发板
eMMC32GB
LPDDR44GB
显示屏 :7英寸HDMI接口显示屏
u-boot2017.09
linux4.19


从今天起我们开始接触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驱动

相比FBFramebuffer)架构,DRM更能适应当前日益更新的显示硬件;

  • 提供一种分离的图形驱动架构,将硬件驱动程序、内核模块和用户空间驱动程序进行分离;
  • 支持多个应用程序同时访问显卡,并提供了更丰富的图形功能,例如硬件加速和3D加速;
  • 提供了一些内核接口,可以让用户空间应用程序与驱动程序进行交互;
  • 支持多显示器(Display)和多GPU的配置;

总之,一句话,DRMLinux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬。尽管FB退出历史舞台,但是并未将其遗弃,而是集合到DRM中,供部分嵌入式设备使用。

有关DRM的发展历史可以参考这篇博客:DRM (Direct Rendering Manager) 的发展历史

1.2 DRM框架

我们来看一下DRM子系统的软件架构:

DRM框架从上到下依次为应用程序、libdrmDRM driverHW

(1) 应用程序:上图中并没有画出;应用程序可以直接操纵DRMioctl进行显示相关操作,后来封装成了libdrm库,让用户可以更加方便的进行显示控制;

(2) libdrmlbdrmDRM框架提供的位于用户空间操作DRM的库,提供了DRM驱动的用户空间接口;对底层接口进行封装,向上层应用程序提供通用的API接口,本质上是对各种ioctl接口进行封装;

(3) DRM coreDRM核心层,由GEMKMS组成;

  • KMSKernel Mode Setting,所谓内核显示模式设置,其实说白了就两件事:更新画面和设置显示参数;
    • 更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置;
    • 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等;
  • GEMGraphic Execution Manager(图形执行管理器),它提供了一种抽象的显存管理方式,使用户空间应用程序可以更方便地管理显存,而不需要了解底层硬件的细节;
    • 实际上,在DRM中包含两个内存管理器,TTMTranslation Table Manager)和GEMGraphic Execution Manager),TTM是第一个开发的DRM内存管理器,关于TTM我们就不做过多的介绍了,知道有这么一个东西就好了。

(4) HW:硬件设备;

1.2.1 KMS

KMS主要负责显示相关功能,在DRM中将其进行抽象,包括:CRTCENCODERCONNECTORPLANEFramebufferVBLANKproperty;它们之间的关系如下图所示:

HDMI接口为例说明,Soc内部一般包含一个Display模块,通过总线连接到HDMI接口上;

  • Display模块对应CRTC
  • HDMI接口对应Connector
  • Framebuffer对应的是显存部分;
  • Plane是对Framebuffer进行描述的部分;
  • Encoder是将像素转化为HDMI接口所需要的信号,一般EncoderConnector放到一块初始化。
1.2.2 GEM

GEM主要负责显示buffer的分配和释放,在DRM中将其进行抽象,包括:DUMPPRIMEfence

1.2.3 元素介绍

学习DRM驱动其实就是学习上面各个元素的实现及用法,如果你能掌握这些知识点,那么在编写DRM驱动的时候就能游刃有余。

元素 说明
CRTC Framebuffer中读取待显示的图像,并按照响应的格式输出给encoder,其主要承担的作用为
(1)配置适合显示的显示模式、分辨率、刷新率等参数,并输出相应的时序;
(2)扫描Framebuffer发送到一个或多个显示器;
(3)更新Framebuffer
概括下就是,对显示器进行扫描,产生时序信号的模块、负责帧切换、电源控制、色彩调整等等。
Encoder 编码器。它的作用就是将内存的pixel像素编码(转换)为显示器所需要的信号。
简单理解就是,如果需要将画面显示到不同的设备(Display Device)上,需要将画面转化为不同的电信号,例如DVIDVGAYPbPrCVBSMIPIeDP 等。
EncoderCRTC之间的交互就是我们所说的Mode Setting,其中包含了前面提到的色彩模式、还有时序(Timing)等
Connector 连接器。它常常对应于物理连接器 (例如VGA,DVI, FPD-Link, HDMI, DisplayPort, S-Video等) ,它不是指物理线。
DRM中,Connector 是一个抽象的数据结构,代表连接的显示设备,从Connector中可以得到当前物理连接的输出设备相关的信息 ;例如连接状态,EDID数据,DPMS状态、支持的视频模式等
Plane 图层,实际输出的图像是多个图层叠加而成的,比如主图层、光标图层。其中有些图层由硬件加速模块生成,每个CRTC至少一个plane
plane一共有三种,分别是:DRM_PLANE_TYPE_PRIMARYDRM_PLANE_TYPE_OVERLAYDRM_PLANE_TYPE_CURSOR。这是配置plane的三个枚举,标注主图层、覆盖图层、光标图层;
Framebuffer Framebuffer,用于存储单个图层(Plane)要实现的内容
它是一块内存区域,可以理解为一块画布,驱动程序和应用都能访问它。绘画前需要将它格式化,设定绘制的色彩模式(例如RGB888YUV等)和画布的大小(分辨率)
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目录下,这下面的文件还是比较多的,我们大概了解一下即可;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/samba/josh/work1/new/rockpi4bA11/kernel$ ls drivers/gpu/drm/ -I "*.o"
amd drm_dp_aux_dev.c drm_legacy.h drm_vma_manager.c rcar-du
arc drm_dp_cec.c drm_lock.c drm_vm.c rockchip
arm drm_dp_dual_mode_helper.c drm_memory.c drm_writeback.c savage
armada drm_dp_helper.c drm_mipi_dsi.c etnaviv scheduler
ast drm_dp_mst_topology.c drm_mm.c exynos selftests
ati_pcigart.c drm_drv.c drm_mode_config.c fsl-dcu shmobile
atmel-hlcdc drm_dsc.c drm_mode_object.c gma500 sis
bochs drm_dumb_buffers.c drm_modes.c hisilicon sti
bridge drm_edid.c drm_modeset_helper.c i2c stm
built-in.a drm_edid_load.c drm_modeset_lock.c i810 sun4i
cirrus drm_encoder.c drm_of.c i915 tdfx
drm_agpsupport.c drm_encoder_slave.c drm_panel.c imx tegra
drm_atomic.c drm_fb_cma_helper.c drm_panel_orientation_quirks.c Kconfig tilcdc
drm_atomic_helper.c drm_fb_helper.c drm_pci.c lib tinydrm
drm_auth.c drm_file.c drm_plane.c Makefile ttm
drm_blend.c drm_flip_work.c drm_plane_helper.c mediatek tve200
drm_bridge.c drm_fourcc.c drm_prime.c meson udl
drm_bufs.c drm_framebuffer.c drm_print.c mga v3d
drm_cache.c drm_gem.c drm_probe_helper.c mgag200 vc4
drm_client.c drm_gem_cma_helper.c drm_property.c modules.builtin vgem
drm_color_mgmt.c drm_gem_framebuffer_helper.c drm_rect.c modules.order via
drm_connector.c drm_global.c drm_scatter.c msm virtio
drm_context.c drm_hashtab.c drm_scdc_helper.c mxsfb vkms
drm_crtc.c drm_info.c drm_simple_kms_helper.c nouveau vmwgfx
drm_crtc_helper.c drm_internal.h drm_sync_helper.c omapdrm xen
drm_crtc_helper_internal.h drm_ioc32.c drm_syncobj.c panel zte
drm_crtc_internal.h drm_ioctl.c drm_sysfs.c pl111
drm_debugfs.c drm_irq.c drm_trace.h qxl
drm_debugfs_crc.c drm_kms_helper_common.c drm_trace_points.c r128
drm_dma.c drm_lease.c drm_vblank.c radeon

其中:

  • drm_drv.cDRM core核心实现;

  • drm_gem.c:提供了GEM相关的API

其中rockchipRockchip官方的实现代码:

1
2
3
4
5
6
7
8
9
10
/samba/josh/work1/new/rockpi4bA11/kernel$ ls drivers/gpu/drm/rockchip/ -I "*.o"
analogix_dp-rockchip.c ebc-dev rk618 rockchip_drm_fb.h rockchip_drm_vop.h
built-in.a inno_hdmi.c rk628 rockchip_drm_gem.c rockchip_drm_vvop.c
cdn-dp-core.c inno_hdmi.h rockchip_drm_backlight.c rockchip_drm_gem.h rockchip_lvds.c
cdn-dp-core.h Kconfig rockchip_drm_backlight.h rockchip_drm_psr.c rockchip-mipi-csi-tx.c
cdn-dp-link-training.c Makefile rockchip_drm_drv.c rockchip_drm_psr.h rockchip-mipi-csi-tx.h
cdn-dp-reg.c modules.builtin rockchip_drm_drv.h rockchip_drm_tve.c rockchip_rgb.c
cdn-dp-reg.h modules.order rockchip_drm_fb.c rockchip_drm_tve.h rockchip_vop2_reg.c
dw_hdmi-rockchip.c rk3066_hdmi.c rockchip_drm_fbdev.c rockchip_drm_vop2.c rockchip_vop_reg.c
dw-mipi-dsi.c rk3066_hdmi.h rockchip_drm_fbdev.h rockchip_drm_vop.c rockchip_vop_reg.h

二、硬件抽象

对于初学者来说,往往让人迷惑的不是DRMobjects的概念,而是如何去建立这些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来获取LCDmode信息,但是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。至于为什么要放在LCDC驱动中实现,纯粹只是为了省事而已,你也可以放在一个虚拟的平台驱动中去实现该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接口也就是我们平时常说的MCUSPI接口屏,这类屏的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进行一番介绍,因为这些objectsDRM框架的核心,它们缺一不可。

上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为drm_mode_object(或者说是modset object),虚线以下为drm_gem_object(或者说是gem objec)。

这些objects之间的关系:

通过上图可以看到,plane是连接framebuffercrtc的纽带,而encoder则是连接crtcconnector的纽带。与物理buffer直接打交道的是gem而不是framebuffer

个人理解:

  • buffer是硬件存储设备, 由gem分配和释放;
  • framebuffer用于描述分配的显存的信息(如formatpitchsize等);
  • plane用于描述图层信息,同一个crtc可以由多个plane组成;
  • crtc控制显卡输出图像信号;
  • encodercrtc输出的图像信号转换成一定格式的数字信号,如HDMIDisplayPortMIPI等;
  • connector用于将encoder输出的信号传递给显示器,并与显示器建立连接;

需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些objects,否则DRM子系统无法正常运行。

3.1 drm_panel

encoder驱动程序负责将图形数据转换为LCD显示器所需的视频信号,而connector驱动程序则负责将这些信号发送到正确的显示设备上。LCD驱动程序需要和encoderconnector这两个驱动程序进行交互,以完成图形输出的控制。

img

耦合的产生:

  • connector的主要作用就是获取显示参数,所以会在LCD驱动中去构造connector object。但是 connector初始化时需要attach上一个encoder object,而这个encoder object往往是在另一个硬件驱动中生成的,为了访问该encoder object,势必会产生一部分耦合的代码;
  • encoder除了扮演信号转换的角色,还担任着通知显示设备休眠唤醒的角色。因此,当encoder通知LCD驱动执行相应的enable/disable操作时,就一定会调用LCD驱动导出的全局函数,这也必然会产生一部分的耦合代码;

为了解决该耦合的问题,DRM子系统为开发人员提供了drm_panel结构体,该结构体封装了connector & encoderLCD访问的常用接口;

在这里插入图片描述

于是,原来的encoder驱动和LCD驱动之间的耦合,就转变成了上图中encoder驱动与drm_paneldrm_panelLCD驱动之间的“耦合”,从而实现了encoder驱动与LCD驱动之间的解耦合。

drm_panel不属于objects的范畴,它只是一堆回调函数的集合。但它的存在降低了LCD驱动与encoder驱动之间的耦合度。

3.2 modeset object

对于planecrtcencoderconnector几个对象,它们有一个公共基类struct drm_mode_object,这几个对象都由此基类扩展而来(该类作为crtc等结构体的成员)。事实上这个基类扩展出来的子类并不是只有上面提到的几种,只不过这四种比较常见。其定义在include/drm/drm_mode_object.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 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_objectstruct drm_object_propertiesstruct drm_property数据结构的关系,我们绘制了如下关系图:

3.2.1 对象类型

type主要包含以下几种类型,定义在include/uapi/drm/drm_mode.h

1
2
3
4
5
6
7
8
9
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 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];
};

该结构体包含以下字段:

  • countproperties数组长度,必须小于或等于DRM_OBJECT_MAX_PROPERTY(值为24);
  • properties:指向drm_property的指针数组;
  • values:用于存储属性值的数组,与properties匹配;

其中struct drm_property定义在include/drm/drm_property.h:

View Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* 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

View Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/**
* 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_getdrm_dev_put获取和释放进一步的引用计数;
  • dev:设备驱动模型中的device,可以将drm_device看做其子类;
  • driverdrm驱动;
  • registered:设备是否已注册;
  • unique:设备的唯一名称;
  • vblank_event_listvblank事件链表;
  • num_crtcsCRTC的数量;
  • debugfs_list:保存struct drm_debugfs_entry的链表;
  • mode_config:当前的显示模式配置,struct drm_mode_config类型。
4.1.1 struct drm_minor

struct drm_device数据结构中,primaryrenderaccel字段都是struct drm_minor类型。

这个数据结构和我们在ALSA中介绍的 struct snd_minor是非常相似的,其创建和注册分别通过drm_minor_allocdrm_minor_register实现。

struct drm_minor定义在include/drm/drm_file.hDRM core会根据driver_features来决定是否为drm_device中的primaryrenderaccel注册字符设备(同时在/dev/dri目录下创建相应的设备节点),比如/dev/dri/card0/dev/dri/renderD128等;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* 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:次设备号;
  • typedrm设备类型;
  • kdev:设备驱动模型中的device
  • devdrm设备;
  • debugfs_rootdeugfs目录项;
  • debugfs_list:与debugfs有关的链表,链表中存放的都是struct drm_debugfs_entry

struct drm_debugfs_entry定义在include/drm/drm_debugfs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 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

View Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/**
* 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_idrstruct idr数据数据结构,基于idrredix树),为framebuffer, crtcplane等分配唯一id
  • fb_list:链表,用于存放所有的struct drm_framebuffer
  • num_fbfb_list链表的元素个数;
  • connector_list:链表,用于存放所有的struct drm_connector
  • encoder_list:链表,用于存放所有的struct drm_encoder
  • num_encoderencoder_list链表的元素个数;
  • plane_list:链表,用于存放所有的struct drm_plane
  • num_total_planeplane_list链表的元素个数;
  • crtc_list:链表,用于存放所有的struct drm_crtc
  • num_crtccrtc_list链表的元素个数;
  • min_widthmin_height:设备上支持的最小帧缓冲区像素宽度和高度;
  • max_widthmax_height:设备上支持的最大帧缓冲区像素宽度和高度;
  • funcs:由核心驱动程序提供的模式设置回调函数,struct drm_mode_config_funcs *类型;
  • cursor_widthcursor_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

View Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/**
* 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

View Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/**
* 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_importdrm_gem_prime_import_dev使用的可选钩子;
  • gem_prime_mmap:用于在GEM驱动程序中实现PRIME helpersdma-buf mmapmmap钩子,这个钩子只是出于历史原因而存在,驱动程序必须使用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操作。
  • fopsDRM设备节点文件操作集,比如我们对设备节点/dev/dri/card0进行读写,就会调用相应的操作方法;
  • dumb_create:该函数用于在驱动程序的后备存储管理器(如 GEM、TTM或其他管理器)中创建一个新的 dumb buffer,并返回相应的缓冲区句柄;这个句柄可以用来创建一个framebuffer modeset对象;
4.2.1 driver_feature

drm驱动特征使用可以使用如下标识位:

  • DRIVER_GEM:驱动程序使用GEM内存管理器,这对于所有现代驱动程序都应该设置;
  • DRIVER_MODESET:驱动程序支持模式设置接口(KMS);
  • DRIVER_RENDERDriver supports dedicated render nodes. See also the section on render nodes for details.
  • DRIVER_ATOMICDriver 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_SYNCOBDriver supports drm_syncobj for explicit synchronization of command submission.
  • DRIVER_SYNCOBJ_TIMELINEDriver supports the timeline flavor of drm_syncobj for explicit synchronization of command submission.
  • DRIVER_COMPUTE_ACCELDriver 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_AGPSet up DRM AGP support, see drm_agp_init(), the DRM core will manage AGP resources. New drivers don't need this.
  • DRIVER_LEGACYDenote a legacy driver using shadow attach. Do not use.
  • DRIVER_PCI_DMADriver is capable of PCI DMA, mapping of PCI DMA buffers to userspace will be enabled. Only for legacy drivers. Do not use.
  • DRIVER_SGDriver can perform scatter/gather DMA, allocation and mapping of scatter/gather buffers will be enabled. Only for legacy drivers. Do not use.
  • DRIVER_HAVE_DMADriver supports DMA, the userspace DMA API will be supported. Only for legacy drivers. Do not use.
  • DRIVER_HAVE_IRQLegacy irq support. Only for legacy drivers. Do not use.
4.2.2 dumb_create

dumb_create是分配物理内存dumb buffer的回调接口;那什么是dumb buffer呢?

dumb buffer代表了所有的绘图操作都由CPU来完成的framebuffer,更多细节可以参考文章《关于DRMDUMBPRIME名字的由来》

1
2
3
int (*dumb_create)(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);

drm_mode_create_dumb参数中指定了widthheightbppdumb_create函数需要为创建的dumb buffer填充handlepitchsize,该回调函数是通过ioctl由用户调用的。

主要完成三件事:

  • 创建gem objec;
  • 创建gem handle;
  • 分配物理内存dumb buffer(可选的) ;

第1、第2 步是所有dumb_create都必须实现的操作,而第3步则是可选的,对于一次性映射需要事先分配好所有的物理内存。

分配完物理内存后,就可以通过mmap将分配好的物理内存映射到用户空间。

4.2.3 struct drm_file

DRM文件私有数据是驱动程序在处理文件操作时使用的附加数据,在DRM中每个打开的文件都与一个struct drm_file实例关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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创建classdrm_class,在/sys/class目录下一个名称为drm的文件夹;
  • 调用debugfs_create_dir/sys/kernel/debug下创建dri目录;
  • 调用register_chrdev申请主设备号为DRM_MAJOR(值为226),同时注册256个字符设备,并将字符设备的opsdrm_stub_fops绑定在一起;

5.1 drm_sysfs_init

drm_sysfs_init定义在drivers/gpu/drm/drm_sysfs.c,用于创建一个DRM class类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 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函数中创建了classdrm_class,名称为drm,并设置devnode指向了drm_devnode

1
2
3
4
5
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还会为这些设备节点文件设置相应的权限、所属用户和组等信息,以确保用户可以正确访问这些设备节点文件。

比如,在ROCK Pi 4B+开发板运行如下命令:

1
2
3
4
5
6
rk3399_ROCKPI4B_Android11:/ $ ls -l /sys/class/drm/*
lrwxrwxrwx 1 root root 0 2024-01-24 09:15 /sys/class/drm/card0 -> ../../devices/platform/display-subsystem/drm/card0
lrwxrwxrwx 1 root root 0 2024-01-24 09:15 /sys/class/drm/card0-DSI-1 -> ../../devices/platform/display-subsystem/drm/card0/card0-DSI-1
lrwxrwxrwx 1 root root 0 2024-01-24 05:30 /sys/class/drm/card0-HDMI-A-1 -> ../../devices/platform/display-subsystem/drm/card0/card0-HDMI-A-1
lrwxrwxrwx 1 root root 0 2024-01-24 09:15 /sys/class/drm/renderD128 -> ../../devices/platform/display-subsystem/drm/renderD128
-r--r--r-- 1 root root 4096 2024-01-24 09:15 /sys/class/drm/version

5.2 drm_stub_fops

字符设备文件操作集被设置为了drm_stub_fops,其定义在drivers/gpu/drm/drm_drv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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驱动为例,

1
2
3
4
5
G:\work1\new\rockpi4bA11\kernel\drivers\gpu\drm\rockchip\rockchip_drm_drv.c
static const struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
....
}

由于driver_features并没有设定DRIVER_COMPUTE_ACCELDRM_MINOR_PRIMARY,因此并不会执行如下代码:

1
2
ret = drm_minor_alloc(dev, DRM_MINOR_ACCEL);
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);

故而不会初始化drm_device成员renderaccel

drm_dev_init函数中会执行以下两个比较重要的步骤,下面我们依次介绍。

1
2
3
4
// 初始化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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);由于tye=0,所以次设备编号为0~1;
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* 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;
}

if (drm_core_check_feature(dev, DRIVER_MODESET))
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

Hidden Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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_replaceminorminor->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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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_rootdrm_core_init函数中被初始化,定义在drivers/gpu/drm/drm_drv.c,该函数会在/sys/kernel/debug下创建dri目录;

1
2
3
4
5
6
7
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,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/***************************************************
* 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方法;

比如,在ROCK Pi 4B+开发板运行如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
rk3399_ROCKPI4B_Android11:/ $ ls -l  /sys/kernel/debug/dri/*
/sys/kernel/debug/dri/0:
total 0
drwxr-xr-x 2 root root 0 2013-01-18 03:50 DSI-1
drwxr-xr-x 2 root root 0 2013-01-18 03:50 HDMI-A-1
-r--r--r-- 1 root root 0 2013-01-18 03:50 clients
drwxr-xr-x 3 root root 0 2013-01-18 03:50 crtc-0
drwxr-xr-x 3 root root 0 2013-01-18 03:50 crtc-1
drwxr-xr-x 2 root root 0 2013-01-18 03:50 ff8f0000.vop
drwxr-xr-x 2 root root 0 2013-01-18 03:50 ff900000.vop
-r--r--r-- 1 root root 0 2013-01-18 03:50 framebuffer
-r--r--r-- 1 root root 0 2013-01-18 03:50 gem_names
-r--r--r-- 1 root root 0 2013-01-18 03:50 internal_clients
-r--r--r-- 1 root root 0 2013-01-18 03:50 mm_dump
-r--r--r-- 1 root root 0 2013-01-18 03:50 name
-r--r--r-- 1 root root 0 2013-01-18 03:50 state
-r--r--r-- 1 root root 0 2013-01-18 03:50 summary

/sys/kernel/debug/dri/128:
total 0
-r--r--r-- 1 root root 0 2013-01-18 03:50 clients
drwxr-xr-x 2 root root 0 2013-01-18 03:50 ff8f0000.vop
drwxr-xr-x 2 root root 0 2013-01-18 03:50 ff900000.vop
-r--r--r-- 1 root root 0 2013-01-18 03:50 framebuffer
-r--r--r-- 1 root root 0 2013-01-18 03:50 gem_names
-r--r--r-- 1 root root 0 2013-01-18 03:50 internal_clients
-r--r--r-- 1 root root 0 2013-01-18 03:50 mm_dump
-r--r--r-- 1 root root 0 2013-01-18 03:50 name
-r--r--r-- 1 root root 0 2013-01-18 03:50 state
-r--r--r-- 1 root root 0 2013-01-18 03:50 summary

当调用drm_minor_register(dev,xxx)时,如果minor->index=0,在会创建/sys/kernel/debug/dri/0 目录以及文件clientsgem_namesname

1
2
3
4
5
6
7
8
9
10
11
12
13
rk3399_ROCKPI4B_Android11:/ $ cat /sys/kernel/debug/dri/0/name
rockchip dev=display-subsystem unique=display-subsystem

rk3399_ROCKPI4B_Android11:/ $ cat /sys/kernel/debug/dri/0/clients
command pid dev master a uid magic
composer@2.1-se 239 0 y y 1000 0
outputmanager@1 283 0 n n 1000 0
omx@1.0-service 387 0 n n 1046 0
omx@1.0-service 387 0 n n 1046 0
omx@1.0-service 387 0 n n 1046 0

rk3399_ROCKPI4B_Android11:/ $ 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

1
2
3
4
5
6
7
8
rk3399_ROCKPI4B_Android11:/ $ ls -l /dev/dri/card0
crw-rw-rw- 1 root graphics 226, 0 2013-01-18 03:50 /dev/dri/card0

rk3399_ROCKPI4B_Android11:/ $ ls -l /sys/class/drm/card0
lrwxrwxrwx 1 root root 0 2024-01-24 09:15 /sys/class/drm/card0 -> ../../devices/platform/display-subsystem/drm/card0

rk3399_ROCKPI4B_Android11:/ $ ls /sys/class/drm/card0/
card0-DSI-1 card0-HDMI-A-1 dev device power subsystem uevent

八、模式配置初始化

drm_mode_config_init用于初始化drm_devicemode_config结构体,函数定义在include/drm/drm_mode_config.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* 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 则分别用于初始化framebufferplanecrtcencoderconnector 这5个 drm_mode_object

具体实现函数drm_framebuffer_initdrm_universal_plane_initdrm_crtc_init_with_planesdrm_encoder_initdrm_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)

[6] Linux DRM(二)基本概念和特性

[7] The DRM/KMS subsystem from a newbie’s point of view

[8] DRM驱动概念、组成、框架、源码分析

[9] linux驱动系列学习之DRM(十)

[10] DRM驱动程序开发(开篇)

[11] DRM驱动程序开发(VKMS

[12] MIPI自学笔记

[13] DRMGEM

[14] DRM 驱动mmap详解:(二)CMA Helper

[15] linux kernel DRM Internals

[16] linux gem initialization