云实验室案例-DRM用户层代码篇(1)
用户层CRTC/encoder/connector的获取
本文档涉及案例包括:
• Linux DRM显示所需CRTC、encoder和connector的获取
相关知识:
本文介绍的是用户层调用libdrm API完成显示功能的测试代码。更加具体的DRM介绍和文档,客户可以参考Linux DRM官网https://www.kernel.org/doc/html/v4.18/gpu/drm-internals.html
libdrm介绍
libdrm是一个用户空间库,主要用于在支持ioctl接口的操作系统上访问Direct Rendering Manager(DRM)
它是一个低级别的库,通常由图形驱动(如Mesa DRI驱动、X驱动、libva等)使用。libdrm提供了对ioctl接口的封装,避免了直接暴露内核接口,并为具有drm内存管理器的芯片集提供了跟踪重定位和缓冲区的支持
libdrm基于DRI协议,通过ioctl与2D图显驱动进行交互,配置图显处理器以及HDMI、MIPI、LVDS等接口。它支持多种功能,包括模式设置、页面翻转、平面、光标管理等。此外,libdrm还提供了测试工具如modetest,用于对显示设备进行测试和调试。
头文件的包含
drm框架调用的是xf86drm.h和xf86drmMode.h内的函数,一般来讲必须要包含的头文件为
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
“mode”的翻译为“模式”,也是显示相关的描述中,非常重要的一个概念。在初学DRM的时候,我们可以简单地把它理解成为一种特定的“分辨率+帧率+时序”组合。
显示驱动的打开
在上文中,提到了如果用户希望单独测试显示功能,则需要提前通过“killall weston”命令,将运行在前台的weston程序关闭掉,从而使其释放掉对于显示的控制权。
在这里,我们更进一步,杀死weston进程实际上释放掉的就是节点“/dev/dri/card0”,而我们单独测试显示功能的第一步,也正是打开该节点。程序正是通过该节点,去进一步与内核交互,来完成显示功能的。
代码使用open函数,打开设备/dev/dri/card0,拿到设备的描述符。
int drm_fd;
drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
获取模式设置的权利
setMaster赋予应用模式设置的权利,即Mode set。
ret = drmSetMaster(drm_fd);
改函数的底层函数在xf86drm.c中实现,具体代码如下,可以看到是通过ioctl陷入的内核态。本文只关心用户层函数及其实现的功能,对于在内核态完成了哪些操作,感兴趣的用户可以在linux源码中,搜索DRM_IOCTL_SET_MASTER并一步一步追踪代码运行流程,这里不再赘述。
drm_public int drmSetMaster(int fd)
{
return drmIoctl(fd, DRM_IOCTL_SET_MASTER, NULL);
}
获取当前drm驱动下挂载的全部资源
通过getResource得到当前drm驱动下的全部设备,函数返回是一个drmModeRes结构变量的指针。
drmModeRes *res;
res = drmModeGetResources(drm_fd);
借助函数返回,可以依次获取到当前DRM设备下挂载的crtc、encoder、connector等资源。这三个名词的概念在上一章DRM-基础篇中有所涉及。
从数据的传输顺序上来讲,数据从plane开始出发,依次通过CRTC,Encoder并最终到达connector。但在用户层面上,代码的配置流程则正好相反。首先从 getResource函数的返回中,拿到当前设备中可用的connector,并回推,搜索到与其连接的的Encoder:
//拿到可用的connector
drmModeConnector *conn;
conn = drmModeGetConnector(drm_fd, res->connectors[0]);
//从connector拿到与其相连的encoder
drmModeEncoder *encoder;
encoder = drmModeGetEncoder(drm_fd, conn->encoders[0]);
encoder使用一个变量possible_crtcs来记录其所支持的对应crtc,而getResource的返回变量则记载了当前设备中的crtc。通过二者的比对,可以拿到当前encoder所对应的crtc号:
int g_crtc_id;
for (int j = 0; j < res->count_crtcs; j++) {
if (encoder->possible_crtcs & (1 << j)) {
g_crtc_id = res->crtcs[j];
}
}
至此,我们拿到了显示所需要的connector,encoder和crtc三部分。完整代码如下:
// SPDX-License-Identifier: GPL-2.0+
/* *
* Copyright 2024 NXP
*/
#include <drm/drm.h>
#include <drm/drm_mode.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
int main()
{
int ret;
int drm_fd;
drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
ret = drmSetMaster(drm_fd);
drmModeRes *res;
res = drmModeGetResources(drm_fd);
drmModeConnector *conn;
conn = drmModeGetConnector(drm_fd, res->connectors[0]);
drmModeEncoder *encoder;
encoder = drmModeGetEncoder(drm_fd, conn->encoders[0]);
int g_crtc_id;
for (int j = 0; j < res->count_crtcs; j++) {
if (encoder->possible_crtcs & (1 << j)) {
g_crtc_id = res->crtcs[j];
}
}
while(1);
return 0;
}
为完整实现显示功能,用户还需要为硬件显示设备提供要显示的内容,这就需要向DRM申请内存,将内存映射到用户空间,并进一步在该内存上绘制出想要显示的画面。并且,用户也需要指定和告知DRM要显示的模式信息,也就是前文中提到的“mode”,包括显示分辨率,显示格式等等,这些将在后续的章节中陆续实现。