DRM(Direct Rendering Manager)学习简介

2023-11-06

 

DRM

DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等,而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护。

DRM从模块上划分,可以简单分为3部分:libdrm、KMS、GEM


(图片来自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


(图片来源: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脏羊 CSDN博客:Linux DRM(二)基本概念和特性
---------------------
作者:何小龙
来源:CSDN
原文:https://blog.csdn.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。

运行结果:(模拟效果)

描述:程序运行后,显示全屏白色,等待用户输入按键;当用户按下任意按键后,程序退出,显示黑屏。

    注意:程序运行之前,请确保没有其它应用或服务占用/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)。

运行结果:(模拟效果)

描述:程序运行后,屏幕在红色和蓝色之间来回切换;当输入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接口进行编程,敬请期待!

参考资料

Linux GPU Driver Developer’s Guide: KMS Properties

文章汇总: DRM (Direct Rendering Manager) 学习简介

---------------------
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();
    ...
}

 

首先通过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() 操作来完成。
运行结果

以上代码运行的效果如下(模拟效果):


---------------------
DRM应用程序进阶 (atomic-plane)

前言

在上一篇《DRM应用程序进阶(atomic-crtc)》文章中,我们学习了如何通过libdrm的atomic接口实现modeseting的操作。本篇,我们将一起来学习如何通过atomic接口,来实现 drmModeSetPlane() 相同的操作。
Atomic Commit

plane update的atomic操作如下:

drmModeAtomicAddProperty(req, plane_id, property_crtc_id, crtc_id);
drmModeAtomicAddProperty(req, plane_id, property_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, property_crtc_x, crtc_x);
drmModeAtomicAddProperty(req, plane_id, property_crtc_y, crtc_y);
drmModeAtomicAddProperty(req, plane_id, property_crtc_w, crtc_w);
drmModeAtomicAddProperty(req, plane_id, property_crtc_h, crtc_h);
drmModeAtomicAddProperty(req, plane_id, property_src_x, src_x);
drmModeAtomicAddProperty(req, plane_id, property_src_y, src_y);
drmModeAtomicAddProperty(req, plane_id, property_src_w, src_w << 16);
drmModeAtomicAddProperty(req, plane_id, property_src_h, src_h << 16);
drmModeAtomicCommit(fd, req, flags, NULL);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

以上各项参数与drmModeSetPlane() 的参数一一对应:

drmModeSetPlane(fd, plane_id, crtc_id, fb_id, flags,
                crtc_x, crtc_y, crtc_w, crtc_h,
                src_x, src_y, src_w << 16, src_h << 16)

    1
    2
    3

其中,参数 flags 可以是如下值的组合:

        DRM_MODE_PAGE_FLIP_EVENT: 请求底层驱动发送PAGE_FLIP事件,上层应用需要调用 drmHandleEvent() 来接收并处理相应事件,详见《最简单的DRM应用程序 (page-flip)》。
        DRM_MODE_ATOMIC_TEST_ONLY: 仅用于试探本次commit操作是否能成功,不会操作真正的硬件寄存器。不能和 DRM_MODE_PAGE_FLIP_EVENT 同时使用。
        DRM_MODE_ATOMIC_NONBLOCK: 允许本次commit操作异步执行,即无需等待上一次commit操作彻底执行完成,就可以发起本次操作。drmModeAtomicCommit() 默认以BLOCK(同步)方式执行。
        DRM_MODE_ATOMIC_ALLOW_MODESET: 告诉底层驱动,本次commit操作修改到了modeseting相关的参数,需要执行一次full modeset动作。

当然,flags 参数为0也是可以的。

drmModeAtomicCommit() 与 drmModeSetPlane() 对比,具有如下优势:
drmModeAtomicCommit     drmModeSetPlane
支持异步调用     只能同步调用
一次调用可以更新多个plane     每次调用只能更新1个plane
支持test功能,提前预知调用结果     不支持
Property Commit

如果你从事过Android底层开发,你会发现在Android external/libdrm 的某些release分支上,会多出 drmModePropertySetAdd()、drmModePropertySetCommit() 等这类函数接口,而在libdrm的社区主线仓库mesa/drm中,其实是找不到这样的接口的。该接口最初由Ville Syrjälä 和 Sean Paul于2012年提交到libdrm的一个临时分支上,后来又被cherry-pick到Android的libdrm仓库中,点此链接查看提交记录。

伪代码

pset = drmModePropertySetAlloc();
drmModePropertySetAdd(pset, object_id, property_id, property_value);
drmModePropertySetCommit(fd, flags, data, pset);
drmModePropertySetFree(pset);

    1
    2
    3
    4

由于这些接口的用法同主线中的Atomic接口大同小异,因此不做详细描述。
参考代码

基于上一篇 modeset-atomic-crtc 程序,我们使用atomic接口替换 drmModeSetPlane() 函数,具体如下:

