DRM应用及驱动浅析

DRM 是linux图形显示子系统,主要分为三个部分:

  1. libdrm:for userspace,提供各种API供用户空间使用,主要是对IOCTL函数的封装;
  2. KMS:kernel mode setting,主要作用是设置参数和控制显示;
  3. GEM:graphic execution manager,主要做内存管理

在驱动侧主要工作在和KMS和GEM上,这两个模块又包含以下元素:

  • KMS:
    • crtc:对显示buffer进行扫描,并产生时序信号的硬件模块,通常指Display Controller;
    • encoder:负责将CRTC输出的timing时序转换成外部设备所需要的信号的模块,如HDMI转换器或DSI Controller;
    • connector:连接物理显示设备的连接器,如HDMI、DisplayPort、DSI总线,通常和Encoder驱动绑定在一起;
    • plane:硬件图层,有的Display硬件支持多层合成显示,但所有的Display Controller至少要有1个plane;
    • framebuffer:Framebuffer,单个图层的显示内容,唯一一个和硬件无关的基本元素;
    • vblank:软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC来实现;
    • property:任何你想设置的参数,都可以做成property,是DRM驱动中最灵活、最方便的Mode setting机制;
  • GEM:
    • dumb:只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景;
    • prime:连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景;
    • fence:buffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题;

2.从最简单的DRM应用程序入手(plane) (legacy)

  • 什么是plane?

    RM中的Plane指的是Display Controller中用于多层合成的单个硬件图层模块,属于硬件层面。plane是连接fb与crtc的纽带,是内存的搬运工。
    上图:

    fb-plane

    以framebuffer为图源,利用drmModeSetPlane函数设置plane。

    当 SRC 与 CRTC 的 X/Y 不相等时,则实现了平移的效果;

    当 SRC 与 CRTC 的 W/H 不相等时,则实现了缩放的效果;

    当 SRC 与 FrameBuffer 的 W/H 不相等时,则实现了裁剪的效果;

    • 一个高级plane通常具有如下功能
    功能 说明
    crop 裁剪 如上图
    scaling 缩放 放大或者缩小
    rotation 旋转 90 180 270 X/Y镜像
    z-order z-顺序 调整当前plane在所有图层中的顺序
    blending 合成 pixel alpha/global alpha
    format 颜色格式,ARGB888 XRGB888 YUV240等
    • plane的类型
    类型 说明
    cursor 光标图层 一般用于pc系统,用于显示鼠标
    overlay 叠加图层 通常用于YUV格式的视频图层
    primary 主要图层 通常用于仅支持RGB格式的简单图层

    DRM框架规定 任何一个CRTC,必须要有一个primary plane

