Rockchip RK3399 - DRM子系统

发布时间 2023-09-04 00:21:26作者: 大奥特曼打小怪兽

从开始接触音频子系统到如今已经两个多月,说实话花费的时间的确有点长了。从今天起我们开始接触DRM,网上已经有很多优秀的关于DRM的文章了,因此我们学习直接去学习一些优秀的文章即可。后面有关DRM相关的文章我们会大量参考[1] DRM (Direct Rendering Manager)

一、DRM介绍

1.1 DRM概述

linux内核中包含两类图形显示设备驱动框架:

  • FB设备:Frame buffer图形显示框架;
  • DRM:直接渲染管理器(Direct Rendering Manager),是linux目前主流的图形显示框架;

在实际场景中,具体选择哪一种图像设备驱动框架取决于我们自己的业务需求。

1.1.1 Frambebuffer驱动

Frambebuffer驱动具有以下特征:

  • 直接控制显卡的帧缓冲区,提供基本的显卡输出功能;

  • 使用一些内核数据结构和API来管理图形界面,并提供一组接口与用户空间的应用程序进行通信;

  • 相对简单,适合于嵌入式系统或者不需要高性能图形的应用场景。

1.1.2 DRM驱动

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

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

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

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

img

1.2 DRM框架

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

img

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

(1) 应用程序:上图中并没有画出;

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

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

  • KMSKernel Mode Setting,所谓内核显示模式设置,其实说白了就两件事:更新画面和设置显示参数;
    • 更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置;
    • 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等;
  • GEMGraphic Execution Manager(图形执行管理器),它提供了一种抽象的显存管理方式,使用户空间应用程序可以更方便地管理显存,而不需要了解底层硬件的细节;

(4) Video CardGPU以及显卡驱动;

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
概括下就是,对显示器进行扫描,产生时序信号的模块、负责帧切换、电源控制、色彩调整等等。
NCODER 负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller
CONNECTOR 连接物理显示设备的连接器,如HDMIDisplayPortDSI总线,通常和Encoder驱动绑定在一起
PLANE 图层,实际输出的图像是多个图层叠加而成的,比如主图层、光标图层。其中有些图层由硬件加速模块生成,每个CRTC至少一个plane
plane一共有三种,分别是:DRM_PLANE_TYPE_PRIMARYDRM_PLANE_TYPE_OVERLAYDRM_PLANE_TYPE_CURSOR。这是配置plane的三个枚举,标注主图层、覆盖图层、光标图层;
FB Framebuffer,用于存储单个图层要实现的内容
VBLANK 软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现
property 任何你想设置的参数都可以做成property,是DRM驱动中最灵活、最方便的Mode setting机制
DUMB 只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景
PRIME 连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景
fence buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题

1.3 目录结构

linux内核将DRM驱动相关的代码都放在drivers/gpu/drm目录下,这下面的文件还是比较多的,我们大概了解一下即可;

root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/ -I "*.o"
amd                         drm_fbdev_generic.c             drm_print.c                logicvc
arm                         drm_fb_dma_helper.c             drm_privacy_screen.c       Makefile
armada                      drm_fb_helper.c                 drm_privacy_screen_x86.c   mcde
aspeed                      drm_file.c                      drm_probe_helper.c         mediatek
ast                         drm_flip_work.c                 drm_property.c             meson
atmel-hlcdc                 drm_format_helper.c             drm_rect.c                 mgag200
bridge                      drm_fourcc.c                    drm_scatter.c              modules.order
built-in.a                  drm_framebuffer.c               drm_self_refresh_helper.c  msm
display                     drm_gem_atomic_helper.c         drm_shmem_helper.ko        mxsfb
drm_agpsupport.c            drm_gem.c                       drm_shmem_helper.mod       nouveau
drm_aperture.c              drm_gem_dma_helper.c            drm_shmem_helper.mod.c     omapdrm
drm_atomic.c                drm_gem_framebuffer_helper.c    drm_simple_kms_helper.c    panel
drm_atomic_helper.c         drm_gem_shmem_helper.c          drm_syncobj.c              panfrost
drm_atomic_state_helper.c   drm_gem_ttm_helper.c            drm_sysfs.c                pl111
drm_atomic_uapi.c           drm_gem_vram_helper.c           drm_trace.h                qxl
drm_auth.c                  drm_hashtab.c                   drm_trace_points.c         radeon
drm_blend.c                 drm_internal.h                  drm_ttm_helper.ko          rcar-du
drm_bridge.c                drm_ioc32.c                     drm_ttm_helper.mod         rockchip
drm_bridge_connector.c      drm_ioctl.c                     drm_ttm_helper.mod.c       scheduler
drm_buddy.c                 drm_irq.c                       drm_vblank.c               shmobile
drm_bufs.c                  drm_kms_helper_common.c         drm_vblank_work.c          solomon
drm_cache.c                 drm_lease.c                     drm_vma_manager.c          sprd
drm_client.c                drm_legacy.h                    drm_vm.c                   sti
drm_client_modeset.c        drm_legacy_misc.c               drm_vram_helper.ko         stm
drm_color_mgmt.c            drm_lock.c                      drm_vram_helper.mod        sun4i
drm_connector.c             drm_managed.c                   drm_vram_helper.mod.c      tegra
drm_context.c               drm_memory.c                    drm_writeback.c            tests
drm_crtc.c                  drm_mipi_dbi.c                  etnaviv                    tidss
drm_crtc_helper.c           drm_mipi_dsi.c                  exynos                     tilcdc
drm_crtc_helper_internal.h  drm_mm.c                        fsl-dcu                    tiny
drm_crtc_internal.h         drm_mode_config.c               gma500                     ttm
drm_damage_helper.c         drm_mode_object.c               gud                        tve200
drm_debugfs.c               drm_modes.c                     hisilicon                  udl
drm_debugfs_crc.c           drm_modeset_helper.c            hyperv                     v3d
drm_displayid.c             drm_modeset_lock.c              i2c                        vboxvideo
drm_dma.c                   drm_of.c                        i915                       vc4
drm_drv.c                   drm_panel.c                     imx                        vgem
drm_dumb_buffers.c          drm_panel_orientation_quirks.c  ingenic                    virtio
drm_edid.c                  drm_pci.c                       Kconfig                    vkms
drm_edid_load.c             drm_plane.c                     kmb                        vmwgfx
drm_encoder.c               drm_plane_helper.c              lib                        xen
drm_encoder_slave.c         drm_prime.c                     lima                       xlnx

其中rockchipRockchip官方的实现代码:

root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/rockchip/ -I "*.o"
analogix_dp-rockchip.c  inno_hdmi.c         rockchip_drm_drv.h   rockchip_drm_vop.h
built-in.a              inno_hdmi.h         rockchip_drm_fb.c    rockchip_lvds.c
cdn-dp-core.c           Kconfig             rockchip_drm_fb.h    rockchip_lvds.h
cdn-dp-core.h           Makefile            rockchip_drm_gem.c   rockchip_rgb.c
cdn-dp-reg.c            modules.order       rockchip_drm_gem.h   rockchip_rgb.h
cdn-dp-reg.h            rk3066_hdmi.c       rockchip_drm_vop2.c  rockchip_vop2_reg.c
dw_hdmi-rockchip.c      rk3066_hdmi.h       rockchip_drm_vop2.h  rockchip_vop_reg.c
dw-mipi-dsi-rockchip.c  rockchip_drm_drv.c  rockchip_drm_vop.c   rockchip_vop_reg.h

二、硬件抽象

对于初学者来说,往往让人迷惑的不是DRMobjects的概念,而是如何去建立这些objects与实际硬件的对应关系。因为并不是所有的Display硬件都能很好的对应上plane/crtc/encoder/connector这些objects

在学如何去抽象显示硬件到具体的RM 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接口屏的硬件连接框图:

img

它在软件架构上与DRM object的对应关系如下图:

img

多余的细节不做介绍,这里只说明为何如此分配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 LCDC没有Overlay硬件,它只有一个数据源通道,被抽象为Primary Plane,同样需要访问 LCDC硬件寄存器,因此放在LCDC驱动中
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框架的核心,它们缺一不可。

img

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

这些objects之间的关系:

img

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

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

3.1 modeset object

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

/**
 * struct drm_mode_object - base structure for modeset objects
 * @id: userspace visible identifier
 * @type: type of the object, one of DRM_MODE_OBJECT\_\*
 * @properties: properties attached to this object, including values
 * @refcount: reference count for objects which with dynamic lifetime
 * @free_cb: free function callback, only set for objects with dynamic lifetime
 *
 * Base structure for modeset objects visible to userspace. Objects can be
 * looked up using drm_mode_object_find(). Besides basic uapi interface
 * properties like @id and @type it provides two services:
 *
 * - It tracks attached properties and their values. This is used by &drm_crtc,
 *   &drm_plane and &drm_connector. Properties are attached by calling
 *   drm_object_attach_property() before the object is visible to userspace.
 *
 * - For objects with dynamic lifetimes (as indicated by a non-NULL @free_cb) it
 *   provides reference counting through drm_mode_object_get() and
 *   drm_mode_object_put(). This is used by &drm_framebuffer, &drm_connector
 *   and &drm_property_blob. These objects provide specialized reference
 *   counting wrappers.
 */
struct drm_mode_object {
        uint32_t id;
        uint32_t type;
        struct drm_object_properties *properties;
        struct kref refcount;
        void (*free_cb)(struct kref *kref);
};

包括以下成员:

  • id:用户空间可见的唯一标识标识符,基于idr算法分配得到的;

  • type:对象的类型,可以是DRM_MODE_OBJECT_*中的一个;

  • properties:附加到该对象的属性,包括属性的值;在DRM驱动中,每个对象都可以拥有一组属性(例如分辨率、刷新率等),并且可以动态地增加、删除或修改属性。这些属性可以被用户空间的应用程序或者其他驱动程序获取或者设置;

  • refcount:具有动态生命周期的对象的引用计数;指drm_mode_object对象在内核中的生命周期的管理,每个drm_mode_object对象都有一个引用计数;

    • 当一个对象被创建时,它的引用计数被初始化为1;
    • 每当一个新的引用指向该对象时,它的引用计数就会增加1;
    • 每当一个引用被释放时,它的引用计数就会减少1;
    • 当对象的引用计数降为0时,内核会自动释放该对象。
    • 这种方式确保了内核中不会存在不再使用的对象,从而避免了内存泄漏。
  • free_cb:释放函数回调,仅对具有动态生命周期的对象设置;

为了更加清晰的了解struct drm_mode_objectstruct drm_object_propertiesstruct snd_jack_kctl数据结构的关系,我们绘制了如下关系图:

3.2 对象类型

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

#define DRM_MODE_OBJECT_CRTC 0xcccccccc
#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0
#define DRM_MODE_OBJECT_MODE 0xdededede
#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0
#define DRM_MODE_OBJECT_FB 0xfbfbfbfb
#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
#define DRM_MODE_OBJECT_ANY 0

该结构体提供了用户空间可见的modeset objects的基本结构,可以通过drm_mode_object_find函数查找对象。

3.3 对象属性

struct drm_object_properties用于描述对象的属性,定义在include/drm/drm_mode_object.h

/**
 * struct drm_object_properties - property tracking for &drm_mode_object
 */
struct drm_object_properties {
        /**
         * @count: number of valid properties, must be less than or equal to
         * DRM_OBJECT_MAX_PROPERTY.
         */

        int count;
        /**
         * @properties: Array of pointers to &drm_property.
         *
         * NOTE: if we ever start dynamically destroying properties (ie.
         * not at drm_mode_config_cleanup() time), then we'd have to do
         * a better job of detaching property from mode objects to avoid
         * dangling property pointers:
         */
        struct drm_property *properties[DRM_OBJECT_MAX_PROPERTY];