modeset-atomic-plane.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;

    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;
    uint32_t property_fb_id;
    uint32_t property_crtc_x;
    uint32_t property_crtc_y;
    uint32_t property_crtc_w;
    uint32_t property_crtc_h;
    uint32_t property_src_x;
    uint32_t property_src_y;
    uint32_t property_src_w;
    uint32_t property_src_h;

    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);

    props = drmModeObjectGetProperties(fd, conn_id,    DRM_MODE_OBJECT_CONNECTOR);
    property_crtc_id = get_property_id(fd, props, "CRTC_ID");
    drmModeFreeObjectProperties(props);

    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);

    drmModeCreatePropertyBlob(fd, &conn->modes[0],
                sizeof(conn->modes[0]), &blob_id);

    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();

    /* get plane properties */
    props = drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
    property_crtc_id = get_property_id(fd, props, "CRTC_ID");
    property_fb_id = get_property_id(fd, props, "FB_ID");
    property_crtc_x = get_property_id(fd, props, "CRTC_X");
    property_crtc_y = get_property_id(fd, props, "CRTC_Y");
    property_crtc_w = get_property_id(fd, props, "CRTC_W");
    property_crtc_h = get_property_id(fd, props, "CRTC_H");
    property_src_x = get_property_id(fd, props, "SRC_X");
    property_src_y = get_property_id(fd, props, "SRC_Y");
    property_src_w = get_property_id(fd, props, "SRC_W");
    property_src_h = get_property_id(fd, props, "SRC_H");
    drmModeFreeObjectProperties(props);

    /* atomic plane update */
    req = drmModeAtomicAlloc();
    drmModeAtomicAddProperty(req, plane_id, property_crtc_id, crtc_id);
    drmModeAtomicAddProperty(req, plane_id, property_fb_id, buf.fb_id);
    drmModeAtomicAddProperty(req, plane_id, property_crtc_x, 50);
    drmModeAtomicAddProperty(req, plane_id, property_crtc_y, 50);
    drmModeAtomicAddProperty(req, plane_id, property_crtc_w, 320);
    drmModeAtomicAddProperty(req, plane_id, property_crtc_h, 320);
    drmModeAtomicAddProperty(req, plane_id, property_src_x, 0);
    drmModeAtomicAddProperty(req, plane_id, property_src_y, 0);
    drmModeAtomicAddProperty(req, plane_id, property_src_w, 320 << 16);
    drmModeAtomicAddProperty(req, plane_id, property_src_h, 320 << 16);
    drmModeAtomicCommit(fd, req, 0, NULL);
    drmModeAtomicFree(req);

    printf("drmModeAtomicCommit SetPlane\n");
    getchar();

    modeset_destroy_fb(fd, &buf);

    drmModeFreeConnector(conn);
    drmModeFreePlaneResources(plane_res);
    drmModeFreeResources(res);

    close(fd);

    return 0;
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191

提示:

        上面的两次 drmModeAtomicCommit() 操作可以合并成一次;
        无需频繁调用 drmModeAtomicAlloc() 、drmModeAtomicFree() ,可以在第一次commit之前Alloc,在最后一次commit之后free,也没问题;
        plane update操作时,可以只add发生变化的property,其它未发生变化的properties即使没有被add,在commit时底层驱动仍然会取上一次的值来配置硬件寄存器。

运行结果

以上代码运行的效果如下(模拟效果):
atomic-plane.gif-59.7kB

    注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。

描述:

    程序运行后,屏幕显示黑色,终端打印“drmModeAtomicCommit SetCrtc”信息,表明当前已经初始化好CRTC/ENCODER/CONNECTOR硬件;
    输入回车后,屏幕显示framebuffer的crop区域,同时终端打印“drmModeAtomicCommit SetPlane”;
    再次输入回车,显示黑屏,程序退出

源码下载

modeset-atomic-plane
参考资料

Android libdrm: atomictest.c
Google Chrome: drm-tests/atomictest.c
libdrm mainline: mesa/drm
---------------------
DRM (Direct Rendering Manager) 的发展历史

 

前言

了解 DRM 的发展历史,对于学习 DRM 驱动开发的人员来说,具有一定的帮助。
因此本人决定将 Wiki 上 Direct Rendering Manager 文章中的 History 章节翻译成中文,并重新组织语言形式,方便初学者阅读和理解。

正文

1999年,Precision Insight公司首次为 XFree86 4.0 Server 开发 DRI 显示框架,主要用于更好的适配 3dfx 公司的显卡。也就是在那时提交了第一版 DRM 代码,并以 kernel patch 的形式提交到了 Mesa3D 的 drm 仓库中。

当时的 mesa/drm 仓库其实是包含两部分代码的,一部分为 user space 代码(即后来的 libdrm),另一部分为 kernel space 代码(即后来的 drm 驱动)。

点击此处,查看 DRM 的第一版提交。需要注意的是,那个时候的 DRM 代码,主要以 buffer 管理为主,不包含 mode-setting 的部分。

同年晚些时候,该笔 patch 的 kernel 部分被 merge 到 kernel 2.3.18 主线中,当时的 drm 源码被放到了 /drivers/char/drm/ 目录下,作为字符设备来使用。

点击此处,查看该kernel提交。

