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

DRM用户层代码篇 (2) 内存申请和模式配置

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

内存申请和模式配置

本文档涉及案例包括:

•        基于Linux DRM的内存申请和模式配置

为完整实现显示功能,用户还需要为硬件显示设备提供要显示的内容,这就需要向DRM申请内存,将内存映射到用户空间,并进一步在该内存上绘制出想要显示的画面。并且,用户也需要指定和告知DRM要显示的模式信息,也就是前文中提到的“mode”,包括显示分辨率,显示格式等等。

在上文中,我们找到了了从CRTC,到encoder再到connector的硬件通路。在本文中,将陆续实现内存申请、内存映射、图像渲染、模式配置,并完成最终显示。

内存申请

区别于之前通过drmSetMaster函数,间接在xf86drm.c调用drmIoctl的方式,后续的诸多操作会直接调用drmIoctl函数陷入内核态。用户在命令层申请内存的行为可以通过传递DRM_IOCTL_MODE_CREATE_DUMB参数的方式实现。

此外,为通知DRM开辟内存的空间大小,还需要为该命令提供一个drm_mode_create_dumb结构的参数传入。该参数通过指定显示分辨率的宽、高和像素格式(bppbits per pixel,即每个像素需要占用多少bit)。具体代码为:

struct drm_mode_create_dumb creq;
memset(&creq, 0, sizeof(creq));//初始化
creq.width = 1280;
creq.height = 800;
creq.bpp = 32;    //ARGB
ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);

参数配置的宽和高根据显示屏的分辨率决定,以像素数为单位。LVDS屏分辨率为1280x800,因此宽配为1280,高为800。显示的像素为ARGB格式,每个像素占32bit位,因此bpp参数为32

DRM_IOCTL_MODE_CREATE_DUMB被正确的传递和处理后,上面代码中的creq变量内容会被更新,申请到内存的信息会被记录在里面。在用户层,需要进一步调用drmModeAddFB函数,获取到申请bufferid号,而后续的映射和显示,将直接通过该bufferid号实现。

__u32 buffer_id;
ret = drmModeAddFB(drm_fd, WIDTH, HEIGHT, creq.bpp, creq.bpp,
                creq.pitch, creq.handle, &buffer_id);

内存映射

上面提到,在DRM_IOCTL_MODE_CREATE_DUMB被正确的传递和处理后,上面代码中的creq变量内容会被更新,申请到内存的信息会被记录在里面。在内存映射阶段,就需要使用更新后的creq变量中的handle句柄,将其赋值给一个drm_mode_map_dumb类型的结构体变量mreq,并伴随DRM_IOCTL_MODE_MAP_DUMB参数一起传递到内核态:

struct drm_mode_map_dumb mreq;
memset(&mreq, 0, sizeof(mreq));//初始化
mreq.handle = creq.handle;
ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);

creq类似,mreq的内容也会被更新,映射的相关信息也会被记录在内。这是只需要使用mreqoffset变量,并调用内存映射函数mmap即可。该函数的函数原型为:

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

•          start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。

•          length:映射区的长度, 是以字节为单位,不足一内存页按一内存页处理

•          prot:期望的内存保护标志

•          flags:指定映射对象的类型,映射选项和映射页是否可以共享。

•          fd:有效的文件描述词,由open()函数返回。

•          offset:被映射对象内容的起点。

实际调用函数为:

__u32 *fb_base = mmap(0, creq.size, PROT_READ | PROT_WRITE, AP_SHARED,
                            drm_fd, mreq.offset);

•          PROT_READ :页内容可以被读取

•          PROT_WRITE :页可以被写入

•          MAP_SHARED:与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

buffer的简单绘制

至此,用户可以使用fb_base指针对buffer进行绘制,并在绘制完毕后送显。本测试案例中,buffer内的像素将都被绘制为红色像素。对于ARGB格式的像素,一共32bit位,按照Aalpha透明度)Rred红)Ggreen绿)Bblue蓝)的顺序由高到低排列,各占8bit。因此全红的像素,十六进制的表示方式为为0x00ff0000

__u32 color[1] = {0xFF0000};
for (int j = 0; j < (creq.size / 4); j++)
    fb_base[j] = color[0];

buffer的送显

至此,用户已经准备好了将要显示的图像内容,距离送显只有一步之遥。最终送显需要调用的函数为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)

缓冲区在屏幕上的xy坐标配成0即可。此外在上述变量中,connectorsmode两个变量需要手动获得并且需要注意传递时的格式。connectors可以从drmModeGetResources返回的res中获得,mode可以从drmModeGetConnector返回的conn变量中获得

//res = drmModeGetResources(drm_fd);
__u32 *conn_id = &(res->connectors[0]);
 
//conn = drmModeGetConnector(drm_fd, res->connectors[0]);
drmModeModeInfo mode;
memcpy(&mode, &conn->modes[0], sizeof(mode));
 
ret = drmModeSetCrtc(drm_fd, g_crtc_id, buffer_id, 0, 0, conn_id, 1, &mode);

至此,本文已完成了一个最基本的DRM显示程序。完整的程序为:

// SPDX-License-Identifier: GPL-2.0+
 /* * 
 * Copyright 2024 NXP
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
 
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
 
#include <drm/drm.h>
#include <drm/drm_mode.h>           
#include <xf86drm.h>
#include <xf86drmMode.h>
 
#define WIDTH 1280
#define HEIGHT 800
 
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;
    drmModeConnector *conn;
 
    res = drmModeGetResources(drm_fd);
    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];
       }
    }
 
    struct drm_mode_create_dumb creq;
    struct drm_mode_map_dumb mreq;
 
    memset(&creq, 0, sizeof(creq));
    creq.width = WIDTH;
    creq.height = HEIGHT;
    creq.bpp = 32; //ARGB
 
    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
 
    __u32 buffer_id;
 
    ret = drmModeAddFB(drm_fd, WIDTH, HEIGHT, creq.bpp, creq.bpp,
              creq.pitch, creq.handle, &buffer_id);
 
    memset(&mreq, 0, sizeof(mreq));
    mreq.handle = creq.handle;
    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
 
    __u32 *fb_base = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED,
                         drm_fd, mreq.offset);
 
    __u32 color[1] = {0xFF0000};
 
    for (int j = 0; j < (creq.size / 4); j++)
       fb_base[j] = color[0];
 
    drmModeModeInfo mode;
    memcpy(&mode, &conn->modes[0], sizeof(mode));
 
    __u32 *conn_id = &(res->connectors[0]);
 
    ret = drmModeSetCrtc(drm_fd, g_crtc_id, buffer_id, 
                      0, 0, conn_id, 1, &mode);
 
    while(1);
 
    return 0;
}

将本文按照之前GCC编译篇文章的方式进行编译执行,即可通过云实验室的实时显示摄像头,看到全红色的LVDS屏幕。