【嵌入式Linux应用开发 | DRM 】在泰山派RK3566使用DRM控制屏幕(超详细)
一、前言
下文以泰山派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.传统模式
- DRM 传统模式的整体的函数调用结构图如下图所示。
单击查看大图
2.原子模式(主流)
- DRM 原子模式的整体的函数调用结构图如下图所示。

三、主要结构体
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 哑缓冲区的结构体。
- 通过
drmIoctl的DRM_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 哑缓冲区的内存映射请求结构体。
- 用于
drmIoctl的DRM_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 数据缓冲区)。size:data指向的数据的字节长度。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接口》
更多推荐




所有评论(0)