一、前言

下文以泰山派RK3566开发板为例

  • 本文系统使用: Ubuntu 20.04.6 LTS
  • Linux 内核版本为:5.15.0-136-generic
  • 嵌入式Linux学习交流群:1005210698
  • 本文相关源码以及更多免费资料可加群获取

涉及技术栈

  • 软件:DRM
  • 硬件:泰山派RK3566、6寸720*1280 MIPI 屏幕

二、概述

1.drm介绍

DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。 比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等, 而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护。

2.DRM显示系统

DRM Framebuffer:

  • 与 framebuffer 一样,DRM Framebuffer 也是一片存放图像的内存区域, 且需要设置图像的格式(RGB888,YUV,C8等)以及画布的大小。

CRTC:

  • CRTC的名字来源于Cathode Ray Tube Controller,中文名为阴极射线显像管控制器。
  • 在DRM显示系统中CRTC会配置display timings和显示分辨率(Planes提供)来扫描framebuffer上的内容,传给Encoder。

Planes:

  • 是一个包含向CRTC发送数据的缓存块的内存对象, 每个CRTC必须关联一个Planes,它是CRTC决定采用哪种视频模式的根据—显示分辨率(宽度和高度),像素大小,像素格式,刷新率等。
  • Planes会分为三种类型:
    • DRM_PLANE_TYPE_PRIMARY: 主要图层,显示背景或者图像内容,每个CRTC中含一个。
    • DRM_PLANE_TYPE_OVERLAY: 用于显示叠加、缩放,每个CRTC中含一个以上。
    • DRM_PLANE_TYPE_CURSOR: 用于显示鼠标,每个CRTC中含0-N个。
  • 通常驱动会把 framebuffer 绑定到 DRM_PLANE_TYPE_PRIMARY 上。
  • 泰山派RK3566有4个 Planes,其中有两个无法正常使用(后文有说明)
  • Planes 叠加的现象如下

Encoder:

  • 译为编码器。它的作用就是将 pixel 像素编码(转换)为显示器所需要的信号。

Connector:

  • 译为连接器。Connector 常常对应于物理连接器 (VGA, DVI, FPD-Link, HDMI, DisplayPort, S-Video …) 他会连接将一个物理显示输出设备 (monitor, laptop panel, …) 。 与当前物理连接的输出设备相关的信息(如连接状态,EDID数据,DPMS状态或支持的视频模式)也存储在 Connector 内。

3.函数调用结构图

1.传统模式

2.原子模式(主流)

  • DRM 原子模式的整体的函数调用结构图如下图所示。

原子模式 DRM 函数调用结构.png

单击查看大图


三、主要结构体

drmModeRes
  • DRM 显示硬件的资源信息结构体,通过 drmModeGetResources() 函数获取。
  • 应用程序可通过这些信息了解硬件支持的显示资源(如能同时驱动多少个显示器、最大分辨率等),进而进行显示配置(如绑定CRTC、连接器和帧缓冲)。
typedef struct _drmModeRes {
    int count_fbs;           // 已分配的帧缓冲数量
    uint32_t *fbs;           // 帧缓冲ID列表(每个ID唯一标识一个帧缓冲对象)

    int count_crtcs;         // 可用的CRTC硬件数量(决定最多可同时驱动的独立显示设备数)
    uint32_t *crtcs;         // CRTC ID列表

    int count_connectors;    // 可用的物理显示接口数量(如HDMI接口数量)
    uint32_t *connectors;    // 连接器ID列表

    int count_encoders;      // 可用的编码器数量
    uint32_t *encoders;      // 编码器ID列表

    uint32_t min_width;      // 支持的最小水平分辨率(像素)
    uint32_t max_width;      // 支持的最大水平分辨率(像素)
    uint32_t min_height;     // 支持的最小垂直分辨率(像素)
    uint32_t max_height;     // 支持的最大垂直分辨率(像素)
} drmModeRes, *drmModeResPtr;
drmModePlane
  • DRM中平面 的详细信息。
  • 平面是 DRM 原子模式设置中的核心组件,负责将帧缓冲的像素数据传输到 CRTC,支持多层合成、硬件加速等功能。
typedef struct _drmModePlane {
    uint32_t count_formats;    // 平面支持的像素格式数量
    uint32_t *formats;         // 像素格式数组(存储FourCC编码值,如0x34325258对应XRGB8888)
    uint32_t plane_id;         // 平面的唯一标识符

    uint32_t crtc_id;          // 当前绑定的CRTC ID(0表示未绑定任何CRTC)
    uint32_t fb_id;            // 当前绑定的帧缓冲ID(0表示未绑定任何帧缓冲)

    uint32_t crtc_x;           // 平面在CRTC坐标系中的X坐标
    uint32_t crtc_y;           // 平面在CRTC坐标系中的Y坐标
    uint32_t x;                // (已废弃,建议使用src_x)帧缓冲中取图的X坐标
    uint32_t y;                // (已废弃,建议使用src_y)帧缓冲中取图的Y坐标

    uint32_t possible_crtcs;   // 平面可关联的CRTC掩码(位掩码)
                               // 	第n位为1表示该平面支持关联索引为n的CRTC(CRTC在drmModeRes->crtcs数组中的位置)
    uint32_t gamma_size;       // 伽马校正表的大小(元素数量),0表示该平面不支持伽马校正
} drmModePlane, *drmModePlanePtr;
drmModePlaneRes
  • DRM 显示平面资源信息结构体,通过 drmModeGetPlaneResources() 函数获取。
  • 在 DRM 的 原子模式设置(Atomic Mode Setting) 中,平面是独立于 CRTC 的图像图层,负责将帧缓冲数据合成到显示输出中,支持多层叠加(如前景图层、背景图层、光标图层等)。