在接下来的几年里,drm 驱动所支持的显卡数量不断在扩增。到2001年1月,Linux 2.4.0 版本发布时,除了已经支持的 3dfx Voodoo3 显卡外,还支持 Creative Labs GMX 2000Intel i810Matrox G200/G400ATI Rage 128 这些显卡。在后续的 Linux 2.4.x 系列版本中,随着 ATI Radeon 显卡,SiS 显卡, Intel 830M 以及再后来的集成显卡这些驱动的合入,DRM 所支持的显卡列表不断的被扩充。

在2004年的下半年,DRM 被分割成了2部分:DRM CoreDRM Driver ,并被 merge 到 kernel 2.6.11 主线版本中。该分割允许多个 DRM 驱动同时访问多个设备,为 multi-GPU 的支持开辟了道路,此次分割也被称为 DRM core/personality split

多年来,开发者们都一致认为,应该把所有 mode-setting 相关的代码放到 kernel 源码中去。但是显卡厂商却对此颇有争议,他们认为实现 mode-setting 操作的唯一途径,就是调用他们显卡厂商自己提供的 API,而这些 API 往往是被固化在显卡的 BIOS 中的,并且只能在 X86 实模式 下运行。这就导致 Linux kernel 根本无法调用这些显卡 API,因为 kernel 本身运行在 X86 保护模式 下。直到后来 Luc Verhaegen 和其它开发人员找到了一种新的方法,使用 natvie mode-setting 来代替 BIOS,这种局面才被打破。该方法证明了使用 kernel 的代码也可以完成 mode-setting 操作,这就为后来的 Kernel Mode Setting 代码奠定了基础。

2007年5月,Jesse Barnes (来自 Intel) 发布了第一版 drm-modesetting API 提案,并在 Intel GPU i915 的 DRM 驱动中实现了 mode-setting 的操作。同年12月,Jerome Glisse 开始将 native mode-setting 代码添加到 ATI Radeon 显卡的 DRM 驱动中。2008年,该项任务的 API 和 driver 部分的工作仍在持续进行,但因为依赖 DRM 另一个重要组件 —— Memory Manager,而迟迟被延期,因为 mode-setting 需要依赖 Memory Manager 来管理 framebuffer。

2008年10月,Linux kernel 2.6.27 进行了一次重大的源码重组,随后 DRM 也迎来了历史性的重大修改:DRM 的整套源码被放到了 /drivers/gpu/drm/ 目录下,不同的GPU厂商代码也被放到了各自的子目录下,同时头文件也被移到了 /include/drm 目录下。

drm重组修改:drm: reorganise drm tree to be more future proof.

随着显存管理的复杂性日益增加,开发人员也想出了多种方法来解决这类问题。最先出场的是 Translation Table Maps (TTM) 内存管理器,由 Thomas Hellstrom (Tungsten Graphics) 与 Eric Anholt (Intel) 和 Dave Airlie (Red Hat) 联合开发完成。在2007年11月,有人就提议将 TTM 纳入 kernel 2.6.25 主线版本中。在2008年5月,该提案又再一次被提及,可是最终还是被驳回了。取而代之的,是一种新的方案,叫做 Graphics Execution Manager (GEM)。GEM 最初是由 Intel 的 Keith Packard 和 Eric Anholt,为了解决 i915 drm driver 的内存管理问题,而开发的一种更加简单的内存管理方案。GEM 后来受到了广泛好评,并被 merge 到了 Linux kernel 2.6.28 版本中,于2008年12月正式发布。而在此期间,TTM 不得不苦苦等待,直到2009年9月,因为新的 Radeon KMS DRM driver 的要求,最终才被 merge 到 Linux 2.6.31 主线版本中。

有了内存管理来操作 buffer object,DRM 开发人员终于可以将早已完成的 mode-setting API 及相关动代码添加到 kernel 中了,这些 API 被称作 Kernel Mode-setting (KMS),而实现这些 API 的驱动也被称之为 KMS Drivers。2009年3月,KMS 被合入到 Linux kernel 2.6.29 版本中,附带一起的还有 i915 KMS 驱动。从 libdrm 2.4.3 版本开始,KMS API 被导出到了用户空间,而Intel DDX 显卡驱动(位于用户空间的 X.Org 中)则是第一家使用 GEM 和 KMS APIs 的厂商驱动。2009年9月,Radeon KMS DRM 驱动被添加到了 Linux 2.6.31 发行版中,该驱动虽然使用的是 TTM 内存管理器,但是却向上层提供了与 GEM 相兼容的 API 和 ioctl。

从2006年开始,nouveau 项目就开始在 Linux kernel 的非官方版本上开发用于免费使用的 NVIDIA DRM 驱动。在2010年,nouveau 项目的源代码被当作一款实验性的驱动 merge 到了 Linux2.6.33 版本中。在提交的过程中,该驱动也被转换成了 KMS 版本,同时使用 TTM 作为它的内存管理器。

