drm api简明操作(一)直接送显

一. 概念

贴一张图(这张图可能和其他地方的不一样,我作了修改,后文会解释):
在这里插入图片描述
DRM架构包含如上图所示的几个部分,显示一张图像,就是分别获取上面的各个节点信息,进行配置,将将fb -> planes -> crtc -> encoder - > connector连接起来 ,最终实现把FB的内容推送到显示器显示的目的。

1.1 FrameBuffer帧缓存

FB是图像实际存放的一段内存区域,是我们想要显示的原始图像所在空间,一般存放的是RGBX或者YUV的图像数据。这些数据可能是虚拟地址空间或者DMA、CMA等内存区域,和显示模组没有硬件关系。

1.2 Plane图层

显示的基本单元,每个图层可能关联一个FB的数据。图层是显示模组硬件,每个图层叠加顺序、支持的透明度等一般是硬件决定的,如果超过图层数量限制就需要软件来额外处理。

注:
很多示例图上是把FB和Planes分开的输入到CRTC的,但我理解,所有要显示的FB都必须要依赖到一个图层才能显示,除非CRTC本身有一个背景层,这个背景层不能叫plane,其它在此基础上做透明叠加等操作的额外图层才能叫Plane???而现在通用的显示控制器一般是可以自由设定图层顺序和透明度的,每个plane可能是等效的,可以分别配置z-order来随意叠加,所以我是把所有FB直接指向了planes。如有错误请指正。

1.3 CRTC 扫描(CRT)控制器

把一个或者多个图层的数据作处理,形成视觉上的一张图像。比如有两个图层,一个是播放文件的视频图层,另一个是UI操作图层,将其叠加为一个新的图,送到下一级(Encoder)作显示。使用CRT猜测是沿用旧时的称呼。

1.4 Encoder 编码器

将CRTC生成的最终画面转化为不同的显示信号。

1.5 Connector连接器

将信号传送到显示器。

2. API简述

drm用户态接口是依赖于libdrm,因此需要先编译安装该库文件。使用libdrm来显示的主要api如下。

2.1 获取显示资源drmModeGetResources

	drmModeRes *res;
	int drm_fd;
	drm_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	res = drmModeGetResources(fd);

drmModeRes 包含了上述各个模组的统计信息和指针:

typedef struct _drmModeRes {
	int count_fbs;
	uint32_t *fbs;

	int count_crtcs;
	uint32_t *crtcs;

	int count_connectors;
	uint32_t *connectors;

	int count_encoders;
	uint32_t *encoders;

	uint32_t min_width, max_width;
	uint32_t min_height, max_height;
} drmModeRes, *drmModeResPtr;

因此我们通过该接口,可以获取到当前系统的显示资源。

2.2 获取连接器drmModeGetConnector

    drmModeConnector *connector;
	connector = drmModeGetConnector(drm_fd, res->connectors[0]);

当然一个系统可能具有多个连接器(比如笔记本电脑一般同时支持LCD、HDMI两个屏幕),我们一般是循环遍历drmModeRes的所有connector,找到我们想要显示的设备。
接下来看看连接器有哪些东东:

typedef struct _drmModeConnector {
	uint32_t connector_id;
	uint32_t encoder_id; /**< Encoder currently connected to */
	uint32_t connector_type;
	uint32_t connector_type_id;
	drmModeConnection connection;
	uint32_t mmWidth, mmHeight; /**< HxW in millimeters */
	drmModeSubPixel subpixel;

	int count_modes;
	drmModeModeInfoPtr modes;

	int count_props;
	uint32_t *props; /**< List of property ids */
	uint64_t *prop_values; /**< List of property values */

	int count_encoders;
	uint32_t *encoders; /**< List of encoder ids */
} drmModeConnector, *drmModeConnectorPtr;

drmModeConnection 是连接状态,比如HDMI设备的插拔检测。
另外连接器有两个重要的信息,一个是对应的编码器Encoder,另一个是显示模式drmModeMode。
count_encoders和 *encoder是其对应的编码器数目和指针。
drmModeModeInfoPtr 是显示模式,包含分辨率(hdisplay,vdisplay),同步,和帧率(vrefresh)等信息:

typedef struct _drmModeModeInfo {
	uint32_t clock;
	uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
	uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;

	uint32_t vrefresh;

	uint32_t flags;
	uint32_t type;
	char name[DRM_DISPLAY_MODE_LEN];
} drmModeModeInfo, *drmModeModeInfoPtr;

我们需要根据分辨率这些信息来获取支持的图像buf大小,以创建FB。

2.3 drmModeGetEncoder从connector获取对应的Encoder

    drmModeEncoder *encoder;
    encoder = drmModeGetEncoder(drm_fd, conn->encoders[i]);

不同的connector对应的Encoder可能存在差异(HDMI的编码和LCD肯定不一样),因此我们需要从connector中
drmModeEncoder 信息如下,

typedef struct _drmModeEncoder {
	uint32_t encoder_id;
	uint32_t encoder_type;
	uint32_t crtc_id;
	uint32_t possible_crtcs;
	uint32_t possible_clones;
} drmModeEncoder, *drmModeEncoderPtr;

