Linux图形栈之Framebuffer驱动
概述
开始记录工作学习中点滴知识,这是一个记录,也是总结。言归正传,Linux图形栈是一个非常庞大复杂的系统,然而今天我们就从framebuffer驱动开始抛砖,窥得其冰山一角。framebuffer就是帧缓存,也就是说开发者希望将图像写入帧缓存,就能看到显示设备上的图像,就是这么简单,而不必关心系统与底层显示设备的操作。即使在DRI(Direct Render Infrastructure)中也保留了framebuffer,特别对于嵌入式设备显示,framebuffer十分重要。
Framebuffer 框架简述
在linux系统中,framebuffer驱动框架已经实现(这里就不具体分析内核中框架了),所以写framebuffer驱动只需要按照系统接口写即可。framebuffer驱动最重要的是把GPU显存给映射到内核空间或者初始化LCD控制器。等到生成设备节点/dev/fbx,app就可以操作显存了,框架如下图所示:
在x86上写个Framebuffer驱动
既然有了思路,我们就开始在x86上具体写个驱动,然后再写个测试app显示图像。环境是ubuntu18.04,硬件是x86。在现代系统中,系统启动开始使用efi固件,所以在引导的过程中efi也会驱动显示器,并会保存屏幕的信息到screen_info中。
数据结构分析
先分析下数据结构,其中最重要的是fb_info,最后注册这个结构体生成fb设备,fb_var_screeninfo 和 fb_fix_screeninfo的值都可以从screen_info中获取。fb_ops 就是设备操作的函数,其中有一些由fb框架实现,自己实现的是下图中的两个函数。数据结构不复杂,但是很多都涉及和显示屏幕的一些参数,写该驱动前,最好先知道显示屏基本参数。
驱动结构分析
首先将平台设备驱动注册,在efi启动中会有efi-framebuffer设备,一旦注册,设备名匹配,则会调用myfb_probe函数。
static struct platform_driver myfb_driver = {
.driver = {
.name = "efi-framebuffer",
},
.probe = myfb_probe,
.remove = myfb_remove,
};
builtin_platform_driver(myfb_driver);
在probe函数中,简单介绍下,获取显卡显存的地址将其映射到内核空间中,接着获取屏幕信息。具体可以看代码。
myfb_fix.smem_start = screen_info.lfb_base;
调试信息
在这里编译的是4.15.0的源码,将该驱动放入drivers/video/fbdev中,修改Makefile。注意在编译中需要把DRI关掉,以及选中framebuffer support。然后选中自己的驱动,关掉其他fb驱动。如下图,在内核启动中可以看到已经加载了自己的fb驱动了,并且能在系统中看到/dev/fb0,至此驱动编写成功。
Apr 24 08:51:06 bzm kernel: [ 4.103360] myfb_probe
Apr 24 08:51:06 bzm kernel: [ 4.103368] myfb: framebuffer at 0xe0000000, using 8100k, total 8100k
Apr 24 08:51:06 bzm kernel: [ 4.103369] myfb: mode is 1920x108032, linelength=7680, pages=1
Apr 24 08:51:06 bzm kernel: [ 4.103369] myfb: scrolling: redraw
Apr 24 08:51:06 bzm kernel: [ 4.103370] myfb: Truecolor: size=8:8:8:8, shift=24:16:8:0
Apr 24 08:51:06 bzm kernel: [ 4.103441] Console: switching to colour frame buffer device 240x67
Apr 24 08:51:06 bzm kernel: [ 4.103461] fb0: EFI VGA frame buffer device
写个app测试
最后当然要测试下该驱动能否正常工作。编写测试app,写了一个colorbar,代码如下:
void draw_colorbar(int x, int y, unsigned char *p)
{
int i, j, a, b;
int width;
width = x / 8;
for (i = 0; i < y; i++) {
for (j = 0; j < x; j++) {
a = j / width;
b = j % width;
// printf("a = %d\n", a);
switch (a) {
case 0:
p[(a*width+b)*4 + 0 + i*x*4] = 255;
p[(a*width+b)*4 + 1 + i*x*4] = 255;
p[(a*width+b)*4 + 2 + i*x*4] = 255;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 1:
p[(a*width+b)*4 + 0 + i*x*4] = 255;
p[(a*width+b)*4 + 1 + i*x*4] = 255;
p[(a*width+b)*4 + 2 + i*x*4] = 0;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 2:
p[(a*width+b)*4 + 0 + i*x*4] = 0;
p[(a*width+b)*4 + 1 + i*x*4] = 255;
p[(a*width+b)*4 + 2 + i*x*4] = 255;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 3:
p[(a*width+b)*4 + 0 + i*x*4] = 0;
p[(a*width+b)*4 + 1 + i*x*4] = 255;
p[(a*width+b)*4 + 2 + i*x*4] = 0;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 4:
p[(a*width+b)*4 + 0 + i*x*4] = 255;
p[(a*width+b)*4 + 1 + i*x*4] = 0;
p[(a*width+b)*4 + 2 + i*x*4] = 255;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 5:
p[(a*width+b)*4 + 0 + i*x*4] = 255;
p[(a*width+b)*4 + 1 + i*x*4] = 0;
p[(a*width+b)*4 + 2 + i*x*4] = 0;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 6:
p[(a*width+b)*4 + 0 + i*x*4] = 0;
p[(a*width+b)*4 + 1 + i*x*4] = 0;
p[(a*width+b)*4 + 2 + i*x*4] = 255;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
case 7:
p[(a*width+b)*4 + 0 + i*x*4] = 0;
p[(a*width+b)*4 + 1 + i*x*4] = 0;
p[(a*width+b)*4 + 2 + i*x*4] = 0;
p[(a*width+b)*4 + 3 + i*x*4] = 255;
break;
}
}
}
}
可以看到屏幕被分成8个竖条纹。
最后提供代码欢迎大家一起学习分享。
myfb驱动.