        /**
         * @values: Array to store the property values, matching @properties. Do
         * not read/write values directly, but use
         * drm_object_property_get_value() and drm_object_property_set_value().
         *
         * Note that atomic drivers do not store mutable properties in this
         * array, but only the decoded values in the corresponding state
         * structure. The decoding is done using the &drm_crtc.atomic_get_property and
         * &drm_crtc.atomic_set_property hooks for &struct drm_crtc. For
         * &struct drm_plane the hooks are &drm_plane_funcs.atomic_get_property and
         * &drm_plane_funcs.atomic_set_property. And for &struct drm_connector
         * the hooks are &drm_connector_funcs.atomic_get_property and
         * &drm_connector_funcs.atomic_set_property .
         *
         * Hence atomic drivers should not use drm_object_property_set_value()
         * and drm_object_property_get_value() on mutable objects, i.e. those
         * without the DRM_MODE_PROP_IMMUTABLE flag set.
         *
         * For atomic drivers the default value of properties is stored in this
         * array, so drm_object_property_get_default_value can be used to
         * retrieve it.
         */
        uint64_t values[DRM_OBJECT_MAX_PROPERTY];
};

该结构体包含以下字段:

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

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

/**
 * struct drm_property - modeset object property
 *
 * This structure represent a modeset object property. It combines both the name
 * of the property with the set of permissible values. This means that when a
 * driver wants to use a property with the same name on different objects, but
 * with different value ranges, then it must create property for each one. An
 * example would be rotation of &drm_plane, when e.g. the primary plane cannot
 * be rotated. But if both the name and the value range match, then the same
 * property structure can be instantiated multiple times for the same object.
 * Userspace must be able to cope with this and cannot assume that the same
 * symbolic property will have the same modeset object ID on all modeset
 * objects.
 *
 * Properties are created by one of the special functions, as explained in
 * detail in the @flags structure member.
 *
 * To actually expose a property it must be attached to each object using
 * drm_object_attach_property(). Currently properties can only be attached to
 * &drm_connector, &drm_crtc and &drm_plane.
 *
 * Properties are also used as the generic metadatatransport for the atomic
 * IOCTL. Everything that was set directly in structures in the legacy modeset
 * IOCTLs (like the plane source or destination windows, or e.g. the links to
 * the CRTC) is exposed as a property with the DRM_MODE_PROP_ATOMIC flag set.
 */
struct drm_property {
        /**
         * @head: per-device list of properties, for cleanup.
         */
        struct list_head head;

        /**
         * @base: base KMS object
         */
        struct drm_mode_object base;

        /**
         * @flags:
         *
         * Property flags and type. A property needs to be one of the following
         * types:
         *
         * DRM_MODE_PROP_RANGE
         *     Range properties report their minimum and maximum admissible unsigned values.
         *     The KMS core verifies that values set by application fit in that
         *     range. The range is unsigned. Range properties are created using
         *     drm_property_create_range().
         *
         * DRM_MODE_PROP_SIGNED_RANGE
         *     Range properties report their minimum and maximum admissible unsigned values.
         *     The KMS core verifies that values set by application fit in that
         *     range. The range is signed. Range properties are created using
         *     drm_property_create_signed_range().
         *
         * DRM_MODE_PROP_ENUM
         *     Enumerated properties take a numerical value that ranges from 0 to
         *     the number of enumerated values defined by the property minus one,
         *     and associate a free-formed string name to each value. Applications
         *     can retrieve the list of defined value-name pairs and use the
         *     numerical value to get and set property instance values. Enum
         *     properties are created using drm_property_create_enum().
         *
         * DRM_MODE_PROP_BITMASK
         *     Bitmask properties are enumeration properties that additionally
         *     restrict all enumerated values to the 0..63 range. Bitmask property
         *     instance values combine one or more of the enumerated bits defined
         *     by the property. Bitmask properties are created using
         *     drm_property_create_bitmask().
         *
         * DRM_MODE_PROP_OBJECT
         *     Object properties are used to link modeset objects. This is used
         *     extensively in the atomic support to create the display pipeline,
         *     by linking &drm_framebuffer to &drm_plane, &drm_plane to
         *     &drm_crtc and &drm_connector to &drm_crtc. An object property can
         *     only link to a specific type of &drm_mode_object, this limit is
         *     enforced by the core. Object properties are created using
         *     drm_property_create_object().
         *
         *     Object properties work like blob properties, but in a more
         *     general fashion. They are limited to atomic drivers and must have
         *     the DRM_MODE_PROP_ATOMIC flag set.
         * DRM_MODE_PROP_BLOB
         *     Blob properties store a binary blob without any format restriction.
         *     The binary blobs are created as KMS standalone objects, and blob
         *     property instance values store the ID of their associated blob
         *     object. Blob properties are created by calling
         *     drm_property_create() with DRM_MODE_PROP_BLOB as the type.
         *
         *     Actual blob objects to contain blob data are created using
         *     drm_property_create_blob(), or through the corresponding IOCTL.
         *
         *     Besides the built-in limit to only accept blob objects blob
         *     properties work exactly like object properties. The only reasons
         *     blob properties exist is backwards compatibility with existing
         *     userspace.
         *
         * In addition a property can have any combination of the below flags:
         *
         * DRM_MODE_PROP_ATOMIC
         *     Set for properties which encode atomic modeset state. Such
         *     properties are not exposed to legacy userspace.
         *
         * DRM_MODE_PROP_IMMUTABLE
         *     Set for properties whose values cannot be changed by
         *     userspace. The kernel is allowed to update the value of these
         *     properties. This is generally used to expose probe state to
         *     userspace, e.g. the EDID, or the connector path property on DP
         *     MST sinks. Kernel can update the value of an immutable property
         *     by calling drm_object_property_set_value().
         */
        uint32_t flags;

        /**
         * @name: symbolic name of the properties
         */
        char name[DRM_PROP_NAME_LEN];

        /**
         * @num_values: size of the @values array.
         */
        uint32_t num_values;

        /**
         * @values:
         *
         * Array with limits and values for the property. The
         * interpretation of these limits is dependent upon the type per @flags.
         */
        uint64_t *values;

        /**
         * @dev: DRM device
         */
        struct drm_device *dev;

        /**
         * @enum_list:
         *
         * List of &drm_prop_enum_list structures with the symbolic names for
         * enum and bitmask values.
         */
        struct list_head enum_list;
};

3.4 gem object

linux内核中使用struct drm_gem_object表示GEM对象,驱动一般需要用私有信息来扩展GEM对象,因此struct drm_gem_object都是嵌入在驱动自定义的GEM结构体内的。

  • 创建一个GEM对象,驱动为自定义GEM对象申请内存;
  • 通过drm_gem_object_init(struct drm_device* ,struct drm_gem_object *,size_t )来初始化嵌入在其中的struct drm_gem_object

struct drm_gem_object定义在include/drm/drm_gem.h:

/**
 * struct drm_gem_object - GEM buffer object
 *
 * This structure defines the generic parts for GEM buffer objects, which are
 * mostly around handling mmap and userspace handles.
 *
 * Buffer objects are often abbreviated to BO.
 */
struct drm_gem_object {
        /**
         * @refcount:
         *
         * Reference count of this object
         *
         * Please use drm_gem_object_get() to acquire and drm_gem_object_put_locked()
         * or drm_gem_object_put() to release a reference to a GEM
         * buffer object.
         */
        struct kref refcount;

        /**
         * @handle_count:
         *
         * This is the GEM file_priv handle count of this object.
         *
         * Each handle also holds a reference. Note that when the handle_count
         * drops to 0 any global names (e.g. the id in the flink namespace) will
         * be cleared.
         *
         * Protected by &drm_device.object_name_lock.
         */
        unsigned handle_count;

        /**
         * @dev: DRM dev this object belongs to.
         */
        struct drm_device *dev;

        /**
         * @filp:
         *
         * SHMEM file node used as backing storage for swappable buffer objects.
         * GEM also supports driver private objects with driver-specific backing
         * storage (contiguous DMA memory, special reserved blocks). In this
         * case @filp is NULL.
         */
        struct file *filp;
        /**
         * @vma_node:
         *
         * Mapping info for this object to support mmap. Drivers are supposed to
         * allocate the mmap offset using drm_gem_create_mmap_offset(). The
         * offset itself can be retrieved using drm_vma_node_offset_addr().
         *
         * Memory mapping itself is handled by drm_gem_mmap(), which also checks
         * that userspace is allowed to access the object.
         */
        struct drm_vma_offset_node vma_node;

        /**
         * @size:
         *
         * Size of the object, in bytes.  Immutable over the object's
         * lifetime.
         */
        size_t size;

        /**
         * @name:
         *
         * Global name for this object, starts at 1. 0 means unnamed.
         * Access is covered by &drm_device.object_name_lock. This is used by
         * the GEM_FLINK and GEM_OPEN ioctls.
         */
        int name;

        /**
         * @dma_buf:
         *
         * dma-buf associated with this GEM object.
         *
         * Pointer to the dma-buf associated with this gem object (either
         * through importing or exporting). We break the resulting reference
         * loop when the last gem handle for this object is released.
         *
         * Protected by &drm_device.object_name_lock.
         */
        struct dma_buf *dma_buf;

        /**
         * @import_attach:
         *
         * dma-buf attachment backing this object.
         *
         * Any foreign dma_buf imported as a gem object has this set to the
         * attachment point for the device. This is invariant over the lifetime
         * of a gem object.
         *
         * The &drm_gem_object_funcs.free callback is responsible for
         * cleaning up the dma_buf attachment and references acquired at import
         * time.
         *
         * Note that the drm gem/prime core does not depend upon drivers setting
         * this field any more. So for drivers where this doesn't make sense
         * (e.g. virtual devices or a displaylink behind an usb bus) they can
         * simply leave it as NULL.
         */
        struct dma_buf_attachment *import_attach;

        /**
         * @resv:
         *
         * Pointer to reservation object associated with the this GEM object.
         *
         * Normally (@resv == &@_resv) except for imported GEM objects.
         */
        struct dma_resv *resv;

        /**
         * @_resv:
         *
         * A reservation object for this GEM object.
         *
         * This is unused for imported GEM objects.
         */
        struct dma_resv _resv;

        /**
         * @funcs:
         *
         * Optional GEM object functions. If this is set, it will be used instead of the
         * corresponding &drm_driver GEM callbacks.
         *
         * New drivers should use this.
         *
         */
        const struct drm_gem_object_funcs *funcs;

        /**
         * @lru_node:
         *
         * List node in a &drm_gem_lru.
         */
        struct list_head lru_node;

        /**
         * @lru:
         *
         * The current LRU list that the GEM object is on.
         */
        struct drm_gem_lru *lru;
};

其中:

  • refcount:表示对象引用计数,
  • handle_count:表示该对象的句柄计数。每个句柄还持有一个引用。当handle_count降为0时,任何全局名称(例如flink命名空间中的id)都将被清除;
  • dev:该对象所属的DRM设备的指针;
  • filp:用作可互换的缓存对象的后备存储的shmem文件节点;
  • vma_node:用于支持内存映射的对象的映射信息;
  • size:对象的大小,以字节为单位;
  • name:该对象的全局名称,从1开始。值为0表示无名称;
  • dma_buf:与此GEM对象关联的dma-buf
  • import_attach:支持此对象的dma-buf附件;
  • resv:与此GEM对象关联的保留对象的指针;
  • _resv:此GEM对象的保留对象;
  • funcs:可选的GEM对象函数。如果设置了此字段,则将使用它而不是对应的drm_driver GEM回调函数;
  • lru_node:在drm_gem_lru中的列表节点;
  • lruGEM对象当前所在的LRU列表;
3.4.1 drm_gem_object_init