legacy版本的简单plane显示代码流程如下

  1. 定义一个buffer_obj:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
  2. 打开显示设备
    int fd = open("/dev/dri/card0", O_RDWR);

  3. 获取显示资源,包括crtc、encoder和connector信息

    1
    2
    3
    4
    5
    6
    struct drmModeRes *res = drmModeGetResources(fd);
    /*
    获取crtc id和connector id,res中包含各个显示元素信息,且crtc和connector可能不止一个
    */
    uint32_t conn_id = res->connectors[0];
    uint32_t crtc_id = res->crtcs[0];
  4. 获取plane有关设备

    1
    2
    3
    4
    //必须设置DRM_CLIENT_CAP_UNIVERSAL_PLANES,如果不设置,drmModeGetPlaneResources只会返回verlay plane,如果设置则会返回所有的支持的plane资源
    drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
    struct drmModePlaneRes *plane_res = drmModeGetPlaneResources(fd); //获取plane资源列表
    uint32_t plane_id = plane_res->planes[0];
  5. 通过conn_id获取connector,connector中包含显示设备的信息,用显示设备的信息初始化buf

    1
    2
    3
    struct srmModeConnector *conn = drmModeGetConnector(fd, conn_id);
    buf.width = conn->modes[0].hdisplay;
    buf.height = conn->modes[0].vdisplay;
  6. 根据buf创建framebuffer

    • (1)根据buf创建一个dumb buf
      1
      2
      3
      4
      5
      struct drm_mode_create_dumb create;
      create.width = buf.width;
      create.height = buf.height;
      create.bpp = 32; //bpp-bits per pixel
      drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); //为create创建dumb 内存,同时creat得到初始化
    • (2)创建一个framebuffer
      1
      2
      3
      4
      5
      //将create初始化后的值反赋给buf
      buf.pitch = create.pitch;
      buf.size = create.size;
      buf.handle = create.handle;
      drmModeAddFB(fd, buf.width, buf.height, 24, 32, buf.pitch, buf.handle, &buf.fb_id);
    • (3)将dumb buffer映射到userspace
      1
      2
      3
      4
         struct drm_mode_map_dumb map;
      map.handle = create.handle;
      drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map); //映射一个用于CPU访问的dumb buffer
      buf.vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, map.offset); //将dumb buffer映射到用户空间
  7. 通过映射的内存地址画framebuffer

    1
    memset(buf.vaddr, 0xff, buf.size); //画白色
  8. 设置crtc,实现显示framebuffer

    1
    drmModeSetCrtc(fd, crtc_id, buf.fb_id, 0, 0, &conn_id, 1, &conn->modes[0]);
  9. 从framebuffer(100,150)位置crop一个320x320的plane放到crtc上,显示plane

    1
    drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,50, 50, 320, 320, 100 << 16, 150 << 16, 320 << 16, 320 << 16);

    drmModeSetPlane函数原型为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int drmModeSetPlane ( int fd, uint32_t plane_id, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, uint32_t crtc_x, uint32_t crtc_y, uint32_t crtc_w, uint32_t crtc_h, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h )
    /******
    fd:打开的DRM设备的文件描述符。
       plane_id:平面需要修改的平面ID。
       crtc_id:CRTC该平面所在CRTC的ID。
       fb_id:Framebuffer在平面上显示的Framebuffer ID,或-1表示保持Framebuffer不变。
       flags:控制函数行为的标志。目前不支持外部使用标志。
       crtc_x:从有源显示区域左侧显示平面的偏移量
       crtc_y:从有源显示区域顶部到显示平面的偏移量。
       crtc_w:显示输出矩形的宽度。
       crtc_h:显示输出矩形的高度
       src_x:从源framebuffer左侧的剪辑偏移量(Q16.16固定点)。
       src_y:来自源framebuffer顶部的剪辑偏移量(Q16.16固定点)。
       src_w:源矩形的宽度(Q16.16固定点)。
       src_h:源矩形的高度(Q16.16固定点)。
    ***/
  10. 完成显示后释放资源

    1
    2
    3
    4
    5
    modeset_destroy_fb(fd, &buf);
    drmModeFreeConnector(conn);
    drmModeFreePlaneResources(plane_res);
    drmModeFreeResources(res);
    close(fd);

3.property简述

