DRM接口
目前DRM主要推荐使用的是Atomic(原子的)接口。Property(属性)是 Atomic 操作必须依赖的基本元素。所谓 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 | description |
ACTIVE | CRTC当前的使能状态,一般用于CRTC上下电 |
MODE_ID | CRTC当前所使用的display mode ID, 通过该ID可以找到具体的display mode配置 |
OUT_FENCE_PTR | 输出fence指针,指向当前正在显示的buffer所对应的fence id,该fence由DRM驱动创建,供上层应用程序使用,用来表示当前buffer CRTC是否还在占用 |
DEGAMMA_LUT | de-gamma查找表参数 |
DEGAMMA_LUT_SIZE | de-gamma查找表参数长度 |
CTM | Color Transformation Matrix, 颜色矩阵转换参数,3*3的矩阵 |
GAMMA_LUT | gamma查找表参数 |
GAMMA_LUT_SIZE | gamma查找表参数长度 |
- 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(MST),即多路显示应用场景 |
TILE | 用于标识当前connector是否应用于多屏拼接场景,如平时随处可见的多屏拼接显示的广告大屏幕 |
Property的类型如下所示:
Property 的类型分为如下几种:
- enum
- bitmask
- range
- signed range
- object
- blob
以上类型中需要着重介绍的是 object 和 blob 类型,其它类型看名字就知道什么意思,所以就不做介绍了。
- object
Object 类型的 property,它的值用 drm_mode_object ID来表示。目前的 DRM 架构中仅用到 2 个 Object Property,它们分别是 "FB_ID" 和 "CRTC_ID" ,它们的 property 值分别表示 framebuffer object ID 和 crtc object ID。
- blob
Blob 类型的 property,它的值用 blob object ID 来表示。所谓 blob,说白了就是一个自定义长度的内存块,用来存放自定义的结构体数据。典型的 Blob Property,如 "MODE_ID" ,它的值为 blob object ID,drm 驱动可以根据该 ID 找到对应的 drm_property_blob 结构体,该结构体中存放着 modeinfo 的相关信息。
下面介绍下如何操作这些property的atomic接口:
操作 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() 来发起真正的修改请求。
modesetting_drv中关于pageflip接口的调用
while (do_queue_flip_on_crtc(ms, crtc, flags, seq)) {
/* We may have failed because the event queue was full. Flush it
* and retry. If there was nothing to flush, then we failed for
* some other reason and should just return an error.
*/
if (ms_flush_drm_events(screen) <= 0) {
/* Aborting will also decrement flip_count and free(flip). */
ms_drm_abort_seq(scrn, seq);
return QUEUE_FLIP_DRM_FLUSH_FAILED;
}
/* We flushed some events, so try again. */
xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue retry\n");
}
/* The page flip succeeded. */
return QUEUE_FLIP_SUCCESS;
}
其中,do_queue_flip_on_crtc()最终会向内核提交flip请求,ms_flush_drm_events()最终调用
drmHandleEvent()监听上一次flip complete消息。
drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data)
{
modesettingPtr ms = modesettingPTR(crtc->scrn);
drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
int ret;
if (ms->atomic_modeset) {
drmModeAtomicReq *req = drmModeAtomicAlloc();
if (!req)
return 1;
ret = plane_add_props(req, crtc, fb_id, crtc->x, crtc->y);
flags |= DRM_MODE_ATOMIC_NONBLOCK;
if (ret == 0)
ret = drmModeAtomicCommit(ms->fd, req, flags, data);
drmModeAtomicFree(req);
return ret;
}
return drmModePageFlip(ms->fd, drmmode_crtc->mode_crtc->crtc_id,
fb_id, flags, data);
}
按照上文所述,主要关注plane_add_props(),代码如下所示:
static int
plane_add_props(drmModeAtomicReq *req, xf86CrtcPtr crtc,
uint32_t fb_id, int x, int y)
{
drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
int ret = 0;
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_FB_ID,
fb_id);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_CRTC_ID,
fb_id ? drmmode_crtc->mode_crtc->crtc_id : 0);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_SRC_X, x << 16);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_SRC_Y, y << 16);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_SRC_W,
crtc->mode.HDisplay << 16);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_SRC_H,
crtc->mode.VDisplay << 16);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_CRTC_X, 0);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_CRTC_Y, 0);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_CRTC_W,
crtc->mode.HDisplay);
ret |= plane_add_prop(req, drmmode_crtc, DRMMODE_PLANE_CRTC_H,
crtc->mode.VDisplay);
return ret;
}
static int
plane_add_prop(drmModeAtomicReq *req, drmmode_crtc_private_ptr drmmode_crtc,
enum drmmode_plane_property prop, uint64_t val)
{
drmmode_prop_info_ptr info = &drmmode_crtc->props_plane[prop];
int ret;
if (!info)
return -1;
ret = drmModeAtomicAddProperty(req, drmmode_crtc->plane_id,
info->prop_id, val);
return (ret <= 0) ? -1 : 0;
}
下面主要关注fb_id的获取:
int
drmmode_bo_import(drmmode_ptr drmmode, drmmode_bo *bo,
uint32_t *fb_id)
{
#ifdef GBM_BO_WITH_MODIFIERS
modesettingPtr ms = modesettingPTR(drmmode->scrn);
if (bo->gbm && ms->kms_has_modifiers &&
gbm_bo_get_modifier(bo->gbm) != DRM_FORMAT_MOD_INVALID) {
int num_fds;
num_fds = gbm_bo_get_plane_count(bo->gbm);
if (num_fds > 0) {
int i;
uint32_t format;
uint32_t handles[4];
uint32_t strides[4];
uint32_t offsets[4];
uint64_t modifiers[4];
memset(handles, 0, sizeof(handles));
memset(strides, 0, sizeof(strides));
memset(offsets, 0, sizeof(offsets));
memset(modifiers, 0, sizeof(modifiers));
format = gbm_bo_get_format(bo->gbm);
format = get_opaque_format(format);
for (i = 0; i < num_fds; i++) {
handles[i] = gbm_bo_get_handle_for_plane(bo->gbm, i).u32;
strides[i] = gbm_bo_get_stride_for_plane(bo->gbm, i);
offsets[i] = gbm_bo_get_offset(bo->gbm, i);
modifiers[i] = gbm_bo_get_modifier(bo->gbm);
}
return drmModeAddFB2WithModifiers(drmmode->fd, bo->width, bo->height,
format, handles, strides,
offsets, modifiers, fb_id,
DRM_MODE_FB_MODIFIERS);
}
}
#endif
return drmModeAddFB(drmmode->fd, bo->width, bo->height,
drmmode->scrn->depth, drmmode->kbpp,
drmmode_bo_get_pitch(bo),
drmmode_bo_get_handle(bo), fb_id);
}
主要使用drmModeAddFB2WIthModifiers()或者drmModeAddFB()来为显存创建并绑定framebuffer object,并获取该framebuffer的唯一标识:fb_id。