云实验室案例- 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结构的参数传入。该参数通过指定显示分辨率的宽、高和像素格式(bpp,bits 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格式,每个像素占32个bit位,因此bpp参数为32。
在DRM_IOCTL_MODE_CREATE_DUMB被正确的传递和处理后,上面代码中的creq变量内容会被更新,申请到内存的信息会被记录在里面。在用户层,需要进一步调用drmModeAddFB函数,获取到申请buffer的id号,而后续的映射和显示,将直接通过该buffer的id号实现。
__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的内容也会被更新,映射的相关信息也会被记录在内。这是只需要使用mreq的offset变量,并调用内存映射函数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格式的像素,一共32个bit位,按照A(alpha透明度)R(red红)G(green绿)B(blue蓝)的顺序由高到低排列,各占8个bit。因此全红的像素,十六进制的表示方式为为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)
缓冲区在屏幕上的x和y坐标配成0即可。此外在上述变量中,connectors和mode两个变量需要手动获得并且需要注意传递时的格式。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屏幕。