property是atomic接口必须依赖的基本元素

  • property:

    • 把legacy接口传入的参数做成一个个独立的全局属性,通过设置这些属性参数,可以完成对显示参数的设置;
    • property由name、id、value三部分组成。其中id为该property在DRM框架中唯一的标识符;
    • property机制的好处:
      • 减少上层应用接口的维护工作量;添加新功能时无需增加新的函数和IOCTL,只需要在底层中新增一个property,然后在应用程序中获取/操作property即可;
      • 增加了参数设置的灵活性;一次IOCTL可以同时设置多个property,减少了usersapce与kernel的切换次数。
  • 常用的property

    • crtc相关
    name description
    ACTIVE crtc当前的使能状态,一般用于CRTC上下电
    MODE_ID crtc当前使用的display mode id,通过该id可以找到具体的display mode参数
    OUT_FENCE_PTR 输出fence指针,指向当前正在显示的buffer所对应的fence id,该fence由DRM驱动创建,供上层应用使用,用来表示当前buffer crtc是否还在占用
    • plane相关
    name description
    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 屏幕显示区域的高度
    IN_FORMATS 用于标识特殊的颜色存储格式,如AFBC、IFBC存储格式,该属性为只读
    rotation 当前图层的旋转角度
    zposition 当前图层在所有图层中的Z轴顺序
    alpha 当前图层的global alpha(非pixel alpha),用于多层合成
    pixel blend mode 当前图层的合成方式,如Pre-multiplied/Coverage等
    • connector相关
    name description
    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
    PATH DisplayPort专用的属性,主要用于Multi-Stream Transport (MST) 功能,即多路显示应用场景
    TILE 用于标识当前connector是否应用于多屏拼接场景,如平时随处可见的多屏拼接显示的广告大屏幕
  • property的类型

    • DRM_MODE_PROP_RANGE:range property会report它们可接受的最大值和最小值范围(unsigned value),KMS内核验证app设定的值是否符合这个范围。范围属性由drm_property_create_range()函数创建;
    • DRM_MODE_PROP_SIGNED_RANGE:range property会report它们可接受的最大值和最小值范围(signed value),KMS内核验证app设定的值是否符合这个范围。范围属性由drm_property_create_signed_range()函数创建;
    • DRM_MODE_PROP_ENUM:Enumerated properties(枚举属性)限定一个范围,从0到属性定义的值减1,每个值有一个字符串符号,应用程序可以通过检索值-名称列表,使用数值来获取或者设置属性实例的值。枚举属性由drm_property_create_enum()函数创建;
    • DRM_MODE_PROP_BITMASK:Bitmask properties(位掩码属性),其实就是将所有枚举值范围限定在0-63的枚举值属性,bitmask property的值将属性定义的一个或者多个枚举位组合表示,位掩码属性由drm_property_create_bitmask()函数创建;
    • DRM_MODE_PROP_OBJECT:object property与modeset_object联系,在atomic中广泛使用,通过链接&drm_frmaebuffer和&drm_plane,链接&drm_plane和&drm_crtc,链接&drm_crtc和&drm_connector创建display_pipeline,Object property由drm_property_create_object()函数创建;
    • DRM_MODE_PROP_BLOB:blob property存储一段没有格式限制的二进制blob,它的值表示它链接的blob obj的id,blob property由drm_property_create()函数使用DRM_MODE_PROP_BLOB类型参数创建,带blob数据的blob obj由drm_property_create_blob()创建;
    • DRM_MODE_PROP_ATOMIC:为atomic模式的属性设置,这些属性不会暴露给legacy 用户空间;
    • DRM_MODE_PROP_IMMUTABLE:为用户空间无法改变其值的属性设置,kernel可以改变这些属性的值,通过drm_object_property_set_value()函数;

4.atomic-crtc显示简单示例 (property)

利用libdrm提供的atomic接口,通过name来获取property,通过id操作property,通过value修改property

