转自:
http://manpages.ubuntu.com/manpages/utopic/man7/drm-kms.7.html
根据自己的理解来转述一下:
摘要:
DRM 是linux 下的图形渲染架构(Direct Render Manager) , 具体的说是显卡驱动的一种架构(驱动如何玩? 把功能封装成 open/close/ioctl 等标准接口,应用程序调用这些接口来驱动设备)。
作为显卡,最基本的功能就是把用户的绘图输出到显示屏上,DRM 如何去实现呢,先看看DRM 把“这件事”给你概括的几个基本要素:
画布(FrameBuffer) , 绘图现场(CRTC) , 输出转换器(Encoder) , 连接器(Connector) , 然后就到显示屏了
1 画布( FrameBuffer )
对计算机来说,FrameBuffer 就是一块驱动和应用层都能访问的内存,当然画图之前要有一定的格式化,比方说我可以规定什么样的色彩模式(RGB24 , I420 , YUUV 等等), 分辨率是多大,还有啥参数,那就要到绘图现场去看了 :p
2 绘图现场(CRTC)
简写翻译过来是阴级摄像管上下文,在DRM 里 CRTC 就表示显示输出的上下文了,首先 CRTC 内指一个 FrameBuffer 地址, 外连一个Encoder。 它们俩之间如何沟通? 这就是显示模式(ModeSet)要做的事情,ModeSet 包括了像前面提到的色彩模式 , 还有说显示的时序(timings , ModeLines 等都代表了这个意西)等, 通常时序可以按以下来表达
PCLK HFP HBP HSW X_RES VFP VBP VSW Y_RES
像素时钟 水平前回扫 水平后回扫 水平同步头 水平有效长度 垂直前回扫 垂直后回扫 垂直同步头 垂直有效长度
一个CRTC 可以连接多个 Encoder , 干啥用,实现复制屏幕功能。
3 输出转换器(Encoder )
想想 CRT 这种土疙瘩就够复杂了,我们的显卡很牛奔的可以连接各种不同的设备,显然输出需要不同的信号转换器,将内存的像素转换成显示器需要的信号(DVID , VGA , YPbPr , CVBS 等等……)
4 连接器 (Connector )
不是指物理线,回到DRM 这是一个抽象的数据结构 ,代表连接的显示设备,从这里我们可以得到设备的EDID , DPMS 连接状态等.
5 显示面(Planner)
咦,怎么多出来一个。我也很呐闷,以上的东东不够地干活? 其实很多创新往往源于人对现实界的不满足。你又要看文字学习,又要看电影打游戏, 还有厉害的可以一边聊天一边看电影。 这里对立出来两个概念,像文字交互这种小范围更新的Graphics 模式,和全幅更新速度奇快的 Video 模式,这两种模式将显卡的使用拉上了两个极端。
于是 Planner 的概念就发挥了很好的作用,它给视频刷新提供了一条绿色通道,偶不和图形搞在一起了,偶是一个新的图层(或overlay),可以叠加在Graphic之上或之下,偶还可以缩放…
文档上说 Planner 也在 FrameBuffer 上,这个没关系,这里我们看出来 CRTC 里要显示的东东应该是一种组合(blending)了。
看懂了概念,下一篇来分析具体的数据结构和接口。
参考文档:
http://manpages.ubuntu.com/manpages/utopic/man7/drm-kms.7.html
http://events.linuxfoundation.org/sites/events/files/lcjpcojp13_pinchart.pdf
http://landley.net/kdocs/htmldocs/drm.html
http://events.linuxfoundation.org/sites/events/files/slides/brezillon-drm-kms.pdf
http://elinux.org/images/7/71/Elce11_dae.pdf
一 上一篇介绍了 linux 的显示驱动drm 的架构,在这里按一定顺序回顾一下:
1 我把显示器连到显卡的DVI输出口, 这个连接抽象成 Connector
2 在 DVI 的 Connector 上驱动会分配 DVI 信号的 Encoder , 如果没分配, connector 资源上会找到 所有可用的 encoders
3 encoder 是为图像扫描现场 crtc 服务的, 驱动可能会给encoder 分配crtc , 或者从 encoder 的 possible_crtc 上能找到可用的
4 crtc 扫描现场要配置显示图像的物理内存区 fb
5 fb -> crtc -> encoder - > connector 这种关系绑定之后,绘图工作已经开始, 你可以在fb 上任意写画,然后立马得到显示!
6 然而为了避免图像撕裂,可以建立多 fb (缓冲) 通过 pageFlip 操作来刷新画图。
7 当然还有专为video 刷新用的plane , plane 也要绑定到 crtc 才能工作。
二 总结 + drm api 的使用:
api 使用参考 David Herrmann
drm api 核心配置就是要绑定一个 crtc 的关系 fb -> crtc -> encoder - > connector
我们来看“一”的回顾,咋是按照逆向的顺序?哈哈其实这样才顺理成章, api 如何用,往下看:
1 首先要打开drm 驱动模块,然后获取所有的资源
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
drmModeRes res = drmModeGetResources(fd);
res 里有啥, res 里告诉了有几个connector , 几个 encoder , 几个 crtc 等以及他们的id , 完全不成套!
2 从 connector 开始, 顺藤摸瓜
先获取 connecotr 的具体资源
drmModeConnector * conn = drmModeGetConnector(fd, res->connectors[i]);
conn资源里重要的有两部分,一部分是通过线缆读出来显示器的 "modes " 如 1920x1080@60 等, 当然你要选一个最喜欢的
另一部分是encoder 按照一章节的2顺序开始找 encoder (看 drmModeConnector 的定义) :p
3 给encoder 寻找合适的 crtc
按照一章节的 3 顺序找 crtc
4 为 crtc 创建fb
drm 的例子 modetest 写的很详细, ARGB 可以直接 drmModeAddFB , 多平面的fb 可以用 drmModeAddFB2
5 绑定
核心四元组 < fb , crtc , conn , mode >
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
uint32_t x, uint32_t y, uint32_t *connectors, int count,
drmModeModeInfoPtr mode);
6 pageFlip
extern int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id,
uint32_t flags, void *user_data);
这个的玩法得好好记下,
首先 poll (drm_fd ) 可以收到 drm 的 POLLIN 消息, 消息里面无非两种一种是 VBLANK , 一种是 pageFlip complete
其次 收到 消息后必须要 调用drmHandleEvent(drm_fd , &evctx); 来处理消息 ,记得必须把你的 pageFlip 处理函数填到evctx里,
pageFlip 处理函数里干啥呢, pageFlip 一帧结束了,当然要 pageFlip 下一帧!
7 plane
plane 的玩法没搞明白,尝试了下,使用多缓冲 调 drmModeSetPlane 来换页可以实现视频的播放,但遇到两个问题:
其一是 SetPlane 的时机, 如何等待一个显示器的场同步? 看了 vblank 的code 感觉 vblank 是针对一个显卡的同步,但我有多个显示器呢?
其二是SetPlane 的运行耗时,我的 i3 cpu 执行一次用了 22 ~ 23 ms , 莫名其妙。
本想着plane 的yuv 通道可以节省资源,有这两大问题在就没法办了, pageFlip 的 fb 只能跟显示器相同颜色空间,并且通常是 ARGB !!
---------------------
作者:walletiger
来源:CSDN
原文:https://blog.csdn.net/walletiger/article/details/46596399
2014年04月13日 20:35:24 Libresoft 阅读数:3725 标签: linux kernel mode setting drm
1、数据及数据结构:
Connector:代表显卡上的插口,有几个Connector表示会有几个输出。
CRTC:crt controller,负责将帧缓存中的数据传送到Connector,数据在传送到Connector之前会经过Encoder。 帧缓存 --> CRTC --> Encoder --> Connector --> 显示器。
FrameBuffer:在这里帧缓存并不是指的显存上的某一块区域,而是Linux DRM抽象出来的一个概念,用fb_id表示。
drm_mode_create_dumb:drmIoctl的参数,意思是请求内核创建缓存。 这个才是真正的内存块,可以使用mmap映射到程序虚拟内存中。Framebuffer的创建中必须指定一个缓存的id。
2、函数:
drmModeSetCrtc:最核心的函数之一,它负责将建立从帧缓存到Connector的关联。只有调用它,显示器才能显示缓存中的数据。
drmModePageFlip:直译就是翻页,笔者的理解是 drm_mode_create_dumb类型的缓存其实是双缓存,只有调用此函数之后,crtc才能将之前写入的数据传送给显示器。
3、代码:
找到处于连接状态的Connector
drmModeConnector* FindConnector(int fd)
{
drmModeRes *resources = drmModeGetResources(fd); //drmModeRes描述了计算机所有的显卡信息:connector,encoder,crtc,modes等。
if (!resources)
{
return NULL;
}
drmModeConnector* conn = NULL;
int i = 0;
for (i = 0; i < resources->count_connectors; i++)
{
conn = drmModeGetConnector(fd, resources->connectors[i]);
if (conn != NULL)
{
//找到处于连接状态的Connector。
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0 && conn == NULL)
{
break;
}
else
{
drmModeFreeConnector(conn);
}
}
}
drmModeFreeResources(resources);
return conn;
}
查找与Connector匹配的Crtc
int FindCrtc(int fd, drmModeConnector *conn)
{
drmModeRes *resources = drmModeGetResources(fd);
if (!resources)
{
fprintf(stderr, "drmModeGetResources failed\n");
return -1;
}
unsigned int i, j;
for (i = 0; i < conn->count_encoders; ++i)
{
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]);
if (NULL != enc)
{
for (j = 0; j < resources->count_crtcs; ++j)
{
// connector下连接若干encoder,每个encoder支持若干crtc,possible_crtcs的某一位为1代表相应次序(不是id哦)的crtc可用。
if ((enc->possible_crtcs & (1 << j)))
{
int id = resources->crtcs[j];
drmModeFreeEncoder(enc);
drmModeFreeResources(resources);
return id;
}
}
drmModeFreeEncoder(enc);
}
}
drmModeFreeResources(resources);
return -1;
}
绘制一张全色的图:
void SetColor(unsigned char *dest, int stride, int w, int h)
{
struct color {
unsigned r, g, b;
};
struct color ccs[] = {
{ 255, 0, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 255, 255, 0 },
{ 0, 255, 255 },
{ 255, 0, 255 }
};
static int i = 0;
unsigned int j, k, off;
unsigned int r = 255;
unsigned int g = 1;
unsigned int b = 1;
for (j = 0; j < h; ++j)
{
for (k = 0; k < w; ++k)
{
off = stride * j + k * 4;
*(uint32_t*)&(dest[off]) = (ccs[i].r << 16) | (ccs[i].g << 8) | ccs[i].b;
}
}
i++;
printf("draw picture\n");
}
主函数:
#define _FILE_OFFSET_BITS 64
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int ret, fd;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (fd < 0)
{
/* Probably permissions error */
fprintf(stderr, "couldn't open %s, skipping\n", "");
return -1;
}
drmSetMaster(fd);
drmModeConnectorPtr connector = FindConnector(fd);
int width = connector->modes[0].hdisplay;
int height = connector->modes[0].vdisplay;
printf("display is %d*%d.\n", width, height);
int crtcid = FindCrtc(fd, connector);
struct drm_mode_create_dumb creq;
memset(&creq, 0, sizeof(creq));
creq.width = width;
creq.height = height;
creq.bpp = 32;
creq.flags = 0;
ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
if (ret)
{
printf("create dumb failed!\n");
}
uint32_t framebuffer = -1;
uint32_t stride = creq.pitch;
//使用缓存的handel创建一个FB,返回fb的id:framebuffer。
ret = drmModeAddFB(fd, width, height, 24, 32, creq.pitch, creq.handle, &framebuffer);
if (ret)
{
printf("failed to create fb\n");
return -1;
}
struct drm_mode_map_dumb mreq; //请求映射缓存到内存。
mreq.handle = creq.handle;
ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
if (ret)
{
printf("map dumb failed!\n");
}
// 猜测:创建的缓存位于显存上,在使用之前先使用drm_mode_map_dumb将其映射到内存空间。
// 但是映射后缓存位于内核内存空间,还需要一次mmap才能被程序使用。
unsigned char* buf = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);
if (buf == MAP_FAILED)
{
printf("mmap failed!\n");
}
memset(buf, 255, creq.size);
//一切准备完毕,只差连接在一起了!
ret = drmModeSetCrtc(fd, crtcid, framebuffer, 0, 0, &connector->connector_id, 1, connector->modes);
if (ret)
{
fprintf(stderr, "failed to set mode: %m\n");
return -1;
}
int cc = 0;
while (cc < 5)
{
SetColor(buf, stride, width, height);
drmModePageFlip(fd, crtcid, framebuffer, DRM_MODE_PAGE_FLIP_EVENT, 0);
cc++;
sleep(2);
}
printf("over\n");
getchar();
close(fd);
exit(0);
return ret;
}