typedef struct _drmModePlaneRes {
    uint32_t count_planes;  // 设备支持的硬件平面总数
    uint32_t *planes;       // 平面ID列表(每个ID唯一标识一个硬件平面)
} drmModePlaneRes, *drmModePlaneResPtr;
drmModeConnector
  • DRM 显示连接器信息结构体,通过 drmModeGetConnector() 函数获取。
  • 应用程序可通过它获取显示器的连接状态、支持的分辨率、物理尺寸等关键信息,是配置显示模式的核心数据。
typedef struct _drmModeConnector {
    uint32_t connector_id;          // 连接器唯一ID
    uint32_t encoder_id;            // 当前绑定的编码器ID(当前正在使用的编码器)
    uint32_t connector_type;        // 接口类型(如DRM_MODE_CONNECTOR_HDMI、DRM_MODE_CONNECTOR_DP等)
    uint32_t connector_type_id;     // 同类型接口的序号(区分多个同类型接口)
    drmModeConnection connection;   // 连接状态(如连接/未连接/未知)
    uint32_t mmWidth, mmHeight;     // 显示器物理尺寸(宽/高,单位毫米)
    drmModeSubPixel subpixel;       // 子像素排列(如DRM_MODE_SUBPIXEL_RGB)

    int count_modes;                // 支持的显示模式总数
    drmModeModeInfoPtr modes;       // 显示模式列表(含分辨率、刷新率等)

    int count_props;                // 支持的可配置属性数量
    uint32_t *props;                // 属性ID列表(如亮度、色域等属性的ID)
    uint64_t *prop_values;          // 属性当前值(与props数组对应)

    int count_encoders;             // 兼容的编码器总数
    uint32_t *encoders;             // 兼容的编码器ID列表(可切换绑定的编码器)
} drmModeConnector, *drmModeConnectorPtr;
drm_mode_create_dumb
  • 用于创建 DRM 哑缓冲区的结构体。
  • 通过 drmIoctlDRM_IOCTL_MODE_CREATE_DUMB 命令使用,用于创建"哑缓冲区"(dumb buffer)。
  • 哑缓冲区是一种简单的帧缓冲,不依赖硬件加速功能,适用于基础的软件渲染场景(如简单UI绘制)。
  • 它的核心是一块连续的内存区域,应用程序可通过后续的映射操作直接访问像素数据
  • 相比硬件加速缓冲区(如 GEM/PRIME),哑缓冲区不支持旋转、缩放等硬件操作,也无法被 GPU 直接访问
struct drm_mode_create_dumb {
	__u32 height;  // 缓冲区垂直方向的像素数量
	__u32 width;   // 缓冲区水平方向的像素数量
	__u32 bpp;     /* 每像素的位数,决定色彩精度和格式:
	                 * - 16:常见于低功耗设备,如RGB565
	                 * - 24:RGB888
	                 * - 32:XRGB8888
	                 */
	__u32 flags;   // 创建标志,目前无实际功能,必须设置为0

	__u32 handle;  // 缓冲区句柄
	__u32 pitch;   // 行跨度:一行像素实际占用的字节数
	__u64 size;    // 缓冲区占用的总字节数
};
drm_mode_destroy_dumb
  • DRM 哑缓冲区销毁请求结构体.
  • 用于 drmIoctl 命令 DRM_IOCTL_MODE_DESTROY_DUMB ,用于释放之前通过 DRM_IOCTL_MODE_CREATE_DUMB 创建的哑缓冲区。
struct drm_mode_destroy_dumb {
    __u32 handle;  // 哑缓冲区句柄
};
drm_mode_map_dumb
  • DRM 哑缓冲区的内存映射请求结构体。
  • 用于 drmIoctlDRM_IOCTL_MODE_MAP_DUMB 调用,实现对缓冲区的用户空间内存映射。
  • 向内核请求该缓冲区的映射偏移量(offset),再用 mmap 系统调用将其映射到用户空间,获得可直接操作的指针。
struct drm_mode_map_dumb {
    __u32 handle;  // 哑缓冲区的句柄
    __u32 pad;     // 32位填充,确保结构体总大小为8字节(64位对齐)
    __u64 offset;  // 内核返回的虚拟地址偏移量,用于mmap时的offset参数
};
drmModeObjectProperties
  • DRM 某个对象的属性集合结构体, 通过 drmModeObjectGetProperties() 函数获取。
  • 描述 DRM 中某个对象的所有的属性信息(如连接器、编码器、平面、CRTC等),DRM 中的”属性“是对硬件功能的抽象(如亮度、刷新率、缩放模式等)。
  • 不同类型的对象支持的属性不同(如连接器可能有亮度属性,平面可能有缩放属性)。