第1-6步与legacy plane应用程序中步骤相同

  1. 创建buffer

  2. 获取资源,ctrc、connector、encoder

  3. 获取plane资源

  4. 获取connector并且根据其值初始化buffer

  5. 根据buffer创建framebuffer

  6. 通过映射的内存地址画framebuffer

  7. 告知DRM驱动该应用程序支持atomic操作

    1
    drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
  8. 获取connector properties

    1
    2
    3
    struct drmModeObjectProperties *props = drmModeObjectGetProperties(fd, conn_id,	DRM_MODE_OBJECT_CONNECTOR);
    uint32_t property_crtc_id = get_property_id(fd, props, "CRTC_ID"); //与connector关联的crtc_id
    drmModeFreeObjectProperties(props); //释放props
  9. 获取crtc properties

    1
    2
    3
    4
    props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
    uint32_t property_active = get_property_id(fd, props, "ACTIVE"); //获取crtc active属性
    uint32_t property_mode_id = get_property_id(fd, props, "MODE_ID"); //获取crtc使用的display mode id
    drmModeFreeObjectProperties(props); //释放props
  10. 创建blob property储存现在的模式,并保存blob id

    1
    2
    uint32_t blob_id;
    drmModeCreatePropertyBlob(fd, &conn->modes[0], sizeof(conn->modes[0]), &blob_id);
  11. mode setting 替代从前的drmModeSetCrtc代码

    1
    2
    3
    4
    5
    6
    7
       struct drmModeAtomicReq *req;
    req = drmModeAtomicAlloc(); //创建atomic请求对象
    drmModeAtomicAddProperty(req, crtc_id, property_active, 1); //设置crtc的property_active值
    drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id); //设置crtc的property_mode_id值
    drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id); //设置connector的property_crtc_id值
    drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); //提交请求
    drmModeAtomicFree(req); //释放请求对象
  12. 从framebuffer(100,150)位置crop一个320x320的plane放到crtc上,显示plane

    1
    drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,50, 50, 320, 320, 100 << 16, 150 << 16, 320 << 16, 320 << 16);
  13. 释放资源

    1
    2
    3
    4
    5
       modeset_destroy_fb(fd, &buf);
    drmModeFreeConnector(conn);
    drmModeFreePlaneResources(plane_res);
    drmModeFreeResources(res);
    close(fd);
  • 第12步仍然使用的是legacy接口drmModeSetPlane(),现在使用atomic接口替换其功能
    • 12.1 获取plane属性
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      props = drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
      uint32_t property_crtc_id = get_property_id(fd, props, "CRTC_ID");
      uint32_t property_fb_id = get_property_id(fd, props, "FB_ID");
      uint32_t property_crtc_x = get_property_id(fd, props, "CRTC_X");
      uint32_t property_crtc_y = get_property_id(fd, props, "CRTC_Y");
      uint32_t property_crtc_w = get_property_id(fd, props, "CRTC_W");
      uint32_t property_crtc_h = get_property_id(fd, props, "CRTC_H");
      uint32_t property_src_x = get_property_id(fd, props, "SRC_X");
      uint32_t property_src_y = get_property_id(fd, props, "SRC_Y");
      uint32_t property_src_w = get_property_id(fd, props, "SRC_W");
      uint32_t property_src_h = get_property_id(fd, props, "SRC_H");
      drmModeFreeObjectProperties(props);
    • 12.2 atomic update plane
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
         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);

5.KMS驱动概述

  • DRM框架核心:

    DRM框架核心

    • 上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象;
    • 虚线以上的为 drm_mode_object,虚线以下为 drm_gem_object;
  • 各个objects之间的关系:
    objs结构

    • plane连接fb和crtc
    • encoder连接crtc和connector

最简单的KMS驱动代码分析(legacy)

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
192
193
194
195
196
197
198
199
#include "drm/drmP.h"
#include "drm/drm_gem_cma_helper.h"
#include "drm/drm_plane.h"
#include "drm/drm_plane_helper.h"
#include "drm/drm_crtc.h"
#include "drm/drm_crtc_helper.h"
#include "drm/drm_encoder.h"
#include "drm/drm_connector.h"
#include "drm/drm_probe_helper.h"
#include "drm/drm_mode_config.h"
#include "drm/drm_gem_framebuffer_helper.h"
static struct drm_device drm; //声明drm设备对象

/*声明drm_obj*/
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;

/*声明各个obj操作函数集接口*/
static struct drm_plane_funcs vkms_plane_funcs;
static struct drm_crtc_funcs vkms_crtc_funcs;
static struct drm_encoder_funcs vkms_encoder_funcs;
static struct drm_connector_funcs vkms_connector_funcs;


//mode setting函数集
struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_gem_fb_create, //使用gem helper函数 创建framebuffer object\
并绑定gem object,drm_gem_framebuffer_helper.h中声明
};

/*声明plane支持的fb formats*/
static uint32_t vkms_formats = {
DRM_FORMAT_XRGB8888,
};

//crtc funcs 中 page flip函数实现
int vkms_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, \
struct drm_pending_vblank_event *event, \
uint32_t flags, struct drm_modeset_acquire_ctx *ctx)
{
unsigned long flags;

crtc->primary->fb = fb;
if (event) //自旋等待vsync事件
{
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
}
return 0;
}