KMS API (包括 GEM 在内) 是 DRM 开发过程中的一个重要里程碑,但是它并没有就此止步不前,而是在接下来的几年里不断的优化自己的 API。在 Linux 2.6.33 版本中,KMS 添加了 page flip 操作,用来与异步 VBLANK 事件进行同步,当时只有 i915 驱动支持该操作。后来在 Linux 2.6.38 发行版中, radeon 和 nouveau 驱动也都添加了对该操作的支持。该 page flip 对应的上层接口也被添加到了 libdrm 2.4.17 版本中。2011年初,在 Linux 2.6.39 发布周期内, dumb buffer 被添加到了 KMS API 中。所谓 “dumb buffer”,就是该 buffer 的操作不依赖于硬件或相关加速器,多用于简单 buffer 的应用场合,适合做 framebuffer 使用。它的目的是降低应用程序操作 DRM 的复杂度,比如 Plymouth 这种应用程序,本身使用场景就比较简单,无需使用 driver 所提供的特定 API 来完成 buffer 的操作。从 libdrm 2.4.25 版本开始,这个新特性就被添加进去了。同年晚些时候,KMS 又添加了一个新类型的 object,即 Plane。有些扫描引擎(即CRTC)本身附带硬件合成能力,这些负责硬件合成功能的模块往往被称为 HW overlay,而 Plane 就是用来抽象 overlay 硬件的。从 Linux 3.3 和 libdrm 2.4.30 版本开始,添加了对 Plane 的支持。在 Linux 3.5 和 libdrm 2.4.36 版本发布期间,另一个新增的重要元素,就是 generic object property —— 一种将常用参数添加到 KMS objects 中的方法。Property 的支持,对于那些需要完成特定行为或功能的 objects(如 CRTCs、planes)来说,是一种极其有效的操作方法。

2010年,Dave Airlie 开发出了可以在 DRM 驱动之间进行卸载 GPU 的原型方案,因为 Airlie 当时想要模仿 NVIDIA Optimus 技术,所以它给自己的这项技术命名为 “PRIME”。2011年底,Airlie 恢复了他在 PRIME 上的工作,不过这次是基于 Linux 3.3 引入的一种新的 buffer 共享机制 —— DMA-BUF。DMA-BUF PRIME 基础框架最终于2012年3月完成,并被合入到 Linux 3.4 和 libdrm 2.4.34 版本中。后来在 Linux 3.5 版本发行期间,越来越多的 DRM 驱动实现了对 PRIME 的支持,其中包括 Intel i915 显卡驱动,AMD radeon 显卡驱动以及 NVIDIA nouveau 显卡驱动。

近年来,DRM API 随着一些新的 feature 加入而不断的被扩展。在2013年,作为 GSoC 项目的一项开发计划,David Herrmann 开发出了 Multi-Render Nodes 的功能。他的这部分代码很快被 merge 到了 Linux 3.12 版本中,并作为实验性的功能被 i915、radeon 和 nouveau 驱动所支持。从 Linux 3.17 版本开始,该功能被默认开启使用。2014年,Matt Roper (Intel) 开发并提出了 Universal Plane(或者叫 Unified Plane)的概念,它将 framebuffer (primary plane)、overlay (secondary plane) 和 cursor (cursor plane) 这些 plane 都视为具有相同 API 的 objects,只是这些 objects 的 type 不一样而已。Universal Plane 的实现,具有更少、更通用的 ioctls,使得 DRM API 更加统一(译者注:这里的 universal plane,个人认为翻译成 “万能 plane ” 更贴切,因为它既可以是 primary,也可以是 overlay 或 cursor)。为了兼容旧的 API,DRM Core 将 universal plane 作为驱动的一个附加功能提供给上层使用。在 Linux3.15 和 libdrm 2.4.55 版本中,universal plane 首次亮相,部分驱动(如 Intel i915)也实现了对该功能的支持。

近期 DRM API 最大的优化就是 atomic mode-setting API,它将原子操作的概念引入到了 mode-setting 和 page flip 操作中。Atomic API 的想法最早是在2012年提出的,Ville Syrjälä (Intel)接管了该 API 设计与实现的主要任务。在他的工作基础上,Rob Clark (TI)采用了同样的方式来实现 atomic page flip 操作。后来在2013年,mode-setting 和 page flip 这两个功能都被整合到同一个 ioctl 中,但是因为需要等待 universal plane 的功能完成,该 feature 一直等到2014年中旬才被合入主线版本。在2014年的下半年,为了让现有的 KMS 驱动转向新的 atomic 框架,Daniel Vetter (Intel)和其它 DRM 开发人员对 atomic 代码做了大量的优化。最终,这些修改都被合入到 Linux 3.19 和 Linux 4.0 发行版中,并且从 Linux 4.2 版本开始默认使用该功能。libdrm 从 2.4.62 版本开始支持这些新的 atomic API,许多驱动也都转而使用这些新的 API。到2018年,又有10个基于 atomic 框架的 DRM 新增驱动被添加到了 Linux kernel 中。

总结

最后用一张图来总结一下 DRM 在历史上的重要事件:

drm-history.png-202.8kB

DRM 驱动程序开发(开篇)

前言

在前面的《最简单的DRM应用程序》系列文章中,我们学习了如何使用 libdrm 接口编写 DRM 应用程序。从本篇开始,我们将进入一个全新的世界,一起来学习如何在 kernel 空间编写 DRM 驱动程序。
Objects

