Direct Rendering Manager - 基本概念
时间:2022-09-16 11:30:00
Direct Rendering Manager 基本概念
- 1 概述
- 2 DRM
-
- 2.1 libdrm
- 2.2 KMS(Kernel Mode Setting)
- 2.3 GEM(Graphics Execution Manager)
-
- 2.3.1 Fence
- 2.3.2 CMA(Contiguous Memory Allocator)
- 2.3.3 DMA-BUF
- 3 DRM代码结构
-
- 3.1 drm文件列表
- 3.2 drm设备操作API
??在以前对于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面纱(1):X Window的前生今世
揭开Wayland面纱(2):Wayland应运而生
1 概述
??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
2 DRM
??初步了解DRM,可以参考The DRM/KMS subsystem from a newbie’s point of view
:root { --mermaid-font-family: “trebuchet ms”, verdana, arial;}
DRM总体分为三个模块:libdrm、KMS、GEM,后两者是DRM中最重要模块,贯穿整个过程的核心。
2.1 libdrm
硬件相关接口:如内存映射、DMA操作、fence管理等。
2.2 KMS(Kernel Mode Setting)
涉及到的元素有:CRTC,ENCODER,CONNECTOR,PLANE,FB,VBLANK,property
FB: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的API下。
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];
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
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;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
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); // 乒乓缓存
...
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
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 由浅入深
3 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
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;
}
- 1
- 2
- 3
- 4
- 5
设置用户支持的能力
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
检索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; } ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
获取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;
}
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
获取 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;
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
设置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;
…
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
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); ...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
从上面大致看出,基本都是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 | 同步对象创建 |