//crtc funcs接口
static struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_crtc_helper_set_config, //drmModeSetCrtc()会调用此接口,使用helper函数
.page_flip = vkms_page_flip, //drmModePageFlip()会调用此接口
.destroy = drm_crtc_cleanup, //crtc清理函数 crtc funcs中必须有destroy
};

//crtc helper funcs中dpms的实现 暂为空
void vkms_crtc_dpms(struct drm_crtc *crtc, int mode)
{

}

//crtc helper funcs中mode_set的实现 暂为空
int vkms_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode, int x, int y,
struct drm_framebuffer *old_fb)
{

}

//crtc helper funcs中mode_set的实现 暂为空
void vkms_crtc_prepare(struct drm_crtc *crtc)
{

}

//crtc helper funcs中commit的实现 暂为空
void vkms_crtc_commit(struct drm_crtc *crtc)
{

}

//crtc helper funcs接口
static struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.dpms = vkms_crtc_dpms, // crtc power levels control
.mode_set = vkms_crtc_mode_set, //This callback is used by the legacy CRTC helpers to set a new mode, \
position and framebuffer.
.prepare = vkms_crtc_prepare, //This callback should prepare the CRTC for a subsequent modeset, \
which in practice means the driver should disable the CRTC if it isrunning.
.commit = vkms_crtc_commit, //This callback should commit the new mode on the CRTC after a modeset,\
which in practice means the driver should enable the CRTC.
};

//plane funcs 接口
static struct drm_plane_funcs vkms_plane_funcs = drm_primary_helper_funcs; //drm_plane_helper.h中声明 drm_plane_helper.c中定义\
包括update_plane disable_plane destroy三个成员的实现\
update 在drmModeSetPlane()调用

//connector funcs 接口
static struct drm_connector_funcs vkms_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes, //drm_probe_helper.c中实现 由drmModeGetConnector()调用
.destroy = drm_connector_cleanup,
};

//connector helper funcs 中 get_modes实现
int vkms_connector_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector, 8192, 8192);
drm_set_preferred_mode(connector, 1024, 768);
return count;
}

//connector helper funcs 中 best_encoder实现
struct drm_encoder *vkms_connector_best_encoder(struct drm_connector *connector)
{
return &encoder;
}
//connector helper funcs 接口
static struct drm_connector_helper_funcs vkms_connector_helper_funcs = {
.get_modes = vkms_connector_get_modes, //Add modes to the connector that the panel is attached to and \
return the number of modes added.
.best_encoder = vkms_connector_best_encoder, //Encoder that should be used for the given connector and connector \
state, or NULL if no suitable encoder exists.
};


//encoder funcs接口
static struct drm_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};

/*初始化各个obj*/
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm); //初始化一些全局数据结构,standard properties在此处创建

drm.mode_config.max_width = 2048;
drm.mode_config.max_height = 2048;

drm.mode_config.funcs = &vkms_mode_funcs; //设置mode config函数集

/*创建drm_mode_objects*/
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs, vkms_formats, \
ARRAY_SIZE(vkms_formats), NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_crtc_helper_add(&crtc, &vkms_crtc_helper_funcs);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(&connector, &vkms_connector_helper_funcs);
drm_connector_attach_encoder(&connector, &encoder);
}


DEFINE_DRM_GEM_CMA_FOPS(drm_driver_fops); //drm_gem_cma_helper.h中已经帮忙定义好了\
drm driver的相关fops接口,drm_driver_fops为自定义的fops名
static struct drm_driver vkms_driver = { //drm driver
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022.10.27",
.major = 1,
.minor = 0,
.driver_features = DRIVER_MODESET | DRIVER_GEM, //驱动支持的操作:mode setting, gem
.fops = drm_driver_fops,
.dumb_create = drm_gem_cma_dumb_create, //用于创建gem object并分配物理buffer,直接使用cma helper函数
};