GEM使用shmem来申请匿名页内存,drm_gem_object_init将会根据传入的size创建一个指定大小的shmfs(共享内存文件系统),并将这个shmfs file放到struct drm_gem_objectfilp区。

当图形硬件使用系统内存,这些内存就会作为对象的主存储直接使用,否则就会作为后备内存。驱动负责调用shmem_read_mapping_page_gfp做实际物理页面的申请,初始化GEM对象时驱动可以决定申请页面,或者延迟到需要内存时再申请(需要内存时是指:用户态访问内存发生缺页中断,或是驱动需要启动DMA用到这段内存)。

drm_gem_object_init函数定义在drivers/gpu/drm/drm_gem.c

/**
 * drm_gem_object_init - initialize an allocated shmem-backed GEM object
 * @dev: drm_device the object should be initialized for
 * @obj: drm_gem_object to initialize
 * @size: object size 对象的大小
 *
 * Initialize an already allocated GEM object of the specified size with
 * shmfs backing store.
 */
int drm_gem_object_init(struct drm_device *dev,
                        struct drm_gem_object *obj, size_t )
{
        struct file *filp;

        drm_gem_private_object_init(dev, obj, size);

        filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
        if (IS_ERR(filp))
                return PTR_ERR(filp);

        obj->filp = filp;

        return 0;
}

函数通过调用 drm_gem_private_object_init 进行对象初始化,然后通过调用 shmem_file_setup 创建一个与该 GEM对象关联的shmem文件,并将文件指针赋值给 obj->filp

3.4.2 drm_gem_handle_create

GEM对象有本地handle、全局名称和文件描述符,都是32bit数。

(1)对于本地handle,函数drm_gem_handle_create用于创建GEM对象handle,这个函数接收三个参数:

  • DRM file的指针;
  • GEM对象;
  • 将创建的句柄返回给调用者的指针handlep

这个handle可以通过drm_gem_handle_delete来删除。

drm_gem_handle_create函数定义在drivers/gpu/drm/drm_gem.c

/**
 * drm_gem_handle_create_tail - internal functions to create a handle
 * @file_priv: drm file-private structure to register the handle for
 * @obj: object to register
 * @handlep: pointer to return the created handle to the caller
 *
 * This expects the &drm_device.object_name_lock to be held already and will
 * drop it before returning. Used to avoid races in establishing new handles
 * when importing an object from either an flink name or a dma-buf.
 *
 * Handles must be release again through drm_gem_handle_delete(). This is done
 * when userspace closes @file_priv for all attached handles, or through the
 * GEM_CLOSE ioctl for individual handles.
 */
int
drm_gem_handle_create_tail(struct drm_file *file_priv,
                           struct drm_gem_object *obj,
                           u32 *handlep)
{
        struct drm_device *dev = obj->dev;
        u32 handle;
        int ret;

        WARN_ON(!mutex_is_locked(&dev->object_name_lock));
        if (obj->handle_count++ == 0)
                drm_gem_object_get(obj);

        /*
         * Get the user-visible handle using idr.  Preload and perform
         * allocation under our spinlock.
         */
        idr_preload(GFP_KERNEL);
        spin_lock(&file_priv->table_lock);   // 获取自旋锁

        ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT); // 基于基数树分配一个唯一的id

        spin_unlock(&file_priv->table_lock);   // 释放自旋锁
        idr_preload_end();

        mutex_unlock(&dev->object_name_lock);
        if (ret < 0)
                goto err_unref;

        handle = ret;

        ret = drm_vma_node_allow(&obj->vma_node, file_priv);
        if (ret)
                goto err_remove;

        if (obj->funcs->open) {
                ret = obj->funcs->open(obj, file_priv); // 回调open函数
                if (ret)
                        goto err_revoke;
        }

        *handlep = handle; // 写回
        return 0;

err_revoke:
        drm_vma_node_revoke(&obj->vma_node, file_priv);
err_remove:
        spin_lock(&file_priv->table_lock);
        idr_remove(&file_priv->object_idr, handle);
        spin_unlock(&file_priv->table_lock);
err_unref:
        drm_gem_object_handle_put_unlocked(obj);
        return ret;
}

/**
 * drm_gem_handle_create - create a gem handle for an object
 * @file_priv: drm file-private structure to register the handle for
 * @obj: object to register
 * @handlep: pointer to return the created handle to the caller
 *
 * Create a handle for this object. This adds a handle reference to the object,
 * which includes a regular reference count. Callers will likely want to
 * dereference the object afterwards.
 *
 * Since this publishes @obj to userspace it must be fully set up by this point,
 * drivers must call this last in their buffer object creation callbacks.
 */
int drm_gem_handle_create(struct drm_file *file_priv,
                          struct drm_gem_object *obj,
                          u32 *handlep)
{
        mutex_lock(&obj->dev->object_name_lock);

        return drm_gem_handle_create_tail(file_priv, obj, handlep);
}

drm_gem_object_lookup可以由handle找出对应的GEM对象。handle仅是一个对GEM对象的引用,在handle销毁时减去这一引用。

(2) 对于全局名称:可以在进程之间传递,不过在DRM API中,全局名称不能直接指到GEM对象,通过ioctlDRM_IOCTL_GEM_FLINK (转成对象)和DRM_IOCTL_GEM_OPEN(转回全局名称),DRM core做此转换。

(3) GEM也支持通过PRIME dma-buf文件描述符的缓存共享,基于GEM的驱动必须使用提供的辅助函数来实现exoprtingimporting。共享文件描述符比可以被猜测的全局名称更安全,因此是首选的缓存共享机制。通过GEM全局名称进行缓存共享仅在传统用户态支持。更进一步的说,PRIME由于其基于dma-buf,还允许跨设备缓存共享。

3.4.3 GEM对象的生命周期

所有GEM对象都有GEM core做引用计数,drm_gem_object_get加数,drm_gem_object_put减数,执行drm_gem_object_get调用者必须持有struct drm_devicestruct_mutex锁,而为了方便也提供了drm_gem_object_put_unlocked也可以不持锁操作。

当最后一个对GEM对象的引用释放时,GEM core调用struct drm_driver gem_free_object_unlocked,这一操作对于使能了GEM的驱动来说是必须,并且必须释放所有相关资源。

driver实现接口void (*gem_free_object)(struct drm_gem_object *obj),负责释放所有GEM对象资源,这其中包括要调用drm_gem_object_release来释放GEM core创建的资源。

3.4.4 GEM对象内存映射

四、DRM核心数据结构

学习DRM驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

4.1 struct drm_device

linux内核使用struct drm_device数据结构来描述一个drm设备,定义在include/drm/drm_device.h

/**
 * struct drm_device - DRM device structure
 *
 * This structure represent a complete card that
 * may contain multiple heads.
 */
struct drm_device {
        /** @if_version: Highest interface version set */
        int if_version;

        /** @ref: Object ref-count */
        struct kref ref;

        /** @dev: Device structure of bus-device */
        struct device *dev;

        /**
         * @managed:
         *
         * Managed resources linked to the lifetime of this &drm_device as
         * tracked by @ref.
         */
        struct {
                /** @managed.resources: managed resources list */
                struct list_head resources;
                /** @managed.final_kfree: pointer for final kfree() call */
                void *final_kfree;
                /** @managed.lock: protects @managed.resources */
                spinlock_t lock;
        } managed;

        /** @driver: DRM driver managing the device */
        const struct drm_driver *driver;

        /**
         * @dev_private:
         *
         * DRM driver private data. This is deprecated and should be left set to
         * NULL.
         *
         * Instead of using this pointer it is recommended that drivers use
         * devm_drm_dev_alloc() and embed struct &drm_device in their larger
         * per-device structure.
         */
        void *dev_private;

        /**
         * @primary:
         *
         * Primary node. Drivers should not interact with this
         * directly. debugfs interfaces can be registered with
         * drm_debugfs_add_file(), and sysfs should be directly added on the
         * hardware (and not character device node) struct device @dev.
         */
        struct drm_minor *primary;

        /**
         * @render:
         *
         * Render node. Drivers should not interact with this directly ever.
         * Drivers should not expose any additional interfaces in debugfs or
         * sysfs on this node.
         */
        struct drm_minor *render;

        /** @accel: Compute Acceleration node */
        struct drm_minor *accel;

        /**
         * @registered:
         *
         * Internally used by drm_dev_register() and drm_connector_register().
         */
        bool registered;

        /**
         * @master:
         *
         * Currently active master for this device.
         * Protected by &master_mutex
         */
        struct drm_master *master;

        /**
         * @driver_features: per-device driver features
         *
         * Drivers can clear specific flags here to disallow
         * certain features on a per-device basis while still
         * sharing a single &struct drm_driver instance across
         * all devices.
         */
        u32 driver_features;

        /**
         * @unplugged:
         *
         * Flag to tell if the device has been unplugged.
         * See drm_dev_enter() and drm_dev_is_unplugged().
         */
        bool unplugged;

        /** @anon_inode: inode for private address-space */
        struct inode *anon_inode;

        /** @unique: Unique name of the device */
        char *unique;

        /**
         * @struct_mutex:
         *
         * Lock for others (not &drm_minor.master and &drm_file.is_master)
         *
         * WARNING:
         * Only drivers annotated with DRIVER_LEGACY should be using this.
         */
        struct mutex struct_mutex;

        /**
         * @master_mutex:
         *
         * Lock for &drm_minor.master and &drm_file.is_master
         */
        struct mutex master_mutex;

        /**
         * @open_count:
         *
         * Usage counter for outstanding files open,
         * protected by drm_global_mutex
         */
        atomic_t open_count;

        /** @filelist_mutex: Protects @filelist. */
        struct mutex filelist_mutex;
        /**
         * @filelist:
         *
         * List of userspace clients, linked through &drm_file.lhead.
         */
        struct list_head filelist;

        /**
         * @filelist_internal:
         *
         * List of open DRM files for in-kernel clients.
         * Protected by &filelist_mutex.
         */
        struct list_head filelist_internal;

        /**
         * @clientlist_mutex:
         *
         * Protects &clientlist access.
         */
        struct mutex clientlist_mutex;

        /**
         * @clientlist:
         *
         * List of in-kernel clients. Protected by &clientlist_mutex.
         */
        struct list_head clientlist;

        /**
         * @vblank_disable_immediate:
         *
         * If true, vblank interrupt will be disabled immediately when the
         * refcount drops to zero, as opposed to via the vblank disable
         * timer.
         *
         * This can be set to true it the hardware has a working vblank counter
         * with high-precision timestamping (otherwise there are races) and the
         * driver uses drm_crtc_vblank_on() and drm_crtc_vblank_off()
         * appropriately. See also @max_vblank_count and
         * &drm_crtc_funcs.get_vblank_counter.
         */
        bool vblank_disable_immediate;

        /**
         * @vblank:
         *
         * Array of vblank tracking structures, one per &struct drm_crtc. For
         * historical reasons (vblank support predates kernel modesetting) this
         * is free-standing and not part of &struct drm_crtc itself. It must be
         * initialized explicitly by calling drm_vblank_init().
         */
        struct drm_vblank_crtc *vblank;

        /**
         * @vblank_time_lock:
         *
         *  Protects vblank count and time updates during vblank enable/disable
         */
        spinlock_t vblank_time_lock;
        /**
         * @vbl_lock: Top-level vblank references lock, wraps the low-level
         * @vblank_time_lock.
         */
        spinlock_t vbl_lock;