typedef struct _drmModeObjectProperties {
    uint32_t count_props;      // 当前对象拥有的属性总数
    uint32_t *props;           // 属性ID列表(每个ID对应一个特定属性,如亮度属性ID、缩放属性ID等)
    uint64_t *prop_values;     // 属性值列表(与props数组索引对应,存储每个属性的当前取值)
} drmModeObjectProperties, *drmModeObjectPropertiesPtr;
drmModePropertyRes
  • DRM 单个对象属性的详细信息结构体,通过 drmModeGetProperty() 函数获取。
  • 该结构体描述 DRM 对象(如连接器、CRTC、平面)的单个属性的详细信息。
  • DRM 中的属性是硬件功能的抽象,应用程序通过属性可查询或配置硬件参数(如调节亮度、设置旋转方向)
typedef struct _drmModeProperty {
    uint32_t prop_id;                  // 属性唯一ID
    uint32_t flags;                    // 属性标志:
	                                       // - DRM_MODE_PROP_READONLY:只读属性(如物理尺寸)
	                                       // - DRM_MODE_PROP_ENUM:枚举类型属性(如旋转模式)
	                                       // - DRM_MODE_PROP_RANGE:范围型属性(如亮度0-255)
	                                       // - DRM_MODE_PROP_BLOB:blob类型(存储复杂数据,如EDID)

    char name[DRM_PROP_NAME_LEN];      // 属性名称

    int count_values;                  // 基础取值数量
    uint64_t *values;                  // 基础取值数组:
	                                       // - 范围型属性:存储最小值、最大值等边界值
	                                       // - blob类型:存储对应blob数据的长度

    int count_enums;                   // 枚举项数量
    struct drm_mode_property_enum *enums; // 枚举项数组

    int count_blobs;                   // blob对象数量
    uint32_t *blob_ids;                // blob对象ID数组
} drmModePropertyRes, *drmModePropertyPtr;
drmModeAtomicReq
  • DRM 原子模式设置的操作请求结构体。
  • 应用程序通过填充该结构体描述需要修改的属性(如帧缓冲绑定、分辨率切换等),再通过 drmModeAtomicCommit() 提交执行。
struct _drmModeAtomicReq {
    uint32_t cursor;                 // 已添加的属性项计数
    uint32_t size_items;             // 数组items的总容量
    drmModeAtomicReqItemPtr items;   // 属性修改项数组,每个元素描述一个“对象-属性-值”的修改请求
}drmModeAtomicReq, *drmModeAtomicReqPtr;

四、主要API函数

drmOpen()
  • 打开指定的 DRM 设备。

函数原型:

int drmOpen(const char *name, const char *busid)

参数说明:

  • name:要打开的 DRM 设备名称(或驱动名称):
    • NULL:自动选择第一个可用的 DRM 设备。
    • 具体驱动名,用于指定打开特定驱动的设备。(泰山派RK3566使用 rockchip)
    • 设备节点名:如 "card0"(对应 /dev/dri/card0),直接指定设备节点。
  • busid:设备的总线 ID(用于多设备场景下精确匹配),通常设为 NULL
    • NULL:忽略总线 ID,仅根据 name 匹配设备。
    • 具体总线 ID:如 PCI 设备的 "PCI:0:2:0",用于系统中存在多个同类型设备时精确选择。
drmModeGetResources()
  • 获取 DRM 设备的整体显示资源信息(包括帧缓冲、CRTC、连接器、编码器等硬件组件的数量及 ID 列表)。

函数原型:

drmModeResPtr drmModeGetResources(int fd)

参数说明:

  • fd:DRM 设备文件描述符。
drmModeFreeResources()
  • 释放通过 drmModeGetResources() 获取的 DRM 设备整体资源信息结构体(drmModeRes)所占用的内存。

函数原型:

void drmModeFreeResources(drmModeResPtr res)

参数说明:

  • res:由 drmModeGetResources() 返回的设备资源结构体指针,指向需要释放的内存。
drmModeGetConnector()
  • 获取指定连接器的详细信息(如连接状态、支持的显示模式、关联的编码器等)。

函数原型:

drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id)

参数说明:

  • fd:DRM 设备文件描述符。
  • connector_id:要查询的连接器 ID,可从 drmModeRes 结构体的 connectors 数组中获取。
drmModeFreeConnector()
  • 释放通过 drmModeGetConnector() 获取的连接器信息结构体 drmModeConnector 所占用的内存。

函数原型:

void drmModeFreeConnector(drmModeConnectorPtr connector)

参数说明:

  • connector:由 drmModeGetConnector() 返回的连接器信息结构体指针,指向需要释放的内存。
drmSetClientCap()
  • 配置 DRM 客户端的特定功能或能力(如启用原子模式设置、设置最大帧缓冲数量等)。

函数原型:

int drmSetClientCap(int fd, uint64_t capability, uint64_t value)

参数说明:

  • fd:DRM 设备文件描述符。
  • capability:要配置的功能类型(能力标识),常用值包括:
    • DRM_CLIENT_CAP_ATOMIC:启用原子模式设置功能。
    • DRM_CLIENT_CAP_UNIVERSAL_PLANES:启用通用平面支持。
    • DRM_CLIENT_CAP_MAX_BUFFER:设置客户端可分配的最大帧缓冲数量。
  • value:功能的配置值,根据 capability 类型确定。
drmModeGetPlaneResources()
  • 原子模式下获取 DRM 设备支持的所有硬件平面(plane)资源信息(包括平面数量及 ID 列表)。