2.4 获取CRTC

上述,取得drmModeEncoder 时,drmModeEncoder包含了possible_crtcs,这是当前Encoder可用的CRTC掩码值,所以需要根据这个值拿到drmModeRes中crtcs数组的index,以取得对应的CRTC。

        for (j = 0; j < res->count_crtcs; j++) {
            if (encoder->possible_crtcs & (1 << j)) {
                crtc_id = res->crtcs[j];
                if (crtc_id > 0) {
                    my_drm->crtc_id = crtc_id;
                    drmModeFreeEncoder(encoder);
                    return 0;
                }
            }
            crtc_id = -1;
        }

2.5 创建FB

现在,我们拿到了Connector、Encoder、CRTC,就可以创建FB了。为啥没有遇到Plane呢?因为每个CRTC会有默认的plane,简单的单图层显示不需要设置plane接口。
为了方便,我们定义一个结构体:

struct my_drm_buffer {
    void *fb_base;
    uint32_t width;
    uint32_t height;
    uint32_t stride;
    uint32_t size;
    uint32_t handle;
    uint32_t buf_id;
    unsigned long paddr;
};

创建FB,我们需要用到上述的各个节点,以及drmModeModeInfo对应的关键信息。
不过在此之前,我们先要用DRM_IOCTL_MODE_CREATE_DUMB创建一个dumb buffer,笨蛋buffer。

2.5.1 创建dumb buffer

 int drm_create_fb(int fd, int index, struct my_drm_buffer *buf)
{
    struct drm_mode_create_dumb creq;
    int ret;

    memset(&creq, 0, sizeof(creq));
    creq.width = buf->width;
    creq.height = buf->height;
    creq.bpp = 32;

    ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
    if (ret < 0) {
        ALOGE("cannot create dumb buffer[%d]", index);
        return ret;
    }

dumb buffer如下,后三个参数值是ioctl之后返回的。其中handle是后续操作dumb buffer需要的句柄。

struct drm_mode_create_dumb {
	__u32 height;
	__u32 width;
	__u32 bpp;
	__u32 flags;
	/* handle, pitch, size will be returned */
	__u32 handle;
	__u32 pitch;
	__u64 size;
};

接下来添加FB。

2.5.2 添加FB

    buf->stride = creq.pitch;
    buf->size = creq.size;
    buf->handle = creq.handle;

    ret = drmModeAddFB(fd, buf->width, buf->height, creq.bpp, creq.bpp,
                buf->stride, buf->handle, &buf->buf_id);

我们将handle等参数传入,会得到一个buf_id。这个buf_id就是后续我们操作FB的句柄。

2.5.3 映射buffer

映射buffer包含两个步骤,映射dumb buf,映射FB。完成之后我们就可以通过buf->fb_base来访问这个buf了。接下来我们把FB默认的画面清为白色0xFF。

    struct drm_mode_map_dumb mreq;
    memset(&mreq, 0, sizeof(mreq));
    mreq.handle = buf->handle;
    ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
    if (ret) {
        ALOGE("Map buffer[%d] dump ioctl fail", index);
    }

    buf->fb_base = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
                            fd, mreq.offset);
    if (buf->fb_base == MAP_FAILED) {
        ALOGE("Cannot mmap dumb buffer[%d]", index);
    }
    memset(buf->fb_base, 255, buf->size);

2.5.4 设置模式drmModeSetCrtc

drmModeSetCrtc将我们上述获取的信息设置到底层,

drmModeSetCrtc(fd, my_drm->crtc_id, buf->buf_id,
								 0, 0, pipe->con_ids, pipe->num_cons,
								 pipe->mode);

该接口的主要功能是设置drm_mode_crtc的参数,函数内部填充该结构体,并通过ioctl传递到驱动。

struct drm_mode_crtc {
	__u64 set_connectors_ptr;
	__u32 count_connectors;

	__u32 crtc_id; /**< Id */
	__u32 fb_id; /**< Id of framebuffer */

	__u32 x; /**< x Position on the framebuffer */
	__u32 y; /**< y Position on the framebuffer */

	__u32 gamma_size;
	__u32 mode_valid;
	struct drm_mode_modeinfo mode;
};

其中第4第5个参数是显示的坐标偏移,其他参数暂未用到,后面再说。
最后,我们可以直接操作FB的buf,将数据送显了。

问题

我们申请了一个buffer,并将其送显。当我们在刷新的时候,显示端就会同步显示刷新的过程。
因此,通常我们需要申请两个buffer,一个是送显的buffer,另一个是我们填充数据的buffer。同一个buffer操作互斥,填充buffer0的时候,送显的是buffer1,等待buffer0填充结束后再送显buffer0,然后填充buffer1。如此反复交替使用。

本文主要对libdrm的接口和数据作梳理,详细请查阅libdrm-2.4.110 estsmodetestmodetest.c代码。
参考:
modeset.c简化描述
最简单的DRM应用程序


版权声明:本文为qq_46416934原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。