一、前言
在本周的工作中为解决客户问题,查看awtk-linux-fb中的源码,其中对于里面关于DRM的内容很感兴趣,请教同事后又上网查了资料,本文对DRM的学习做了总结记录,并以C语言练习了DRM的使用。在此感谢回答我疑问的同事。
注:awtk-linux-fb是AWTK
针对arm-linux平台的移植,github仓库:https://github.com/zlgopen/awtk-linux-fb。
二、什么是DRM
DRM( Direct Rendering Manager)即直接渲染管理器。它是为了解决多个程序对 Video Card 资源的协同使用问题而产生的。它向用户空间提供了一组 API,用以访问操纵 GPU。
简单理解,DRM是Linux下的图形渲染架构,用来管理显示输出和分配buffer。应用程序可以直接操纵 DRM的 ioctl 或者是用 framebuffer 提供的接口进行显示相关操作。后来封装成了 libdrm 库,让用户可以更加方便的进行显示控制。
三、DRM包含的基本概念
要弄明白 DRM 是怎么把用户的绘图输出到显示屏上,绕不开以下几个概念,具体关系如下图所示:
- Framebuffer
- CRTC
- Encoder
- Connector
- Display Device(LCD)
3.1 DRM Framebuffer
它是一块内存区域,可以理解为一块画布,驱动和应用层都能访问它。绘制前需要将它格式化,设定绘制的色彩模式(例如RGB24,YUV 等)和画布的大小(分辨率)。
3.2 CRTC
阴极摄像管上下文。这个看名字很很难懂,但简单的来说他就是显示输出的上下文,可以理解为扫描仪。CRTC对内连接 Framebuffer 地址,对外连接 Encoder,会扫描 Framebuffer 上的内容,叠加上 Planes 的内容,最后传给Encoder。
3.3 Planes
平面。它和 Framebuffer 一样是内存地址。它的作用是干什么呢?打个比方,在电脑上,一边打字聊微信一边看电影,这里对立出来两个概念,打字是文字交互,是小范围更新的 Graphics 模式;看电影是全幅高速更新的 Video 模式,这两种模式将显卡的使用拉上了两个极端。
这时Planes就发挥了很好的作用,它给 Video 刷新提供了高速通道,使 Video 单独为一个图层,可以叠加在 Graphic 上或之下,并具有缩放等功能。
Planes 是可以有多个的,相当于图层叠加,因此扫描仪(CRTC)扫描的图像实际上往往是 Framebuffer 和 Planes 的组合(Blending)。
3.4 Encoder
编码器。它的作用就是将内存的 pixel 像素编码(转换)为显示器所需要的信号。简单理解就是,如果需要将画面显示到不同的设备(Display Device)上,需要将画面转化为不同的电信号,例如 DVID、VGA、YPbPr、CVBS、Mipi、eDP 等。
Encoder 和 CRTC 之间的交互就是我们所说的 ModeSetting,其中包含了前面提到的色彩模式、还有时序(Timing)等。
3.5 Connector
连接器。它常常对应于物理连接器 (例如 VGA, DVI, FPD-Link, HDMI, DisplayPort, S-Video等) ,它不是指物理线,在 DRM中,Connector 是一个抽象的数据结构,代表连接的显示设备,从Connector中可以得到当前物理连接的输出设备相关的信息 ,例如,连接状态,EDID数据,DPMS状态、支持的视频模式等。
四、DRM使用示例(C语言)
根据上文,可以知道 DRM 是一个显示驱动框架,也就是把功能封装成 open/close/ioctl 等标准接口,应用程序调用这些接口来驱动设备,显示数据。本文接下来将从使用者的角度来看,验证和使用DRM驱动。
DRM设备节点 :DRM 驱动会在/ dev/dri 下创建3个设备节点:
card0
controlD64
renderD128
libdrm库 :DRM驱动,对用户空间,提供了专门的的调用库libdrm.so,用户空间通过该库可以间接的调用和使用驱动。
4.1 打开设备
/* 打开设备有专门的接口:drmOpen ,但此处为方便,使用open函数 */
int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
if (fd < 0) {
ret = -errno;
fprintf(stderr, "cannot open '%s': %m\n", node);
return ret;
}
4.2 检查DRM的能力
DRM的能力通过drmGetCap接口获取,用drm_get_cap结构描述:
/** DRM_IOCTL_GET_CAP ioctl argument type */
struct drm_get_cap {
__u64 capability;
__u64 value;
};
int drmGetCap(int fd, uint64_t capability, uint64_t *value)
{
struct drm_get_cap cap;
int ret;
memclear(cap);
cap.capability = capability;
ret = drmIoctl(fd, DRM_IOCTL_GET_CAP, &cap);
if (ret)
return ret;
*value = cap.value;
return 0;
}
使用示例:
uint64_t has_dumb;
if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb) {
fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
node);
close(fd);
return -EOPNOTSUPP;
}
4.3 检索Resource
获取Resource具体看以下函数:
drmModeResPtr drmModeGetResources(int fd)
Resource结构封装:
struct drm_mode_card_res {
__u64 fb_id_ptr;
__u64 crtc_id_ptr;
__u64 connector_id_ptr;
__u64 encoder_id_ptr;
__u32 count_fbs;
__u32 count_crtcs;
__u32 count_connectors;
__u32 count_encoders;</