/**模块入口**/
static int __init vkms_init(void)
{
/****
* 初始化drm_dev设备;
* 将drm device和driver关联
* 此函数会使drm_device结构体下的driver成员变量指向drm_driver
*/
drm_dev_init(&drm, &vkms_driver, NULL);

vkms_modeset_init();

drm_dev_register(&drm, 0); //将drm dev注册进drm core
}

/*模块出口*/
static void __exit vkms_exit(void)
{

}

module_init(vkms_init);
module_exit(vkms_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hector");
  1. 定义drm设备对象和drm_mode_objects(crtc plane encoder connector);
  2. 创建驱动核心drm_driver,设置其相关information和features;
  3. 完成driver相关的fops,使用DEFINE_DRM_GEM_CMA_FOPS(drm_driver_fops);
  4. 完成mode_config_funcs;
  5. 完成各个object的drm_xxx_funcs和drm_xxx_helper_funcs
  6. drm_dec_init
  7. drm_mode_config_init,完成设备的modeconfig设置
  8. 用drm_xxx_funcs创建各个drm_mode_objects,利用drm_xxx_helper_add绑定各个drm_xxx_helper_funcs
  9. drm_dev_register

以上为legacy接口驱动,且为最简单的驱动实现,各个drm_xxx_funcs和drm_xxx_helper_funcs无法更精简

drm_xxx_funcs 是 drm ioctl 操作的最终入口,但是对于大多数 SoC 厂商来说,它们的 drm_xxx_funcs 操作流程基本相同,只是在寄存器配置上存在差异,因此开发者们将那些 common 的操作流程做成了 helper 函数,而将那些厂商差异化的代码放到了 drm_xxx_helper_funcs 中去,由 SoC 厂商自己实现。

  • legacy接口重点
    • xxx_funcs必须有,xxx_helper_funcs可以没有
    • drm_xxx_init必须有,drm_xxx_helper_add()取决于是否有xxx_helper_funcs
    • 只有当xxx_funcs采用DRM标准的helper函数实现时才有可能需要定义xxx_helper_funcs接口
    • xxx_funcs.destroy()接口必须实现

最简单的KMS驱动代码分析(atomic)

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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include "drm/drmP.h"
#include "drm/drm_gem_cma_helper.h"
#include "drm/drm_plane.h"
#include "drm/drm_plane_helper.h"
#include "drm/drm_crtc.h"
#include "drm/drm_crtc_helper.h"
#include "drm/drm_encoder.h"
#include "drm/drm_connector.h"
#include "drm/drm_probe_helper.h"
#include "drm/drm_mode_config.h"
#include "drm/drm_gem_framebuffer_helper.h"
#include "linux/hrtimer.h"
#include "drm/drm_atomic_helper.h"

static struct drm_device drm; //声明drm设备对象

/*声明drm_obj*/
static struct drm_plane primary;
static struct drm_crtc crtc;
static struct drm_encoder encoder;
static struct drm_connector connector;

/*声明各个obj操作函数集接口*/
static struct drm_plane_funcs vkms_plane_funcs;
static struct drm_crtc_funcs vkms_crtc_funcs;
static struct drm_encoder_funcs vkms_encoder_funcs;
static struct drm_connector_funcs vkms_connector_funcs;

//vblank依赖的高精度定时器
static struct hrtimer vblank_timer;

//定时器回调函数
enum hrtimer_restart vkms_vblank_timer_function(struct hrtimer *)
{
drm_crtc_handle_vblank(&crtc); //Drivers should call this routine in their vblank interrupt handlers to \
update the vblank counter and send any signals that may be pending.
hrtimer_forward_now(&vblank_hrtimer, 16666667); //重新设置定时器的触发时间,相当于重复使用定时器
return HRTIMER_RESTART;
}


//mode setting函数集
struct drm_mode_config_funcs vkms_mode_funcs = {
.fb_create = drm_gem_fb_create, //使用gem helper函数 创建framebuffer object\
并绑定gem object,drm_gem_framebuffer_helper.h中声明
.atomic_check = drm_atomic_helper_check, //检查硬件或者驱动是否支持atomic操作
.atomic_commit = drm_atomic_helper_commit, //This is the only hook to commit an atomic modeset update.
};

/*声明plane支持的fb formats*/
static uint32_t vkms_formats = {
DRM_FORMAT_XRGB8888,
};


//crtc funcs接口
static struct drm_crtc_funcs vkms_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, //Duplicated atomic state or NULL when the allocation failed
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, //Destroy a state duplicated with @atomic_duplicate_state and release \ or unreference all resources it references
.destroy = drm_crtc_cleanup,
};


