这是一个文章系列的一部分,介绍基于MK802这类MiniPC的扩展开发,并展示他在计算机视觉、机器人控制方面的潜能
欢迎转载,但请保留原始作者信息(Shikai Chen, http://www.csksoft.net),以及指向本文原始出处的链接!
访问目录:基于MK802 MiniPC的扩展开发应用-简介篇(http://www.csksoft.net/blog/post/mk802_dev_intro.html)
revision: 1
这部分将介绍各种针对MK802硬件以及软件的修改技巧,相对来说,这部分介绍的都属于各种杂碎的小技巧,文章构成上比较零碎,并且相对于整个系列文章其他部分独立。
虽然这些都是各种小技巧,有些甚至属于雕虫小技,不过在必要的地方我也会通过问题本身做一些对linux kernel的进一步介绍。
引出MK802的内部调试串口信号,用于kernel的调试和开发
通过fex脚本配置hdmi的输出分辨率至1080p和其他尺寸以及色彩深度
在自制系统内利用MK802的内置Flash存储文件
将CPU主频超频至1.1G,以及实现动态频率调节降低功耗
这么短的文章不指望能把整个硬件构架情况交代清楚,这里仅给出对本文后续Hack所需要的背景信息。
完全了解MK802的硬件构成,需要拥有厂家的相关文档,而这些文档往往是需要签署NDA协议后提供的,并不能公开获取,所以这里的介绍只是透过我的拆解分析,以及基于网上相关资料(见参考文献)汇总得出的。他们可能与真实情况存在偏差。
这里介绍CPU和相关SOC的目的是为了让大家了解MK802的性能情况。就目前厂家提供的固件以及我们自制的固件而言,大部分的SOC资源都没有得到有效的利用。就像在前一篇文章中我也提到,目前在传统Linux发行版上还大部分没有上述SOC的驱动,就比如Mali 400 GPU和G2D 2D图形加速的支持,以及视频编解码的驱动支持。不过目前社区也有人在尝试着通过逆向工程的手段,实现在传统linux发行版上使用这类SOC。
透过厂家以及之前文章的介绍,MK802是采用Allwinner A10 CPU芯片的系统方案,该芯片基于ARM Cortex A8构架,工作在1Ghz左右的频率,GPU SOC采用了ARM Mali-400 MP[2]。
其他的片上资源包括[1]:
支持2160p(支持3D视频格式输出)的HD视频decoder,包含对H.264,MPEG-1/2/4的格式的硬件解码支持,以及对应的硬件编码器
硬件2D图像加速(G2D)
USB2.0高速的HOST接口
USB2.0高速的OTG接口
SDIO总线x4
CSI总线
100Mbps以太网控制器
SATA2.0接口 (3Gbps)
IDE(PATA)接口
Can总线
I2S、SPDIF、AC97音频接口
PS2、SPI、TWI(I2C)、UART(串口)接口
LVDS、VGA、HDMI视频输出接口
电阻触摸屏控制器
这块芯片的SOC资源还是比较不错的,连SATA2.0也有,不过我们的MK802并没有把这些资源的信号都提供出来。所以像SATA口我们是无法再MK802上使用到了。不过市面上也有其他不少基于Allwinner A10的机器,其中不乏这些资源都可用的机型[3]。本系列文章的内容同样对他们有效。
MK802的硬件组成如下图所示,诸如HDMI这类大家都已经清楚地接口,这里就不标出了。
图:MK802硬件设备
资源/设备/接口 |
图中标号 |
描述 |
Nand Flash |
1 |
4Gb |
Allwinner A10 CPU |
2 |
见上文 |
DDR3 RAM |
3 |
按配置有1G/512M |
DCDC供电 |
4 |
简单的DCDC模块,非PMIC |
USB WIFI模组 |
5 |
采用rtl8192芯片的USB WIFI模块 |
USB 2.0 OTG |
6 |
可以用作USB host连接外设 |
调试串口触点 |
7 |
后文将介绍将其信号引出用于调试 |
可以看出MK802的PCB布局比较紧凑,能够做信号扩展的机会不多,可以进行的有:
将内部的调试串口信号引出
利用USB WIFI模组额外的一条USB总线,外接更多的设备
替换DCDC模组,或者将其输出引出
本文接下来将介绍这里列举的第一条,其他如果大家有兴趣可以自己尝试。
参考文献[1]给出了同样采用Allwinner A10芯片的另一台机器Mele A1000的功耗情况,由于它与MK802的硬件相似度极高,因此可以基本认为与MK802一致。这里我就直接引用[1]的数据:
使用场景 |
功耗情况 |
u-boot idle (uboot自动启动被打断) |
1.15W (0.23A, 5V) |
Android系统空闲状态 |
1.7W (0.34A, 5V) |
Android频繁浏览网页时 |
2.75W (0.55A, 5V) |
Android在youtube播放720p视频 |
4.6W |
Ubuntu 12.04 + SATA HDD 下载bt,不接显示器输出 |
5.0 – 6.5W |
这个功耗水平还是很令人满意的,需要注意上表的数据是基于Mele A1000采集得到的,由于MK802具有更少的硬件外设,因此功耗水平应该更低。另外上表的最后一项还包含SATA硬盘,因此5W的功耗中应该也包含了硬盘消耗。
如果希望降低MK802的功耗,除了在运行期间禁用相关的硬件功能(比如关闭hdmi图形输出),还有一个有效措施是按照需求降低CPU主频,这部分将在后文介绍。
在前一篇文章[4]的uboot/linux kernel编译环节,我提到了Allwinner A10 CPU的kernel使用了一个fex脚本的配置文件,用于对于系统各种参数的配置。这里将专门介绍一下这个文件。
图:在自制系统过程中需要编译的配置脚本
该文件的原始形式是如上图所示的文本格式的配置文件,在前文[4]中提到了在实际系统制作中,该文本格式的文件将使用fex2bin的程序,将他转化成二进制的格式,并最终以文件名script.bin同linux kernel镜像(uImage)一起发布在SD卡的FAT分区当中。
在MK802启动阶段,该fex脚本的二进制形式首先会通过uboot脚本boot.scr加载到一个固定的内存地址。随后在Linux kernel启动的不同时期,各种相关的驱动程序在初始化阶段将读取该配置脚本,完成对设备的初始化。具体过程将在下文的fex script深入讨论部分介绍。这里需要了解的是我们可以通过对该文件进行修改达到对某些设备的配置。
如果感兴趣,大家可以先下载该文件观察一下:
https://github.com/cnxsoft/a10-config/blob/master/script.fex/mk802.fex
可以观察到其中有诸如对于TWI(I2C)、DDR RAM、串口、SPI、HDMI各类设备的参数配置。以及还有一些MK802上并不存在的设备的配置,比如LCD、加速度传感器等。
这可能是由于该配置文件是社区基于其他的Allwinner A10方案的设备修改而来的,另外通过分析MK802的linux kernel可以知道,其中包含了很多应该是来自Allwinner A10 的参考开发系统的代码,其中诸如加速度传感器、LCD等的驱动程序仍旧包含在现在的代码中。这些代码虽然不会起到实际作用,但在加载的过程中仍旧可能会从fex脚本中读取对应的配置信息。因此并不是其中所有的配置项目都是会起作用的。
那么如何进行配置呢?该配置文件的具体介绍网上并没有公开的文档进行解释(在文献[1]同提到有泄露的厂家文档,可以自行验证),因此主要通过如下2个途径:
1. 猜测
2. 查看Linux kernel代码
对于一些显而易见的配置项目,比如这条:
[target]
boot_clock = 1008
dcdc2_vol = 1400
dcdc3_vol = 1250
ldo2_vol = 3000
ldo3_vol = 2800
ldo4_vol = 2800
其中这类XXX_vol的项目对于懂行的朋友而言一看就知道是配置PMIC芯片的供电输出的。但是即使这样猜测知道了这个配置项目的意义,但也不能保证该配置项就可以起到作用。正如上文所说,MK802并没有带有PMIC,DCDC的供电应该是无法配置的。另外Linux kernel代码中或许就没有对这项配置项进行读取(比如配置项的DDR内存大小,目前的kernel就是忽略的,需要修改uboot代码实现1Gb内存的使用,见上一篇文章[4])。
对于无法猜测含义或者不确定是否有效的配置项,就需要阅读Linux kernel的代码来了解他的具体配置方法和是否起作用了。考虑到不是所有人对这个过程感兴趣,因此我将他的介绍安排到了文章模块的深入探讨部分。我将在那部分以实际的代码为基础介绍。
我blog早些时候曾介绍过这个部分[5],这里介绍的内容和之前的基本一致,如果已经看过该文章,可以略过此小节。
使用该串口信号我们可以即时的获取MK802的底层工作情况(uboot和kernel的log),以及能够得到一个Shell终端,用于登陆linux系统,在没有网络或者显示器的情况下就可以对MK802进行操作。并且如果在自制系统时候出现问题,导致MK802无法正常启动时,使用调试串口几乎就是唯一的查找原因的方式了。另外这个调试串口也可以作为与外部设备通讯的接口,但我不推荐这种做法,因此本文也不会介绍。
一般做linux kenrel和设备驱动的开发,没有VC IDE debugger这种好用的工具,就连gdb server很多时候都不管用。最常见的办法就是通过printf把日志从串口打印出来调试。(当然如果正在开发串口驱动,那只好通过点亮几个LED灯来调试了,这听上去很疯狂,但这是事实)。虽然也有ICE/JTAG这类硬件调试器,但对于linux kernel这类OS的调试,硬件调试器就显得很不直观,而且很多time critical的逻辑无法通过下断点复现。因此,一般做硬件/kernel/驱动层次开发,有一个用于打印printk信息的串口是非常必要的。
MK802内部含有调试用的串口,但并没有将他的信号连接出来。通过简单的焊接工作,我们可以把该信号连接出来,并使用usb转串口模块与pc相连。
图:将MK802外壳小心拆开
这里需要将MK802拆开,拆开过程需要比较小心,外壳之间是采用卡口相互固定的,可用硬质的塑料板或者专门的拆机撬棒打开,随后可以观察到在A10芯片的那侧有如下图所示的几个金色的焊盘:
图:MK802 PCB上测试点的信号定义
这些测试点没有丝印标出信号含义,但其实很容易猜到:右起第二个肉眼就能看出是GND。最右侧通过万用表测量是3.3V。那自然是VCC。左边2个自然有很大嫌疑是TXD和RXD的TTL电平的串口信号。那么怎么确定那个是TX哪个是RX? 注意左起第二个有一个上拉电阻。一般输入信号才要上拉/下拉。那很可能就是RX。用示波器看了下,果然最左侧有信号发出。
这个我们仅需要用到的信号就是TX/RX/GND。VCC也可以接出来给外部一些低功耗的设备提供3.3V的电能。
小心的将上述信号用导线焊接连出来,并且使用诸如基于FT232/CP2102等芯片的usb转串口(TTL)的适配器上。注意MK802的RX信号需要与适配器的TX端连接。
图:在测试点上焊接导线,并用热熔胶固定
图:使用usb转串口连接
到这里对硬件的修改工作就完成了。
将上文提到的usb转串口适配器连接电脑。使用Putty(Windows)[6]、高级终端(Windows)、Minicom(Linux\Mac)等支持串口终端的工具打开此串口。并将MK802通电,就能看到启动过程中从uboot以及linux kernel输出的各种log了。
这里的串口设置是比较通用的115200bps 8bit 1 stopbit的设置。对于绝多大多数的嵌入式设备(以及PC),串口调试均采用该配置。
图:使用Putty作为串口调试终端
图:从串口输出的启动一开始的uboot log
图:串口输出的kernel log
对于通过上一篇文章[4]制作的系统,我们可以在系统启动完毕后在串口终端得到一个bash shell,对于熟悉命令行操作的人来说,已经可以很自如的使用起MK802了。在后续的系列文章中,我也将介绍如何在没有HDMI接口的显示器且没有配置过网络的情况下,仅使用串口完成wifi网络的配置,以及设置vnc供后续远程图形桌面登陆的技巧。
图:在启动完毕后将显示bash shell的提示符,可以进行命令行操作
这里开始将介绍软件部分的各种修改或者扩展,主要围绕前文提到的fex脚本的修改和Linux kernel代码修改展开。
前文介绍过MK802所使用的Allwinner A10 CPU带有在HDMI上输出1080p的画面的能力,但是官方的Android系统以及前一篇文章介绍的自制系统仍旧以720p(1280x720)作为输出分辨率。
这并不是硬件问题,而是软件(驱动)的配置问题。不过对于Android,目前的4.0版本在设计上并不能支持1080p的画面,因此我们无法对这个系统进行修改。而对于我们自制的基于传统linux发行版的系统,就可以很简单的修改fex脚本的一行参数达到1080p的配置:
打开上文提到的mk802.fex文件。(具体请参考上文对fex脚本介绍的章节),定位到314行附近,找到screen0_output_mode参数的设置语句,将他赋值为9(之前是5)。
图:修改mk802.fex,实现1080p输出
完成修改以后,使用上一篇文章[4]提到的fex2bin程序将改fex脚本编译成bin格式:
fex2bin mk802.fex script.bin
这里提供编译好的bin文件,方便大家:
http://www.csksoft.net/data/mk802/script.1080p.7z
接下来,只要将该文件把存放自制系统SD卡的第一个分区的对应文件替换,重新启动mk802,就可以得到1080p分辨率的hdmi输出画面了。
如果不熟悉这个过程,可在linux下使用如下命令:
mkdir –p sdcard
sudo mount /dev/sdd1 sdcard
sudo cp script.bin sdcard
sudo umount /dev/sdd1
请将上述代码的下划线部分替换成符合你实际情况的sd卡的设备名,如果不清楚,可以参考[4]的 1.8. 将系统部署到SD卡 章节。
对于为何这里要将screen0_output_mode设置为9,如果对此感兴趣,可以参考本文后文的深入讨论部分。
MK802本身具有4Gbyte的NAND flash用于存放自身的android系统以及用户数据。当我们使用从SD卡启动的自制系统以后,也可以将这自身带有的4Gb的flash空间加以利用保存自己的数据。
不过这个过程会将内置flash中的原有数据破坏,因此这里也将介绍如何去备份自身flash的数据并恢复。另外我们不需要担心因为破坏了自身android系统导致系统变砖的问题,正像前文[4]提到的,Allwinner A10芯片支持从usb修复的模式,任何时候我们都可以利用官方提供的修复包恢复到出厂时候的情况,不需要担心变砖。
在我们的自制系统中,使用sudo fdisk –l查看目前系统可被作为文件系统的设备列表。(如果提示fdisk命令不存在,可以使用apt-get安装)
sudo fdisk –l
在输出数据中可以找到类似下图的信息:
图:利用fdisk查看可用的块设备
通过fdisk的输出,我们可以看到诸如/dev/nandX格式的设备,一共有如下的几个:
Nand Flash设备名 |
容量 |
标签 |
/dev/nanda |
16 Mbyte |
bootloader |
/dev/nandb |
2 Mbyte |
env |
/dev/nandc |
33 Mbyte |
boot |
/dev/nandd |
536 Mbyte |
system |
/dev/nande |
1073 Mbyte |
data |
/dev/nandf |
1 Mbyte |
misc |
/dev/nandg |
33 Mbyte |
recovery |
/dev/nandh |
134 Mbyte |
cache |
/dev/nandi |
1 Mbyte |
private |
/dev/nandj |
335 Mbyte |
sysrecovery |
/dev/nandk |
1815 Mbyte |
UDISK |
上表的设备名和容量是可以从前面的fdisk –l命令中知道的,而对应的标签,可以通过dmesg命令查看系统启动时的log得到:
dmesg | less
然后搜索“nand”字符串,可以找到这块log:
图:kernel驱动的输出信息包含了内部flash设备的分区信息
通过对应的标签名,我们大致可以猜到这些分区在原生Android系统中的作用。不过相比这个,我们更关心如何使用这些分区。
我们接下来对这些内部flash的使用会破坏原始的数据。如果你需要保留这些数据的话,不妨按照这里的操作备份他们。这里以下现在需要备份上表中UDISK标签的那个nand flash块(/dev/nandk)为例子,如果你需要备份所有的分块内容,需要用这里给的命令对每个分块进行操作。
首先需要明确用于存放备份的目的地有足够的空间,假设我们保存在自制系统的SD卡上,可以用df –h命令查看现在的剩余空间:
root@linaro-alip:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mmcblk0p2 7.3G 1.7G 5.3G 24% /
devtmpfs 408M 4.0K 408M 1% /dev
none 408M 4.0K 408M 1% /tmp
none 82M 264K 82M 1% /run
none 5.0M 0 5.0M 0% /run/lock
none 408M 0 408M 0% /run/shm
none 408M 0 408M 0% /var/tmp
上面粗体字的一行是当前根文件系统的总大小(7.3G),已用空间和剩余空间(5.3G)。而我们要备份的分块只要1.8G左右,足够存放。
接下来就是用dd命令备份:
sudo dd if=/dev/nandk of=nandk_backup.img bs=1M
这段命令将/dev/nandk分块设备的内容保存到当前目录下名为nandk_backup.img的文件。
在备份完成后,就可以放心修改这个分块的数据了。今后如果需要将他恢复,同样使用dd命令:
sudo dd of=/dev/nandk if= nandk_backup.img bs=64k
这里可以看到dd这个命令的灵活性,只要对调of(output flow)/if(input flow)参数的内容,就能实现备份和恢复。因此使用上必须当心,如果弄错方向,不但破坏了要备份的数据,更可能有其他灾难后果(写错了地方)。另外大家可能注意到恢复命令的参数(bs=64k)与之前备份的(bs=1M)不同。这是因为nand flash的特性造成的。Nand flash不像DDR内存那样可以随意读写修改,他的写操作总是伴随着页擦除动作。而一个页一般为64kbyte(或者是其约数),因此制定64kbyte为写入单位可以显著提高性能。
另外如果觉得这样直接将原始的分块数据备份太浪费空间,可以在备份的同时加上压缩,使用如下的命令进行备份即可直接产生gzip压缩的备份镜像:
sudo dd if=/dev/nandk | gzip –c > nandk_backup.img.gz
在恢复时使用如下命令:
gunzip –c nandk_backup.img.gz | sudo dd of=/dev/nandk
如果要备份的分块实际内容较少,则可以产生体积相对小的多的备份镜像。
要使用每个分块作为存储设备其实和使用移动硬盘或者u盘操作一样:进行分区、格式化以及挂载。如果熟悉linux下这些操作,就可以跳过这里的介绍了
我们使用cfdisk这个分区工具简化操作,你也可以使用前一篇文章用到的parted命令。
如果系统没有这个程序,可以用apt-get安装:
sudo apt-get install cfdisk
这里仍旧以/dev/nandk这个分块作为例子:
sudo cfdisk /dev/nandk
此时出现如下画面:
图:cfdisk进入画面
这里我们打算将这个分块作为一个分区,则直接选择[NEW],按回车,随后选择[Primary],再逐步确认。最后出现如下画面:
图:完成预先分区意向
虽然此时显示已经为分区后的结果,但其实并没有真的操作nand flash写入我们的分区设置,因此此时退出程序还可以撤销。我们选择[Write]写入我们的设定,并确定该操作后(输入yes),选择[Quit]退出即可。
此时可以再运行fdisk确认我们的分区动作已经执行了:
sudo fdisk –l /dev/nandk
图:fdisk提示分区动作已生效
随后我们将该分区(/dev/nandk1)格式化为ext4文件系统:
图:将分区格式化为ext4文件系统
此时我们就可以使用mount进行挂载了,假设挂在到/media/internal目录上:
sudo mkdir –p /media/internal
mount /dev/nandk1 /media/internal
这样一来,目录/media/internal中的任何文件都会保存在MK802内部的flash空间上了,这个空间大小是大约是1.8G。我们可以用df –h命令来验证:
图:使用df –h验证内部flash空间已经挂载
可以看到最后一条显示,/media/internal目录属于/dev/nandk1设备,并且有1.7G空间。
大家可能会有疑问,前面介绍中MK802使用的是一整块nand flash芯片,为何现在会有那么多的分块设备?是否可以将这些分块设备合并起来组成一个完整的4Gb分块呢?
答案是肯定的。其实这里的分块也是人为设定的,分块的配置同样也保存在这个nand flash芯片上。在linux kernel启动时,由维护nand flash芯片操作的驱动读取分块配置,才有了现在看到的分块组织。
因此我们只要改写这个分块配置,就可以达到重新分块的效果。这部分的原理和操作将在本文后面的深入讨论部分介绍。
MK802这类基于ARM CPU的设备其实都可以超频,只是没有PC那样简单。这里将介绍将MK802 CPU频率从原先的1.008Ghz提高到1.104Ghz。虽然提高的比例幅度不高,只有100Mhz左右,但是从实际体验中,还是能明显感受到速度提高的。如果对MK802的性能有所不满,不妨做下尝试,这里也给出我做过超频修改过的hardware pack包,可以按照上一篇文章[4]中的操作,给自制系统加上。
http://www.csksoft.net/data/mk802/mk802_hwpack_cskbuild_overclock.7z
不过需要注意的是这样的超频可能会造成不稳定因素,毕竟官方将他的主频设定在现在的数值上也是经过周密考虑的。因此对于稳定性又要求的应用,请不要用这里的修改。
这里我们需要修改Linux kernel进行超频,kernel代码采用前一篇文章[4]采用的版本。由于Allwinner A10的kernel带有了cpufreq特性支持,并且代码本身其实已经支持更高频率的设定,因此这里的超频修改显得十分容易:
修改位于kernel代码树下arch/arm/mach-sun4i/cpu-freq/cpu-freq.h这个文件,定位到第43行,将SUN4I_CPUFREQ_MAX宏定义为目标频率1104000000即可:
图:修改cpu-freq.h提高频率变更上限
这个宏用于对于allwinner a10 cpu频率调节(cpufreq.c)模块中的最高频率上限的定义,也就是说cpu的主频可以达到这个宏所定义的频率。
至于为何是1104000000这样一个数值,主要是根据芯片时钟的PLL倍频和分频参数得来的,对于接触过数字电路时钟部分设计或者单片机时钟编程的朋友会比较熟悉这个话题。不过我们目前没有a10 cpu的手册,分频参数其实在kernel代码中找到的,见cpu-freq同目录下的cpu-freq-table.c
图:对于不同a10有效频率范围的定义
原来目前的linux kernel已经包含了对更高频率的定义支持,我们这里的选值正是通过这里的定义得出的。该代码还有更加高的频率可以选择,比如1.2G,我的测试显示,目前1.104Ghz已经是可以长期工作的最高频率了,更高的频率会导致系统运行中的随机crash。
在修改了上述代码后还不够,如果是按照上一篇文章介绍的方法编译的kernel,则需要重新设置config,将之前禁用的cpufreq支持重新开启:
图:增加CPU Freq的支持
随后重新编译kernel,并部署到SD卡上的系统,即可以使用超频了。
不过目前我给出的镜像会在系统系统后将CPU频率模式重新设置成ondemand governor[8]。该模式会随着CPU负载动态调节频率,因此大部分情况下我们设定的1.104Ghz频率并不会达到。为了强制CPU工作在最高频率,使用下面的命令:
echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
该命令将设置performance governor,此时查看cpu当前主频,已经显示工作在我们期望的1.104Ghz了。另外也可以通过/proc/cpuinfo得到验证:
图:验证CPU已经工作在超频
这里从CPU主频的角度考虑MK802的节能问题。这其实自然是主频越高,能耗越大了。因此如果需要节能,MK802的频率就应该降低。上文介绍的超频方案同时也开启了CPU动态调整的支持(CPU Freq)。同时例子中也展示了MK802在运行当中是可以动态改变主频的。
如果打算将MK802用于对能耗有所考虑的场合,则可以考虑加入cpufreq特性的支持,并将系统运行在ondemand的scaling governor上。这样CPU的主频将按当前的负载状态动态调整。而如果需要工作在最大频率,则使用performance governor。对应的,有powersaving governor可以将cpu主频设置到最低主频上。这可以用于实现系统休眠。
不过由于CPU主频调整本身也会消耗性能和时间,而且ondemand模式并不能非常有效的将主频调节到一个正好合适的地步。在实际使用中(主要是图形化桌面操作中),将发现采用了ondemand governor模式,系统会变得非常不流畅。
本文主要介绍的hack技巧就介绍到这里。在接下来的系列文章将开始介绍如何为MK802平台开发应用程序,并且会介绍一些会用到的工具。同样本文到到此并未结束,下边的部分是为想更进一步了解hack背后细节的朋友准备的。
这里将结合Kernel代码深入介绍上述每个hack的具体细节。这里我就假设读者具有相关的经验了,不过多介绍背景知识。
fex的binary形式文件首先会在uboot执行脚本阶段被加载到固定的内存地址上。这部分的代码可以参考uboot脚本mk20050pxd:
…
setenv boot_mmc 'fatload mmc 0 0x43000000 script.bin; fatload mmc 0 0x48000000 ${kernel}; if fatload mmc 0 0x43100000 uInitrd; then bootm 0x48000000 0x43100000; else bootm 0x48000000; fi'
setenv bootcmd 'run boot.scr setargs boot_mmc'
setenv bootdelay '3'
setenv console 'ttyS0,115200'
setenv extraargs 'rootwait'
…
上述代码将script.bin(fex脚本)加载到内存的0x43000000区域,随后uboot负责加载linux kernel并跳转至kernel的入口函数。
在Linux kernel启动的前期会执行arch/arm相关的平台初始化代码,其中位于arch/arm/mach-sun4i/sys_config.c文件为fex脚本的对应操作函数代码。其中包含了初始化过程,见LN:586:
int gpio_init(void)
{
printk("Init eGon pin module V2.0\n");
gpio_g_pioMemBase = (u32)CSP_OSAL_PHY_2_VIRT(CSP_PIN_PHY_ADDR_BASE , CSP_PIN_PHY_ADDR_SIZE);
#ifdef FPGA_RUNTIME_ENV
return script_parser_init((char *)(sys_cofig_data));
#else
return script_parser_init((char *)__va(SYS_CONFIG_MEMBASE));
#endif
}
其中,SYS_CONFIG_MEMBASE宏的定义:
#define SYS_CONFIG_MEMBASE (PLAT_PHYS_OFFSET + SZ_32M + SZ_16M)
刚好可以计算得到地址:0x43000000。也就是script.bin的加载地址。此时fex的操作函数库初始化完毕,该函数库提供了如下主要接口,供其他的驱动模块调用查询fex中的对应配置项:
int script_parser_fetch(char *main_name, char *sub_name, int value[], int count);
因此,只要搜索对函数script_parser_fetch的引用,就能明确整个kernel中对fex脚本的读取代码的位置,以及具体哪些fex项目会被真正生效。
图:通过查找对script_parser_fetch的引用,明确对应的驱动代码
这里以后文接着提到的framebuffer驱动为例,该驱动位于drivers/video/sun4i/disp/dev_fb.c。其中对于script_parser_fetch采用了一个wrapper函数:OSAL_Script_FetchParser_Data:
见LN:42:
图:framebuffer驱动对于fex配置读取的代码
这部分的代码将在该设备驱动的加载初始化被执行,从而完成对fex配置的读取和对应模式的设置工作。
上文已经交代了这里所说的framebuffer设备的驱动代码位置以及负责解析fex脚本的代码块。这里回到前文提出的问题,为何screen0_output_mode设置成9即可?
我们可以定位到LN:95:
if(OSAL_Script_FetchParser_Data("disp_init", "screen0_output_mode", &value, 1) < 0)
{
__wrn("fetch script data disp_init.screen0_output_mode fail\n");
return -1;
}
if(init_para->output_type[0] == DISP_OUTPUT_TYPE_TV || init_para->output_type[0] == DISP_OUTPUT_TYPE_HDMI)
{
init_para->tv_mode[0]= (__disp_tv_mode_t)value;
}
else if(init_para->output_type[0] == DISP_OUTPUT_TYPE_VGA)
{
init_para->vga_mode[0]= (__disp_vga_mode_t)value;
}
这部分代码即为对screen0_output_mode配置的读取,并按照screen0_output_type设置到对应的域中,这里我们真正关心的是tv_mode[0]对应类型__disp_tv_mode_t的定义:
typedef enum
{
DISP_TV_MOD_480I = 0,
DISP_TV_MOD_576I = 1,
DISP_TV_MOD_480P = 2,
DISP_TV_MOD_576P = 3,
DISP_TV_MOD_720P_50HZ = 4,
DISP_TV_MOD_720P_60HZ = 5,
DISP_TV_MOD_1080I_50HZ = 6,
DISP_TV_MOD_1080I_60HZ = 7,
DISP_TV_MOD_1080P_24HZ = 8,
DISP_TV_MOD_1080P_50HZ = 9,
DISP_TV_MOD_1080P_60HZ = 0xa,
DISP_TV_MOD_1080P_24HZ_3D_FP = 0x17,
DISP_TV_MOD_720P_50HZ_3D_FP = 0x18,
DISP_TV_MOD_720P_60HZ_3D_FP = 0x19,
DISP_TV_MOD_PAL = 0xb,
DISP_TV_MOD_PAL_SVIDEO = 0xc,
DISP_TV_MOD_NTSC = 0xe,
DISP_TV_MOD_NTSC_SVIDEO = 0xf,
DISP_TV_MOD_PAL_M = 0x11,
DISP_TV_MOD_PAL_M_SVIDEO = 0x12,
DISP_TV_MOD_PAL_NC = 0x14,
DISP_TV_MOD_PAL_NC_SVIDEO = 0x15,
DISP_TV_MODE_NUM = 0x1a,
}__disp_tv_mode_t;
这里贴出了该枚举型的定义(include/linux/drv_display_sun4i.h LN:171),可以看到加粗的一行:
DISP_TV_MOD_1080P_50HZ = 9,
这便是9对应的实际显示模式:1080p 50hz。同样,按照这个枚举定义表,还有更多的配置,诸如DISP_TV_MOD_1080P_24HZ_3D_FP(3D画面?)可以使用,这就等待大家挖掘了。对于上面的3D画面模式,我猜想一旦开启后,他会将原先对应2个显示器输出的framebuffer分别用于左右眼的画面,这个有待验证。
通过分析这些代码,我们还可以知道fex脚本中更多配置的含义和对应的配置值。因此给我们hack的空间还有很多。
可以通过之前提到的dmesg中的log追查到Nand Flash的驱动代码。它位于:drivers/block/sun4i_nand/。可以看出这是Allwinner专门设计的一个Nandflash驱动库。
从代码结构上看,这个驱动库完成了从NAND flash芯片基本的读写、擦出操作的坏块标注等高级的功能的实现,感觉还是比较高级的。
这里重点关注的是drivers/block/sun4i_nand/nfd/mbr.c这个文件,位于LN:126
for(part_cnt = 0; part_cnt < mbr->PartCount && part_cnt < MAX_PART_COUNT; part_cnt++)
{
if((mbr->array[part_cnt].user_type == 2) || (mbr->array[part_cnt].user_type == 0))
{
PRINT("The %d disk name = %s, class name = %s, disk size = %d\n", part_index, mbr->array[part_cnt].name,
mbr->array[part_cnt].classname, mbr->array[part_cnt].lenlo);
disk_array[part_index].offset = mbr->array[part_cnt].addrlo;
disk_array[part_index].size = mbr->array[part_cnt].lenlo;
part_index ++;
}
}
加粗代码就是产生之前提到的nand分块和对应标签名称log的部分。分析代码可以知道这些信息来自于全局结构mbr,而该结构体数据在该文件LN:82得到加载:
if(LML_Read((MBR_START_ADDRESS + MBR_SIZE*i)/512,MBR_SIZE/512,mbr) == 0)
从函数LML_Read的定义注释(sun4i_nand/src/logic/logic_ctl.c) :
Description: Read data from logic disk area to buffer
可以知道,这个函数的作用就是把(MBR_START_ADDRESS + MBR_SIZE*i)/512地址的flash空间数据读取到mbr指向的内存中,而mbr的结构定义是:
/* mbr info */
typedef struct tag_MBR{
__u32 crc32; // crc, from byte 4 to mbr tail
__u32 version; // version
__u8 magic[8]; // magic number
__u8 copy; // mbr backup count
__u8 index; // current part no
__u16 PartCount; // part counter
PARTITION array[MAX_PART_COUNT];// part info
__u8 res[MBR_RESERVED]; // reserved space
}MBR;
以及其中的PARTITION array的类型定义:
/* part info */
typedef struct tag_PARTITION{
__u32 addrhi; //start address high 32 bit
__u32 addrlo; //start address low 32 bit
__u32 lenhi; //size high 32 bit
__u32 lenlo; //size low 32 bit
__u8 classname[12]; //major device name
__u8 name[12]; //minor device name
unsigned int user_type;
unsigned int ro;
__u8 res[16]; //reserved
}PARTITION;
这里我们很明确的可以知道控制nand flash区块的数据存在地址,以及如何来修改它了:只要自己构造一系列PARTITION array对象,并会写到这段区域即可。
不过我发现其实这个工作社区中已经有人做了[7]。可以将它的代码做必要修改后并编译后,修改MK802 内部nand flash的分区表,这样就可以实现我们前面所说的区块合并了。
[1] Allwinner A10 - ARM Cortex A8 SoC
http://rhombus-tech.net/allwinner_a10/
[2] Mali (GPU)
http://en.wikipedia.org/wiki/Mali_(GPU)
[3] Allwinner A10 devices
http://wiki.xbmc.org/index.php?title=Allwinner_A10_devices
[4] 基于MK802 MiniPC的扩展开发应用--系统自制
http://www.csksoft.net/blog/post/mk802_dev_sysbuild.html
[5] 给MK802(USB大小的Android4.0小PC)引出串口信号,变成ARM开发版
http://www.csksoft.net/blog/post/288.html
[6] PuTTY: A Free Telnet/SSH Client
http://www.chiark.greenend.org.uk/~sgtatham/putty/
[7] 修改内部nand flash分区的工具(git commit)
https://github.com/amery/sunxi-tools/commit/c8ae7438a79e8973cf572b4cbf6206c658f8e943
[8] CPU Governors explained
http://forum.xda-developers.com/showthread.php?t=1736168