硬件软件系统方案应用范例智能车大赛技术支持学习园地关于我们恩智浦官网

DRM用户层代码篇(1) 用户层CRTC/encoder/connector的获取

云实验室案例-DRM用户层代码篇(1)

用户层CRTC/encoder/connector的获取

本文档涉及案例包括:

•        Linux DRM显示所需CRTCencoderconnector的获取

相关知识:

本文介绍的是用户层调用libdrm API完成显示功能的测试代码。更加具体的DRM介绍和文档,客户可以参考Linux DRM官网https://www.kernel.org/doc/html/v4.18/gpu/drm-internals.html

libdrm介绍

libdrm‌是一个用户空间库,主要用于在支持ioctl接口的操作系统上访问Direct Rendering ManagerDRM

它是一个低级别的库,通常由图形驱动(如Mesa DRI驱动、X驱动、libva等)使用。libdrm提供了对ioctl接口的封装,避免了直接暴露内核接口,并为具有drm内存管理器的芯片集提供了跟踪重定位和缓冲区的支持

libdrm基于DRI协议,通过ioctl2D图显驱动进行交互,配置图显处理器以及HDMIMIPILVDS等接口。它支持多种功能,包括模式设置、页面翻转、平面、光标管理等。此外,libdrm还提供了测试工具如modetest,用于对显示设备进行测试和调试

头文件的包含

drm框架调用的是xf86drm.hxf86drmMode.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设备下挂载的crtcencoderconnector等资源。这三个名词的概念在上一章DRM-基础篇中有所涉及。

从数据的传输顺序上来讲,数据从plane开始出发,依次通过CRTCEncoder并最终到达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];
    }
}

至此,我们拿到了显示所需要的connectorencodercrtc三部分。完整代码如下:

// 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”,包括显示分辨率,显示格式等等,这些将在后续的章节中陆续实现。