//crtc helper funcs中atomic enable的实现
static void vkms_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
{
hrtimer_init(&vblank_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); //初始化定时器
vblank_timer.function = vkms_vblank_timer_function; //定时器回调函数
hrtimer_start(&vblank_timer, 16666667, HRTIMER_MODE_REL);
}

//crtc helper funcs中atomic disable的实现
static void vkms_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
{
hrtimer_cancel(&vblank_timer); //等待回调函数执行完取消定时器
}

//crtc helper funcs中atomic flush的实现
static void vkms_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_crtc_state *old_crtc_state)
{
unsigned long flags;

if (crtc->state->event) {
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);

crtc->state->event = NULL;
}
}

//crtc helper funcs接口
static struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
.atomic_enable = vkms_crtc_atomic_enable, //This callback should be used to enable the CRTC.
.atomic_disable = vkms_crtc_atomic_disable, //This callback should be used to disable the CRTC.
.atomic_flush = vkms_crtc_atomic_flush, //Drivers should finalize an atomic update of multiple planes on a CRTC in this hook.
};

//plane funcs 接口
static struct drm_plane_funcs vkms_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};

//plane helper funcs中的atomic_update实现 暂为空
static void vkms_plane_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state)
{

}

//plane helper funcs接口
static struct drm_plane_helper_funcs vkms_plane_helper_funcs = {
.atomic_update = vkms_plane_atomic_update, //Drivers should use this function to update the plane state.
};

//connector funcs 接口
static struct drm_connector_funcs vkms_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes, //drm_probe_helper.c中实现 由drmModeGetConnector()调用
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

//connector helper funcs 中 get_modes实现
int vkms_connector_get_modes(struct drm_connector *connector)
{
int count;
count = drm_add_modes_noedid(connector, 8192, 8192);
drm_set_preferred_mode(connector, 1024, 768);
return count;
}

//connector helper funcs 接口
static struct drm_connector_helper_funcs vkms_connector_helper_funcs = {
.get_modes = vkms_connector_get_modes, //Add modes to the connector that the panel is attached to and \
return the number of modes added.
};


//encoder funcs接口
static struct drm_encoder_funcs vkms_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};