函数原型:

drmModePlaneResPtr drmModeGetPlaneResources(int fd)

参数说明:

  • fd:DRM 设备文件描述符。
drmModeFreePlaneResources()
  • 原子模式下释放通过 drmModeGetPlaneResources() 获取的平面资源信息结构体 drmModePlaneResPtr 所占用的内存。

函数原型:

void drmModeFreePlaneResources(drmModePlaneResPtr res)

参数说明:

  • res:由 drmModeGetPlaneResources() 返回的平面资源结构体指针,指向需要释放的内存。
drmModeGetPlane
  • 用于获取指定平面的详细属性信息。

函数原型:

drmModePlanePtr drmModeGetPlane(int fd, uint32_t plane_id);

参数说明

  • fd:DRM 设备文件描述符。
  • plane_id:目标平面的唯一标识符。
drmModeFreePlane
  • 用于释放获取指定的平面。

函数原型:

void drmModeFreePlane( drmModePlanePtr ptr )

参数说明

  • plane_id:目标平面的唯一标识符。
drmIoctl()
  • 执行 DRM 相关的 IO 控制命令,是应用程序与 DRM 内核驱动交互的底层接口,用于完成创建 / 销毁帧缓冲、模式设置、属性配置等核心操作。

函数原型:

int drmIoctl(int fd, unsigned long request, void *arg)

参数说明:

  • fd:DRM 设备文件描述符。
  • request:要执行的 DRM IO 控制命令,常用命令包括:
    • DRM_IOCTL_MODE_CREATE_DUMB:创建哑缓冲区(dumb buffer)。
    • DRM_IOCTL_MODE_MAP_DUMB:获取哑缓冲区的映射偏移量(用于后续 mmap )。
    • DRM_IOCTL_MODE_DESTROY_DUMB:销毁哑缓冲区。
    • DRM_IOCTL_MODE_SETCRTC:设置 CRTC(显示控制器)的显示模式(传统模式设置)。
    • DRM_IOCTL_MODE_ADDFB:将帧缓冲与 CRTC 关联(传统模式设置)。
  • arg:指向与命令对应的结构体指针,结构体类型由 request 决定。
drmModeAddFB()
  • 传统模式下将帧缓冲(framebuffer)与 DRM 设备关联,使其可被 CRTC(显示控制器)使用,是设置绑定帧缓冲的关键函数。
  • 早期的帧缓冲注册函数,支持基本的帧缓冲创建,适用于简单像素格式(如 XRGB8888、RGB565 等)。
  • 不支持多平面像素格式(如 YUV420 等需分离亮度 / 色度平面的格式),也不支持缓冲区布局修饰符

函数原型:

int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth, uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *fb_id)

参数说明:

  • fd:DRM 设备文件描述符。
  • width:帧缓冲的宽度。
  • height:帧缓冲的高度。
  • depth:帧缓冲的颜色深度(位 / 像素),通常与 bpp 相同或略小(如 RGB888 的 depth 为 24)。
  • bpp:每像素位数,如 32(XRGB8888)、24(RGB888)、16(RGB565)。
  • pitch:帧缓冲的行跨度(一行像素占用的字节数)。
  • bo_handle:帧缓冲的内存对象句柄。
  • fb_id:指向存储新创建的帧缓冲 ID 的变量,后续用于绑定到 CRTC。
drmModeAddFB2()
  • 用于注册支持多平面像素格式的帧缓冲对象,在 drmModeAddFB 基础上扩展,支持 YUV420 等多平面格式,无需单独指定颜色深度和每像素位数,通过像素格式代码统一描述格式。
  • 不过一般建议直接使用 drmModeAddFB2WithModifiers() 函数,因为 drmModeAddFB2() 函数里面是直接调用 drmModeAddFB2WithModifiers() 的。

函数原型:

int drmModeAddFB2(int fd, uint32_t width, uint32_t height, uint32_t pixel_format, const uint32_t *handles, const uint32_t *pitches, const uint32_t *offsets, uint32_t *fb_id, uint32_t flags)

参数说明:

  • fd:DRM 设备文件描述符。
  • width:帧缓冲的宽度。
  • height:帧缓冲的高度。
  • pixel_format:像素格式代码,使用 DRM 定义的标准格式。
  • handles:缓冲区句柄数组(多平面格式时,每个平面对应一个句柄)。
  • pitches:行跨度数组(每个平面的每行像素占用的字节数)。
  • offsets:偏移量数组(多平面格式时,每个平面相对于缓冲区起始地址的偏移)。
  • fb_id:存储新创建的帧缓冲 ID 的变量,后续用于绑定到 CRTC 或平面。
  • flags:标志位,目前预留未使用,通常设为 0。
drmModeAddFB2WithModifiers()
  • 扩展 drmModeAddFB2 功能,支持缓冲区布局修饰符(modifiers),用于注册支持多平面格式及硬件特定内存布局(如 tiled、压缩格式)的帧缓冲对象,是原子模式设置的核心函数。

函数原型:

int drmModeAddFB2WithModifiers(int fd, uint32_t width, uint32_t height, uint32_t pixel_format, const uint32_t *handles, const uint32_t *pitches, const uint32_t *offsets, const uint64_t *modifiers, uint32_t *fb_id, uint32_t flags)