在开始编写 DRM 驱动程序之前,我有必要对 DRM 内部的 Objects 进行一番介绍。因为这些 Objects 是 DRM 框架的核心,它们缺一不可。

图片3.png-17.2kB

上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为 drm_mode_object,虚线以下为 drm_gem_object。

之前曾在《DRM(Direct Rendering Manager)学习简介》中对这些 objects 做过简要介绍,作为本篇的重中之重,我觉得有必要再强调一下这些 objects 的概念:
object     说明
crtc     RGB 信号发生源(TCON),显存切换控制器(Dislay Controller)
plane     Display Controller 的数据源通道,每个 crtc 至少要有一个 plane
encoder     RGB 信号转换器(DSI Contoller),同时也控制显示设备的休眠唤醒
connector     凡是能获取到显示参数的硬件设备,但通常和 encoder 绑定在一起
framebuffer     只用于描述显存信息(如 format、pitch、size 等),不负责显存的分配释放
property     atomic 操作的基础,任何想要修改的参数都可以做成 property,供用户空间使用
gem     负责显存的分配、映射和释放

这些 objects 之间的关系:

图片2.png-20.7kB

通过上图可以看到,plane 是连接 framebuffer 和 crtc 的纽带,而 encoder 则是连接 crtc 和 connector 的纽带。与物理 buffer 直接打交道的是 gem 而不是 framebuffer。

需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些 objects,否则 DRM 子系统无法正常运行。
drm_panel

drm_panel 不属于 objects 的范畴,它只是一堆回调函数的集合。但它的存在降低了 LCD 驱动与 encoder 驱动之间的耦合度。

图片3.png-9.2kB

耦合的产生:
(1)connector 的主要作用就是获取显示参数,所以会在 LCD 驱动中去构造 connector object。但是 connector 初始化时需要 attach 上一个 encoder object,而这个 encoder object 往往是在另一个硬件驱动中生成的,为了访问该 encoder object,势必会产生一部分耦合的代码。
(2)encoder 除了扮演信号转换的角色,还担任着通知显示设备休眠唤醒的角色。因此,当 encoder 通知 LCD 驱动执行相应的 enable/disable 操作时,就一定会调用 LCD 驱动导出的全局函数,这也必然会产生一部分的耦合代码。

为了解决该耦合的问题,DRM 子系统为开发人员提供了 drm_panel 结构体,该结构体封装了 connector & encoder 对 LCD 访问的常用接口。

图片4.png-10.2kB

于是,原来的 Encoder 驱动和 LCD 驱动之间的耦合,就转变成了上图中 Encoder 驱动与 drm_panel、drm_panel 与 LCD 驱动之间的“耦合”,从而实现了 Encoder 驱动与 LCD 驱动之间的解耦合。

小技巧:

    为了方便驱动程序设计,通常都将 encoder 与 connector 放在同一个驱动中初始化,即 encoder 在哪,connector 就在哪。

如何抽象硬件

对于初学者来说,往往让他们迷惑的不是 DRM 中 objects 的概念,而是如何去建立这些 objects 与实际硬件的对应关系。因为并不是所有的 Display 硬件都能很好的对应上 plane/crtc/encoder/connector 这些 objects。下面我们就来一起学习,如何去抽象显示硬件到具体的 DRM object。
MIPI DSI 接口

下图为一个典型的 MIPI DSI 接口屏的硬件连接框图:

图片5.png-15.3kB

它在软件架构上与 DRM object 的对应关系如下图:

图片10.png-46.8kB

多余的细节不做介绍,这里只说明为何如此分配 drm object:
object     说明
crtc     RGB timing的产生,以及显示数据的更新,都需要访问 Dislay Controller 硬件寄存器,因此放在 Display Controller 驱动中
plane     对 Overlay 硬件的抽象,同样需要访问 Display Controller 寄存器,因此也放在 Display Controller 驱动中
encoder     将 RGB 并行信号转换为 DSI 串行信号,需要配置 DSI 硬件寄存器,因此放在 DSI Controller 驱动中
connector     可以通过 drm_panel 来获取 LCD 的 mode 信息,但是 encoder 在哪,connector 就在哪,因此放在 DSI Controller 驱动中
drm_panel     用于获取 LCD mode 参数,并提供 LCD 休眠唤醒的回调接口,供 encoder 调用,因此放在 LCD 驱动中

    驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c

MIPI DPI 接口

DPI 接口也就是我们常说的 RGB 并行接口,Video 数据通过 RGB 并行总线传输,控制命令(如初始化、休眠、唤醒等)则通过 SPI/I2C 总线传输,比如早期的 S3C2440 SoC 平台。下图为一个典型的 MIPI DPI 接口屏的硬件连接框图:

图片8.png-14.3kB

该硬件连接在软件架构上与 DRM object 的对应关系如下图:

图片1.png-26.1kB

