DRM
DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等,而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护。
DRM从模块上划分,可以简单分为3部分:libdrm、KMS、GEM
DRM_architecture.svg-44.9kB
(图片来自Wiki)
libdrm
对底层接口进行封装,向上层提供通用的API接口,主要是对各种IOCTL接口进行封装。
KMS
Kernel Mode Setting,所谓Mode setting,其实说白了就两件事:更新画面和设置显示参数。
更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置。
设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等。
GEM
Graphic Execution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。
基本元素
DRM框架涉及到的元素很多,大致如下:
KMS:CRTC,ENCODER,CONNECTOR,PLANE,FB,VBLANK,property
GEM:DUMB、PRIME、fence
Screenshot from 2018-11-04 17:02:56.png-34.9kB
(图片来源:The DRM/KMS subsystem from a newbie’s point of view)
元素 说明
CRTC 对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller
ENCODER 负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller
CONNECTOR 连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder驱动绑定在一起
PLANE 硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少要有1个plane
FB Framebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素
VBLANK 软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现
property 任何你想设置的参数,都可以做成property,是DRM驱动中最灵活、最方便的Mode setting机制
DUMB 只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景
PRIME 连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景
fence buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题
学习DRM驱动其实就是学习上面各个元素的实现及用法,如果你能掌握这些知识点,那么在编写DRM驱动的时候就能游刃有余。
目录(持续更新中)
本篇博客将作为本人DRM学习教程的目录汇总,后续我会以示例代码的形式和大家分享上述知识点的学习过程,并不断更新目录链接,敬请期待!
最简单的DRM应用程序 (single-buffer)
最简单的DRM应用程序 (double-buffer)
最简单的DRM应用程序 (page-flip)
最简单的DRM应用程序 (plane-test)
DRM应用程序进阶 (Property)
DRM应用程序进阶 (atomic-crtc)
DRM应用程序进阶 (atomic-plane)
DRM (Direct Rendering Manager) 的发展历史
DRM 驱动程序开发(开篇)
参考资料
Wiki: Direct Rendering Manager
wowotech: Linux graphic subsystem(2)_DRI介绍
Boris Brezillon: The DRM/KMS subsystem from a newbie’s point of view
线·飘零 博客园:Linux环境下的图形系统和AMD R600显卡编程(1)
Younix脏羊 ****博客:Linux DRM(二)基本概念和特性
---------------------
作者:何小龙
来源:****
原文:https://blog.****.net/hexiaolong2009/article/details/83720940
最简单的DRM应用程序 (single-buffer)
在上一篇DRM (Direct Rendering Manager) 学习简介 中,我们学习了DRM的基本概念以及基本组成元素。从本篇开始,我将以示例代码的形式,给大家分享学习DRM驱动开发的整个学习过程。
在学习DRM驱动之前,应该首先了解如何使用DRM驱动。以下使用伪代码的方式,简单介绍如何编写一个最简单的DRM应用程序。
伪代码:
int main(int argc, char **argv)
{
/* open the drm device */
open("/dev/dri/card0");
/* get crtc/encoder/connector id */
drmModeGetResources(...);
/* get connector for display mode */
drmModeGetConnector(...);
/* create a dumb-buffer */
drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB);
/* bind the dumb-buffer to an FB object */
drmModeAddFB(...);
/* map the dumb buffer for userspace drawing */
drmIoctl(DRM_IOCTL_MODE_MAP_DUMB);
mmap(...);
/* start display */
drmModeSetCrtc(crtc_id, fb_id, connector_id, mode);
}
当执行完mmap之后,我们就可以直接在应用层对framebuffer进行绘图操作了。
详细参考代码如下:
modeset-single-buffer.c
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint8_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf;
static int modeset_create_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
/* create a dumb-buffer, the pixel format is XRGB888 */
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
/* bind the dumb-buffer to an FB object */
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
/* map the dumb-buffer to userspace */
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
/* initialize the dumb-buffer with white-color */
memset(bo->vaddr, 0xff, bo->size);
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
int main(int argc, char **argv)
{
int fd;
drmModeConnector *conn;
drmModeRes *res;
uint32_t conn_id;
uint32_t crtc_id;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
conn = drmModeGetConnector(fd, conn_id);
buf.width = conn->modes[0].hdisplay;
buf.height = conn->modes[0].vdisplay;
modeset_create_fb(fd, &buf);
drmModeSetCrtc(fd, crtc_id, buf.fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
getchar();
modeset_destroy_fb(fd, &buf);
drmModeFreeConnector(conn);
drmModeFreeResources(res);
close(fd);
return 0;
}
上面代码有一个关键的函数,它就是drmModeSetCrtc(),该函数需要crtc_id、connector_id、fb_id、drm_mode 这几个参数。可以看到,几乎所有的代码都是为了该函数能够顺利传参而编写的:
为了获取 crtc_id 和 connector_id,需要调用 drmModeGetResources()
为了获取 fb_id,需要调用 drmModeAddFB()
为了获取 drm_mode,需要调用 drmModeGetConnector
通过调用drmModeSetCrtc(),整个底层显示pipeline硬件就都初始化好了,并且还在屏幕上显示出了FB的内容,非常简单。
以上代码其实是基于kernel DRM maintainer David Herrmann 所写的drm-howto/modeset.c 文件修改的,需要注意的是,以上参考代码删除了许多异常错误处理,且只有在以下条件都满足时,才能正常运行:
DRM驱动支持MODESET;
DRM驱动支持dumb-buffer(即连续物理内存);
DRM驱动至少支持1个CRTC,1个Encoder,1个Connector;
DRM驱动的Connector至少包含1个有效的drm_display_mode。
运行结果:(模拟效果)
modeset-single-buffer.gif-46.9kB
描述:程序运行后,显示全屏白色,等待用户输入按键;当用户按下任意按键后,程序退出,显示黑屏。
注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。
---------------------
最简单的DRM应用程序 (double-buffer)
在上一篇 最简单的DRM应用程序 (single-buffer)中,我们学习了如何去编写一个最基本的DRM应用程序。而本篇文章,将在 modeset-single-buffer 的基础上,对其进行扩展,使用双buffer机制的案例,来加深大家对drmModeSetCrtc()函数的印象。
使用上一节中的modeset-single-buffer程序,如果用户想要修改画面内容,就只能对mmap()后的buffer进行修改,这就会导致用户能很明显的在屏幕上看到软件修改buffer的过程,用户体验大大降低。而双buffer机制则能很好的避免这种问题,双buffer的概念无需过多赘述,大家听名字就知道什么意思了,即前后台buffer切换机制。
伪代码:
int main(void)
{
...
while(1) {
drmModeSetCrtc(fb0);
...
drmModeSetCrtc(fb1);
...
}
...
}
详细参考代码如下:
modeset-double-buffer.c
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint32_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf[2];
static int modeset_create_fb(int fd, struct buffer_object *bo, uint32_t color)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
uint32_t i;
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
for (i = 0; i < (bo->size / 4); i++)
bo->vaddr[i] = color;
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
int main(int argc, char **argv)
{
int fd;
drmModeConnector *conn;
drmModeRes *res;
uint32_t conn_id;
uint32_t crtc_id;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
conn = drmModeGetConnector(fd, conn_id);
buf[0].width = conn->modes[0].hdisplay;
buf[0].height = conn->modes[0].vdisplay;
buf[1].width = conn->modes[0].hdisplay;
buf[1].height = conn->modes[0].vdisplay;
modeset_create_fb(fd, &buf[0], 0xff0000);
modeset_create_fb(fd, &buf[1], 0x0000ff);
drmModeSetCrtc(fd, crtc_id, buf[0].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
getchar();
drmModeSetCrtc(fd, crtc_id, buf[1].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
getchar();
modeset_destroy_fb(fd, &buf[1]);
modeset_destroy_fb(fd, &buf[0]);
drmModeFreeConnector(conn);
drmModeFreeResources(res);
close(fd);
return 0;
}
以上代码是基于David Herrmann 所写的 drm-howto/modeset-double-buffered.c 文件修改的。和modeset-single-buffer案例一样,为了方便大家关注重点,以上代码删除了许多异常错误处理,并对程序功能做了简化。
从上面的代码我们可以看出,drmModeSetCrtc() 的功能除了可以初始化整条显示pipeline,建立crtc到connector之间的连接关系外,它还可以更新屏幕显示内容,即通过修改fb_id,来完成显示buffer的切换。
有的同学可能会担心,重复调用drmModeSetCrtc()会导致硬件链路被重复初始化。其实不必担心,因为DRM驱动框架会对传入的参数进行检查,只要display mode 和 pipeline 链路连接关系没有发生变化,就不会重新初始化硬件。
运行结果:(模拟效果)
modeset-double-buffer.gif-58.4kB
描述:程序运行后,屏幕显示红色;输入回车后,屏幕显示蓝色;再次输入回车后,程序退出。
注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。
---------------------
最简单的DRM应用程序 (page-flip)
在上一篇 最简单的DRM应用程序 (double-buffer)中,我们了解了DRM更新图像的一个重要接口drmModeSetCrtc()。在本篇文章中,我们将一起来学习DRM另一个重要的刷图接口:drmModePageFlip()。
drmModePageFlip()的功能也是用于更新显示内容的,但是它和drmModeSetCrtc()最大的区别在于,drmModePageFlip()只会等到VSYNC到来后才会真正执行framebuffer切换动作,而drmModeSetCrtc()则会立即执行framebuffer切换动作。很明显,drmModeSetCrtc()对于某些硬件来说,很容易造成 撕裂(tear effect)问题,而drmModePageFlip()则不会造成这种问题。
由于drmModePageFlip()本身是基于VSYNC事件机制的,因此底层DRM驱动必须支持VBLANK事件。
伪代码:
void my_page_flip_handler(...)
{
drmModePageFlip(DRM_MODE_PAGE_FLIP_EVENT);
...
}
int main(void)
{
drmEventContext ev = {};
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = my_page_flip_handler;
...
drmModePageFlip(DRM_MODE_PAGE_FLIP_EVENT);
while (1) {
drmHandleEvent(&ev);
}
}
详细参考代码如下:
modeset-page-flip.c
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint32_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf[2];
static int terminate;
static int modeset_create_fb(int fd, struct buffer_object *bo, uint32_t color)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
uint32_t i;
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
for (i = 0; i < (bo->size / 4); i++)
bo->vaddr[i] = color;
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
static void modeset_page_flip_handler(int fd, uint32_t frame,
uint32_t sec, uint32_t usec,
void *data)
{
static int i = 0;
uint32_t crtc_id = *(uint32_t *)data;
i ^= 1;
drmModePageFlip(fd, crtc_id, buf[i].fb_id,
DRM_MODE_PAGE_FLIP_EVENT, data);
usleep(500000);
}
static void sigint_handler(int arg)
{
terminate = 1;
}
int main(int argc, char **argv)
{
int fd;
drmEventContext ev = {};
drmModeConnector *conn;
drmModeRes *res;
uint32_t conn_id;
uint32_t crtc_id;
/* register CTRL+C terminate interrupt */
signal(SIGINT, sigint_handler);
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = modeset_page_flip_handler;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
conn = drmModeGetConnector(fd, conn_id);
buf[0].width = conn->modes[0].hdisplay;
buf[0].height = conn->modes[0].vdisplay;
buf[1].width = conn->modes[0].hdisplay;
buf[1].height = conn->modes[0].vdisplay;
modeset_create_fb(fd, &buf[0], 0xff0000);
modeset_create_fb(fd, &buf[1], 0x0000ff);
drmModeSetCrtc(fd, crtc_id, buf[0].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
drmModePageFlip(fd, crtc_id, buf[0].fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &crtc_id);
while (!terminate) {
drmHandleEvent(fd, &ev);
}
modeset_destroy_fb(fd, &buf[1]);
modeset_destroy_fb(fd, &buf[0]);
drmModeFreeConnector(conn);
drmModeFreeResources(res);
close(fd);
return 0;
}
以上代码是基于David Herrmann 所写的 drm-howto/modeset-vsync.c 文件修改的。和前两篇的案例一样,为了方便大家关注重点,以上代码删除了许多异常错误处理,并对程序功能做了简化。
从上面的代码可以看出,要使用drmModePageFlip(),就必须依赖drmHandleEvent()函数,该函数内部以阻塞的形式等待底层驱动返回相应的vblank事件,以确保和VSYNC同步。需要注意的是,drmModePageFlip()不允许在1个VSYNC周期内被调用多次,否则只有第一次调用有效,后面几次调用都会返回-EBUSY错误(-16)。
运行结果:(模拟效果)
page-flip.gif-87.2kB
描述:程序运行后,屏幕在红色和蓝色之间来回切换;当输入CTRL+C后,程序退出。
注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。
---------------------
最简单的DRM应用程序 (plane-test)
在上一篇 最简单的DRM应用程序 (page-flip)中,我们学习了drmModePageFlip()的用法。而在更早的两篇文章中,我们还学习了drmModeSetCrtc()的使用方法。但是这两个接口都只能全屏显示framebuffer的内容,如何才能在屏幕上只显示framebuffer的一部分内容呢?本篇我们将一起来学习DRM另一个重要的刷图接口:drmModeSetPlane()。
在学习该函数之前,我们首先来了解一下,什么是Plane?在开篇 DRM (Direct Rendering Manager) 学习简介 文章中,曾简单描述过Plane的概念,即硬件图层。今天,我们将详细了解下Plane的概念。
DRM中的Plane和我们常说的YUV/YCbCr图形格式中的plane完全是两个不同的概念。YUV图形格式中的plane指的是图像数据在内存中的排列形式,一般Y通道占一段连续的内存块,UV通道占另一段连续的内存块,我们称之为YUV-2plane (也叫YUV 2平面),属于软件层面。而DRM中的Plane指的是Display Controller中用于多层合成的单个硬件图层模块,属于硬件层面。二者概念上不要混淆。
Plane的历史
随着软件技术的不断更新,对硬件的性能要求越来越高,在满足功能正常使用的前提下,对功耗的要求也越来越苛刻。本来GPU可以处理所有图形任务,但是由于它运行时的功耗实在太高,设计者们决定将一部分简单的任务交给Display Controller去处理(比如合成),而让GPU专注于绘图(即渲染)这一主要任务,减轻GPU的负担,从而达到降低功耗提升性能的目的。于是,Plane(硬件图层单元)就诞生了。
Plane是连接FB与CRTC的纽带,是内存的搬运工。
伪代码:
int main(void)
{
...
drmSetClientCap(DRM_CLIENT_CAP_UNIVERSAL_PLANES);
drmModeGetPlaneResources();
drmModeSetPlane(plane_id, crtc_id, fb_id, 0,
crtc_x, crtc_y, crtc_w, crtc_h,
src_x, src_y, src_w << 16, src_h << 16);
...
}
先来了解一下drmModeSetPlane()参数含义:
plane-draft.png-42.4kB
(上图实现了裁剪、平移和放大的效果)
当 SRC_X/Y 和 CRTC_X/Y 不相等时,就实现了平移的效果;
当 SRC_W/H 和 CRTC_W/H 不相等时,就实现了缩放的效果;
当 SRC_W/H 和 FB_W/H 不相等时,就实现了裁剪的效果;
一个高级的Plane,通常具有如下功能:
功能 说明
Crop 裁剪,如上图
Scaling 缩放,放大或缩小
Rotation 旋转,90° 180° 270° X/Y镜像
Z-Order Z-顺序,调整当前层在总图层中的Z轴顺序
Blending 合成,pixel alpha / global alpha
Format 颜色格式,ARGB888 XRGB888 YUV420 等
再次强调,以上这些功能都是由硬件直接完成的,而非软件实现。
在DRM框架中,Plane又分为如下3种类型:
类型 说明
Cursor 光标图层,一般用于PC系统,用于显示鼠标
Overlay 叠加图层,通常用于YUV格式的视频图层
Primary 主要图层,通常用于仅支持RGB格式的简单图层
其实随着现代半导体技术的飞速发展,Overlay Plane和Primary Plane之间已经没有明显的界限了,许多芯片的图层处理能力已经非常强大,不仅仅可以处理简单的RGB格式,也可以处理YUV视频格式,甚至FBC压缩格式。针对这类硬件图层,它既可以是Overlay Plane,也可以是Primary Plane,至于驱动如何定义,就要看工程师的喜好了。
而对于一些早期处理能力比较弱的硬件,为了节约成本,每个图层支持的格式并不一样,比如将平常使用格式最多的RGB图层作为Primary Plane,而将平时用不多的YUV视频图层作为Overlay Plane,那么这个时候上层应用程序在使用这两种plane的时候就需要区别对待了。
需要注意的是,并不是所有的Display Controller都支持Plane,从前面single-buffer 案例中的drmModeSetCrtc()函数也能看出,即使没有plane_id,屏幕也能正常显示。比如s3c2440这种骨灰级ARM9 SoC,它的LCDC就没有Plane的概念。但是DRM框架规定,任何一个CRTC,必须要有1个Primary Plane。 即使像S3C2440这种不带真实Plane硬件的Display Controller,我们也认为它的Primary Plane就是LCDC本身,因为它实现了从Framebuffer到CRTC的数据搬运工作,而这正是一个Plane最基本的功能。
为什么要设置DRM_CLIENT_CAP_UNIVERSAL_PLANES ?
因为如果不设置,drmModeGetPlaneResources()就只会返回Overlay Plane,其他Plane都不会返回。而如果设置了,DRM驱动则会返回所有支持的Plane资源,包括cursor、overlay和primary。
详细参考代码如下:
modeset-plane-test.c
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint8_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf;
static int modeset_create_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
memset(bo->vaddr, 0xff, bo->size);
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
int main(int argc, char **argv)
{
int fd;
drmModeConnector *conn;
drmModeRes *res;
drmModePlaneRes *plane_res;
uint32_t conn_id;
uint32_t crtc_id;
uint32_t plane_id;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
plane_res = drmModeGetPlaneResources(fd);
plane_id = plane_res->planes[0];
conn = drmModeGetConnector(fd, conn_id);
buf.width = conn->modes[0].hdisplay;
buf.height = conn->modes[0].vdisplay;
modeset_create_fb(fd, &buf);
drmModeSetCrtc(fd, crtc_id, buf.fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
getchar();
/* crop the rect from framebuffer(100, 150) to crtc(50, 50) */
drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,
50, 50, 320, 320,
100, 150, 320 << 16, 320 << 16);
getchar();
modeset_destroy_fb(fd, &buf);
drmModeFreeConnector(conn);
drmModeFreePlaneResources(plane_res);
drmModeFreeResources(res);
close(fd);
return 0;
}
以上代码参考Google Android工程中external/libdrm/tests/planetest/planetest.c文件,为了演示方便,仅仅实现了一个最简单的drmModeSetPlane()调用。需要注意的是,该函数调用之前,必须先通过drmModeSetCrtc()初始化整个显示链路,否则Plane设置将无效。
运行结果:(模拟效果)
DRM应用程序进阶 (Property)
前言
通过前面几篇《最简单的DRM应用程序》系列文章,我们学习了如何编写一个最基本的DRM应用程序。但是,这些程序所使用的接口,在如今的DRM架构中其实早已经被标记为 Legacy(过时的) 接口了,而目前DRM主要推荐使用的是 Atomic(原子的) 接口。Atomic接口我会在下篇文章中重点介绍,本篇主要介绍Atomic操作必须依赖的基本元素,Property(属性)。
Property
所谓Property,其实就是把前几篇的legacy接口传入的参数单独抽出来,做成一个个独立的全局属性。通过设置这些属性参数,即可完成对显示参数的设置。
Property的结构简单概括主要由3部分组成:name、id 和 value。其中id为该property在DRM框架中全局唯一的标识符。
采用property机制的好处是:
减少上层应用接口的维护工作量。当开发者有新的功能需要添加时,无需增加新的函数名和IOCTL,只需在底层驱动中新增一个property,然后在自己的应用程序中获取/操作该property的值即可。
增强了参数设置的灵活性。一次IOCTL可以同时设置多个property,减少了user space与kernel space切换的次数,同时最大限度的满足了不同硬件对于参数设置的要求,提高了软件效率。
DRM中的property大多以功能进行划分,并且还定义了一组 Standard Properties,这些标准properties在任何平台上都会被创建。
下表列出了应用程序开发中,常用的property:
CRTC
name desctription
ACTIVE CRTC当前的使能状态,一般用于控制CRTC上下电
MODE_ID CRTC当前所使用的display mode ID,通过该ID可以找到具体的display mode配置参数
OUT_FENCE_PTR 输出fence指针,指向当前正在显示的buffer所对应的fence fd,该fence由DRM驱动创建,供上层应用程序使用,用来表示当前buffer CRTC是否还在占用
(optional)
name desctription
DEGAMMA_LUT de-gamma查找表参数
DEGAMMA_LUT_SIZE de-gamma查找表参数长度
CTM Color Transformation Matrix,颜色矩阵转换参数,3x3的矩阵
GAMMA_LUT gamma查找表参数
GAMMA_LUT_SIZE gamma查找表参数长度
PLANE
name desctription
type plane的类型,CURSOR、PRIMARY或者OVERLAY
FB_ID 与当前plane绑定的framebuffer object ID
IN_FENCE_FD 与当前plane相关联的input fence fd,由buffer的生产者创建,供DRM底层驱动使用,用来标识当前传下来的buffer是否可以开始访问
CRTC_ID 当前plane所关联的CRTC object ID,与CONNECTOR中的CRTC_ID属性是同一个property
SRC_X 当前framebuffer crop区域的起始偏移x坐标
SRC_Y 当前framebuffer crop区域的起始偏移y坐标
SRC_W 当前framebuffer crop区域的宽度
SRC_H 当前framebuffer crop区域的高度
CRTC_X 屏幕显示区域的起始偏移x坐标
CRTC_Y 屏幕显示区域的起始偏移y坐标
CRTC_W 屏幕显示区域的宽度
CRTC_H 屏幕显示区域的高度
(optional)
name desctription
IN_FORMATS 用于标识特殊的颜色存储格式,如AFBC、IFBC存储格式,该属性为只读
rotation 当前图层的旋转角度
zposition 当前图层在所有图层中的Z轴顺序
alpha 当前图层的global alpha(非pixel alpha),用于多层合成
pixel blend mode 当前图层的合成方式,如Pre-multiplied/Coverage等
CONNECTOR
name desctription
EDID Extended Display Identification Data,标识显示器的参数信息,是一种VESA标准数据格式
DPMS Display Power Management Signaling,用于控制显示器的电源状态,如休眠唤醒。也是一种VESA标准
link-status 用于标识当前connector的连接状态,如Good/Bad
CRTC_ID 当前connector所连接的CRTC object ID,与PLANE中CRTC_ID属性是同一个property
(optional)
name desctription
PATH DisplayPort专用的属性,主要用于Multi-Stream Transport (MST) 功能,即多路显示应用场景
TILE 用于标识当前connector是否应用于多屏拼接场景,如平时随处可见的多屏拼接显示的广告大屏幕
Property Type
Property的类型分为如下几种:
enum
bitmask
range
signed range
object
blob
以上类型中需要着重介绍的是object和blob类型,其它类型看名字就知道什么意思,所以就不做介绍了。
Object Property
Object类型的property,它的值用drm object ID来表示。目前的DRM架构中仅用到2个Object Property,它们分别是 "FB_ID" 和 "CRTC_ID" ,它们的property值分别表示framebuffer object ID和crtc object ID。
Blob Property
Blob类型的property,它的值用blob object ID来表示。所谓blob,说白了就是一个自定义长度的内存块,用来存放自定义的结构体数据。典型的Blob Property,如 "MODE_ID" ,它的值为blob object ID,drm驱动可以根据该ID找到对应的drm_property_blob结构体,该结构体中存放着modeinfo的相关信息。
在DRM的property type中,还有2种特殊的type,它们分别是 IMMUTABLE TYPE 和 ATOMIC TYPE。这两种type的特殊性在于,它们可以和上面任意一种property进行组合使用,用来修饰上面的property。
IMMUTABLE TYPE:表示该property为只读,应用程序无法修改它的值,如"IN_FORMATS"。
ATOMIC TYPE:表示该property只有在drm应用程序(drm client)支持ATOMIC操作时才可见。
概念图
还是习惯性的上一张图吧:
property.png
总结
DRM的Property,其实有点类似于kernel中的sysfs属性节点。DRM将kernel空间中的重要参数通过property机制导出给上层应用,使得上层应用可以通过修改property的值,来轻松实现参数的传递,而无需额外的IOCTL操作。
通过本篇的学习,我们了解了DRM Property的基本概念,这有助于我们学习后面的Atomic编程。在下一篇文章中,我们将一起来学习如何使用libdrm的Atomic接口进行编程,敬请期待!
---------------------
DRM应用程序进阶 (atomic-crtc)
前言
在上一篇《DRM应用程序进阶(Property)》中,我们学习了Property的基本概念及作用。在本篇中,我们将一起来学习如何操作这些Property,即libdrm Atomic接口的用法。
Atomic
为什么叫“Atomic Commit”?
初学者第一次接触到DRM时,总会好奇当初开发者为什么要起 Atomic 这个名字。Wiki上关于该名词有较详细的解释,如果大家感兴趣可以通过本篇结尾的参考资料获取链接查看。我这里用白话简单概括就是:本次commit操作,要么成功,要么保持原来的状态不变。 即如果中途操作失败了,那些已经生效的配置需要恢复成之前的状态,就像没发生过commit操作似的,这就是Atomic的含义。
而用Commit 一词,是因为本次操作可能会修改到多个参数,等修改好这些参数后,再一次性发起操作请求,有点类似与填表后“提交”材料的意思。
如何操作property?
在上一篇我们了解了Property的基本组成结构,即name、id 和 value。因此操作property就变得非常简单,通过name 来获取property,通过id 来操作property,通过value 来修改property的值。而完成这些操作的应用接口,就是libdrm提供的Atomic接口。
需要记住一点,在libdrm中,所有的操作都是以Object ID来进行访问的,因此要操作property,首先需要获取该property的Object ID。
伪代码:
int main(void)
{
...
drmSetClientCap(DRM_CLIENT_CAP_ATOMIC);
drmModeObjectGetProperties(...);
drmModeGetProperty(property_id)
...
drmModeAtomicAlloc();
drmModeAtomicAddProperty(..., property_id, property_value);
drmModeAtomicCommit(...);
drmModeAtomicFree();
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
首先通过drmModeGetProperty()来获取property的相关信息,然后通过drmModeAtomicAddProperty()来修改property的值,最后通过drmModeAtomicCommit()来发起真正的修改请求。
为什么要设置 DRM_CLIENT_CAP_ATOMIC ?
在上一篇《DRM应用程序进阶(Property)》中有介绍过,凡是被DRM_MODE_PROP_ATOMIC修饰过的property,只有在drm应用程序支持Atomic操作时才可见,否则该property对应用程序不可见。因此通过设置DRM_CLIENT_CAP_ATOMIC这个flag,来告知DRM驱动该应用程序支持Atomic操作。
参考代码
基于之前的《最简单的DRM应用程序(plane-test)》的参考代码,我们使用Atomic接口来替代原来的drmModeSetCrtc()接口,从而通过差异对比来学些Atomic接口的操作。
modeset-atomic-crtc.c
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint8_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf;
static int modeset_create_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
memset(bo->vaddr, 0xff, bo->size);
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
static uint32_t get_property_id(int fd, drmModeObjectProperties *props,
const char *name)
{
drmModePropertyPtr property;
uint32_t i, id = 0;
/* find property according to the name */
for (i = 0; i < props->count_props; i++) {
property = drmModeGetProperty(fd, props->props[i]);
if (!strcmp(property->name, name))
id = property->prop_id;
drmModeFreeProperty(property);
if (id)
break;
}
return id;
}
int main(int argc, char **argv)
{
int fd;
drmModeConnector *conn;
drmModeRes *res;
drmModePlaneRes *plane_res;
drmModeObjectProperties *props;
drmModeAtomicReq *req;
uint32_t conn_id;
uint32_t crtc_id;
uint32_t plane_id;
uint32_t blob_id;
uint32_t property_crtc_id;
uint32_t property_mode_id;
uint32_t property_active;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
plane_res = drmModeGetPlaneResources(fd);
plane_id = plane_res->planes[0];
conn = drmModeGetConnector(fd, conn_id);
buf.width = conn->modes[0].hdisplay;
buf.height = conn->modes[0].vdisplay;
modeset_create_fb(fd, &buf);
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
/* get connector properties */
props = drmModeObjectGetProperties(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
property_crtc_id = get_property_id(fd, props, "CRTC_ID");
drmModeFreeObjectProperties(props);
/* get crtc properties */
props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
property_active = get_property_id(fd, props, "ACTIVE");
property_mode_id = get_property_id(fd, props, "MODE_ID");
drmModeFreeObjectProperties(props);
/* create blob to store current mode, and retun the blob id */
drmModeCreatePropertyBlob(fd, &conn->modes[0],
sizeof(conn->modes[0]), &blob_id);
/* start modeseting */
req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, crtc_id, property_active, 1);
drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id);
drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id);
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
drmModeAtomicFree(req);
printf("drmModeAtomicCommit SetCrtc\n");
getchar();
drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,
50, 50, 320, 320,
0, 0, 320 << 16, 320 << 16);
printf("drmModeSetPlane\n");
getchar();
modeset_destroy_fb(fd, &buf);
drmModeFreeConnector(conn);
drmModeFreePlaneResources(plane_res);
drmModeFreeResources(res);
close(fd);
return 0;
}
通过上面的代码我们可以看出,原来的 drmModeSetCrtc(crtc_id, fb_id, conn_id, &mode) 被下面这部分代码取代了:
req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, crtc_id, property_active, 1);
drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id);
drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id);
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
drmModeAtomicFree(req);
虽然代码量增加了,但是应用程序的灵活性和可扩展性也增强了。
由于以上代码没有添加对 fb_id 的操作,因此它的作用只是初始化CRTC/ENCODER/CONNECTOR硬件,以及建立硬件链路的连接关系,并不会显示framebuffer的内容,即保持黑屏状态。framebuffer的显示将由后面的 drmModeSetPlane() 操作来完成。
运行结果
以上代码运行的效果如下(模拟效果):
---------------------