参数说明:

  • fd:DRM 设备文件描述符。
  • width:帧缓冲的宽度。
  • height:帧缓冲的高度。
  • pixel_format:像素格式代码,同 drmModeAddFB2
  • handles:缓冲区句柄数组,同 drmModeAddFB2,每个元素对应一个平面的内存句柄。
  • pitches:行跨度数组,同 drmModeAddFB2 ,每个元素对应一个平面的行字节数。
  • offsets:平面偏移量数组,同 drmModeAddFB2,指定各平面的偏移量。
  • modifiers:修饰符数组,每个元素对应一个平面的内存布局修饰符(如DRM_FORMAT_MOD_LINEAR 表示线性布局,或硬件特定的 tiled 修饰符),与 handles 数组长度一致。
  • fb_id:存储新创建的帧缓冲 ID 的变量。
  • flags:标志位,预留未使用,通常设为 0。
drmModeRmFB()
  • 删除通过创建的帧缓冲对象(framebuffer),释放其占用的资源,是帧缓冲生命周期管理的收尾函数。

函数原型:

int drmModeRmFB(int fd, uint32_t fb_id)

参数说明:

  • fd:DRM 设备文件描述符。
  • fb_id:要移除的帧缓冲 ID。
drmModeSetCrtc()
  • 传统模式下设置中配置 CRTC 的显示参数,包括绑定帧缓冲、设置显示位置、关联连接器及指定显示模式。

函数原型:

int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
                   uint32_t x, uint32_t y, uint32_t *connectors, int count,
                   drmModeModeInfoPtr mode);

参数说明:

  • fd:DRM 设备文件描述符。
  • crtcId:目标 CRTC 的 ID,指定要配置的显示控制器。
  • bufferId:要绑定到 CRTC 的帧缓冲ID;若为 0,将关闭 CRTC 输出(黑屏)。
  • x:帧缓冲在 CRTC 上的水平起始位置,通常设为 0(从左上角开始显示)。
  • y:帧缓冲在 CRTC 上的垂直起始位置,通常设为 0。
  • connectors:连接器 ID 数组,指定要与当前 CRTC 关联的连接器(如 HDMI、LVDS 接口)。
  • count:关联的连接器数量。
  • mode:显示模式指针,指定分辨率、刷新率等参数;若为NULL,使用当前模式。
drmModeObjectGetProperties()
  • 获取 DRM 对象(如 CRTC、连接器、平面、帧缓冲等)的属性信息(包括属性 ID、当前值、支持的可能值等),是查询和配置 DRM 对象属性的核心函数,尤其在原子模式设置中频繁使用。

函数原型:

drmModeObjectPropertiesPtr drmModeObjectGetProperties(int fd, uint32_t object_id, uint32_t object_type)

参数说明:

  • fd:DRM 设备文件描述符。
  • object_id:目标对象的 ID。
  • object_type:目标对象的类型,指定为以下宏之一:
    • DRM_MODE_OBJECT_CRTC:CRTC(显示控制器)对象。
    • DRM_MODE_OBJECT_CONNECTOR:连接器对象。
    • DRM_MODE_OBJECT_PLANE:平面对象。
    • DRM_MODE_OBJECT_FB:帧缓冲对象。
    • DRM_MODE_OBJECT_ENCODER:编码器对象。
drmModeFreeObjectProperties()
  • 用于释放由drmModeObjectGetProperties()函数分配的对象属性信息结构体所占用的内存。

函数原型:

void drmModeFreeObjectProperties(drmModeObjectPropertiesPtr props)

参数说明:

  • props:由 drmModeObjectGetProperties() 返回的 drmModeObjectPropertiesPtr 类型指针,指向需要释放的对象属性结构体。
drmModeGetProperty()
  • 原子模式下获取指定 DRM 属性的详细元信息(如属性名称、类型、取值范围、枚举项等),用于解析 drmModeObjectProperties 返回的属性 ID 和值的具体含义。

函数原型:

drmModePropertyPtr drmModeGetProperty(int fd, uint32_t property_id)

参数说明:

  • fd:DRM 设备文件描述符。
  • prop_id:要查询的属性 ID。
drmModeFreeProperty()
  • 用于释放由drmModeGetProperty()函数分配的属性信息结构体所占用的内存,避免内存泄漏。

函数原型:

void drmModeFreeProperty(drmModePropertyPtr prop)
  • prop:由 drmModeGetProperty() 返回的 drmModePropertyPtr 类型指针,指向需要释放的属性信息结构体
drmModeCreatePropertyBlob()
  • 原子模式下创建一个 DRM blob 对象,用于存储复杂或变长数据(如 EDID 信息、自定义时序参数等),这些数据可作为属性值关联到 DRM 对象(如连接器、CRTC)。

函数原型:

int drmModeCreatePropertyBlob(int fd, const void *data, size_t length, uint32_t *id)

参数说明:

  • fd:DRM 设备文件描述符。
  • data:指向要存储在 blob 中的数据的指针(如 EDID 数据缓冲区)。
  • sizedata 指向的数据的字节长度。
  • blob_id:存储新创建的 blob 对象 ID 的变量,后续可通过该 ID 引用此 blob。
drmModeAtomicAlloc()
  • 分配一个 DRM 原子操作请求结构体(drmModeAtomicReq),用于封装原子模式设置中的一系列属性修改操作,是原子模式配置的起点。

