DRM (Direct Rendering Manager) 的基本概念

64 篇文章 124 订阅 ¥199.90 ¥299.90
本文介绍了Linux DRM (Direct Rendering Manager) 子系统,包括libdrm、KMS (Kernel Mode Setting) 和GEM (Graphics Execution Manager)。KMS用于设置显示模式,GEM管理内存并支持连续和非连续物理内存。DRM提供了一个框架,允许应用程序直接访问硬件,提升性能,尤其在游戏和高性能场景中。
摘要由CSDN通过智能技术生成

本文转自:《Direct Rendering Manager - 基本概念》,欢迎前去阅读原文



在以前对于 Linux 图形子系统接触中只涉及了 FB 架构,FBDEVapp 提供 /dev/fbx 设备节点来访问 display controller 和帧缓存,通常来说是由用户来填充 mmap 映射过来的显存,然后拿去送显。这种方式比较简单,操作起来并不复杂,

随着内核更替,衍生出一种新的显示框架-DRMDRMFB,内容更加丰富,功能更加齐全(支持多层合成、Vsyncdma-buf、异步更新、fence机制等等),能够应对复杂多变的显示应用场景。

尽管FB退出历史舞台,在DRM 中也并未将其遗弃,而是集合到DRM中,供部分嵌入式设备使用。
出于对DRM架构的兴趣及想了解GPUDRM联系,这里进行一个简要记录。
对于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 SystemX windowWayland CompositiorAndroid SurfaceFlinger

  • ① 遵从client-server架构,serverdisplay server / window server / compositor,管理输入设备、输出设备
  • client 绘图请求,交给 display server,以一定规则混合、叠加
  • clientdisplay server之间以某种协议交互,如 Binder

例:X Windows System:只提供实现 GUI 环境的基本框架

  1. 窗口管理器(window manager):负责控制应用程序窗口的布局和外观,使每个应用程序窗口尽量以统一、一致的方式呈现给用户
  2. GUI工具集(GUI Toolkits):Windowing system之上的进一步的封装
  3. 桌面环境(desktop environment):应用程序级别的封装,通过提供一系列界面一致、操作方式一致的应用程序,使系统以更为友好的方式向用户提供服务


  1. DRI (Direct Render Infrastructure)
    Application<---->Service<---->Driver<---->Hardware软件架构下,APP无法直接访问硬件导致游戏等3D场景达不到性能最优,
    DRI3D Rendering提供直接访问硬件的框架,使以X server为中心的设计转向以Kernel及组件为中心的设计。
    LinuxDRI开辟了两条路径:DRMKMS(Kernel Mode Setting),分别实现Rendering和送显。
    在这里插入图片描述

  2. DRM( Direct Rendering Manager)
    libdrm + kms(ctrc、encoder、connector、plane、fb、vblank、property) + gem(dumb、prime、fence)
    ① 统一管理、调度多个应用程序向显卡发送的命令请求,可以类比为管理CPU资源的进程管理(process management)模块
    ② 统一管理显示有关的memorymemory可以是GPU专用的,也可以是system ram划给GPU的,后一种方法在嵌入式系统比较常用),该功能由GEM(Graphics Execution Manager)模块实现

  3. KMS( Kernel Mode Setting ):也称为 Atomic KMS
    ① 显示模式(display mode)的设置,包括屏幕分辨率(resolution)、颜色深的(color depth)、屏幕刷新率(refresh rate)等等
    ② 一般来说,是通过控制 display controller 的来实现上述功能的

  4. GEM( Graphic Execution Manager ):负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方,涉计dma-buf


二、DRM

初步了解DRM,可以参考 《The DRM/KMS subsystem from a newbie’s point of view

在这里插入图片描述
DRM 总体分为三个模块:libdrmKMSGEM,后两者是DRM中最重要模块,贯穿整个过程的核心。

2.1 libdrm

硬件相关接口:如内存映射、DMA操作、fence管理等。


2.2 KMS(Kernel Mode Setting)

涉及到的元素有:CRTCENCODERCONNECTORPLANEFB( DRM Framebuffer )VBLANKproperty

DRM Framebuffer用于存储显示数据的内存区域,使用GEM or TTM管理。

TTM( Translation Table Maps)出于GEM之前,设计用于管理 GPU 访问不同类型的内存,如Video RAMGART-Graphics Address Remapping TableCPU不可直接访问的显存,此外还用于维护内存一致性,最重要的概念就是fences,来确保CPU-GPU内存一致性。由于TTM将独显与集显于一体化管理,导致其复杂度高,在现在系统中,已经使用更简单、API 更好的 GEM 用以替换 TTM,但考虑TTM对涉及独显、IOMMU场合适配程度更好,目前并未舍弃TTM,而是将其融入GEMAP下。
  
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;
};


  1. CRTC:阴极摄像管上下文
    可以通过CRT/ LCD / VGA Information and Timing了解下 LCD成像原理。
    CRTC 对内连接 FrameBuffer,对外连接Encoder,完成对buffer扫描、产生时序。
    上述功能的主要通过struct drm_crtc_funcsstruct 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. Planes:硬件图层
    简单说就是图层,每一次显示可能传入多个图层,通过 Blending 操作最终显示到屏幕。
    这种好处在于可以控制某一个图层处于特定模式,如给 Video 刷新提供了高速通道,使 Video 单独为一个图层。

  2. Encoder:编码器
    它的作用就是将内存的 pixel 像素编码(转换)为显示器所需要的信号,将CRTC输出的 timing时序转换为外部设备所需的信号模块。例如 DVIDVGAYPbPrCVBSMipieDP 等。

  3. Connector
    连接物理显示设备的连接器,如 HDMIDisplayPortDSI 总线,通常和 Encoder 驱动绑定在一起。