/*初始化各个obj*/
static void vkms_modeset_init(void)
{
drm_mode_config_init(&drm); //初始化一些全局数据结构,standard properties在此处创建

drm.mode_config.max_width = 2048;
drm.mode_config.max_height = 2048;

drm.mode_config.funcs = &vkms_mode_funcs; //设置mode config函数集

/*创建drm_mode_objects*/
drm_universal_plane_init(&drm, &primary, 0, &vkms_plane_funcs, vkms_formats, \
ARRAY_SIZE(vkms_formats), NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
drm_plane_helper_add(&primary, &vkms_plane_helper_funcs);
drm_crtc_init_with_planes(&drm, &crtc, &primary, NULL, &vkms_crtc_funcs, NULL);
drm_crtc_helper_add(&crtc, &vkms_crtc_helper_funcs);
drm_encoder_init(&drm, &encoder, &vkms_encoder_funcs, DRM_MODE_ENCODER_VIRTUAL, NULL);
drm_connector_init(&drm, &connector, &vkms_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(&connector, &vkms_connector_helper_funcs);
drm_connector_attach_encoder(&connector, &encoder);
drm_mode_config_reset(&drm); //This functions calls all the crtc's, encoder's and connector's ->reset callback.
}


DEFINE_DRM_GEM_CMA_FOPS(drm_driver_fops); //drm_gem_cma_helper.h中已经帮忙定义好了\
drm driver的相关fops接口,drm_driver_fops为自定义的fops名
static struct drm_driver vkms_driver = { //drm driver
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022.10.27",
.major = 1,
.minor = 0,
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, //驱动支持的操作:mode setting, gem, atomic
.fops = drm_driver_fops,
.dumb_create = drm_gem_cma_dumb_create, //用于创建gem object并分配物理buffer,直接使用cma helper函数
};


/**模块入口**/
static int __init vkms_init(void)
{
/****
* 初始化drm_dev设备;
* 将drm device和driver关联
* 此函数会使drm_device结构体下的driver成员变量指向drm_driver
*/
drm_dev_init(&drm, &vkms_driver, NULL);

vkms_modeset_init();

drm_dev_register(&drm, 0); //将drm dev注册进drm core
}

/*模块出口*/
static void __exit vkms_exit(void)
{

}

module_init(vkms_init);
module_exit(vkms_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hector");
  • 与legacy框架相似,区别主要在于
    • 在drm_mode_Config中添加了atomic_check等操作函数
    • 在driver中添加了DRIVER_ATOMIC feature
    • 各个drm_mode_objects的funcs和helper funcs改为了atomic接口
    • 在创建完各个drm_mode_objests后使用drm_mode_config_reset
  • atomic重点在于
    • driver.feature添加DRIVER_ATOMIC标志
    • drm_mode_config_funcs.atomic_commit()接口是atomic操作的主要入口函数,必须实现
    • atomic操作依赖vsync,即vblank,因此需要hrtimer提供软件中断信号,驱动初始化时调用drm_vblank_init(),定时器回调函数中调用drm_handle_vblank
    • 在各个drm_mode_objs初始化完成后必须调用drm_mode_congfig_reset()来动态创建各个pipeline的软件状态,即drm_xxx_state
    • atomic的xxx_funcs必须实现以下接口,它们主要用于维护drm_xxx_state数据结构
      • reset()
      • atomic_duplicate_state()
      • atomic_destroy_state()
    • drm_plane_helper_funcs.atomic_update()必须实现

6.GEM驱动概述

前面的代码只实现了KMS相关驱动的框架,还需要为驱动添加GEM的功能

最简单的GEM驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DEFINE_DRM_GEM_CMA_FOPS(drm_driver_fops);   //drm_gem_cma_helper.h中已经帮忙定义好了\
drm driver的相关fops接口,drm_driver_fops为自定义的fops名
static struct drm_driver vkms_driver = { //drm driver
.name = "vkms",
.desc = "virtual kernel mode setting",
.date = "2022.10.27",
.major = 1,
.minor = 0,
.driver_features = DRIVER_GEM, //驱动支持的操作: gem
.fops = drm_driver_fops,
.dumb_create = drm_gem_cma_dumb_create, //用于创建gem object并分配物理buffer,直接使用cma helper函数
.gem_vm_ops = &drm_gem_cma_vm_ops,
.gem_free_ovject_unlocked = drm_gem_cma_free_object,
};
  • driver.feature = DRIVER_GEM,表明驱动支持GEM操作
  • driver.dumb_create : 分配dumb buffer 的或调接口,主要完成三件事
    • 创建gem object
    • 分配gem handle
    • 分配物理buffer
  • fops中有mmap函数,通常用drm_gem_cma_mmap()实现
  • gem_vm_ops:主要为mmap服务,必须实现

    其实driver中还有dumb_map_offset和dumb_destroy两个接口,分别对应各自的ioctl函数,如果驱动没有实现这两个回调接口, 那么 DRM 框架会使用默认的 drm_gem_dumb_map_offset() 和 drm_gem_dumb_destroy() 代替。


DRM应用及驱动浅析
http://example.com/2022/10/21/DRM综合/
作者
Hector
发布于
2022年10月21日
许可协议