多余的细节不做介绍,这里只说明为何如此分配 drm object:
object     说明
crtc     RGB timing的产生,以及显示数据的更新,都需要访问 LCD Controller 硬件寄存器,因此放在 LCD Controller 驱动中
plane     LCDC 没有 Overlay 硬件,它只有一个数据源通道,被抽象为 Primary Plane,同样需要访问 LCDC 硬件寄存器,因此放在 LCDC 驱动中
encoder     由于 DPI 接口本身不需要对 RGB 信号做任何转换,因此没有哪个硬件与之对应。但是 drm objects 又缺一不可,因此实现了一个虚拟的 encoder object。至于为什么要放在 LCDC 驱动中实现,纯粹只是为了省事而已,你也可以放在一个虚拟的平台驱动中去实现该 encoder object。
connector     encoder 在哪,connector 就在哪,没什么好说的了
drm_panel     用于获取 LCD mode 参数,并提供 LCD 休眠唤醒的回调接口,供 encoder 调用,因此放在 LCD 驱动中

    驱动参考:https://elixir.bootlin.com/linux/v5.0/source/drivers/gpu/drm/panel/panel-sitronix-st7789v.c

MIPI DBI 接口

DBI 接口也就是我们平时常说的 MCU 或 SPI 接口屏,这类屏的 VIDEO 数据和控制命令都是通过同一总线接口(I80、SPI接口)进行传输,而且这类屏幕必须内置 GRAM 显存,否则屏幕无法维持正常显示。

下图为一个典型的 DBI 接口屏的硬件连接框图:

图片9.png-9.8kB

该硬件连接在软件架构上与 DRM object 的对应关系如下:

上图参考 kernel4.19 tinydrm 软件架构。
object     说明
crtc     这类硬件本身不需要任何 RGB timing 信号,因此也没有实际的硬件与之对应。但是 drm objects 缺一不可,需要实现一个虚拟的 crtc object。由于更新图像数据的动作需要通过 SPI 总线发送命令才能完成,因此放在了 LCD 驱动中
plane     没有实际的硬件与之对应,但 crtc 初始化时需要一个 plane object 作为参数传递,因此和 crtc 放在一起
encoder     没有实际的硬件与之对应,使用虚拟的 encoder object。因为这类硬件并不是将 RGB 信号转换为 SPI 信号,而是根本就没有 RGB 信号源,也就无从谈起 encoder 设备。但是为了通知 LCD 休眠唤醒,需要调用 LCD 驱动的相应接口,因此放在 LCD 驱动中
connector     由于没有了 drm_panel,需要调用 LCD 接口来获取 mode 参数,因此放在 LCD 驱动中

    驱动参考:https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/tinydrm/ili9341.c

总结

        这 7 个 objects 缺一不可
        framebuffer 只是负责描述显存信息,gem 则负责显存的分配/释放等操作
        encoder 在哪里,connector 就在哪里

在下一篇文章中,我们将开始真正动手编写一个《最简单的 DRM 驱动程序》,尽情期待!

原文:https://blog.csdn.net/hexiaolong2009/article/details/89810355
 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