函数原型:

drmModeAtomicReqPtr drmModeAtomicAlloc(void)
drmModeAtomicFree()
  • 释放通过 drmModeAtomicAlloc() 分配的原子操作请求结构体所占用的内存,避免内存泄漏。

函数原型:

void drmModeAtomicFree(drmModeAtomicReqPtr req)

参数说明:

  • req:原子操作请求结构体指针,指向需要释放的内存。
drmModeAtomicAddProperty()
  • 原子模式下请求结构体中添加一个属性修改项,用于指定某个 DRM 对象(如 CRTC、平面、连接器)的属性值,是构建原子操作序列的核心函数。

函数原型:

int drmModeAtomicAddProperty(drmModeAtomicReqPtr req, uint32_t object_id, uint32_t property_id, uint64_t value)

参数说明:

  • req:原子操作请求结构体指针,用于存储属性修改项。
  • object_id:目标对象的 ID(如平面 ID、CRTC ID、连接器 ID 等),指定要修改属性的对象。
  • prop_id:要修改的属性 ID,如平面的FB_ID(帧缓冲绑定)、CRTC_ID(关联的显示控制器)等。
  • value:属性的目标值,需与属性类型匹配。
drmModeAtomicCommit()
  • 提交原子模式下请求,使所有通过 drmModeAtomicAddProperty() 添加的属性修改项原子性地生效,确保显示配置的一致性,是原子模式设置的核心提交函数。

函数原型:

int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req, uint32_t flags, void *user_data)

参数说明:

  • fd:DRM 设备文件描述符。
  • req:原子操作请求结构体指针。
  • flags:提交标志,控制提交行为,常用值:
    • DRM_MODE_ATOMIC_NONBLOCK:非阻塞模式提交,函数立即返回,不等待硬件完成操作。
    • DRM_MODE_ATOMIC_ALLOW_MODESET:允许在此次提交中包含模式设置操作(如分辨率切换)。
    • DRM_MODE_ATOMIC_TEST_ONLY:仅测试配置有效性,不实际应用修改(用于预检查)。
  • user_data:用户数据指针,通常设为NULL,部分驱动可能用其传递额外信息。

五、代码编写

1.传统模式

1.DRM初始化

1. 打开drm设备

// 设备会随设备树的更改而改变, 多个设备时,请留一下每个屏幕设备对应的drm设备
fd = drmOpen("rockchip", NULL);

在这里插入图片描述

2. 获取drm的信息

res     = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];

printf("===== DRM设备资源详情 (drmModeRes) =====\n");
printf("帧缓冲(FB)信息:\n");
printf("  数量: %d\n", res->count_fbs);
if (res->count_fbs > 0 && res->fbs) {
	printf("  ID列表: ");
	for (int i = 0; i < res->count_fbs; i++)
		printf("%u ", res->fbs[i]);
	printf("\n");
}
printf("\nCRTC(显示控制器)信息:\n");
printf("  数量: %d\n", res->count_crtcs);
if (res->count_crtcs > 0 && res->crtcs) {
	printf("  ID列表: ");
	for (int i = 0; i < res->count_crtcs; i++)
		printf("%u ", res->crtcs[i]);
	printf("\n");
}
printf("\n连接器(Connector)信息:\n");
printf("  数量: %d\n", res->count_connectors);
if (res->count_connectors > 0 && res->connectors) {
	printf("  ID列表: ");
	for (int i = 0; i < res->count_connectors; i++)
		printf("%u ", res->connectors[i]);
	printf("\n");
}
printf("\n编码器(Encoder)信息:\n");
printf("  数量: %d\n", res->count_encoders);
if (res->count_encoders > 0 && res->encoders) {
	printf("  ID列表: ");
	for (int i = 0; i < res->count_encoders; i++)
		printf("%u ", res->encoders[i]);
	printf("\n");
}
printf("\n支持的显示尺寸范围:\n");
printf("  宽度: %u (最小) ~ %u (最大) 像素\n", res->min_width, res->max_width);
printf("  高度: %u (最小) ~ %u (最大) 像素\n", res->min_height, res->max_height);
printf("======================================\n\n");

在这里插入图片描述

3. 获取显示模式连接器的信息

conn       = drmModeGetConnector(fd, conn_id);
buf.width  = conn->modes[0].hdisplay;
buf.height = conn->modes[0].vdisplay;

printf("== 连接器资源详情 (drmModeConnector) ==\n");
printf("连接器信息 (ID: %u)\n", conn->connector_id);
printf("类型: %s (ID: %u)\n", get_connector_type(conn->connector_type), conn->connector_type_id);
printf("连接状态: %s\n", conn->connection == DRM_MODE_CONNECTED ? "已连接" : "未连接");
printf("显示器尺寸: %ux%u mm\n", conn->mmWidth, conn->mmHeight);
printf("子像素排列: %s\n", get_sub_format(conn->subpixel));
printf("支持的显示模式数量: %d\n", conn->count_modes);
if (conn->count_modes > 0) {
	printf("  模式列表(分辨率@刷新率):\n");
	for (int i = 0; i < conn->count_modes; i++)
	printf("  %d: %ux%u @ %dHz %s\n", i, 
			conn->modes[i].hdisplay, conn->modes[i].vdisplay, conn->modes[i].vrefresh,
			(conn->modes[i].flags & DRM_MODE_TYPE_PREFERRED) ? "(首选)" : "");
}
printf("\n可关联的编码器数量: %d\n", conn->count_encoders);
if (conn->count_encoders > 0) {
	printf("  编码器ID列表: ");
	for (int i = 0; i < conn->count_encoders; i++)
		printf("%u ", conn->encoders[i]);
	printf("\n");
}
printf("\n当前关联的编码器ID: %u\n", conn->encoder_id);
printf("======================================\n\n");

