从开始接触音频子系统到如今已经两个多月,说实话花费的时间的确有点长了。从今天起我们开始接触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
驱动
相比FB
(frame buffer
)架构,DRM
更能适应当前日益更新的显示硬件;
- 提供一种分离的图形驱动架构,将硬件驱动程序、内核模块和用户空间驱动程序进行分离;
- 支持多个应用程序同时访问显卡,并提供了更丰富的图形功能,例如硬件加速和
3D
加速; - 提供了一些内核接口,可以让用户空间应用程序与驱动程序进行交互;
- 支持多显示器(
Display
)和多GPU
的配置;
总之,一句话,DRM
是Linux
目前主流的图形显示框架,相比FB
架构,DRM
更能适应当前日益更新的显示硬。尽管FB
退出历史舞台,在DRM
中也并未将其遗弃,而是集合到DRM
中,供部分嵌入式设备使用。
有关DRM
的发展历史可以参考这篇博客:DRM (Direct Rendering Manager) 的发展历史。
1.2 DRM
框架
我们来看一下DRM
子系统的软件架构:
DRM
框架从上到下依次为应用程序、libdrm
、DRM driver
、Video Card
;
(1) 应用程序:上图中并没有画出;
(2) libdrm
:lbdrm
是DRM
框架提供的位于用户空间操作DRM
的库,提供了DRM
驱动的用户空间接口;对底层接口进行封装,向上层应用程序提供通用的API
接口,本质上是对各种IOCTL
接口进行封装;
(3) DRM core
:DRM
核心层,由GEM
和KMS
组成;
KMS
:Kernel Mode Setting
,所谓内核显示模式设置,其实说白了就两件事:更新画面和设置显示参数;- 更新画面:显示
buffer
的切换,多图层的合成方式,以及每个图层的显示位置; - 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等;
- 更新画面:显示
GEM
:Graphic Execution Manager
(图形执行管理器),它提供了一种抽象的显存管理方式,使用户空间应用程序可以更方便地管理显存,而不需要了解底层硬件的细节;
(4) Video Card
:GPU
以及显卡驱动;
1.2.1 KMS
KMS
主要负责显示相关功能,在DRM
中将其进行抽象,包括:CRTC
、ENCODER
、CONNECTOR
、PLANE
、Framebuffer
、VBLANK
、property
;它们之间的关系如下图所示:
以HDMI
接口为例说明,Soc
内部一般包含一个Display
模块,通过总线连接到HDMI
接口上;
Display
模块对应CRTC
;HDMI
接口对应Connector
;Framebuffer
对应的是显存部分;Plane
是对Framebuffer
进行描述的部分;Encoder
是将像素转化为HDMI
接口所需要的信号,一般Encoder
和Connector
放到一块初始化。
1.2.2 GEM
GEM
主要负责显示buffer
的分配和释放,在DRM
中将其进行抽象,包括:DUMP
、PRIME
、fence
;
1.2.3 元素介绍
学习DRM
驱动其实就是学习上面各个元素的实现及用法,如果你能掌握这些知识点,那么在编写DRM
驱动的时候就能游刃有余。
元素 | 说明 |
CRTC |
从Framebuffer 中读取待显示的图像,并按照响应的格式输出给encoder ,其主要承担的作用为(1)配置适合显示的显示模式、分辨率、刷新率等参数,并输出相应的时序; (2)扫描 Framebuffer 发送到一个或多个显示器;(3)更新 Framebuffer ;概括下就是,对显示器进行扫描,产生时序信号的模块、负责帧切换、电源控制、色彩调整等等。 |
NCODER |
负责将CRTC 输出的timing 时序转换成外部设备所需要的信号的模块,如HDMI 转换器或DSI Controller |
CONNECTOR |
连接物理显示设备的连接器,如HDMI 、DisplayPort 、DSI 总线,通常和Encoder 驱动绑定在一起 |
PLANE |
图层,实际输出的图像是多个图层叠加而成的,比如主图层、光标图层。其中有些图层由硬件加速模块生成,每个CRTC 至少一个plane ;plane 一共有三种,分别是:DRM_PLANE_TYPE_PRIMARY 、DRM_PLANE_TYPE_OVERLAY 、DRM_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
其中rockchip
为Rockchip
官方的实现代码:
root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/rockchip/ -I "*.o"
analogix_dp-rockchip.c inno_hdmi.c rockchip_drm_drv.h rockchip_drm_vop.h
built-in.a inno_hdmi.h rockchip_drm_fb.c rockchip_lvds.c
cdn-dp-core.c Kconfig rockchip_drm_fb.h rockchip_lvds.h
cdn-dp-core.h Makefile rockchip_drm_gem.c rockchip_rgb.c
cdn-dp-reg.c modules.order rockchip_drm_gem.h rockchip_rgb.h
cdn-dp-reg.h rk3066_hdmi.c rockchip_drm_vop2.c rockchip_vop2_reg.c
dw_hdmi-rockchip.c rk3066_hdmi.h rockchip_drm_vop2.h rockchip_vop_reg.c
dw-mipi-dsi-rockchip.c rockchip_drm_drv.c rockchip_drm_vop.c rockchip_vop_reg.h
二、硬件抽象
对于初学者来说,往往让人迷惑的不是DRM
中objects
的概念,而是如何去建立这些objects
与实际硬件的对应关系。因为并不是所有的Display
硬件都能很好的对应上plane/crtc/encoder/connector
这些objects
。
在学如何去抽象显示硬件到具体的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
接口屏的硬件连接框图:
它在软件架构上与DRM object
的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配drm object
:
object | 说明 |
---|---|
crtc |
RGB timing 的产生,以及显示数据的更新,都需要访问Dislay Controller 硬件寄存器,因此放在Display Controller 驱动中 |
plane |
对Overlay 硬件的抽象,同样需要访问Display Controller 寄存器,因此也放在Display Controller 驱动中 |
encoder |
将RGB 并行信号转换为DSI 行信号,需要配置DSI 硬件寄存器,因此放在DSI Controller 驱动中 |
connector |
可以通过drm_panel 来获取LCD 的mode 信息,但是encoder 在哪,connector 就在哪,因此放在DSI Controller 驱动中 |
drm_panel |
用于获取LCD mode 参数,并提供LCD 休眠唤醒的回调接口,供encoder 调用,因此放在LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c。
有关MIPI DSI
可以参考MIPI 系列之 DSI。
2.2 MIPI DPI
接口
DPI
接口也就是我们常说的RGB
并行接口,Video
数据通过RGB
并行总线传输,控制命令(如初始化、休眠、唤醒等)则通过SPI/I2C
总线传输,比如早期的S3C2440 SoC
平台。下图为一个典型的MIPI DPI
接口屏的硬件连接框图:
该硬件连接在软件架构上与DRM object
的对应关系如下图:
多余的细节不做介绍,这里只说明为何如此分配drm object
:
object | 说明 |
---|---|
crtc |
RGB timing 的产生,以及显示数据的更新,都需要访问LCD Controller 硬件寄存器,因此放在LCD Controller 驱动中 |
plane |
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
接口也就是我们平时常说的MCU
或SPI
接口屏,这类屏的VIDEO
数据和控制命令都是通过同一总线接口(I80、SPI
接口)进行传输,而且这类屏幕必须内置GRAM
显存,否则屏幕无法维持正常显示。
下图为一个典型的DBI
接口屏的硬件连接框图:
该硬件连接在软件架构上与DRM object
的对应关系如下:
上图参考kernel4.19 tinydrm
软件架构。
object | 说明 |
---|---|
crtc |
这类硬件本身不需要任何RGB timing 信号,因此也没有实际的硬件与之对应。但是drm objects 缺一不可,需要实现一个虚拟的crtc object 。由于更新图像数据的动作需要通过SPI 总线发送命令才能完成,因此放在了LCD 驱动中 |
plane |
没有实际的硬件与之对应,但crtc 初始化时需要一个plane object 作为参数传递,因此和crtc 放在一起 |
encoder |
没有实际的硬件与之对应,使用虚拟的encoder object 。因为这类硬件并不是将RGB 信号转换为SPI 信号,而是根本就没有RGB 信号源,也就无从谈起encoder 设备。但是为了通知LCD 休眠唤醒,需要调用LCD 驱动的相应接口,因此放在LCD 驱动中 |
connector |
由于没有了drm_panel ,需要调用LCD 接口来获取mode 参数,因此放在LCD 驱动中 |
驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/tinydrm/ili9341.c。
三、DRM Objects
在编写DRM
驱动程序之前,我们先对DRM
内部的Objects
进行一番介绍,因为这些Objects
是DRM
框架的核心,它们缺一不可。
上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为drm_mode_object
(或者说是`modset object
这些objects
之间的关系:
通过上图可以看到,plane
是连接framebuffer
和crtc
的纽带,而encoder
则是连接crtc
和connector
的纽带。与物理buffer
直接打交道的是gem
而不是framebuffer
。
需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些objects
,否则DRM
子系统无法正常运行。
3.1 modeset object
对于plane
、crtc
、encoder
、connector
几个对象,它们有一个公共基类struct drm_mode_object
,这几个对象都由此基类扩展而来(该类作为crtc
等结构体的成员)。事实上这个基类扩展出来的子类并不是只有上面提到的几种,只不过这四种比较常见。其定义在include/drm/drm_mode_object.h
:
/**
* struct drm_mode_object - base structure for modeset objects
* @id: userspace visible identifier
* @type: type of the object, one of DRM_MODE_OBJECT\_\*
* @properties: properties attached to this object, including values
* @refcount: reference count for objects which with dynamic lifetime
* @free_cb: free function callback, only set for objects with dynamic lifetime
*
* Base structure for modeset objects visible to userspace. Objects can be
* looked up using drm_mode_object_find(). Besides basic uapi interface
* properties like @id and @type it provides two services:
*
* - It tracks attached properties and their values. This is used by &drm_crtc,
* &drm_plane and &drm_connector. Properties are attached by calling
* drm_object_attach_property() before the object is visible to userspace.
*
* - For objects with dynamic lifetimes (as indicated by a non-NULL @free_cb) it
* provides reference counting through drm_mode_object_get() and
* drm_mode_object_put(). This is used by &drm_framebuffer, &drm_connector
* and &drm_property_blob. These objects provide specialized reference
* counting wrappers.
*/
struct drm_mode_object {
uint32_t id;
uint32_t type;
struct drm_object_properties *properties;
struct kref refcount;
void (*free_cb)(struct kref *kref);
};
包括以下成员:
-
id
:用户空间可见的唯一标识标识符,基于idr
算法分配得到的; -
type
:对象的类型,可以是DRM_MODE_OBJECT_*
中的一个; -
properties
:附加到该对象的属性,包括属性的值;在DRM
驱动中,每个对象都可以拥有一组属性(例如分辨率、刷新率等),并且可以动态地增加、删除或修改属性。这些属性可以被用户空间的应用程序或者其他驱动程序获取或者设置; -
refcount
:具有动态生命周期的对象的引用计数;指drm_mode_object
对象在内核中的生命周期的管理,每个drm_mode_object
对象都有一个引用计数;- 当一个对象被创建时,它的引用计数被初始化为1;
- 每当一个新的引用指向该对象时,它的引用计数就会增加1;
- 每当一个引用被释放时,它的引用计数就会减少1;
- 当对象的引用计数降为0时,内核会自动释放该对象。
- 这种方式确保了内核中不会存在不再使用的对象,从而避免了内存泄漏。
-
free_cb
:释放函数回调,仅对具有动态生命周期的对象设置;
为了更加清晰的了解struct drm_mode_object
、struct drm_object_properties
、struct 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];
};
该结构体包含以下字段:
count
:properties
数组长度,必须小于或等于DRM_OBJECT_MAX_PR