DRM(Direct Rendering Manager)学习简介 的相关文章

  • Linux DRM Graphic 显示简单介绍

    好久没有写过博客了 xff0c 工作之后平时都没有总结 xff0c 感觉像做了学到很多东西 xff0c 但是又感觉什么都没有学到 xff0c 似懂非懂 xff08 真尼玛纠结 xff09 突然别人说的一句话 xff0c 学东西不仅仅是自己学
  • Linux DRM KMS 驱动简介

    Whoops xff0c 上次写完 Linux DRM Graphic 显示简单介绍 博文后 xff0c 心情还是蛮愉悦的 xff0c 来来 xff0c 这次在说说具体的显卡驱动 1 DRM 框架分解 DRM 框架提供了一系列的 IOCTL
  • DRM驱动代码分析:图层参数更新

    前言 无业居家 闭门造车 非常欢迎大家帮忙指正 有些代码流程是看代码分析的 没有去验证是否正确 我对DRM框架的很多东西都不了解 所以有些地方会比较生硬 熟悉学习需要时间 文章一直堆在草稿箱可能会降低我的积极性 所以我还是先发布了文章 后面
  • 图显系统DRM CRTC完全解析

    目录 CRTC 工作原理和意义 CRTC 模块的初始化和功能 0 引言 DRM 下的 CRTC 代表 RGB 数据管道 从 drm plane 接收像素数据并将其混合到一起 传输给下级显示设备 drm encoder 由 drm displ
  • 最简单的DRM应用程序 (single-buffer)

    在上一篇DRM Direct Rendering Manager 学习简介 中 我们学习了DRM的基本概念以及基本组成元素 从本篇开始 我将以示例代码的形式 给大家分享学习DRM驱动开发的整个学习过程 在学习DRM驱动之前 应该首先了解如何
  • DRM(Direct Rendering Manager)学习简介

    DRM DRM是Linux目前主流的图形显示框架 相比FB架构 DRM更能适应当前日益更新的显示硬件 比如FB原生不支持多层合成 不支持VSYNC 不支持DMA BUF 不支持异步更新 不支持fence机制等等 而这些功能DRM原生都支持
  • DRM驱动代码分析:色彩管理

    高通PQ有哪些子模块 DSPP sub blocks SDE DSPP IGC DSPP Inverse gamma correction block SDE DSPP PCC Panel color correction block SD
  • Rockchip

    1 Graphics介绍 1 1 使用X11 Graphics Xserver 是在常规桌面 Linux 平台上使用的显示系统 Rockchip 有一个定制的 Xserver 可以实现 glamor 2D acceleration Xser
  • DRM框架(vkms)分析(3)----connector->func && connector->helper_private的使用

    一 connector gt func drm connector funcs类型的对象实例 其中有些对象实例可直接使用helper函数 有些可以自定义 还有一些可以忽略掉不赋值的 struct drm connector funcs co
  • DRM驱动(七)之atomic_commit

    上节已经把应用的参数check了一遍 这次就可以把对应的参数配置到硬件里进行刷图操作了 int drm atomic commit struct drm atomic state state struct drm mode config c
  • 图显系统DRM ENCODER和CONNECTOR完全解析

    0 引言 DRM ENCODER 和 CONNECTOR 模块由图显外设抽象而来 从传统意义上来讲 ENCODER 包含外设控制器的功能 而 CONNECTOR 包含外设 PHY 或者显示器参数 但是这两部分又紧密关联 因此 软件 DRM
  • MIPI DSI的linux kernel驱动原理

    为了点亮一块MIPI屏幕 我们除了要了解MIPI DSI的工作原理之外 大前提是要了解整个MIPI DSI图显系统的组成 更需要清楚点亮一块MIPI屏幕需要做哪些事情 本文会捋顺各个环节所实现的功能以及基于RK3399来分析各个环节实现的原
  • Delphi Win32 的最佳共享软件锁 [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 目前不接受答案 与我上一个问题相同的介绍 我正在重写和 或合并一堆我的 应用程序框架 类 基本主窗体 关于框 锁定例程和购买链接 自动更新 数据模块初始化程序等 基
  • 如何使用android.drm框架

    我正在开发一个基于 DRM 的 Android 应用程序 应用程序旨在在下载并获得对文件 音频 视频 的控制访问权限后对 音频 视频 文件进行加密 防止文件 音频 视频 复制和粘贴并使文档过期 从而无法再查看它们 为此我使用 android
  • 与加密流相比,DRM 的附加值是多少?

    这个问题是关于使用 MPEG DASH 和 或 HLS 的视频流 我试图了解商业 DRM 系统 例如 EZDRM BuyDRM 等 与简单的加密流媒体 例如 DRM 相比的附加值 AES 128 加密的 HLS 我对商业口号有点迷失了 因此
  • Silverlight - 保护网络内的内容(DRM?)

    我想使用Windows 2003 的流媒体服务器和Silverlight 设置一些WMV 视频流 现在 不幸的是 Silverlight 仅支持 HTTP 这意味着人们只能下载视频 虽然这本身不是问题 但我想知道有哪些选项可以阻止它们在网络
  • 三星智能电视通过 PlayRead 串流 MPEG DASH

    我尝试在 Orsay TV Tizen 之前的三星电视 上播放流 带有 PlayReady 的 MPEG DASH 未加密的 DASH 工作正常 我试图严格按照 DRM Today 手册 即我的 DRM 提供商 获取许可证 但播放无法开始
  • 如何确定 Android 应用程序是否使用可信执行环境 (TEE) 和安全元件 (SE)?

    我已经解决了问题One https stackoverflow com questions 61225795 how to check whether android phone supports tee 64422042 64422042
  • 有没有办法在 HTML5 视频上使用 DRM?

    由于 Flash 正在节节败退 我想知道是否有办法通过 DRM H264 ogg 和 WebM 保护 html5 视频 On the HTML5 上的 W3C 常见问题解答 http www w3 org html wiki FAQs Is
  • 如何在 Cast Receiver Player 中续订过期的 DRM 许可证?

    我基于以下内容开发自定义 Cast Receiver 应用程序Google Cast 应用程序框架 https developers google com cast docs caf receiver 接收器应用程序负责播放 Widevin

随机推荐

  • Vue学习-基础篇4

    目录 组件结构讲解 如何在组件中引入其它组件 组件中如何使用外部插件 组件间的传值 Vue cli项目创建 什么是脚手架 创建项目 Vue cli项目结构 Vue cli 入口文件main js分析 组件结构讲解 把每个组件都放到一个独立的
  • 【Shell牛客刷题系列】SHELL28 nginx日志分析6-统计每分钟的请求数

    该系列是基于牛客Shell题库 针对具体题目进行查漏补缺 学习相应的命令 刷题链接 牛客题霸 Shell篇 该系列文章都放到专栏下 专栏链接为 专栏 Shell 欢迎关注专栏 本文知识预告 本文首先学习了sprintf 函数的用法 然后复习
  • python爬虫实战之模拟正方教务系统登录查询成绩

    最近由于某些需要 开始入门Python网络爬虫 想通过一个Python程序来访问正方教务管理系统并且抓取到期末的成绩 由于我并没有深入了解过过其他的编程语言 所以 也比较不出Python和其他语言 如JAVA PHP 的优缺点 只是因为我会
  • JAVA通配符

    上限通配符 我们想要的是一个确切元素类型未知的列表 这一点与数组是不同的 List
  • 一、红外遥控介绍(NEC协议)

    目录 1 1 红外线的介绍 1 2 红外遥控器的工作原理 1 3 NEC协议的介绍 1 引导码的组成 2 地址码 3 数据码 4 反码 5 连发码 1 4 红外接受装置 1 在数码管显示红外解码遥控器的按键值 1 1 红外线的介绍 红外是红
  • Kitti数据集标签中yaw角在不同坐标系的转换

    KITTI数据集中坐标系的定义如下图 相机坐标系 激光雷达坐标系 在标签文件 label 中 最后一个值为物体前进方向与x轴夹角的弧度值 在相机坐标系下 且以顺时针为正 逆时针为负 举个例子 如上图第一个car数据为例 其前进方向与x轴夹角
  • 三、IOC容器原理

    三 IOC容器原理 1 概述 Sun ONE技术体系下的IOC容器有 轻量级的有Spring Guice Pico Container Avalon HiveMind 重量级的有EJB 不轻不重的有JBoss Jdon等等 Spring框架
  • nodejs安装

    CentOS7安装NodeJS 1 官网下载二进制文件 https nodejs org en download 2 下载后上传到目标位置 3 解压包 依次执行 cd soft nodejs xz d node v12 18 2 linux
  • Python3 + xpath + excel 实现对boss直聘网的爬取

    一 项目简介 1 内容 抓取boss直聘网上广州地区的 python 相关工作的招聘信息 并且将招聘信息循环保存在excel表中 2 目标网站 https www zhipin com c101280100 query python pag
  • Linux中用gdb 查看代码堆栈的信息

    core dump 一般是在segmentation fault 段错误 的情况下产生的文件 需要通过ulimit来设置才会得到的 调试的话输入 gdb filename core filename就是产生core文件的可执行文件 core
  • Windows server 远程桌面连接用户不活动自动注销配置

    Windows server 远程桌面连接用户不活动自动注销配置
  • VMware三种网络模式配置详解。

    VMware网络 本质上不会配置VMware网络 是因为对其不够熟悉 VMware提供了三种可靠的网络模式 我相信只要了解了他们的区别 配置起来应该是如鱼得水的 如果还在为你的虚拟机上不了网而发愁 那么请跟随我的脚步 让我们来探究他们到底有
  • Flowable 用户问题

    Flowable用户和系统用户问题 springboot集成flowable modeler 实现免登 权限管理 Flowable引擎使用统一权限管理
  • 《信号与系统》解读 第4章 连续信号的离散化:采样与采样定理、奈奎斯特准则、脉冲编码调制PCM

    前言 如果你对采样定理和奈奎斯特准则一知半解 本文将给茅塞顿开 如果你对为什么采样频率必须大于等于原始信号的带宽的2倍 本文将给你答案 目录 1 信号与系统的模型 2 为什么要对连续信号离散化 3 连续信号离散化 采样 的模型 3 1 采样
  • 使用Hexo搭建博客并部署到Github

    一 博客环境搭建 Hexo 是一个静态博客框架 基于 Node js 将 Markdown 文章通过渲染引擎 生成一个静态网页 再结合 Git 命令 ssh Hexo 是一个快速 简洁且高效的博客框架 Hexo 使用 Markdown 或其
  • 深入理解 Java 垃圾回收机制

    一 垃圾回收机制的意义 java 语言中一个显著的特点就是引入了java回收机制 是c 程序员最头疼的内存管理的问题迎刃而解 它使得java程序员在编写程序的时候不在考虑内存管理 由于有个垃圾回收机制 java中的额对象不在有 作用域 的概
  • 解决Excel打开UTF-8编码的CSV文件乱码的问题

    解决Excel打开UTF 8编码的CSV文件乱码的问题 引用自 https en wikipedia org wiki Comma separated values CSV formats are not limited to a part
  • 透彻解析Qt入门级项目——贪吃蛇游戏

    1 项目目的 本项目主要通过编写贪吃蛇游戏来学习 熟悉Qt中封装的类 2 编译环境 VS2019 Qt5 9 3 功能实现 主要实现下面所列基本功能 控制贪吃蛇吃食物 表示蛇 控制贪吃蛇上下左右移动 控制食物的随机分配 控制蛇的增长 暂停游
  • MATLAB上关于复数矩阵的转置与共轭知识详解

    首选随机用A randn 2 2 randn 2 2 1i生成一个二维的复数矩阵 执行后结果如下 A 3 5784 0 7254i 1 3499 0 7147i 2 7694 0 0631i 3 0349 0 2050i 执行 A 得到如下
  • DRM(Direct Rendering Manager)学习简介

    DRM DRM是Linux目前主流的图形显示框架 相比FB架构 DRM更能适应当前日益更新的显示硬件 比如FB原生不支持多层合成 不支持VSYNC 不支持DMA BUF 不支持异步更新 不支持fence机制等等 而这些功能DRM原生都支持