        /**
         * @max_vblank_count:
         *
         * Maximum value of the vblank registers. This value +1 will result in a
         * wrap-around of the vblank register. It is used by the vblank core to
         * handle wrap-arounds.
         *
         * If set to zero the vblank core will try to guess the elapsed vblanks
         * between times when the vblank interrupt is disabled through
         * high-precision timestamps. That approach is suffering from small
         * races and imprecision over longer time periods, hence exposing a
         * hardware vblank counter is always recommended.
         *
         * This is the statically configured device wide maximum. The driver
         * can instead choose to use a runtime configurable per-crtc value
         * &drm_vblank_crtc.max_vblank_count, in which case @max_vblank_count
         * must be left at zero. See drm_crtc_set_max_vblank_count() on how
         * to use the per-crtc value.
         *
         * If non-zero, &drm_crtc_funcs.get_vblank_counter must be set.
         */
        u32 max_vblank_count;
        /** @vblank_event_list: List of vblank events */
        struct list_head vblank_event_list;

        /**
         * @event_lock:
         *
         * Protects @vblank_event_list and event delivery in
         * general. See drm_send_event() and drm_send_event_locked().
         */
        spinlock_t event_lock;

        /** @num_crtcs: Number of CRTCs on this device */
        unsigned int num_crtcs;

        /** @mode_config: Current mode config */
        struct drm_mode_config mode_config;

        /** @object_name_lock: GEM information */
        struct mutex object_name_lock;

        /** @object_name_idr: GEM information */
        struct idr object_name_idr;

        /** @vma_offset_manager: GEM information */
        struct drm_vma_offset_manager *vma_offset_manager;

        /** @vram_mm: VRAM MM memory manager */
        struct drm_vram_mm *vram_mm;

        /**
         * @switch_power_state:
         *
         * Power state of the client.
         * Used by drivers supporting the switcheroo driver.
         * The state is maintained in the
         * &vga_switcheroo_client_ops.set_gpu_state callback
         */
        enum switch_power_state switch_power_state;

        /**
         * @fb_helper:
         *
         * Pointer to the fbdev emulation structure.
         * Set by drm_fb_helper_init() and cleared by drm_fb_helper_fini().
         */
        struct drm_fb_helper *fb_helper;

        /**
         * @debugfs_mutex:
         *
         * Protects &debugfs_list access.
         */
        struct mutex debugfs_mutex;
        /**
         * @debugfs_list:
         *
         * List of debugfs files to be created by the DRM device. The files
         * must be added during drm_dev_register().
         */
        struct list_head debugfs_list;

        /* Everything below here is for legacy driver, never use! */
        /* private: */
#if IS_ENABLED(CONFIG_DRM_LEGACY)
        /* List of devices per driver for stealth attach cleanup */
        struct list_head legacy_dev_list;

#ifdef __alpha__
        /** @hose: PCI hose, only used on ALPHA platforms. */
        struct pci_controller *hose;
#endif

        /* AGP data */
        struct drm_agp_head *agp;

        /* Context handle management - linked list of context handles */
        struct list_head ctxlist;

        /* Context handle management - mutex for &ctxlist */
        struct mutex ctxlist_mutex;

        /* Context handle management */
        struct idr ctx_idr;

        /* Memory management - linked list of regions */
        struct list_head maplist;

        /* Memory management - user token hash table for maps */
        struct drm_open_hash map_hash;

        /* Context handle management - list of vmas (for debugging) */
        struct list_head vmalist;

        /* Optional pointer for DMA support */
        struct drm_device_dma *dma;

        /* Context swapping flag */
        __volatile__ long context_flag;

        /* Last current context */
        int last_context;

        /* Lock for &buf_use and a few other things. */
        spinlock_t buf_lock;

        /* Usage counter for buffers in use -- cannot alloc */
        int buf_use;

        /* Buffer allocation in progress */
        atomic_t buf_alloc;
        struct {
                int context;
                struct drm_hw_lock *lock;
        } sigdata;

        struct drm_local_map *agp_buffer_map;
        unsigned int agp_buffer_token;

        /* Scatter gather memory */
        struct drm_sg_mem *sg;

        /* IRQs */
        bool irq_enabled;
        int irq;
#endif
};

初识这个数据结构,我们发现这个数据结构包含的字段属实有点多,如果要将每个字段的含义都搞清楚,定然不是一件容易的事情,因此我们只关注如下字段即可:

  • dev:设备驱动模型中的device,可以将drm_device看做其子类;
  • driverdrm驱动;
  • registered:设备是否已注册;
  • unique:设备的唯一名称;
  • vblank_event_listvblank事件链表;
  • num_crtcsCRTC的数量;
  • mode_config:当前的显示模式配置,struct drm_mode_config类型。
4.1.1 struct drm_mode_config

linux内核使用struct drm_mode_config来描述显示模式配置信息,drm_mode_config的主要功能之一是提供对显示器模式的管理和配置。这包括添加、删除、修改和查询显示器模式的能力。此外,drm_mode_config还提供了与模式相关的配置选项,例如色彩空间、刷新率、分辨率等等。

struct drm_mode_config定义在include/drm/drm_mode_config.h

/**
 * struct drm_mode_config - Mode configuration control structure
 * @min_width: minimum fb pixel width on this device
 * @min_height: minimum fb pixel height on this device
 * @max_width: maximum fb pixel width on this device
 * @max_height: maximum fb pixel height on this device
 * @funcs: core driver provided mode setting functions
 * @poll_enabled: track polling support for this device
 * @poll_running: track polling status for this device
 * @delayed_event: track delayed poll uevent deliver for this device
 * @output_poll_work: delayed work for polling in process context
 * @preferred_depth: preferred RBG pixel depth, used by fb helpers
 * @prefer_shadow: hint to userspace to prefer shadow-fb rendering
 * @cursor_width: hint to userspace for max cursor width
 * @cursor_height: hint to userspace for max cursor height
 * @helper_private: mid-layer private data
 *
 * Core mode resource tracking structure.  All CRTC, encoders, and connectors
 * enumerated by the driver are added here, as are global properties.  Some
 * global restrictions are also here, e.g. dimension restrictions.
 *
 * Framebuffer sizes refer to the virtual screen that can be displayed by
 * the CRTC. This can be different from the physical resolution programmed.
 * The minimum width and height, stored in @min_width and @min_height,
 * describe the smallest size of the framebuffer. It correlates to the
 * minimum programmable resolution.
 * The maximum width, stored in @max_width, is typically limited by the
 * maximum pitch between two adjacent scanlines. The maximum height, stored
 * in @max_height, is usually only limited by the amount of addressable video
 * memory. For hardware that has no real maximum, drivers should pick a
 * reasonable default.
 *
 * See also @DRM_SHADOW_PLANE_MAX_WIDTH and @DRM_SHADOW_PLANE_MAX_HEIGHT.
 */
struct drm_mode_config {
        /**
         * @mutex:
         *
         * This is the big scary modeset BKL which protects everything that
         * isn't protect otherwise. Scope is unclear and fuzzy, try to remove
         * anything from under its protection and move it into more well-scoped
         * locks.
         *
         * The one important thing this protects is the use of @acquire_ctx.
         */
        struct mutex mutex;

        /**
         * @connection_mutex:
         *
         * This protects connector state and the connector to encoder to CRTC
         * routing chain.
         *
         * For atomic drivers specifically this protects &drm_connector.state.
         */
        struct drm_modeset_lock connection_mutex;

        /**
         * @acquire_ctx:
         *
         * Global implicit acquire context used by atomic drivers for legacy
         * IOCTLs. Deprecated, since implicit locking contexts make it
         * impossible to use driver-private &struct drm_modeset_lock. Users of
         * this must hold @mutex.
         */
        struct drm_modeset_acquire_ctx *acquire_ctx;

        /**
         * @idr_mutex:
         *
         * Mutex for KMS ID allocation and management. Protects both @object_idr
         * and @tile_idr.
         */
        struct mutex idr_mutex;

        /**
         * @object_idr:
         *
         * Main KMS ID tracking object. Use this idr for all IDs, fb, crtc,
         * connector, modes - just makes life easier to have only one.
         */
        struct idr object_idr;
        /**
         * @tile_idr:
         *
         * Use this idr for allocating new IDs for tiled sinks like use in some
         * high-res DP MST screens.
         */
        struct idr tile_idr;

        /** @fb_lock: Mutex to protect fb the global @fb_list and @num_fb. */
        struct mutex fb_lock;
        /** @num_fb: Number of entries on @fb_list. */
        int num_fb;
        /** @fb_list: List of all &struct drm_framebuffer. */
        struct list_head fb_list;

        /**
         * @connector_list_lock: Protects @num_connector and
         * @connector_list and @connector_free_list.
         */
        spinlock_t connector_list_lock;
        /**
         * @num_connector: Number of connectors on this device. Protected by
         * @connector_list_lock.
         */
        int num_connector;
        /**
         * @connector_ida: ID allocator for connector indices.
         */
        struct ida connector_ida;
        /**
         * @connector_list:
         *
         * List of connector objects linked with &drm_connector.head. Protected
         * by @connector_list_lock. Only use drm_for_each_connector_iter() and
         * &struct drm_connector_list_iter to walk this list.
         */
        struct list_head connector_list;
        /**
         * @connector_free_list:
         *
         * List of connector objects linked with &drm_connector.free_head.
         * Protected by @connector_list_lock. Used by
         * drm_for_each_connector_iter() and
         * &struct drm_connector_list_iter to savely free connectors using
         * @connector_free_work.
         */
        struct llist_head connector_free_list;
        /**
         * @connector_free_work: Work to clean up @connector_free_list.
         */
        struct work_struct connector_free_work;
        /**
         * @num_encoder:
         *
         * Number of encoders on this device. This is invariant over the
         * lifetime of a device and hence doesn't need any locks.
         */
        int num_encoder;
        /**
         * @encoder_list:
         *
         * List of encoder objects linked with &drm_encoder.head. This is
         * invariant over the lifetime of a device and hence doesn't need any
         * locks.
         */
        struct list_head encoder_list;

        /**
         * @num_total_plane:
         *
         * Number of universal (i.e. with primary/curso) planes on this device.
         * This is invariant over the lifetime of a device and hence doesn't
         * need any locks.
         */
        int num_total_plane;
        /**
         * @plane_list:
         *
         * List of plane objects linked with &drm_plane.head. This is invariant
         * over the lifetime of a device and hence doesn't need any locks.
         */
        struct list_head plane_list;

        /**
         * @num_crtc:
         *
         * Number of CRTCs on this device linked with &drm_crtc.head. This is invariant over the lifetime
         * of a device and hence doesn't need any locks.
         */
        int num_crtc;
        /**
         * @crtc_list:
         *
         * List of CRTC objects linked with &drm_crtc.head. This is invariant
         * over the lifetime of a device and hence doesn't need any locks.
         */
        struct list_head crtc_list;

        /**
         * @property_list:
         *
         * List of property type objects linked with &drm_property.head. This is
         * invariant over the lifetime of a device and hence doesn't need any
         * locks.
         */
        struct list_head property_list;
        /**
         * @privobj_list:
         *
         * List of private objects linked with &drm_private_obj.head. This is
         * invariant over the lifetime of a device and hence doesn't need any
         * locks.
         */
        struct list_head privobj_list;

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

        /* output poll support */
        bool poll_enabled;
        bool poll_running;
        bool delayed_event;
        struct delayed_work output_poll_work;

        /**
         * @blob_lock:
         *
         * Mutex for blob property allocation and management, protects
         * @property_blob_list and &drm_file.blobs.
         */
        struct mutex blob_lock;

        /**
         * @property_blob_list:
         *
         * List of all the blob property objects linked with
         * &drm_property_blob.head. Protected by @blob_lock.
         */
        struct list_head property_blob_list;

        /* pointers to standard properties */

        /**
         * @edid_property: Default connector property to hold the EDID of the
         * currently connected sink, if any.
         */
        struct drm_property *edid_property;
        /**
         * @dpms_property: Default connector property to control the
         * connector's DPMS state.
         */
        struct drm_property *dpms_property;
        /**
         * @path_property: Default connector property to hold the DP MST path
         * for the port.
         */
        struct drm_property *path_property;
   
