STi7167 GAL 开发笔记
更新日期:
以前没弄过和板子相关,包里的howto文档不太具体,所以这次把一些基本的步骤也记下来。
硬件环境搭建
STi7167开发板、HDMI接口线、usb转com口线、网线、1080p显示器、开发宿主机。用接口线这这些设备连接起来,注意宿主机和开发版都要接入同一个局域网。
软件环境搭建
开发环境使用 minicom 连接开发板,通过uboot使用nfs挂载宿主机的根文件系统启动。板子上跑的是 STLinux,图像驱动库是 DirectFB 或者是 ST 自己的 ioctl。DirectFB 是经过封装的,稍微上层点的。其实 DirectFB 的加速插件也就是使用 ST 的 ioctl 的。
安装STLinux
先在宿主机上搭建nfs服务。nfs-client, nfs-common, nfs-kernel-server, nfs-server(用 sudo apt-get install 安装)。然后修改 /etc/exports 设置nfs目录(这里设置的是 /home 为nfs目录。然后 /home/nfs 目录增加响应权限。):
/home/ *(rw,sync,no_subtree_check,anonuid=1007,anongid=125) /home/nfs *(rw,sync,no_subtree_check,no_root_squash,anonuid=1007,anongid=125)
解压 STM.tar.gz(要sudo解压,其中要创建一些链接节点)。将 /STLinux-2.3/devkit/sh4/target 文件夹放到 /home/mingming/nfs 下。要把target文件夹的权限改一下。
在宿主机启动 minicom(配置文件在用户目录/.minirc.dlf)。ctrl+A+Z改下配置。查看下/dev/下的usb节点是哪个(一般第一是ttyUSB0),修改串口端口为相应的usb端口(例如ttyUSB0)。关掉硬件、软件流控制。打开 linewrap 功能(因为后面的配置命令很长)。
上电启动板子,在 minicom 的终端按任意键进入配置命令行。根据实际情况修改howto文档里给出的配置命令:
setenv bootargs console=ttyAS0,115200 ip=192.168.1.233:192.168.1.101:192.168.1.1:255.255.0.0::eth0:off nwhwconf=device:eth0,hwaddr:00:FA:E0:FA:E0:00 rw root=/dev/nfs nfsroot=/home/mingming/nfs/target,nfsvers=2,tcp bigphysarea=2000
这里宿主机的ip是 192.168.1.101,板子的ip是 192.168.1.233,网关为 192.168.1.1,子网掩码为 255.255.0.0。然后saveenv,reset重启,之后就可以正常启动了。在根文件系统中的 /root/ 下有个 load_fb.sh 的脚本,可以通过修改里面的参数设置 framebuffer 的颜色格式(FULLHD 那里,format0 可以改成 RGB565,就可以改掉逐点 alpha 的颜色格式,一般就用这2种颜色格式):
# Resolution for main output # -------------------------- if [ $# -eq 0 ] || [ $1 = "PAL" ]; then mode0=720x576-50i mem0=4 format0=ARGB1555 ratio0="4TO3" else if [ $1 = "HD" ]; then mode0=1280x720-50 mem0=7 format0=ARGB1555 ratio0="4TO3" else if [ $1 = "FULLHD" ]; then mode0=1920x1080-50i mem0=10 format0=ARGB1555 ratio0="4TO3" fi fi fi
DirectFB 测试例子
启动STLinux后,用root登录。在/bin目录下,ln -s telnetd busybox,用busybox链接出telneted。然后用telneted命令启动telnetd服务。之后就可以按照howto文档里说的加载模块,启动DirectFB,在宿主机的另外一个终端用telnet登录到板子上,然后运行 df_dok 的测试例子。
验证交叉工具链
宿主机终端命令: export PATH=/opt/STM/STLinux-2.3/devkit/sh4/bin/:$PATH (这里的PATH是解压STLinux平台交叉编译工具的路径)。然后在终端试试能运行 sh4-linux-gcc —version 查看交叉工具链的版本号。从hwoto文档里给出的 DirectFB 的网站里下载到填充背景和画线的simple,用 sh4-linux-gcc simple.c -I/opt/STM/STLinux-2.3/devkit/sh4/target/usr/include/directfb/ -ldirectfb -o dfbsimple 交叉编译(注意这里的路径要是你自己解压的路径)。然后把编译出来的程序复制到之前的 nfs 根文件系统的一个位置(例如/roo/test/)。然后telnet登录的终端就可以运行编译出来的程序。能看到HDMI输出填充指定背景颜色和画线就说明交叉工具链正常。
基于 DirectFB 的 GAL
… …
基于 stgfb_ioctl 的 GAL
这个是使用更加底层的方式,直接调用ST提供的ioctl操作,不使用 DirectFB 封装。优点是更加灵活,直接的操作ST的底层芯片,能够使用ST芯片的所有功能,不受封装库的限制。缺点就是比较麻烦,ST的底层ioctl命令参数很多。7167的2D图像加速硬件叫 STBlit ,有2种方式可以访问:
通过调用 stblit 的封装函数。这些函数是对 stblit_ioctl 的封装。见图1。
直接使用 stgfb_ioctl,这个是 STBlit 扩展的 FrameBuffer 的 ioctl 操作(也就是7167 2D图像硬件特有的ioctl操作,这里就叫做 stgfb_ioctl)。见图2。
ST 是推荐使用第2种方式,因为第二种方式不需要经过 stblit 函数的封装,速度更快。我是选择使用第2种方式的,倒不是因为 ST 的推荐,而是因为我手头上只有第2种方式的例子。ST 的那个 stapi 的源代码,看得我头都晕了。这里要特别感谢佐朝同志提供的示例。
基本流程
使用 stgfb 的基本流程如下:
首先要加载 7167 上 ST 和图像相关的驱动模块:stblit_ioctl.ko, stlayer_ioctl.ko 等。这些在前面说的按照 howto 文档里加载内核模块驱动的时候就已经加载好了。
打开 FrameBuffer 设备。按前面的加载好内核模块,并加载 fb 设备的话。fb 的设备节点应该是 /dev/fb0, /dev/fb1,fb0是主屏,fb1是副屏。这里只使用到了主屏。这里的打开代码可以参照 MiniGUI fb GAL 部分的代码。
获取视图(ViewPort)处理句柄(Handler)。
初始化 7167 主显示层(layer)。首先要打开层管理设备(/dev/stapi/stlayer_ioctl),然后通过 ioctl 发送使能命令激活该层,最后通过 ioctl 获取该层的一些信息(也就是屏幕的信息)。
到这里初始化工作就完成了。可以通过 fb 设备文件描述符号(这个就是之前打开的 FrameBuffer 设备),使用 stgfb_ioctl 发送 STGFB_IO_BLIT_COMMAND 命令(要填写响应的命令参数),让 STBlit 进行图像绘制操作。
退出的时候,关闭之前打开的设备即可。
stgfb io 接口命令
|
|
ViewPort Handler
通过 STGFB_IO_GET_VIEWPORT_HANDLE 命令发送,设备是 fb 设备。参数为 STLAYER_ViewPortHandle_t ,这应该是一个地址。该句柄在是后面获取 layer 信息的参数,一定要取得。
Layer
首先是要激活一个 layer。通过 STGFB_IO_ENABLE_LAYER 命令发送,设备是 fb 设备。参数为 int,1为激活,0为禁用,注意传的时候,要传入该变量的地址,而不是把该变量的值传过去。
激活后,就可以通过 STLAYER_IOC_GETVIEWPORTPARAMS 命令发送获取层信息,注意这时候的设备是 layer 的设备(就是 /dev/stapi/stlayer_ioctl)。参数是 STLAYER_Ioctl_GetViewPortParams_t 。其中的 VPHandle 是传入参数,就是之前获取的那个。STLAYER_Ioctl_FlatViewPortParams_t 是输出参数,具体的参数就在里面。在 ST 内部,图像是以 STGXOBJ_Bitmap_t 为单位的,一个图像就是这么一个位图。从这个结构体定义就可以知道屏幕的一些重要信息了(以下的注释是我自己写的,非官方的):
代码:
|
|
Blit
blit 使用 STGFB_IO_BLIT_COMMAND 命令来实现。stgfb 的 blit 参数主要是设置源和目标(STGXOBJ_Bitmap_t)以及一些 blit 时的一些参数:例如 colorkey、层alpha等。这里调用 ioctl 的设置是 fb 的设备。下面就这些参数分析下吧:
|
|
FillRectangle
FillRectangle 可以用2种命令来实现(都是STGFB_IO_BLIT_COMMAND,只是参数不同而已):
- blit 时使用 STGFB_BLIT_FillRectangleParams_t 参数。这个好像不能带 colorkey 和 alpha,不推荐用这种。
- blit 时使用 STGFB_BLIT_BlitParams_t 参数。这个可以带 colorkey 和 alpha,推荐用这种。
这里就说说推荐的第二种方法吧。这种方法, ioctl 的参数类型和 blit 是一样的,但是设置值不一样,主要是在 blit 上下文参数那里的 SrcType 设置成 STBLIT_SOURCE_TYPE_COLOR (这里可以参看上面的blit),然后 SrcData_p 这个指针指向一个 STGXOBJ_Color_t 的地址。在 STGXOBJ_Color_t 里设置你要填充的颜色。colorkey 和 alpha 的设置和 blit 是一样的。下面来看看 STGXOBJ_Color_t 参数吧:
|
|
同步
同步的意思就是说,等待底层 blit 命令完成后,再执行后面的操作。如果不同步的话,就会遇到我日志里的问题。这里同步的方法理论上有2种,但是目前我只弄成功了一种:
使用 ST 的通知回调。前面看到过,blit 的上下文参数里有一个就是设置回调参数的。这个回调是在 blit 完成后,底层调用的。不过要用这个比较麻烦。流程好像是:
- 先要打开 stevt_ioctl 这个设备,取得stevt设置handle。
- 然后注册通知设备(这个好像是说哪个blit设备吧,这里没太搞清楚)。
- 通过得到的 handle 在 blit 上下文参数设置回调函数(通过 stevt 设备的 ioctl 命令)。
- 使用完后,删除注册通知设备。
- 退出时,关闭 stevt 设备。
具体的可以看看 ST 那个控制台的代码。不过我弄了下,发现不行,在 blit 上下文那些一设置回调就包内核错误了 -_-||。估计是哪里没弄对。算了,不管了,下面有比这个更好用的~~嘿嘿(这个就算回调弄成功了,回调里锁这、锁那的也麻烦)。
使用 stgfb 提供的blit设备同步 ioctl 命令。上面列出的的 stgfb io 接口里有这个一个接口:STGFB_IO_SYNC_BLITTER 。用这个就可以了。这个命令其实是没有参数的,ioctl 最后那个参数直接赋值0就可以了。在 blit ioctl 后面调用这个,能保证 blit 完成后,ioctl 才返回。这个正是我们需要的功能。用这个简单的 ioctl 命令就OK了,不用使用 ST 的底层事件功能了:
代码:
|
|
显存管理
板子上可用来分配给 MiniGUI memdc(离屏surface) 的显存一般是除去了屏幕暂用的显存(pitch x 屏幕高度)之后所剩余的(一般板子上的显存都会比屏幕大,用总显存减去屏幕所暂用的显存就可以得到剩下的显存大小了)。这样就需要一定的管理策略来有效的管理这些显存(要求分配到的显存要是连续的)。目前的 GAL 采用的办法是:
将显存分块(block)。用双向链表(借用linux内核的链表实现)来管理这些块(bucket)。块中保存了块的pitch、高度、相对于显存首地址的偏移、以及是已经使用的标志。初始化时,将所有的显存创建成一个标记未使用过的大块。
当新申请一块显存时(传入所需块的pitch和高度):通过传入的参数计算所需要申请的显存大小。从链表头开始查找没有使用过的并且足够大的块。如果找到了就把这个块拆成2块:一块是申请的大小(新插入到这个块的前面),一块是原来剩余,并且把新申请到的块标记为使用的;如果没找到就返回失败。
当释放一块显存时(传入block的指针):分别向这块显存的前一个和后一个块检查。看临近的这2块的标记是否是未使用的,如果是则合并这2块(在链表中删掉其中一块,并把这一块的大小加入到未删除块上,修改未删除块相应的偏移信息),并设置标志为未使用。这样坐的目的是为了尽可能的保持未使用的显存的连续性,从而尽量保证申请显存的成功率。
下面举个简单的例子:显存有10M,初始化时,分配屏幕显存(1920x1080-16bpp),需要一个 640x480-16bpp 的 surface (图中没写偏移地址):
然后使用完成后,就要释放。会先查找前面一个块,这个块是正在使用的,不合并;然后查找到后面一个块,这个块是没有使用的,所以就要合并。如果是前、后都在使用的,那就简单的把这个块标记为未使用即可(从图中可以看出,这种策略的特点:第一个块一般是屏幕的显存,最后一个块一般是最大的未使用的块;链表中间有可能会出现一些小的未使用的块,但是随着临近块的释放,这些块会慢慢的合并到最后的大块中):
贴贴代码,对着看下呗:
|
|
overlapped blit
overlapped blit 的意思就是说:源数据地址与目的数据地址有重叠,见下面的图:
如果是软件 blit 的话(使用 framebuffer 映射到的内存来 memcpy),是不需要考虑这种情况的。或是有些硬件处理了这种情况,也不需要考虑。7167 底层没处理,所以就要自己处理。overlapped 造成的问题是:一般来说底层的复制是逐行、逐行的复制的,所以上面的这种情况,上面的数据会把重叠的地方的给覆盖了,当复制到重叠的地方(就是源数据的下面部分)就会把被覆盖后的数据复制到目标区域,这样就会原来的图像就不正确了。解决办法有2个:
创建一个和源一样大的临时显存块,把源先 blit 到临时显存块,然后再把临时显存块 blit 到目标区域,最后释放临时显存块。
把源区域分块 blit ,保证不重叠。
第一种方法不许要计算分块,blit 次数固定为2;但是需要创建临时显存块,如果源比较大,并且显存使用比较多的情况下,会造成 blit 失败。所以采用第2种方法。分块的策略为(这里以图中的垂直向下为例,垂直向上,水平向左、水平向右是类似的):
计算出相交的区域矩形大小(图中绿色的部分)。
用相交区域的高与源区域的高相减得到未相交区域的大小(delta)。
将源区域每次按 delta 大小从下向上 blit,当最后区域不足 delta 时就取最后剩下区域的大小(这样做的目的就是为了每次 blit 都不相交)。
这里讨论了水平、垂直的情况(4种),还有剩下4种斜的情况。因为斜的情况,水平、垂直的策略都适用,但是不同的情况使用不同的策略速度会不同。仔细看下相交区域:当相交区域的高比较大的时候,垂直分块策略会比较慢;当相交区域的宽比较大的情况下,水平分块策略会比较慢。所以这里如果是斜的情况,可以根据相交区域来选择最快的分块策略。贴一下代码吧,这样对着看会比较清晰些:
|
|
进程版
MiniGUI 的多进程间通信通过 Unix socket ,采用 C/S 模型(桌面是服务器,其它的应用进程是客户端)。 在线程版的基础修改以下的地方:
初始化的时候,需要的设备(framebuffer、stlayer等)每个进程都要打开。frambuffer 的内存映射(mmap)每个进程要做,因为 mmap 是映射到当前进程的地址空间,所以如果只有服务器映射的话,就算给了客户端地址,客户端也无法访问。每个进程都要计算屏幕相应的内存映射地址偏移(这个是用户空间的地址,每个进程的都不一样的,因为之前各自映射各自的,所以每个客户端都要做)。只在服务器初始化显存管理链表(之前说的bucket),同时申请屏幕显存(分配硬件显存地址,一般只能做一次,所以只在服务器进程分配)。
退出时,每个进程都要关闭打开的设备(因为之前每个进程都打开了)。每个进程都要卸载 framebuffer 的内存映射(munmap)。只在服务器销毁显存管理链表(因为只在服务器创建了)。
客户端向服务器发申请显存请求;服务器响应客户端请求并分配显存。客户端向服务器发送释放显存请求;服务器响应客户端请求并释放显存。这里要注意一点:一个进程是不允许访问另外一个进程的内存地址空间的(这里的是指用户态的,虚拟的内存地址),除非是创建共享内存(这个好像稍微麻烦点)。这样不管是服务器向客户端发送数据分配的显存数据(MiniGUI 里一般是一个 void* 类型,保证的是数据的地址),还是客户端向服务器发送要释放的显存数据;如果像多线程那样直接访问的话,是要出信号失败的错误的。解决办法一个是创建共享内存(这个说了会麻烦点);还有一个就是不要访问地址(指针),只访问数值。这里的技巧就是:
显存申请时:服务器分配显存是会申请一个 block(这是个结构体),这里把 block 指针、block 的 offset 和 pitch 发送给客户端。客户端得到数据后,把服务器发过来的 block 指针保存下来,这里注意仅仅是保存下这个指针(地址)就行了,不要去访问(原因前面说了)。然后根据得到的 block 的 offset 和 pitch 计算出物理显存地址和映射的framebuffer地址(客户端需要的 block 数据仅仅是 offset 和 pitch)。这样显存申请就 OK 了。
显存释放时:客户端把之前服务器发过来的 block 地址发送给服务器。服务器就可以拿着这个 block 地址调用 bucket 的释放函数释放显存了。这里的 block 是服务器的进程创建的,所以服务器端就可以访问。
使用 stapi 的注意事项
要包括 ST 的一些头文件(我把这些复制到 MiniGUI 源码树里去了):
* src/newgal/stgfb/st_include/stblit_ioctl.h * src/newgal/stgfb/st_include/stddefs.h * src/newgal/stgfb/st_include/stcommon.h * src/newgal/stgfb/st_include/stevt_ioctl.h * src/newgal/stgfb/st_include/linuxwrapper.h * src/newgal/stgfb/st_include/stblit.h * src/newgal/stgfb/st_include/stlite.h * src/newgal/stgfb/st_include/stevt.h * src/newgal/stgfb/st_include/stgfb.h * src/newgal/stgfb/st_include/stdevice.h * src/newgal/stgfb/st_include/linuxcommon.h * src/newgal/stgfb/st_include/stavmem.h * src/newgal/stgfb/st_include/stlayer_ioctl.h * src/newgal/stgfb/st_include/stgxobj.h * src/newgal/stgfb/st_include/stlayer.h * src/newgal/stgfb/st_include/stsys.h * src/newgal/stgfb/st_include/layer_rev.h * src/newgal/stgfb/st_include/stos.h
要加上几个编译宏:
CFLAGS="$CFLAGS -DST_OSLINUX -DST_7105 -DARCHITECTURE_ST40 -DDEFINED_BOOL"
编译环境要包括 linux 内核头文件。这个在 ST 的根文件系统的包里有,我默认安装就在这:
/opt/STM/STLinux-2.3/devkit/sources/kernel/linux-sh4
编译、运行 MiniGUI (DirectFB)
… …
编译、运行 MiniGUI (stgfb)
MiniGUI source
rel-3-0 分支 svn 13675 以上: URL: svn+ssh://devsrv/home/projects/svn/minigui/branches/rel-3-0
编译环境设置
按上面写的安装好 STLinux。这里假设你安装的路径是 /opt/STM/STLinux-2.3,那么:
- 交叉工具链路径是: /opt/STM/STLinux-2.3/devkit/sh4/bin
- 根文件系统路径是: /opt/STM/STLinux-2.3/devkit/sh4/target
- kernel源码路径是: /opt/STM/STLinux-2.3/devkit/sources/kernel/linux-sh4
要编译基于 stgfb GAL 的 MiniGUI 的话要设置如下的 CFLAGS 和 LDFLAGS:
/* 设置安装路径 */ export PREFIX="/home/mingming/nfs/st7167_procs" /* 设置根文件系统的头文件路径 */ export SH4_ROOT=/opt/STM/STLinux-2.3/devkit/sh4 export KERNEL_ROOT=/opt/STM/STLinux-2.3/devkit/sources/kernel/linux-sh4 /* 要特别设置下 c++ 的头文件路径,编译 mgplus、mdolphin、mdtv 用 */ cxx_path="${SH4_ROOT}/target/usr/include/c++/4.2.1" /* 设置 CFLAGS,包括根文件系统自带的一些头文件、和内核头文件 */ export CFLAGS="-I. -I.. -I${PREFIX}/include -I${SH4_ROOT}/include -I${SH4_ROOT}/target/usr/include -I${SH4_ROOT}/taget/usr/local/include -I${KERNEL_ROOT}/include" /* 设置 C++ flag,除了包括 CFLAGS 的一些头文件路径还要包括 c++ 文件路径 */ export CPPFLAGS="${CFLAGS} -I${cxx_path} -I${cxx_path}/sh4-linux" export CXXFLAGS=$CPPFLAGS /* 设置 pkg 路径,包括编译安装路径和根文件系统的,这个不设置的话,编译会报错 */ export PKG_CONFIG_PATH=$PREFIX/lib/pkgconfig:$SH4_ROOT/target/usr/lib/pkgconfig /* 设置 LDFLAGS,注意如果 MiniGUI 打开了 freetype,要自己编译个高版本的 freetype(用 2.3.9 版本的),根文件系统自带的不行。 * 所以设置的路径要把安装路径设置到根文件系统的前面 */ export libs_base="-L${PREFIX}/lib -Wl,-rpath-link=${PREFIX}/lib -L${SH4_ROOT}/target/lib -L${SH4_ROOT}/target/usr/lib -L${SH4_ROOT}/target/usr/local/lib -Wl,-rpath-link=${SH4_ROOT}/target/lib -Wl,-rpath-link=${SH4_ROOT}/target/usr/lib -Wl,-rpath-link=${SH4_ROOT}/target/usr/local/lib" export extra_libs="-lm -lpthread -ljpeg -lpng -lfreetype" export LDFLAGS="${libs_base} ${extra_libs}" /* 设置 bin 路径,这里主要设置交叉编译链路径和一些库的 config 路径,注意先后顺序 */ extra_path="${SH4_ROOT}/bin" export PATH=$PREFIX/bin:$extra_path:/home/mingming/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MiniGUI 编译配置
configure --prefix=$PREFIX \ --build=i386-linux \ --host=sh4-linux \ --enable-videofbcon \ --enable-videostgfb \ --enable-procs \ --disable-videoqvfb \ --disable-pcxvfb \ --disable-qvfbial \ --with-ttfsupport=ft2 \ --enable-ttfcache \ --enable-jpgsupport \ --enable-pngsupport \ --disable-splash \ --disable-screensaver
MiniGUI 运行时配置
可以使用不带加速的 fbcon 来验证下带加速的 stgfb 。如果鼠标设备位置不对,可以改为 mdev=/dev/mouse0。framebuffer 的颜色格式设置可以参看上面写的改 load_fb.sh 里的设置。
[system] # GAL engine and default options #gal_engine=fbcon gal_engine=stgfb defaultmode=1920x1080-16bpp # IAL engine ial_engine=console mdev=/dev/input/mice mtype=IMPS2
最后还要设置下系统运行时环境,这里提供一个脚本(要先安装howto文档里,先把 ST 的控制台先跑来, 然后另外一个console telnet登录上去运行该脚本;注意脚本的运行方式:soucre xx.sh 或是 . ./xx.sh):
/* howto 文档里写的加载 framebuffer 设备,分辨率 1920x1080 */ source /root/load_fb.sh FULLHD PAL /* 挂载 sawp,没有的话,可以删掉这个 */ mkswap /dev/sda swapon /dev/sda /* 设置动态库路径,非重重要。这里我将 MiniGUI 的安装路径设上去了。 */ export LD_LIBRARY_PATH=/root/nfs_ths/lib:$LD_LIBRARY_PATH export MG_CFG_PATH=/root/nfs_ths/myetc export MG_RES_PATH=/root/nfs_ths/share/minigui/res /* 挂载 nfs,上面的 MiniGUI 安装路径就是这里挂载上去的 */ umount /root/nfs_ths mount 192.168.1.102:/home/mingming/nfs/st7167 /root/nfs_ths -o nolock /* 设置 ST 环境,这个可有、可无,最好还是加上 */ source modules/load_env.sh
注意事项
链接时,会出现乱链库的情况。这个时候要去把根文件系统下把那些自带库的 .la 文件里的路径改对;还有一些 bin 下的 config 文件,也要改对。
板子出现无法解析host的情况,修改 /etc/resolv.conf 文件,设置正确的 nameserver ,例如,目前深圳这边的设置为 192.168.1.1