DRM(Direct Rendering Manager)的基本概念
本文转自:《Direct Rendering Manager - 基本概念》,欢迎前去阅读原文
在以前对于 Linux
图形子系统接触中只涉及了 FB
架构,FBDEV
向 app
提供 /dev/fbx
设备节点来访问 display controller
和帧缓存,通常来说是由用户来填充 mmap
映射过来的显存,然后拿去送显。这种方式比较简单,操作起来并不复杂,
随着内核更替,衍生出一种新的显示框架-DRM
,DRM
较 FB
,内容更加丰富,功能更加齐全(支持多层合成、Vsync
、dma-buf
、异步更新、fence
机制等等),能够应对复杂多变的显示应用场景。
尽管FB
退出历史舞台,在DRM
中也并未将其遗弃,而是集合到DRM
中,供部分嵌入式设备使用。
出于对DRM
架构的兴趣及想了解GPU
与DRM
联系,这里进行一个简要记录。
对于DRM
架构介绍,大牛很多,写的已经很全面了,这里只是在他们基础上做一个简要总结,方便学习过程中的查找。
以 《The DRM/KMS subsystem from a newbie’s point of view》 的说明,在Linux
中用于显示的子系统存在以下几种:
① FBDEV
: Framebuffer Device
② DRM/KMS
: Direct Rendering Manager / Kernel Mode Setting
③ V4L2
: Video For Linux 2
选择DRM
在于:维护积极、使用广泛、功能齐全且高级;FBDEV
维护不积极、功能欠缺;V4L2
较适合于视频输出,不适于复杂显示
相关大神文章链接:
《何小龙:DRM (Direct Rendering Manager)》
《蜗窝科技:图形子系统》
《Younix脏羊:Linux DRM》
《揭开Wayland的面纱(一):X Window的前生今世》
《揭开Wayland的面纱(二):Wayland应运而生》
一、概述
Linux
子系统众多,而其中的图形子系统作为与用户息息相关的子系统:
对下,它要管理形态各异的、性能各异的显示相关的器件;
对上,它要向应用程序提供易用的、友好的、功能强大的图形用户界面(GUI
)。
在 《Linux graphic subsytem(1)_概述》与《Linux graphic subsystem(2)_DRI》介绍中对于图形子系统的描述非常清晰,这里对一些主要概念进行概括:
Windows System
:X window
、Wayland Compositior
、Android SurfaceFlinger
- ① 遵从
client-server
架构,server
即display server
/window server
/compositor
,管理输入设备、输出设备 - ②
client
绘图请求,交给display server
,以一定规则混合、叠加 - ③
client
与display server
之间以某种协议交互,如Binder
例:X Windows System
:只提供实现 GUI
环境的基本框架
- 窗口管理器(
window manager
):负责控制应用程序窗口的布局和外观,使每个应用程序窗口尽量以统一、一致的方式呈现给用户 GUI
工具集(GUI Toolkits
):Windowing system
之上的进一步的封装- 桌面环境(
desktop environment
):应用程序级别的封装,通过提供一系列界面一致、操作方式一致的应用程序,使系统以更为友好的方式向用户提供服务
-
DRI
(Direct Render Infrastructure
)
在Application
<---->Service
<---->Driver
<---->Hardware
软件架构下,APP
无法直接访问硬件导致游戏等3D
场景达不到性能最优,
DRI
为3D Rendering
提供直接访问硬件的框架,使以X server
为中心的设计转向以Kernel
及组件为中心的设计。
Linux
为DRI
开辟了两条路径:DRM
与KMS(Kernel Mode Setting)
,分别实现Rendering
和送显。
-
DRM
(Direct Rendering Manager
)
libdrm
+kms(ctrc、encoder、connector、plane、fb、vblank、property)
+gem(dumb、prime、fence)
① 统一管理、调度多个应用程序向显卡发送的命令请求,可以类比为管理CPU
资源的进程管理(process management
)模块
② 统一管理显示有关的memory
(memory
可以是GPU
专用的,也可以是system ram
划给GPU
的,后一种方法在嵌入式系统比较常用),该功能由GEM(Graphics Execution Manager)
模块实现 -
KMS
(Kernel Mode Setting
):也称为Atomic KMS
① 显示模式(display mode
)的设置,包括屏幕分辨率(resolution
)、颜色深的(color depth
)、屏幕刷新率(refresh rate
)等等
② 一般来说,是通过控制display controller
的来实现上述功能的 -
GEM
(Graphic Execution Manager
):负责显示buffer
的分配和释放,也是GPU
唯一用到DRM
的地方,设计dma-buf
二、DRM
初步了解DRM
,可以参考 《The DRM/KMS subsystem from a newbie’s point of view》
DRM
总体分为三个模块:libdrm
、KMS
、GEM
,后两者是DRM
中最重要模块,贯穿整个过程的核心。
2.1 libdrm
硬件相关接口:如内存映射、DMA
操作、fence
管理等。
2.2 KMS(Kernel Mode Setting)
涉及到的元素有:CRTC
,ENCODER
,CONNECTOR
,PLANE
,FB( DRM Framebuffer )
,VBLANK
,property
DRM Framebuffer
用于存储显示数据的内存区域,使用GEM
or TTM
管理。
TTM
( Translation Table Maps
)出于GEM
之前,设计用于管理 GPU
访问不同类型的内存,如Video RAM
、GART-Graphics Address Remapping Table
、CPU
不可直接访问的显存,此外还用于维护内存一致性,最重要的概念就是fences
,来确保CPU-GPU
内存一致性。由于TTM
将独显与集显于一体化管理,导致其复杂度高,在现在系统中,已经使用更简单、API
更好的 GEM
用以替换 TTM
,但考虑TTM
对涉及独显、IOMMU
场合适配程度更好,目前并未舍弃TTM
,而是将其融入GEM
的AP
下。
kernel
使用struct drm_framebuffer
表示Framebuffer
:
// include/drm/drm_framebuffer.h
struct drm_framebuffer {
struct drm_device *dev;
struct list_head head;
struct drm_mode_object base;
const struct drm_format_info *format; // drm格式信息
const struct drm_framebuffer_funcs *funcs;
unsigned int pitches[4]; // Line stride per buffer
unsigned int offsets[4]; // Offset from buffer start to the actual pixel data in bytes, per buffer.
uint64_t modifier; // Data layout modifier
unsigned int width;
unsigned int height;
int flags;
int hot_x;
int hot_y;
struct list_head filp_head;
struct drm_gem_object *obj[4];
};
struct drm_framebuffer
主要元素的展示如下图所示(来自brezillon-drm-kms
):
内存缓冲区组织,采取FOURCC
格式代码
// include/drm/drm_fourcc.h
struct drm_format_info {
u32 format;
u8 depth;
union {
u8 cpp[3];
u8 char_per_block[3];
};
u8 block_w[3];
u8 block_h[3];
u8 hsub;
u8 vsub;
bool has_alpha;
bool is_yuv;
};
CRTC
:阴极摄像管上下文
可以通过CRT
/LCD
/VGA Information and Timing
了解下LCD
成像原理。
CRTC
对内连接FrameBuffer
,对外连接Encoder
,完成对buffer
扫描、产生时序。
上述功能的主要通过struct drm_crtc_funcs
和struct drm_crtc_helper_funcs
这两个描述符实现。
struct drm_crtc_funcs {
...
int (*set_config)(struct drm_mode_set *set,
struct drm_modeset_acquire_ctx *ctx); // 更新待送显数据
int (*page_flip)(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t flags,
struct drm_modeset_acquire_ctx *ctx); // 乒乓缓存
...
};
-
Planes
:硬件图层
简单说就是图层,每一次显示可能传入多个图层,通过Blending
操作最终显示到屏幕。
这种好处在于可以控制某一个图层处于特定模式,如给Video
刷新提供了高速通道,使Video
单独为一个图层。 -
Encoder
:编码器
它的作用就是将内存的pixel
像素编码(转换)为显示器所需要的信号,将CRTC
输出的timing
时序转换为外部设备所需的信号模块。例如DVID
、VGA
、YPbPr
、CVBS
、Mipi
、eDP
等。 -
Connector
:
连接物理显示设备的连接器,如HDMI
、DisplayPort
、DSI
总线,通常和Encoder
驱动绑定在一起。
显示过程其实就是通过 CRTC
扫描 FrameBuffer
与 Planes
的内容,通过 Encoder
转换为外部设备能够识别的信号,再通过Connector
连接器到处到显示屏上,完成显示。
2.3 GEM(Graphics Execution Manager)
涉及到的元素有:DUMB
、PRIME
、Fence
这几种概念强烈建议看一下龙哥的 《关于 DRM中DUMB和PRIME名字的由来》
DUMB
:只支持连续物理内存,基于kernel
中通用CMA API
实现,多用于小分辨率简单场景PRIME
:连续、非连续物理内存都支持,基于DMA-BUF
机制,可以实现buffer
共享,多用于大内存复杂场景。dma-buf
建议看龙哥的《dma-buf 系列》Fence
:buffer
同步机制,基于内核dma_fence
机制实现,用于防止显示内容出现异步问题。注意一点,GPU
中处理的buffer
的fence
是由display
来创建的
2.3.1 Fence
这里暂时只解释一下 “GPU
中处理的 buffer
的 fence
是由 display
来创建的”的意思,
以 Android
显示中的 triple buffer
为例解释,
在display
占用 bufferC
时,SurfaceFlinger Acquire BufferA
,在commit
时,display
为 BufferC
创建 releasefence
,
在一次 Vsync
信号到来时,释放releasefence
,致使 GPU
可以立即拿到 BufferC
进行渲染操作。
推荐:《简图记录-android fence机制》
2.3.2 CMA(Contiguous Memory Allocator)
CMA
是内存管理子系统的一个模块,负责物理地址连续的内存分配,处于需要连续内存的其他内核模块和内存管理模块之间的一个中间层模块,专用于分配物理连续的大块内存,以满足大内存需求设备(Display
、Camera
)。
Linux
中使用 4K
作为 page size
,对于 huge page
的处理,是通过 MMU
将连续的所需大小的 huge page
的虚拟地址 mapping
到连续的物理地址去,相当于将 huge page
拆分成连续的 4K page frame
。对于驱动而言,大内存数据交互需要用到 DMA
,同样驱动分配的 DMA-Buffer
也必须是物理连续的( DMA-Buffer
与 huge page
的差别在于 huge page
需要物理地址首地址地址对齐)。有了 DMA-Buffer
,为何又引入CMA
呢?
《CMA模块学习笔记》:
① 应用启动分配的 DMA-Buffer
容易造成内存资源浪费;
② 设备驱动分配的 DMA-Buffer
在内存碎片化下变得不可靠;CMA
的出现能够使分配的内存可以被其他模块使用,驱动分配 CMA
后,其他模块需要吐出来。
驱动通过 DMA mapping framework
间接使用 CMA
服务:
2.3.3 DMA-BUF
mmap
知识推荐:《DRM 驱动 mmap 详解》
推荐:《dma-buf 由浅入深》
三、DRM代码结构
3.1 drm文件列表
// ls drivers/gpu/drm/
amd drm_blend.c drm_dp_aux_dev.c drm_fourcc.c drm_lock.c drm_prime.c drm_vm.c meson savage vc4
arc drm_bridge.c drm_dp_dual_mode_helper.c drm_framebuffer.c drm_memory.c drm_print.c etnaviv mga selftests vgem
arm drm_bufs.c drm_dp_helper.c drm_gem.c drm_mipi_dsi.c drm_probe_helper.c exynos mgag200 shmobile via
armada drm_cache.c drm_dp_mst_topology.c drm_gem_cma_helper.c drm_mm.c drm_property.c fsl-dcu msm sis virtio
ast drm_color_mgmt.c drm_drv.c drm_gem_framebuffer_helper.c drm_mode_config.c drm_rect.c gma500 mxsfb sprd vmwgfx
ati_pcigart.c drm_connector.c drm_dumb_buffers.c drm_global.c drm_mode_object.c drm_scatter.c hisilicon nouveau sti zte
atmel-hlcdc drm_context.c drm_edid.c drm_hashtab.c drm_modes.c drm_scdc_helper.c i2c omapdrm stm
bochs drm_crtc.c drm_edid_load.c drm_info.c drm_modeset_helper.c drm_simple_kms_helper.c i810 panel sun4i
bridge drm_crtc_helper.c drm_encoder.c drm_internal.h drm_modeset_lock.c drm_syncobj.c i915 pl111 tdfx
cirrus drm_crtc_helper_internal.h drm_encoder_slave.c drm_ioc32.c drm_of.c drm_sysfs.c imx qxl tegra
drm_agpsupport.c drm_crtc_internal.h drm_fb_cma_helper.c drm_ioctl.c drm_panel.c drm_trace.h Kconfig r128 tilcdc
drm_atomic.c drm_debugfs.c drm_fb_helper.c drm_irq.c drm_pci.c drm_trace_points.c lib radeon tinydrm
drm_atomic_helper.c drm_debugfs_crc.c drm_file.c drm_kms_helper_common.c drm_plane.c drm_vblank.c Makefile rcar-du ttm
drm_auth.c drm_dma.c drm_flip_work.c drm_legacy.h drm_plane_helper.c drm_vma_manager.c mediatek rockchip udl
3.2 drm设备操作API
Open
设备
fd = open(DRM_DEVICE, O_RDWR, S_IRWXU);
if (fd < 0) {
ALOGE("open drm device failed fd=%d.", fd);
return -1;
}
设置用户支持的能力
drm_public int drmSetClientCap(int fd, uint64_t capability, uint64_t value)
{
struct drm_set_client_cap cap;
...
return drmIoctl(fd, DRM_IOCTL_SET_CLIENT_CAP, &cap);
}
检索 Resource
drm_public drmModeResPtr drmModeGetResources(int fd)
{
struct drm_mode_card_res res, counts;
...
if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res))
return 0;
counts = res;
if (res.count_fbs) {
res.fb_id_ptr = VOID2U64(drmMalloc(res.count_fbs*sizeof(uint32_t)));
if (!res.fb_id_ptr)
goto err_allocs;
}
if (res.count_crtcs) {
res.crtc_id_ptr = VOID2U64(drmMalloc(res.count_crtcs*sizeof(uint32_t)));
if (!res.crtc_id_ptr)
goto err_allocs;
}
if (res.count_connectors) {
res.connector_id_ptr = VOID2U64(drmMalloc(res.count_connectors*sizeof(uint32_t)));
if (!res.connector_id_ptr)
goto err_allocs;
}
if (res.count_encoders) {
res.encoder_id_ptr = VOID2U64(drmMalloc(res.count_encoders*sizeof(uint32_t)));
if (!res.encoder_id_ptr)
goto err_allocs;
}
if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res))
goto err_allocs;
/* The number of available connectors and etc may have changed with a
* hotplug event in between the ioctls, in which case the field is
* silently ignored by the kernel.
*/
if (counts.count_fbs < res.count_fbs ||
counts.count_crtcs < res.count_crtcs ||
counts.count_connectors < res.count_connectors ||
counts.count_encoders < res.count_encoders)
{
drmFree(U642VOID(res.fb_id_ptr));
drmFree(U642VOID(res.crtc_id_ptr));
drmFree(U642VOID(res.connector_id_ptr));
drmFree(U642VOID(res.encoder_id_ptr));
goto retry;
}
...
}
获取Connector
static drmModeConnectorPtr
_drmModeGetConnector(int fd, uint32_t connector_id, int probe)
{
struct drm_mode_get_connector conn, counts;
drmModeConnectorPtr r = NULL;
struct drm_mode_modeinfo stack_mode;
...
if (drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn))
return 0;
retry:
counts = conn;
if (conn.count_props) {
conn.props_ptr = VOID2U64(drmMalloc(conn.count_props*sizeof(uint32_t)));
if (!conn.props_ptr)
goto err_allocs;
conn.prop_values_ptr = VOID2U64(drmMalloc(conn.count_props*sizeof(uint64_t)));
if (!conn.prop_values_ptr)
goto err_allocs;
}
if (conn.count_modes) {
conn.modes_ptr = VOID2U64(drmMalloc(conn.count_modes*sizeof(struct drm_mode_modeinfo)));
if (!conn.modes_ptr)
goto err_allocs;
} else {
conn.count_modes = 1;
conn.modes_ptr = VOID2U64(&stack_mode);
}
if (conn.count_encoders) {
conn.encoders_ptr = VOID2U64(drmMalloc(conn.count_encoders*sizeof(uint32_t)));
if (!conn.encoders_ptr)
goto err_allocs;
}
if (drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn))
goto err_allocs;
/* The number of available connectors and etc may have changed with a
* hotplug event in between the ioctls, in which case the field is
* silently ignored by the kernel.
*/
if (counts.count_props < conn.count_props ||
counts.count_modes < conn.count_modes ||
counts.count_encoders < conn.count_encoders) {
drmFree(U642VOID(conn.props_ptr));
drmFree(U642VOID(conn.prop_values_ptr));
if (U642VOID(conn.modes_ptr) != &stack_mode)
drmFree(U642VOID(conn.modes_ptr));
drmFree(U642VOID(conn.encoders_ptr));
goto retry;
}
...
}
获取 Encoder
drm_public drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id)
{
struct drm_mode_get_encoder enc;
drmModeEncoderPtr r = NULL;
...
if (drmIoctl(fd, DRM_IOCTL_MODE_GETENCODER, &enc))
return 0;
...
}
设置CRTC
与获取CRTC
drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode)
{
struct drm_mode_crtc crtc;
...
return DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc);
}
drm_public drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId)
{
struct drm_mode_crtc crtc;
drmModeCrtcPtr r;
...
if (drmIoctl(fd, DRM_IOCTL_MODE_GETCRTC, &crtc))
return 0;
...
}
FrameBuffer
static int modeset_create_fb(int fd, struct modeset_dev *dev)
{
struct drm_mode_create_dumb creq;
struct drm_mode_destroy_dumb dreq;
struct drm_mode_map_dumb mreq;
...
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); // 创建DUMB Buffer
...
/* create framebuffer object for the dumb-buffer */
ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride,
dev->handle, &dev->fb); // 添加FB
...
/* prepare buffer for memory mapping */
memset(&mreq, 0, sizeof(mreq)); // 准备map
mreq.handle = dev->handle;
ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
...
/* perform actual memory mapping */
dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, mreq.offset); // 做map操作
...
/* clear the framebuffer to 0 */
memset(dev->map, 0, dev->size);
...
}
从上面大致看出,基本都是ioctl,《DRM 驱动程序开发(VKMS)》总结了这些IOCTL含义,大致如下:
IOCTL | API | Desc |
---|---|---|
DRM_IOCTL_VERSION | drmGetVersion | 查询驱动版本 |
DRM_IOCTL_GET_UNIQUE | drmGetBusid | 获取设备总线ID |
DRM_IOCTL_GET_MAGIC | drmGetMagic | 获取 Magic Number,用于 GEM ioctl 权限检查 |
DRM_IOCTL_IRQ_BUSID | drmGetInterruptFromBusID | 从总线ID获取IRQ |
DRM_IOCTL_GET_MAP | drmGetMap | 获取mapping后内存 |
DRM_IOCTL_GET_CLIENT | drmGetClient | 获取当前 DRM 设备上的所有 client 进程 |
DRM_IOCTL_GET_CAP | drmGetCap | 获取当前 DRM 设备所支持的能力 |
DRM_IOCTL_SET_CLIENT_CAP | drmSetClientCap | 告诉 DRM 驱动当前用户进程所支持的能力 |
DRM_IOCTL_CONTROL | drmCtlInstHandler | 安装IRQ处理程序 |
DRM_IOCTL_ADD_MAP | drmAddMap | 内存映射相关 |
DRM_IOCTL_SET_MASTER | drmSetMaster | 获取 DRM-Master 访问权限 |
DRM_IOCTL_ADD_CTX | drmCreateContext | 创建上下文 |
DRM_IOCTL_DMA | drmDMA | 保留DMA缓冲区 |
DRM_IOCTL_LOCK | drmGetLock | 获取重量级锁 |
DRM_IOCTL_PRIME_HANDLE_TO_FD | drmPrimeHandleToFD | 将fd与handle绑定 |
DRM_IOCTL_AGP_ACQUIRE | drmAgpAcquire | 获取AGP设备 |
DRM_IOCTL_WAIT_VBLANK | drmWaitVBlank | 等待VBLANK |
DRM_IOCTL_MODE_GETRESOURCES | drmModeGetResources | 检索Resource |
DRM_IOCTL_MODE_GETCRTC | drmModeGetCrtc | 检索CRTC |
DRM_IOCTL_MODE_CURSOR | drmModeSetCursor | 光标操作 |
DRM_IOCTL_MODE_GETENCODER | drmModeGetEncoder | 检索Encoder |
DRM_IOCTL_MODE_GETPROPERTY | drmModeGetProperty | 检索属性 |
DRM_IOCTL_MODE_GETFB | drmModeGetFB | 获取指定 ID 的 framebuffer object |
DRM_IOCTL_MODE_PAGE_FLIP | drmModePageFlip | 基于 VSYNC 同步机制的显示刷新 |
DRM_IOCTL_MODE_GETPLANERESOURCES | drmModeGetPlaneResources | 获取 Plane 资源列表 |
DRM_IOCTL_MODE_OBJ_GETPROPERTIES | drmModeObjectGetProperties | 获取该 object 所拥有的所有 Property |
DRM_IOCTL_MODE_CREATEPROPBLOB | drmModeCreatePropertyBlob | 创建1个 Property Blob 对象 |
DRM_IOCTL_SYNCOBJ_CREATE | drmSyncobjCreate | 同步对象创建 |