在这里插入图片描述

4. 创建 dumb 缓冲区,并绑定到 FB 对象

// 创建一个缓冲区,像素格式为 XRGB888
buf.create.width  = buf.width;
buf.create.height = buf.height;
buf.create.bpp    = 32;

// 返回 handle,pitch,size
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &buf.create);

printf("== struct drm_mode_create_dumb 详情 ==\n");
printf("输入参数:\n");
printf("  width:  %u 像素\n", buf.create.width);
printf("  height: %u 像素\n", buf.create.height);
printf("  bpp:    %u 位/像素\n", buf.create.bpp);
printf("  flags:  %u\n", buf.create.flags);
printf("输出参数:\n");
printf("  handle: %u\n", buf.create.handle);
printf("  pitch:  %u 字节\n", buf.create.pitch);
printf("  size:   %llu 字节\n", (unsigned long long)buf.create.size);
printf("========================================\n");

// 将缓冲区绑定到一个FB对象
buf.pitch  = buf.create.pitch;
buf.size   = buf.create.size;
buf.handle = buf.create.handle;
drmModeAddFB(fd, buf.width, buf.height, 24, buf.create.bpp, buf.pitch, buf.handle, &buf.fb_id);

在这里插入图片描述

5. 将缓冲区映射到用户空间

buf.map.handle = buf.create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &buf.map);

printf("== struct drm_mode_map_dumb 详情 ==\n");
printf("输入参数:\n");
printf("  handle: %u\n", buf.map.handle);
printf("  填充位: %u\n", buf.map.pad);
printf("输出参数:\n");
printf("  offset: 0x%llx\n", (unsigned long long)buf.map.offset);
printf("====================================\n");

buf.vaddr = mmap(0, buf.create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.map.offset);

在这里插入图片描述

6. 缓冲区与特定的显示器连接器、显示模式等进行关联

drmModeSetCrtc(fd, crtc_id, buf.fb_id, 0, 0, &conn_id, 1, &conn->modes[0]);

2.显示图像

  • 可以看到全屏的红色
for (i = 0; i < buf.width * buf.height; i++) 
	buf.vaddr[i] = RED;

3.清理资源

struct drm_mode_destroy_dumb destroy = {0};
drmModeRmFB(fd, buf.fb_id);
munmap(buf.vaddr, buf.size);
destroy.handle = buf.handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
drmModeFreeConnector(conn);
drmModeFreeResources(res);
close(fd);

2.原子模式(主流)

1. 初始化与资源获取

1. 打开 DRM 设备

fd = drmOpen("rockchip", NULL);
printf("DRM设备打开成功:fd=%d\n", fd);

在这里插入图片描述

2. 获取drm的信息

res = drmModeGetResources(fd);
drmModeRes_print();

在这里插入图片描述

3. 选择 CRTC 和连接器

crtc_id = res->crtcs[0];         // 第一个CRTC的ID
conn_id = res->connectors[0];    // 第一个连接器的ID
printf("\n选中的CRTC ID: %u, 连接器ID: %u\n", crtc_id, conn_id);

在这里插入图片描述

4. 开启通用平面支持,并获取平面资源

drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);  // 开启通用平面支持

// 根据图像格式寻找平面
uint32_t format = DRM_FORMAT_XRGB8888;
plane_res = drmModeGetPlaneResources(fd);
for (int i = 0; i < plane_res->count_planes; ++i) {
	plane = drmModeGetPlane(fd, current_plane_id);
	for (int j = 0; j < plane->count_formats; ++j)
            if (plane->formats[j] == format)
                break;
    plane_id = plane->plane_id;
    drmModeFreePlane(plane);
}
drmModeFreePlaneResources(plane_res);

在这里插入图片描述
注意!!!(可通过 modetest 命令查看屏幕信息)

  • 平面1所在层是光标层,通常不支持全屏分辨率,在 drmModeAtomicCommit() 会提交失败。在这里插入图片描述
  • 而平面4虽在覆盖层,但和平面1一样,布局格式是块布局(ARM_BLOCK_SIZE=16x16),不支持全屏分辨率。
  • 如果要是用块布局,必须得在 drmModeAddFB2WithModifiers() 中配置 cmodifiers

  • 平面0和2则是线性布局(LINEAR),即像素数据在内存中连续存储,这是大多数应用(如图形渲染、视频播放)的默认要求。
    在这里插入图片描述

5. 获取连接器详细信息

conn = drmModeGetConnector(fd, conn_id);
drmModeConnector_print();

在这里插入图片描述

6. 创建并设置帧缓冲

// 创建缓冲区
buf.create.width  = conn->modes[0].hdisplay;
buf.create.height = conn->modes[0].vdisplay;
buf.create.bpp    = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &buf.create);
drm_mode_create_dumb_print();