        .... 大量的struct drm_property
            
        /**
         * @hdcp_content_type_property: DRM ENUM property for type of
         * Protected Content.
         */
        struct drm_property *hdcp_content_type_property;

        /* dumb ioctl parameters */
        uint32_t preferred_depth, prefer_shadow;

        /**
         * @prefer_shadow_fbdev:
         *
         * Hint to framebuffer emulation to prefer shadow-fb rendering.
         */
        bool prefer_shadow_fbdev;

        /**
         * @quirk_addfb_prefer_xbgr_30bpp:
         *
         * Special hack for legacy ADDFB to keep nouveau userspace happy. Should
         * only ever be set by the nouveau kernel driver.
         */
        bool quirk_addfb_prefer_xbgr_30bpp;

        /**
         * @quirk_addfb_prefer_host_byte_order:
         *
         * When set to true drm_mode_addfb() will pick host byte order
         * pixel_format when calling drm_mode_addfb2().  This is how
         * drm_mode_addfb() should have worked from day one.  It
         * didn't though, so we ended up with quirks in both kernel
         * and userspace drivers to deal with the broken behavior.
         * Simply fixing drm_mode_addfb() unconditionally would break
         * these drivers, so add a quirk bit here to allow drivers
         * opt-in.
         */
        bool quirk_addfb_prefer_host_byte_order;

        /**
         * @async_page_flip: Does this device support async flips on the primary
         * plane?
         */
        bool async_page_flip;

        /**
         * @fb_modifiers_not_supported:
         *
         * When this flag is set, the DRM device will not expose modifier
         * support to userspace. This is only used by legacy drivers that infer
         * the buffer layout through heuristics without using modifiers. New
         * drivers shall not set fhis flag.
         */
        bool fb_modifiers_not_supported;

        /**
         * @normalize_zpos:
         *
         * If true the drm core will call drm_atomic_normalize_zpos() as part of
         * atomic mode checking from drm_atomic_helper_check()
         */
        bool normalize_zpos;

        /**
         * @modifiers_property: Plane property to list support modifier/format
         * combination.
         */
        struct drm_property *modifiers_property;

        /* cursor size */
        uint32_t cursor_width, cursor_height;

        /**
         * @suspend_state:
         *
         * Atomic state when suspended.
         * Set by drm_mode_config_helper_suspend() and cleared by
         * drm_mode_config_helper_resume().
         */
        struct drm_atomic_state *suspend_state;

        const struct drm_mode_config_helper_funcs *helper_private;
};

同样,我们只关系核心字段:

  • 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.2 struct drm_mode_config_funcs

struct drm_mode_config结构体中存在一个类型为struct drm_mode_config_funcs的回调函数funcs.