显示过程其实就是通过 CRTC 扫描 FrameBufferPlanes的内容,通过 Encoder 转换为外部设备能够识别的信号,再通过Connector 连接器到处到显示屏上,完成显示。


2.3 GEM(Graphics Execution Manager)

涉及到的元素有:DUMBPRIMEFence
这几种概念强烈建议看一下龙哥的 《关于 DRM中DUMB和PRIME名字的由来

  • DUMB:只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景
  • PRIME:连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景。dma-buf建议看龙哥的《dma-buf 系列
  • Fencebuffer同步机制,基于内核 dma_fence机制实现,用于防止显示内容出现异步问题。注意一点,GPU中处理的 bufferfence是由 display来创建的

2.3.1 Fence

这里暂时只解释一下 “GPU中处理的 bufferfence 是由 display 来创建的”的意思,
Android 显示中的 triple buffer 为例解释,
display 占用 bufferC时,SurfaceFlinger Acquire BufferA,在commit时,displayBufferC 创建 releasefence
在一次 Vsync信号到来时,释放releasefence,致使 GPU 可以立即拿到 BufferC进行渲染操作。

推荐:《简图记录-android fence机制


2.3.2 CMA(Contiguous Memory Allocator)

CMA 是内存管理子系统的一个模块,负责物理地址连续的内存分配,处于需要连续内存的其他内核模块和内存管理模块之间的一个中间层模块,专用于分配物理连续的大块内存,以满足大内存需求设备(DisplayCamera)。

Linux 中使用 4K 作为 page size,对于 huge page 的处理,是通过 MMU 将连续的所需大小的 huge page 的虚拟地址 mapping 到连续的物理地址去,相当于将 huge page 拆分成连续的 4K page frame。对于驱动而言,大内存数据交互需要用到 DMA,同样驱动分配的 DMA-Buffer 也必须是物理连续的( DMA-Bufferhuge 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含义,大致如下:

IOCTLAPIDesc
DRM_IOCTL_VERSIONdrmGetVersion查询驱动版本
DRM_IOCTL_GET_UNIQUEdrmGetBusid获取设备总线ID
DRM_IOCTL_GET_MAGICdrmGetMagic获取 Magic Number,用于 GEM ioctl 权限检查
DRM_IOCTL_IRQ_BUSIDdrmGetInterruptFromBusID从总线ID获取IRQ
DRM_IOCTL_GET_MAPdrmGetMap获取mapping后内存
DRM_IOCTL_GET_CLIENTdrmGetClient获取当前 DRM 设备上的所有 client 进程
DRM_IOCTL_GET_CAPdrmGetCap获取当前 DRM 设备所支持的能力
DRM_IOCTL_SET_CLIENT_CAPdrmSetClientCap告诉 DRM 驱动当前用户进程所支持的能力
DRM_IOCTL_CONTROLdrmCtlInstHandler安装IRQ处理程序
DRM_IOCTL_ADD_MAPdrmAddMap内存映射相关
DRM_IOCTL_SET_MASTERdrmSetMaster获取 DRM-Master 访问权限
DRM_IOCTL_ADD_CTXdrmCreateContext创建上下文
DRM_IOCTL_DMAdrmDMA保留DMA缓冲区
DRM_IOCTL_LOCKdrmGetLock获取重量级锁
DRM_IOCTL_PRIME_HANDLE_TO_FDdrmPrimeHandleToFD将fd与handle绑定
DRM_IOCTL_AGP_ACQUIREdrmAgpAcquire获取AGP设备
DRM_IOCTL_WAIT_VBLANKdrmWaitVBlank等待VBLANK
DRM_IOCTL_MODE_GETRESOURCESdrmModeGetResources检索Resource
DRM_IOCTL_MODE_GETCRTCdrmModeGetCrtc检索CRTC
DRM_IOCTL_MODE_CURSORdrmModeSetCursor光标操作
DRM_IOCTL_MODE_GETENCODERdrmModeGetEncoder检索Encoder
DRM_IOCTL_MODE_GETPROPERTYdrmModeGetProperty检索属性
DRM_IOCTL_MODE_GETFBdrmModeGetFB获取指定 ID 的 framebuffer object
DRM_IOCTL_MODE_PAGE_FLIPdrmModePageFlip基于 VSYNC 同步机制的显示刷新
DRM_IOCTL_MODE_GETPLANERESOURCESdrmModeGetPlaneResources获取 Plane 资源列表
DRM_IOCTL_MODE_OBJ_GETPROPERTIESdrmModeObjectGetProperties获取该 object 所拥有的所有 Property
DRM_IOCTL_MODE_CREATEPROPBLOBdrmModeCreatePropertyBlob创建1个 Property Blob 对象
DRM_IOCTL_SYNCOBJ_CREATEdrmSyncobjCreate同步对象创建
  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值