// 将缓冲区注册为帧缓冲
uint32_t handles[2] = {bo->handle, 0};
uint32_t pitches[2] = {bo->pitch, 0};
uint32_t offsets[2] = {0};
buf.pitch  = buf.create.pitch;     // 保存行跨度
buf.size   = buf.create.size;      // 保存总大小
buf.handle = buf.create.handle;    // 保存句柄
ret = drmModeAddFB2WithModifiers(fd, bo->width, bo->height, format, handles, pitches, offsets, NULL, &bo->fb_id, 0);

// 映射哑缓冲区到用户空间
buf.map.handle = buf.create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &buf.map);
drm_mode_map_dumb_print();

buf.vaddr = mmap(0, buf.create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.map.offset);

在这里插入图片描述

7. 开启原子模式支持

drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);

8. 获取连接器和 CRTC 属性

// 获取连接器属性, 关联CRTC的属性ID
props = drmModeObjectGetProperties(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
pc.property_crtc_id = get_property_id(fd, props, "CRTC_ID");
drmModeFreeObjectProperties(props);

// 获取CRTC属性, 激活CRTC的属性ID 设置模式的属性ID
props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
pc.property_active  = get_property_id(fd, props, "ACTIVE");     
pc.property_mode_id = get_property_id(fd, props, "MODE_ID");
drmModeFreeObjectProperties(props);

在这里插入图片描述

9. 创建显示模式的 blob,将显示模式封装为 blob,供 CRTC 设置使用

drmModeCreatePropertyBlob(fd, &conn->modes[0], sizeof(conn->modes[0]), &pc.blob_id);
printf("显示模式 blob 创建成功:blob_id=%u\n", pc.blob_id);

在这里插入图片描述

10. 原子模式初始化CRTC和连接器,激活CRTC、设置显示模式、连接器关联CRTC

req = drmModeAtomicAlloc();    // 分配原子请求
drmModeAtomicAddProperty(req, crtc_id, pc.property_active, 1);    // 激活CRTC
drmModeAtomicAddProperty(req, crtc_id, pc.property_mode_id, pc.blob_id);    // 设置CRTC的显示模式
drmModeAtomicAddProperty(req, conn_id, pc.property_crtc_id, crtc_id);    // 连接器关联CRTC

drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);  // 提交原子请求
drmModeAtomicFree(req);    // 释放请求

2.图像显示

1. 获取一个平面的属性ID

drmModeObjectProperties *props;

pp.plane_id = plane_id;    // 记录平面ID

// 获取平面的所有属性
props = drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
get_property(fd, props);

// 保存平面关键属性的ID
pp.property_fb_id  = get_property_id(fd, props, "FB_ID");     // 绑定帧缓冲
pp.property_crtc_x = get_property_id(fd, props, "CRTC_X");    // 显示位置X
pp.property_crtc_y = get_property_id(fd, props, "CRTC_Y");    // 显示位置Y
pp.property_crtc_w = get_property_id(fd, props, "CRTC_W");    // 显示宽度
pp.property_crtc_h = get_property_id(fd, props, "CRTC_H");    // 显示高度
pp.property_src_x  = get_property_id(fd, props, "SRC_X");     // 源图X
pp.property_src_y  = get_property_id(fd, props, "SRC_Y");     // 源图Y
pp.property_src_w  = get_property_id(fd, props, "SRC_W");     // 源图宽度
pp.property_src_h  = get_property_id(fd, props, "SRC_H");     // 源图高度
drmModeFreeObjectProperties(props);

在这里插入图片描述

2. 配置平面和填充帧缓冲

// 填充帧缓冲:将缓冲区分为3部分,分别填充红、绿、蓝
for (int j = 0; j < 3; j++) {
	int start = j * buf.width * buf.height / 3;
	int end   = (j + 1) * buf.width * buf.height / 3;
	for (i = start; i < end; i++)
		buf.vaddr[i] = color_table[j];
}

// 配置平面:将填充好的帧缓冲通过第一个平面显示
ps.plane_id = plane_id[0];    // 使用第一个平面,要和 “获取的平面的属性ID” 一致
ps.fb_id    = buf.fb_id;      // 绑定创建的帧缓冲
// 平面在CRTC上的显示位置和大小(全屏显示)
ps.crtc_x = 0;
ps.crtc_y = 0;
ps.crtc_w = buf.width;
ps.crtc_h = buf.height;
// 从帧缓冲中取图的区域(全图)
ps.src_x = 0;
ps.src_y = 0;
ps.src_w = buf.width;
ps.src_h = buf.height;

// 提交平面配置
drm_set_plane(fd, &ps)
  • 此时,屏幕会显示如下效果
    在这里插入图片描述

3.清理资源

drm_destroy_fb(&buf);                    // 销毁帧缓冲
drmModeFreeConnector(conn);              // 释放连接器信息
drmModeFreePlaneResources(plane_res);    // 释放平面资源
drmModeFreeResources(res);               // 释放设备资源
close(fd);                               // 关闭设备文件描述符

六、源码获取

https://download.csdn.net/download/2302_80277720/91710930?spm=1001.2014.3001.5501

七、参考

本片文章参考野火:
屏幕显示(DRM)介绍
DRM应用编程–legacy接口
DRM应用编程–atomic接口

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