``drm_mode_config_func是一个函数指针结构体,用于驱动程序向内核注册显示器模式配置(Mode Setting)的回调函数。这些函数指针包括添加和删除连接器CRTC`和编解码器,以及更新显示模式等功能。

当内核需要对显示器模式进行配置或管理时,它将调用这些回调函数以执行相应操作。

struct drm_mode_config_funcs定义在include/drm/drm_mode_config.h

/**
 * struct drm_mode_config_funcs - basic driver provided mode setting functions
 *
 * Some global (i.e. not per-CRTC, connector, etc) mode setting functions that
 * involve drivers.
 */
struct drm_mode_config_funcs {
        /**
         * @fb_create:
         *
         * Create a new framebuffer object. The core does basic checks on the
         * requested metadata, but most of that is left to the driver. See
         * &struct drm_mode_fb_cmd2 for details.
         *
         * To validate the pixel format and modifier drivers can use
         * drm_any_plane_has_format() to make sure at least one plane supports
         * the requested values. Note that the driver must first determine the
         * actual modifier used if the request doesn't have it specified,
         * ie. when (@mode_cmd->flags & DRM_MODE_FB_MODIFIERS) == 0.
         *
         * IMPORTANT: These implied modifiers for legacy userspace must be
         * stored in struct &drm_framebuffer, including all relevant metadata
         * like &drm_framebuffer.pitches and &drm_framebuffer.offsets if the
         * modifier enables additional planes beyond the fourcc pixel format
         * code. This is required by the GETFB2 ioctl.
         *
         * If the parameters are deemed valid and the backing storage objects in
         * the underlying memory manager all exist, then the driver allocates
         * a new &drm_framebuffer structure, subclassed to contain
         * driver-specific information (like the internal native buffer object
         * references). It also needs to fill out all relevant metadata, which
         * should be done by calling drm_helper_mode_fill_fb_struct().
         *
         * The initialization is finalized by calling drm_framebuffer_init(),
         * which registers the framebuffer and makes it accessible to other
         * threads.
         *
         * RETURNS:
         *
         * A new framebuffer with an initial reference count of 1 or a negative
         * error code encoded with ERR_PTR().
         */
        struct drm_framebuffer *(*fb_create)(struct drm_device *dev,
                                             struct drm_file *file_priv,
                                             const struct drm_mode_fb_cmd2 *mode_cmd);
        /**
         * @get_format_info:
         *
         * Allows a driver to return custom format information for special
         * fb layouts (eg. ones with auxiliary compression control planes).
         *
         * RETURNS:
         *
         * The format information specific to the given fb metadata, or
         * NULL if none is found.
         */
        const struct drm_format_info *(*get_format_info)(const struct drm_mode_fb_cmd2 *mode_cmd);

        /**
         * @output_poll_changed:
         *
         * Callback used by helpers to inform the driver of output configuration
         * changes.
         *
         * Drivers implementing fbdev emulation use drm_kms_helper_hotplug_event()
         * to call this hook to inform the fbdev helper of output changes.
         *
         * This hook is deprecated, drivers should instead use
         * drm_fbdev_generic_setup() which takes care of any necessary
         * hotplug event forwarding already without further involvement by
         * the driver.
         */
        void (*output_poll_changed)(struct drm_device *dev);

        /**
         * @mode_valid:
         *
         * Device specific validation of display modes. Can be used to reject
         * modes that can never be supported. Only device wide constraints can
         * be checked here. crtc/encoder/bridge/connector specific constraints
         * should be checked in the .mode_valid() hook for each specific object.
         */
        enum drm_mode_status (*mode_valid)(struct drm_device *dev,
                                           const struct drm_display_mode *mode);
        /**
         * @atomic_check:
         *
         * This is the only hook to validate an atomic modeset update. This
         * function must reject any modeset and state changes which the hardware
         * or driver doesn't support. This includes but is of course not limited
         * to:
         *
         *  - Checking that the modes, framebuffers, scaling and placement
         *    requirements and so on are within the limits of the hardware.
         *
         *  - Checking that any hidden shared resources are not oversubscribed.
         *    This can be shared PLLs, shared lanes, overall memory bandwidth,
         *    display fifo space (where shared between planes or maybe even
         *    CRTCs).
         *
         *  - Checking that virtualized resources exported to userspace are not
         *    oversubscribed. For various reasons it can make sense to expose
         *    more planes, crtcs or encoders than which are physically there. One
         *    example is dual-pipe operations (which generally should be hidden
         *    from userspace if when lockstepped in hardware, exposed otherwise),
         *    where a plane might need 1 hardware plane (if it's just on one
         *    pipe), 2 hardware planes (when it spans both pipes) or maybe even
         *    shared a hardware plane with a 2nd plane (if there's a compatible
         *    plane requested on the area handled by the other pipe).
         *
         *  - Check that any transitional state is possible and that if
         *    requested, the update can indeed be done in the vblank period
         *    without temporarily disabling some functions.
         *
         *  - Check any other constraints the driver or hardware might have.
         *
         *  - This callback also needs to correctly fill out the &drm_crtc_state
         *    in this update to make sure that drm_atomic_crtc_needs_modeset()
         *    reflects the nature of the possible update and returns true if and
         *    only if the update cannot be applied without tearing within one
         *    vblank on that CRTC. The core uses that information to reject
         *    updates which require a full modeset (i.e. blanking the screen, or
         *    at least pausing updates for a substantial amount of time) if
         *    userspace has disallowed that in its request.
         *
         *  - The driver also does not need to repeat basic input validation
         *    like done for the corresponding legacy entry points. The core does
         *    that before calling this hook.
         *
         * See the documentation of @atomic_commit for an exhaustive list of
         * error conditions which don't have to be checked at the in this
         * callback.
         *
         * See the documentation for &struct drm_atomic_state for how exactly
         * an atomic modeset update is described.
         *
         * Drivers using the atomic helpers can implement this hook using
         * drm_atomic_helper_check(), or one of the exported sub-functions of
         * it.
         *
         * RETURNS:
         *
         * 0 on success or one of the below negative error codes:
         *
         *  - -EINVAL, if any of the above constraints are violated.
         *
         *  - -EDEADLK, when returned from an attempt to acquire an additional
         *    &drm_modeset_lock through drm_modeset_lock().
         *
         *  - -ENOMEM, if allocating additional state sub-structures failed due
         *    to lack of memory.
         *
         *  - -EINTR, -EAGAIN or -ERESTARTSYS, if the IOCTL should be restarted.
         *    This can either be due to a pending signal, or because the driver
         *    needs to completely bail out to recover from an exceptional
         *    situation like a GPU hang. From a userspace point all errors are
         *    treated equally.
         */
        int (*atomic_check)(struct drm_device *dev,
                            struct drm_atomic_state *state);

        /**
         * @atomic_commit:
         *
         * This is the only hook to commit an atomic modeset update. The core
         * guarantees that @atomic_check has been called successfully before
         * calling this function, and that nothing has been changed in the
         * interim.
         *
         * See the documentation for &struct drm_atomic_state for how exactly
         * an atomic modeset update is described.
         *
         * Drivers using the atomic helpers can implement this hook using
         * drm_atomic_helper_commit(), or one of the exported sub-functions of
         * it.
         *
         * Nonblocking commits (as indicated with the nonblock parameter) must
         * do any preparatory work which might result in an unsuccessful commit
         * in the context of this callback. The only exceptions are hardware
         * errors resulting in -EIO. But even in that case the driver must
         * ensure that the display pipe is at least running, to avoid
         * compositors crashing when pageflips don't work. Anything else,
         * specifically committing the update to the hardware, should be done
         * without blocking the caller. For updates which do not require a
         * modeset this must be guaranteed.
         *
         * The driver must wait for any pending rendering to the new
         * framebuffers to complete before executing the flip. It should also
         * wait for any pending rendering from other drivers if the underlying
         * buffer is a shared dma-buf. Nonblocking commits must not wait for
         * rendering in the context of this callback.
         *
         * An application can request to be notified when the atomic commit has
         * completed. These events are per-CRTC and can be distinguished by the
         * CRTC index supplied in &drm_event to userspace.
         *
         * The drm core will supply a &struct drm_event in each CRTC's
         * &drm_crtc_state.event. See the documentation for
         * &drm_crtc_state.event for more details about the precise semantics of
         * this event.
         *
         * NOTE:
         *
         * Drivers are not allowed to shut down any display pipe successfully
         * enabled through an atomic commit on their own. Doing so can result in
         * compositors crashing if a page flip is suddenly rejected because the
         * pipe is off.
         *
         * RETURNS:
         *
         * 0 on success or one of the below negative error codes:
         *
         *  - -EBUSY, if a nonblocking updated is requested and there is
         *    an earlier updated pending. Drivers are allowed to support a queue
         *    of outstanding updates, but currently no driver supports that.
         *    Note that drivers must wait for preceding updates to complete if a
         *    synchronous update is requested, they are not allowed to fail the
         *    commit in that case.
         *
         *  - -ENOMEM, if the driver failed to allocate memory. Specifically
         *    this can happen when trying to pin framebuffers, which must only
         *    be done when committing the state.
         *
         *  - -ENOSPC, as a refinement of the more generic -ENOMEM to indicate
         *    that the driver has run out of vram, iommu space or similar GPU
         *    address space needed for framebuffer.
         *
         *  - -EIO, if the hardware completely died.
         *
         *  - -EINTR, -EAGAIN or -ERESTARTSYS, if the IOCTL should be restarted.
         *    This can either be due to a pending signal, or because the driver
         *    needs to completely bail out to recover from an exceptional
         *    situation like a GPU hang. From a userspace point of view all errors are
         *    treated equally.
         *
         * This list is exhaustive. Specifically this hook is not allowed to
         * return -EINVAL (any invalid requests should be caught in
         * @atomic_check) or -EDEADLK (this function must not acquire
         * additional modeset locks).
         */
        int (*atomic_commit)(struct drm_device *dev,
                             struct drm_atomic_state *state,
                             bool nonblock);
        /**
         * @atomic_state_alloc:
         *
         * This optional hook can be used by drivers that want to subclass struct
         * &drm_atomic_state to be able to track their own driver-private global
         * state easily. If this hook is implemented, drivers must also
         * implement @atomic_state_clear and @atomic_state_free.
         *
         * Subclassing of &drm_atomic_state is deprecated in favour of using
         * &drm_private_state and &drm_private_obj.
         *
         * RETURNS:
         *
         * A new &drm_atomic_state on success or NULL on failure.
         */
        struct drm_atomic_state *(*atomic_state_alloc)(struct drm_device *dev);

        /**
         * @atomic_state_clear:
         *
         * This hook must clear any driver private state duplicated into the
         * passed-in &drm_atomic_state. This hook is called when the caller
         * encountered a &drm_modeset_lock deadlock and needs to drop all
         * already acquired locks as part of the deadlock avoidance dance
         * implemented in drm_modeset_backoff().
         *
         * Any duplicated state must be invalidated since a concurrent atomic
         * update might change it, and the drm atomic interfaces always apply
         * updates as relative changes to the current state.
         *
         * Drivers that implement this must call drm_atomic_state_default_clear()
         * to clear common state.
         *
         * Subclassing of &drm_atomic_state is deprecated in favour of using
         * &drm_private_state and &drm_private_obj.
         */
        void (*atomic_state_clear)(struct drm_atomic_state *state);

        /**
         * @atomic_state_free:
         *
         * This hook needs driver private resources and the &drm_atomic_state
         * itself. Note that the core first calls drm_atomic_state_clear() to
         * avoid code duplicate between the clear and free hooks.
         *
         * Drivers that implement this must call
         * drm_atomic_state_default_release() to release common resources.
         *
         * Subclassing of &drm_atomic_state is deprecated in favour of using
         * &drm_private_state and &drm_private_obj.
         */
        void (*atomic_state_free)(struct drm_atomic_state *state);
};

其中:

  • fb_create:根据给定的帧缓冲参数,创建一个新的framebuffer object(并不是分配内存,只是创建帧缓冲对象,因为framebuffer不涉及内存的分配与释放),并返回其句柄;
  • get_format_info:获取DRM格式信息,返回的数据类型为struct drm_format_info

4.2 struct drm_driver

linux内核使用struct drm_driver数据结构来描述drm驱动,定义在include/drm/drm_drv.h

/**
 * struct drm_driver - DRM driver structure
 *
 * This structure represent the common code for a family of cards. There will be
 * one &struct drm_device for each card present in this family. It contains lots
 * of vfunc entries, and a pile of those probably should be moved to more
 * appropriate places like &drm_mode_config_funcs or into a new operations
 * structure for GEM drivers.
 */
struct drm_driver {
        /**
         * @load:
         *
         * Backward-compatible driver callback to complete initialization steps
         * after the driver is registered.  For this reason, may suffer from
         * race conditions and its use is deprecated for new drivers.  It is
         * therefore only supported for existing drivers not yet converted to
         * the new scheme.  See devm_drm_dev_alloc() and drm_dev_register() for
         * proper and race-free way to set up a &struct drm_device.
         *
         * This is deprecated, do not use!
         *
         * Returns:
         *
         * Zero on success, non-zero value on failure.
         */
        int (*load) (struct drm_device *, unsigned long flags);

        /**
         * @open:
         *
         * Driver callback when a new &struct drm_file is opened. Useful for
         * setting up driver-private data structures like buffer allocators,
         * execution contexts or similar things. Such driver-private resources
         * must be released again in @postclose.
         *
         * Since the display/modeset side of DRM can only be owned by exactly
         * one &struct drm_file (see &drm_file.is_master and &drm_device.master)
         * there should never be a need to set up any modeset related resources
         * in this callback. Doing so would be a driver design bug.
         *
         * Returns:
         *
         * 0 on success, a negative error code on failure, which will be
         * promoted to userspace as the result of the open() system call.
         */
        int (*open) (struct drm_device *, struct drm_file *);
        /**
         * @postclose:
         *
         * One of the driver callbacks when a new &struct drm_file is closed.
         * Useful for tearing down driver-private data structures allocated in
         * @open like buffer allocators, execution contexts or similar things.
         *
         * Since the display/modeset side of DRM can only be owned by exactly
         * one &struct drm_file (see &drm_file.is_master and &drm_device.master)
         * there should never be a need to tear down any modeset related
         * resources in this callback. Doing so would be a driver design bug.
         */
        void (*postclose) (struct drm_device *, struct drm_file *);

        /**
         * @lastclose:
         *
         * Called when the last &struct drm_file has been closed and there's
         * currently no userspace client for the &struct drm_device.
         *
         * Modern drivers should only use this to force-restore the fbdev
         * framebuffer using drm_fb_helper_restore_fbdev_mode_unlocked().
         * Anything else would indicate there's something seriously wrong.
         * Modern drivers can also use this to execute delayed power switching
         * state changes, e.g. in conjunction with the :ref:`vga_switcheroo`
         * infrastructure.
         *
         * This is called after @postclose hook has been called.
         *
         * NOTE:
         *
         * All legacy drivers use this callback to de-initialize the hardware.
         * This is purely because of the shadow-attach model, where the DRM
         * kernel driver does not really own the hardware. Instead ownershipe is
         * handled with the help of userspace through an inheritedly racy dance
         * to set/unset the VT into raw mode.
         *
         * Legacy drivers initialize the hardware in the @firstopen callback,
         * which isn't even called for modern drivers.
         */
        void (*lastclose) (struct drm_device *);

        /**
         * @unload:
         *
         * Reverse the effects of the driver load callback.  Ideally,
         * the clean up performed by the driver should happen in the
         * reverse order of the initialization.  Similarly to the load
         * hook, this handler is deprecated and its usage should be
         * dropped in favor of an open-coded teardown function at the
         * driver layer.  See drm_dev_unregister() and drm_dev_put()
         * for the proper way to remove a &struct drm_device.
         *
         * The unload() hook is called right after unregistering
         * the device.
         *
         */
        void (*unload) (struct drm_device *);

        /**
         * @release:
         *
         * Optional callback for destroying device data after the final
         * reference is released, i.e. the device is being destroyed.
         *
         * This is deprecated, clean up all memory allocations associated with a
         * &drm_device using drmm_add_action(), drmm_kmalloc() and related
         * managed resources functions.
         */
        void (*release) (struct drm_device *);

        /**
         * @master_set:
         *
         * Called whenever the minor master is set. Only used by vmwgfx.
         */
        void (*master_set)(struct drm_device *dev, struct drm_file *file_priv,
                           bool from_open);
        /**
         * @master_drop:
         *
         * Called whenever the minor master is dropped. Only used by vmwgfx.
         */
        void (*master_drop)(struct drm_device *dev, struct drm_file *file_priv);

        /**
         * @debugfs_init:
         *
         * Allows drivers to create driver-specific debugfs files.
         */
        void (*debugfs_init)(struct drm_minor *minor);

        /**
         * @gem_create_object: constructor for gem objects
         *
         * Hook for allocating the GEM object struct, for use by the CMA
         * and SHMEM GEM helpers. Returns a GEM object on success, or an
         * ERR_PTR()-encoded error code otherwise.
         */
        struct drm_gem_object *(*gem_create_object)(struct drm_device *dev,
                                                    size_t size);

        /**
         * @prime_handle_to_fd:
         *
         * Main PRIME export function. Should be implemented with
         * drm_gem_prime_handle_to_fd() for GEM based drivers.
         *
         * For an in-depth discussion see :ref:`PRIME buffer sharing
         * documentation <prime_buffer_sharing>`.
         */
        int (*prime_handle_to_fd)(struct drm_device *dev, struct drm_file *file_priv,
                                uint32_t handle, uint32_t flags, int *prime_fd);
        /**
         * @prime_fd_to_handle:
         *
         * Main PRIME import function. Should be implemented with
         * drm_gem_prime_fd_to_handle() for GEM based drivers.
         *
         * For an in-depth discussion see :ref:`PRIME buffer sharing
         * documentation <prime_buffer_sharing>`.
         */
        int (*prime_fd_to_handle)(struct drm_device *dev, struct drm_file *file_priv,
                                int prime_fd, uint32_t *handle);

        /**
         * @gem_prime_import:
         *
         * Import hook for GEM drivers.
         *
         * This defaults to drm_gem_prime_import() if not set.
         */
        struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev,
                                struct dma_buf *dma_buf);
        /**
         * @gem_prime_import_sg_table:
         *
         * Optional hook used by the PRIME helper functions
         * drm_gem_prime_import() respectively drm_gem_prime_import_dev().
         */
        struct drm_gem_object *(*gem_prime_import_sg_table)(
                                struct drm_device *dev,
                                struct dma_buf_attachment *attach,
                                struct sg_table *sgt);
        /**
         * @gem_prime_mmap:
         *
         * mmap hook for GEM drivers, used to implement dma-buf mmap in the
         * PRIME helpers.
         *
         * This hook only exists for historical reasons. Drivers must use
         * drm_gem_prime_mmap() to implement it.
         *
         * FIXME: Convert all drivers to implement mmap in struct
         * &drm_gem_object_funcs and inline drm_gem_prime_mmap() into
         * its callers. This hook should be removed afterwards.
         */
        int (*gem_prime_mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);

        /**
         * @dumb_create:
         *
         * This creates a new dumb buffer in the driver's backing storage manager (GEM,
         * TTM or something else entirely) and returns the resulting buffer handle. This
         * handle can then be wrapped up into a framebuffer modeset object.
         *
         * Note that userspace is not allowed to use such objects for render
         * acceleration - drivers must create their own private ioctls for such a use
         * case.
         *
         * Width, height and depth are specified in the &drm_mode_create_dumb
         * argument. The callback needs to fill the handle, pitch and size for
         * the created buffer.
         *
         * Called by the user via ioctl.
         *
         * Returns:
         *
         * Zero on success, negative errno on failure.
         */
        int (*dumb_create)(struct drm_file *file_priv,
                           struct drm_device *dev,
                           struct drm_mode_create_dumb *args);
        /**
         * @dumb_map_offset:
         *
         * Allocate an offset in the drm device node's address space to be able to
         * memory map a dumb buffer.
         *
         * The default implementation is drm_gem_create_mmap_offset(). GEM based
         * drivers must not overwrite this.
         *
         * Called by the user via ioctl.
         *
         * Returns:
         *
         * Zero on success, negative errno on failure.
         */
        int (*dumb_map_offset)(struct drm_file *file_priv,
                               struct drm_device *dev, uint32_t handle,
                               uint64_t *offset);
        /**
         * @dumb_destroy:
         *
         * This destroys the userspace handle for the given dumb backing storage buffer.
         * Since buffer objects must be reference counted in the kernel a buffer object
         * won't be immediately freed if a framebuffer modeset object still uses it.
         *
         * Called by the user via ioctl.
         *
         * The default implementation is drm_gem_dumb_destroy(). GEM based drivers
         * must not overwrite this.
         *
         * Returns:
         *
         * Zero on success, negative errno on failure.
         */
        int (*dumb_destroy)(struct drm_file *file_priv,
                            struct drm_device *dev,
                            uint32_t handle);

        /** @major: driver major number */
        int major;
        /** @minor: driver minor number */
        int minor;
        /** @patchlevel: driver patch level */
        int patchlevel;
        /** @name: driver name */
        char *name;
        /** @desc: driver description */
        char *desc;
        /** @date: driver date */
        char *date;

        /**
         * @driver_features:
         * Driver features, see &enum drm_driver_feature. Drivers can disable
         * some features on a per-instance basis using
         * &drm_device.driver_features.
         */
        u32 driver_features;

        /**
         * @ioctls:
         *
         * Array of driver-private IOCTL description entries. See the chapter on
         * :ref:`IOCTL support in the userland interfaces
         * chapter<drm_driver_ioctl>` for the full details.
         */

        const struct drm_ioctl_desc *ioctls;
        /** @num_ioctls: Number of entries in @ioctls. */
        int num_ioctls;

        /**
         * @fops:
         *
         * File operations for the DRM device node. See the discussion in
         * :ref:`file operations<drm_driver_fops>` for in-depth coverage and
         * some examples.
         */
        const struct file_operations *fops;

#ifdef CONFIG_DRM_LEGACY
        /* Everything below here is for legacy driver, never use! */
        /* private: */

        int (*firstopen) (struct drm_device *);
        void (*preclose) (struct drm_device *, struct drm_file *file_priv);
        int (*dma_ioctl) (struct drm_device *dev, void *data, struct drm_file *file_priv);
        int (*dma_quiescent) (struct drm_device *);
        int (*context_dtor) (struct drm_device *dev, int context);
        irqreturn_t (*irq_handler)(int irq, void *arg);
        void (*irq_preinstall)(struct drm_device *dev);
        int (*irq_postinstall)(struct drm_device *dev);
        void (*irq_uninstall)(struct drm_device *dev);
        u32 (*get_vblank_counter)(struct drm_device *dev, unsigned int pipe);
        int (*enable_vblank)(struct drm_device *dev, unsigned int pipe);
        void (*disable_vblank)(struct drm_device *dev, unsigned int pipe);
        int dev_priv_size;
#endif
};

同样,该数据结构也包含了大量的成员,其中:

  • name:名称;
  • desc:描述信息;
  • major:主设备号;
  • minor:次设备号;
  • 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对象;
  • gem_vm_ops
  • gem_free_object_unlocked
4.2.1 dumb_create

dumb_create分配dumb buffer的回调接口;

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

主要完成三件事:

  • 创建gem objec;
  • 创建gem handle;
  • 分配物理内存表dumb buffer

GEM分配的内存区域通过映射之后交给framebuffer驱动程序使用。framebuffer驱动程序不直接分配内存,而是通过访问GEM对象来获取内存区域的物理地址等信息,以便正确地管理和利用帧缓冲区中的显示数据。

在将GEM映射到framebuffer时,需要经过以下两个步骤:

  • GEM对象中的内存区域映射到内核空间的虚拟地址空间中;
  • 将内核空间中的GEM内存区域映射到显示设备的显存中。

五、DRM驱动API

5.1 drm_dev_init

5.2 drm_dev_register

5.3 drm_mode_config_init

drm_mode_config_init用于初始化drm_device中mode_config结构体;

5.4 drm_xxx_init

drm_xxx_init 则分别用于创建 planecrtcencoderconnector 这4个 drm_mode_object

具体实现函数drm_universal_plane_initdrm_crtc_init_with_planesdrm_encoder_initdrm_connector_init我们在后面章节单独介绍。

六、驱动程序测试

这里我们介绍一个DRM驱动的案例,具体流程如下:

(1) 定义struct drm_device,并调用drm_dev_init函数进行初始化;

(2) 定义struct drm_driver,并初始化成员namedescdatamajorminordriver_featuresfopsdumb_create等;

(3) 调用drm_mode_config_init初始化drm_devicemode_config结构体;

(4) 调用drm_xxx_init创建 planecrtcencoderconnector 这4个 drm_mode_object

( 5) 调用drm_dev_register注册drm_device

6.1 目录结构

这里我们以RK3399 DRM驱动为例进行介绍:

root@zhengyang:/work/sambashare/rk3399/linux-6.3# ll drivers/gpu/drm/rockchip/rockchip_drm*.c
-rw-rw-r-- 1 root root 13006 Jun 13 20:29 drivers/gpu/drm/rockchip/rockchip_drm_drv.c
-rw-rw-r-- 1 root root  2460 Apr 24 03:02 drivers/gpu/drm/rockchip/rockchip_drm_fb.c
-rw-rw-r-- 1 root root 13430 Apr 24 03:02 drivers/gpu/drm/rockchip/rockchip_drm_gem.c
-rw-rw-r-- 1 root root 80149 Apr 24 03:02 drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
-rw-rw-r-- 1 root root 60878 Apr 24 03:02 drivers/gpu/drm/rockchip/rockchip_drm_vop.c

其中:

  • rockchip_drm_drv.c:实现了DRM驱动;
  • rockchip_drm_fb.c:实现了framebuffer驱动;
  • rockchip_drm_gem.c:实现了gem驱动, 主要负责显示buffer的分配和释放;

下面我们以源码rockchip_drm_drv.c作为切入点进行介绍。

6. 2 头文件以及常量等定义

6.2.1 引入头文件
#include <drm/drm_aperture.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_generic.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
6.2.2 定义全局变量
#define DRIVER_NAME     "rockchip-zhengyang"
#define DRIVER_DESC     "RockChip Soc DRM-zhengyang"
#define DRIVER_DATE     "20230903"
#define DRIVER_MAJOR    1
#define DRIVER_MINOR    1     // 修改为1

static const struct drm_driver rockchip_drm_driver;

6.3 定义struct drm_driver

static const struct drm_driver rockchip_drm_driver = {
        .driver_features        = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
        .dumb_create            = rockchip_gem_dumb_create,
        .prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
        .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
        .gem_prime_import_sg_table      = rockchip_gem_prime_import_sg_table,
        .gem_prime_mmap         = drm_gem_prime_mmap,
        .fops                   = &rockchip_drm_driver_fops,
        .name   = DRIVER_NAME,
        .desc   = DRIVER_DESC,
        .date   = DRIVER_DATE,
        .major  = DRIVER_MAJOR,
        .minor  = DRIVER_MINOR,
};
6.3.1 driver_features
  • 添加上 DRIVER_GEM 标志位,告诉DRM Core该驱动支持GEM操作;
  • 添加上 DRIVER_MODESET 标志位,告诉DRM Core 该驱动支持kernel Mode Setting操作;
  • 添加上 DRIVER_ATOMIC 标志位,告诉 DRM Core该驱动支持Atomic操作。

6.4 rockchip_gem_dumb_create

其中dumb_create配置为rockchip_gem_dumb_create,该函数用于分配物理内存dumb buffer,函数定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.c

/*
 * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
 * function
 *
 * This aligns the pitch and size arguments to the minimum required. wrap
 * this into your own function if you need bigger alignment.
 */
int rockchip_gem_dumb_create(struct drm_file *file_priv,
                             struct drm_device *dev,
                             struct drm_mode_create_dumb *args)
{
        struct rockchip_gem_object *rk_obj;
        int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);  // 宽度*每像素位数/8

        /*
         * align to 64 bytes since Mali requires it.
         */
        args->pitch = ALIGN(min_pitch, 64);
        args->size = args->pitch * args->height;  // 宽*高*每像素位数/8,即得到总大小(单位字节)

        rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
                                                 &args->handle);

        return PTR_ERR_OR_ZERO(rk_obj);
}

函数调用栈:

rockchip_gem_dumb_create(file_priv,dev,args)
    rockchip_gem_create_with_handle(file_priv, dev, args->size,
                                                 &args->handle)
        rockchip_gem_create_object(drm, size, is_framebuffer) // ①
      	    rockchip_gem_alloc_object(drm, size)
	            drm_gem_object_init(drm, obj, size)
 	        rockchip_gem_alloc_dma(rk_obj, alloc_kmap)
	            dma_alloc_attrs(drm->dev, obj->size,
                                &rk_obj->dma_addr, GFP_KERNEL,
                                rk_obj->dma_attrs)	
        drm_gem_handle_create(file_priv, obj, handle)    // ②
        drm_gem_object_put(obj)  // ③
6.4.1 rockchip GEM对象

数据结构rockchip_gem_objectrockchip驱动自定义的GEM对象,内部包含 struct drm_gem_object,了定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h

struct rockchip_gem_object {             // rockchip私有的GEM对象 
        struct drm_gem_object base;      // GEM对象
        unsigned int flags;

        void *kvaddr;
        dma_addr_t dma_addr;             // dma地址   
        /* Used when IOMMU is disabled */
        unsigned long dma_attrs;

        /* Used when IOMMU is enabled */
        struct drm_mm_node mm;
        unsigned long num_pages;
        struct page **pages;
        struct sg_table *sgt;
        size_t size;
};
6.4.2 rockchip_gem_create_with_handle

函数rockchip_gem_create_with_handle定义如下,函数分配一个struct rockchip_gem_object

/*
 * rockchip_gem_create_with_handle - allocate an object with the given
 * size and create a gem handle on it
 *
 * returns a struct rockchip_gem_object* on success or ERR_PTR values
 * on failure.
 */
static struct rockchip_gem_object *
rockchip_gem_create_with_handle(struct drm_file *file_priv,
                                struct drm_device *drm, unsigned int size,
                                unsigned int *handle)
{
        struct rockchip_gem_object *rk_obj;
        struct drm_gem_object *obj;
        bool is_framebuffer;
        int ret;

        is_framebuffer = drm->fb_helper && file_priv == drm->fb_helper->client.file;

        rk_obj = rockchip_gem_create_object(drm, size, is_framebuffer);
        if (IS_ERR(rk_obj))
                return ERR_CAST(rk_obj);

        obj = &rk_obj->base; // 获取GEM对象

        /*
         * allocate a id of idr table where the obj is registered
         * and handle has the id what user can see.
         */
        ret = drm_gem_handle_create(file_priv, obj, handle); 
        if (ret)
                goto err_handle_create;

        /* drop reference from allocate - handle holds it now. */
        drm_gem_object_put(obj);

        return rk_obj;

err_handle_create:
        rockchip_gem_free_object(obj);

        return ERR_PTR(ret);
}

函数执行流程如下:

  • 调用rockchip_gem_create_object动态分配一个struct rockchip_gem_objec
  • 调用drm_gem_handle_create创建GEM对象handle
  • 调用drm_gem_object_putGEM对象的引用计数-1;
6.4.3 rockchip_gem_create_object

rockchip_gem_create_object函数定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.c

struct rockchip_gem_object *
rockchip_gem_create_object(struct drm_device *drm, unsigned int size,
                           bool alloc_kmap)
{
        struct rockchip_gem_object *rk_obj;
        int ret;

        rk_obj = rockchip_gem_alloc_object(drm, size); 
        if (IS_ERR(rk_obj))
                return rk_obj;

        ret = rockchip_gem_alloc_buf(rk_obj, alloc_kmap);
        if (ret)
                goto err_free_rk_obj;

        return rk_obj;

err_free_rk_obj:
        rockchip_gem_release_object(rk_obj);
        return ERR_PTR(ret);
}

(1) 首先调用rockchip_gem_alloc_object

static const struct drm_gem_object_funcs rockchip_gem_object_funcs = {
        .free = rockchip_gem_free_object,
        .get_sg_table = rockchip_gem_prime_get_sg_table,
        .vmap = rockchip_gem_prime_vmap,
        .vunmap = rockchip_gem_prime_vunmap,
        .mmap = rockchip_drm_gem_object_mmap,
        .vm_ops = &drm_gem_dma_vm_ops,
};

static struct rockchip_gem_object *
        rockchip_gem_alloc_object(struct drm_device *drm, unsigned int size)
{
        struct rockchip_gem_object *rk_obj;
        struct drm_gem_object *obj;

        size = round_up(size, PAGE_SIZE);

        rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
        if (!rk_obj)
                return ERR_PTR(-ENOMEM);

        obj = &rk_obj->base;

        obj->funcs = &rockchip_gem_object_funcs;

        drm_gem_object_init(drm, obj, size);

        return rk_obj;
}

流程如此:

  • 调用kzalloc动态分配struct rockchip_gem_object
  • 然后设定GEM对象funcsrockchip_gem_object_funcs
  • 最后调用drm_gem_object_init初始化GEM对象,创建一个大小为sizeshmfs(共享内存文件系统),并将这个shmfs file放到struct drm_gem_objectfilp区。

(2)然后调用rockchip_gem_alloc_buf

static int rockchip_gem_alloc_iommu(struct rockchip_gem_object *rk_obj,
                                    bool alloc_kmap)
{
        int ret;

        ret = rockchip_gem_get_pages(rk_obj);
        if (ret < 0)
                return ret;

        ret = rockchip_gem_iommu_map(rk_obj);
        if (ret < 0)
                goto err_free;

        if (alloc_kmap) {
                rk_obj->kvaddr = vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
                                      pgprot_writecombine(PAGE_KERNEL));
                if (!rk_obj->kvaddr) {
                        DRM_ERROR("failed to vmap() buffer\n");
                        ret = -ENOMEM;
                        goto err_unmap;
                }
        }

        return 0;

err_unmap:
        rockchip_gem_iommu_unmap(rk_obj);
err_free:
        rockchip_gem_put_pages(rk_obj);

        return ret;
}

static int rockchip_gem_alloc_dma(struct rockchip_gem_object *rk_obj,
                                  bool alloc_kmap)
{
        struct drm_gem_object *obj = &rk_obj->base;
        struct drm_device *drm = obj->dev;

        rk_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE;

        if (!alloc_kmap)
                rk_obj->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;

        rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
                                         &rk_obj->dma_addr, GFP_KERNEL,
                                         rk_obj->dma_attrs);
        if (!rk_obj->kvaddr) {
                DRM_ERROR("failed to allocate %zu byte dma buffer", obj->size);
                return -ENOMEM;
        }

        return 0;
}

static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj,
                                  bool alloc_kmap)
{
        struct drm_gem_object *obj = &rk_obj->base;
        struct drm_device *drm = obj->dev;
        struct rockchip_drm_private *private = drm->dev_private; // 获取drm驱动私有数据

        if (private->domain)
                return rockchip_gem_alloc_iommu(rk_obj, alloc_kmap);
        else
                return rockchip_gem_alloc_dma(rk_obj, alloc_kmap);
}

6.5 fops

其中字符设备节点文件操作集rockchip_drm_driver_fops定义如下:

DEFINE_DRM_GEM_FOPS(rockchip_drm_driver_fops);

DEFINE_DRM_GEM_FOPS宏定义在include/drm/drm_gem.h

/**
 * DRM_GEM_FOPS - Default drm GEM file operations
 *
 * This macro provides a shorthand for setting the GEM file ops in the
 * &file_operations structure.  If all you need are the default ops, use
 * DEFINE_DRM_GEM_FOPS instead.
 */
#define DRM_GEM_FOPS \
        .open           = drm_open,\
        .release        = drm_release,\
        .unlocked_ioctl = drm_ioctl,\
        .compat_ioctl   = drm_compat_ioctl,\
        .poll           = drm_poll,\
        .read           = drm_read,\
        .llseek         = noop_llseek,\
        .mmap           = drm_gem_mmap

/**
 * DEFINE_DRM_GEM_FOPS() - macro to generate file operations for GEM drivers
 * @name: name for the generated structure
 *
 * This macro autogenerates a suitable &struct file_operations for GEM based
 * drivers, which can be assigned to &drm_driver.fops. Note that this structure
 * cannot be shared between drivers, because it contains a reference to the
 * current module using THIS_MODULE.
 *
 * Note that the declaration is already marked as static - if you need a
 * non-static version of this you're probably doing it wrong and will break the
 * THIS_MODULE reference by accident.
 */
#define DEFINE_DRM_GEM_FOPS(name) \
        static const struct file_operations name = {\
                .owner          = THIS_MODULE,\
                DRM_GEM_FOPS,\
        }

其中nmap用于将物理内存dumb buffer映射到用户空间,让应用程序可以直接访问设备内存。drm_gem_mmap函数定义在drivers/gpu/drm/drm_gem.c,内容如下:

/**
 * drm_gem_mmap - memory map routine for GEM objects
 * @filp: DRM file pointer
 * @vma: VMA for the area to be mapped
 *
 * If a driver supports GEM object mapping, mmap calls on the DRM file
 * descriptor will end up here.
 *
 * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
 * contain the fake offset we created when the GTT map ioctl was called on
 * the object) and map it with a call to drm_gem_mmap_obj().
 *
 * If the caller is not granted access to the buffer object, the mmap will fail
 * with EACCES. Please see the vma manager for more information.
 */
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{
        struct drm_file *priv = filp->private_data;
        struct drm_device *dev = priv->minor->dev;
        struct drm_gem_object *obj = NULL;
        struct drm_vma_offset_node *node;
        int ret;

        if (drm_dev_is_unplugged(dev))
                return -ENODEV;

        drm_vma_offset_lock_lookup(dev->vma_offset_manager);
        node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
                                                  vma->vm_pgoff,
                                                  vma_pages(vma));
        if (likely(node)) {
                obj = container_of(node, struct drm_gem_object, vma_node);
                /*
                 * When the object is being freed, after it hits 0-refcnt it
                 * proceeds to tear down the object. In the process it will
                 * attempt to remove the VMA offset and so acquire this
                 * mgr->vm_lock.  Therefore if we find an object with a 0-refcnt
                 * that matches our range, we know it is in the process of being
                 * destroyed and will be freed as soon as we release the lock -
                 * so we have to check for the 0-refcnted object and treat it as
                 * invalid.
                 */
                if (!kref_get_unless_zero(&obj->refcount))
                        obj = NULL;
        }
        drm_vma_offset_unlock_lookup(dev->vma_offset_manager);

        if (!obj)
                return -EINVAL;

        if (!drm_vma_node_is_allowed(node, priv)) {
                drm_gem_object_put(obj);
                return -EACCES;
        }

        ret = drm_gem_mmap_obj(obj, drm_vma_node_size(node) << PAGE_SHIFT,
                               vma);

        drm_gem_object_put(obj);

        return ret;
}

6.6 rockchip_drm_bind

static int rockchip_drm_bind(struct device *dev)
{
        struct drm_device *drm_dev;
        struct rockchip_drm_private *private;
        int ret;

        /* Remove existing drivers that may own the framebuffer memory. */
        ret = drm_aperture_remove_framebuffers(false, &rockchip_drm_driver);
        if (ret) {
                DRM_DEV_ERROR(dev,
                              "Failed to remove existing framebuffers - %d.\n",
                              ret);
                return ret;
        }

        drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev); // 动态分配drm_device
        if (IS_ERR(drm_dev))
                return PTR_ERR(drm_dev);

        dev_set_drvdata(dev, drm_dev); // 设置drm设备驱动私有数据为drm_dev

        private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL); // 动态分配rockchip_drm_private
        if (!private) {
                ret = -ENOMEM;
                goto err_free;
        }

        drm_dev->dev_private = private;  // 设置drm_dev私有数据为drm_dev

        ret = drmm_mode_config_init(drm_dev);
        if (ret)
                goto err_free;

        rockchip_drm_mode_config_init(drm_dev);

        /* Try to bind all sub drivers. */
        ret = component_bind_all(dev, drm_dev);
        if (ret)
                goto err_free;

        ret = rockchip_drm_init_iommu(drm_dev);
        if (ret)
                goto err_unbind_all;

        ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
        if (ret)
                goto err_iommu_cleanup;

        drm_mode_config_reset(drm_dev);

        /* init kms poll for handling hpd */
        drm_kms_helper_poll_init(drm_dev);

        ret = drm_dev_register(drm_dev, 0); // 注册drm设备
        if (ret)
                goto err_kms_helper_poll_fini;

        drm_fbdev_generic_setup(drm_dev, 0);

        return 0;
err_kms_helper_poll_fini:
        drm_kms_helper_poll_fini(drm_dev);
err_iommu_cleanup:
        rockchip_iommu_cleanup(drm_dev);
err_unbind_all:
        component_unbind_all(dev, drm_dev);
err_free:
        drm_dev_put(drm_dev);
        return ret;
}

6.6.1 初始化struct drm_device
6.6.3 初始化mode_config

调用drm_mode_config_init初始化drm_devicemode_config结构体;

ret = drmm_mode_config_init(drm_dev);
if (ret)
    goto err_free;

rockchip_drm_mode_config_init(drm_dev);

rockchip_drm_mode_config_init函数定义在drivers/gpu/drm/rockchip/rockchip_drm_fb.c,内容如下:

void rockchip_drm_mode_config_init(struct drm_device *dev)
{
        dev->mode_config.min_width = 0;
        dev->mode_config.min_height = 0;

        /*
         * set max width and height as default value(4096x4096).
         * this value would be used to check framebuffer size limitation
         * at drm_mode_addfb().
         */
        dev->mode_config.max_width = 4096;
        dev->mode_config.max_height = 4096;

        dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
        dev->mode_config.helper_private = &rockchip_mode_config_helpers;

        dev->mode_config.normalize_zpos = true;
}

(1)drm显示器模式设置mode_config回调函数funcs被设置为rockchip_drm_mode_config_funcs

static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
        .fb_create = rockchip_fb_create,
        .atomic_check = drm_atomic_helper_check,
        .atomic_commit = drm_atomic_helper_commit,
};

fb_create 回调接口用于创建framebuffer object,并绑定GEM对象。rockchip_fb_create定义如下:

static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
        .destroy       = drm_gem_fb_destroy,
        .create_handle = drm_gem_fb_create_handle,
        .dirty         = drm_atomic_helper_dirtyfb,
};

static struct drm_framebuffer *
rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
                   const struct drm_mode_fb_cmd2 *mode_cmd)
{
        struct drm_afbc_framebuffer *afbc_fb;
        const struct drm_format_info *info;
        int ret;

        info = drm_get_format_info(dev, mode_cmd);
        if (!info)
                return ERR_PTR(-ENOMEM);

        afbc_fb = kzalloc(sizeof(*afbc_fb), GFP_KERNEL);
        if (!afbc_fb)
                return ERR_PTR(-ENOMEM);

        ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
                                         &rockchip_drm_fb_funcs);
        if (ret) {
                kfree(afbc_fb);
                return ERR_PTR(ret);
        }

        if (drm_is_afbc(mode_cmd->modifier[0])) {
                int ret, i;

                ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb);
                if (ret) {
                        struct drm_gem_object **obj = afbc_fb->base.obj;

                        for (i = 0; i < info->num_planes; ++i)
                                drm_gem_object_put(obj[i]);

                        kfree(afbc_fb);
                        return ERR_PTR(ret);
                }
        }

        return &afbc_fb->base;
}

(2) drm显示器模式设置mode_config中间层私有数据helper_private被设置为了rockchip_mode_config_helpers

static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers = {
        .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
};

七、测试应用程序编写

参考文章

[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] DRM的GEM

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