zl程序教程

您现在的位置是:首页 >  前端

当前栏目

Sunxi Framebuffer框架

2023-09-11 14:15:46 时间

Framebuffer的总体架构如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8atysPNM-1607837015684)(framebuffer学习.assets/image-20201117112323324.png)]

在V833 Tina SDK上,framebuffer的实现基于Display Engine的channel 2 的第 0 个图层.

在framebuffer架构的实现中,struct fb_info *info中的成员info->screen_base起者至关重要的作用,它即是framebuffer,也是DE中对应的图层,所对应的真实的内核虚拟地址,显示的最终目的,就是将这片的buffer在DE中生效.

info->screen_base的分配:

fb_read的实现,直接读取info->screen_base的内容:

fb_write的实现,直接写到screen_base中.

用户态怎么写这个图层呢?当然是通过mmap来实现:

可以看到,fb_mmap的本质仍然是将info->screen_base的地址映射到用户空间去.

所以,可以看到,通过内核,DE硬件以及用户态指针都可以访问到fb这个图层,framebuffer的工作原理就是通过修改完图层之后,在刷新率中断作用下,将framebuffer中的内容显示出来。

Framebuffer的申请大小:

从上面的fb_map_video_memory函数实现中可以看出,framebuffer 的大小保存在变量smem_len中,它的而初始化很有意思,按照ARGB8888像素格式计算,它的大小计算公式为:

xres_virtual * 4 * yres_virtual * 2

 重点是为何最后要乘以2,其实这是FrameBuffer实现的双Buffer机制(也叫乒乓buffer),为了防止画面被撕裂,原理如下:

为了避免画面撕裂的情况, 绝不应该在当前显示的画面上绘图,所以,为了满足这种需求,framebuffer在申请的时候,一共申请了两张,一张用于前景显示,另一张则作为后背可编辑画面,配合VSYNC(刷新帧率)中断,就可以避免撕裂的情况发生。

以本例中的640*480的屏为例,每张framebuffer 的大小是:

sizeof(framebuffer) = 640 * 480 * 4 = 0x‭12C000‬

两个framebuffer就是640 * 480 * 4 * 2 = 0x‭12C000‬ * 2 = 0x‭258000‬

所以就是如下图的情况:

调试调用堆栈观察buffer的使用情况:

开启设置buffer地址的打印LOG

LOG如下:

看起来有两个像是地址的东西0x42c000和0x300000,我们姑且先不去追究其来源,it is a long story,我们姑且认为它就是framebuffer对应的物理地址(实际上确实是).这里的理解是错误的,这两个地址不同,实际上由于要做旋转操作,0x42c000和0x300000是G2D分配的旋转buffer.而info->screen_base是用于做旋转中转,不不是最终的硬件图层。

layer:ch2, layer0, format=0, size=<480,640>, crop=<0,0,480,640>,frame=<480,640>, en=1 addr[0x42c000,0x0,0x0> alpha=<0,255>

layer:ch2, layer0, format=0, size=<480,640>, crop=<0,0,480,640>,frame=<480,640>, en=1 addr[0x300000,0x0,0x0> alpha=<0,255>

可以看到第一个framebuffer的地址是0x300000,第二个是0x42c000(忽略后面的两个0,0,其对用的是YUV图的U,V分量地址,ARGB没有UV分量)

可以看到0x42c000-0x300000 = 0x‭12C000‬,恰好符合一个framebuffer的大小,所以示意图如下:

 DS5调试现场:

这个和RTOS上播放视频时的调用堆栈如出一辙,都是通过set config接口将存储当前帧数据的物理地址设置给DE的对应图层,然后等待下次的VSYNC(刷新率)中断过来生效这个地址的当前帧。

地址的魔法:

上面提到的两个地址0x42c000和0x300000怎么看也不像虚拟地址,我们找到它的虚拟地址,实际上,虚拟地址存储在info->screen_base中

图中有两个指针指向framebuffer,一个是screen_buffer,另一个是screen_base,可是,寻遍代码也只找到对screen_base赋值的地方,没有找到对screen_buffer赋值的地方,为何两个同时改变呢?原因是他们竟然是一个联合体,具有共同的地址:

猜的没错的话,0xD1001000即是framebuffer对应的内核虚拟地址,而0x300000是其对应的物理地址

怎么证明我们的猜想呢?使用HACK MMU的方法!

我们将MMU关闭,让物理地址显露真身,如果他们两个地址的内容完全一样,就能说明他们是同一个地址了。

试验失败!!!因为使用了IOMMU.

设置图层物理地址的地方:

de_rtmx_set_lay_laddr,可以看到,和上面的调用是同一个堆栈.

实际上,代码中得到上述两个物理地址的地方在这里

终于找到了config.info.fb.addr[0]的初始化地方了,令人意外的是,它竟然是在G2D的调用中:

 关于pan_display时候乒乓buffer的切换,找到了这个点,在fb_g2d_rot_apply函数中

 一下是LOG输出,可以看到fb addr整个过程中不会发生变化,而地址会在ODD/EVEN中不断地切换。

我们终于从上面地LOG中,找到了0x30000和0x42c000地来历地线索. 

地址地分配和大小:

下面是fb_g2d_rot_create接口中一小段实现,它创建了两个framebuffer作为乒乓buffer,分配的地址即使物理地址。

添加LOG:

 得到的LOG如下:

rotate操作定义

根据以上LOG,得到一下几个重要信息:

  1. 由于使用的是竖屏,所以画面投到framebuffer要做rotate 270度的操作.
  2. rotate操作是通过G2D完成的,具体的讲是通过调用g2d的API g2d_blit_h完成的。
  3. 源地址来源于上面讲的info->screen_base/info->screen_buffer对应的物理地址,目的物理地址则是上面给G2D分配的target buffer, 也就是最终用于显示的图层0x30000和0x42c000.
  4. pan buffer的乒乓实现是通过yoffset的偏移来计算的,比如第一次yoffset=0,下一次则为480,在下次又是0.
  5. smem_start为0并不奇怪,0是有效地址,否则无法解释这里log中用laddr=0 作为G2D的SRC地址,更进一步,出现物理地址为0很可能是因为使用IOMMU的缘故,由于DEMO起来后,framebuffer很可能是使用IOMMU的第一个IP,所以0地址分配隔了smem_start。

追本溯源,这个rotate和旋转角度从何而来呢?

从以上截图可以看到,disp_rotation_used和degree%d两个变量均来自于外部配置文件,在tina中,所有的配置参数最终都来源于devicetree,我们在devictree中看一下是否能找到两个变量:

在tina-v83x/out/v833-perf1/image/.sunxi.dts 中,我们找到了反编译后的devicetree文本文件,可以看到,这里明显给出了关于旋转参数的配置数据.

加上旋转后,整个过程图解如下:

所以,包括旋转用到的两个framebuffer, 实际上分配了四张buffer.

fbtest用例:

tina 集成了fbtest工具用来对/dev/fb0节点进行基本功能的验证,正确配置后fbtest工具会被打包进rootfs中:

编译后烧录,控制台执行命令fbtest,即可在LCD屏上看到测试效果。

总结:

1./dev/fbX节点对应某片叫做framebuffer的内存空间,这片内存空间同时对应到DE 多个UI图层中的某一个图层。

2.DE的作用是将各个图层送来的显示数据进行混合后送给TCON去显示,TCON驱动外设接口,比如HDMI,CVBS等将混合好的图层输出到屏幕上。


结束!