0x1. 简单说几句
前几天某宝买了一个海思3516DV300小板子,打算玩玩它的NNIE,这玩意1T的算力还算不错了,值得玩下。但是买来之后发现被坑了,店家给刷写的程序不带那个telnetd,只能使用串口连接上去,但是那个板子带一个店家给的摄像头应用,它开机自启还开了看门狗。串口登录上去,会不停打印日志,停了之后又自动重启。这就非常难受。和店家问了下,只能自己从头开始搞它的固件了,作为一个算法工程师(划掉,实际是搬砖工),我用我为数不多的嵌入式知识踩了不少坑,所以打算写点啥记下来。
店家给了Uboot、内核的uImage、rootfs,由于这个板子硬件是店家开发的,需要对uboot和内核做修改,所以,这里直接用他们提供内核还有uboot,然后偷梁换柱,换个自己编译的rootfs上去。
0x2. 准备
1.海思SDK
首先,得有海思的SDK,这里需要海思的编译器arm-himix200-linux-*
,并在终端里已经配置了环境变量。编译海思rootfs需要的文件,在 Hi3516CV500R001C02SPC020.rar这个压缩包里
解压了这个压缩包后,执行里面的sdk.unpack脚本。海思3516DV300和3516CV500有很多东西是相同的,所以在这个SDK里面,可以看到很多CV500的东西,但是实际上也给DV300用,这并不奇怪。
2. 安装依赖、配置环境
安装一些依赖,我这里使用的是Ubuntu 18.04的环境。需要安装如下的包,这些直接用apt-get install
来安装就可以了。
zlib1g-dev liblzo2-dev uuid-dev pkg-config libuuid1 bison
mtd-utils u-boot-tools libncurses5-dev libcrypto++-dev
binutils uuid uuid-dev gpref make gcc g++
海思的编译,有一个坑,是文档里需要安装的依赖库和实际上需要的并不相同
我很不明白,为啥,编译一个rootfs,还要装TeX的工具?难道是编译的时候生成文档?还是要编译的时候去写论文?我没装这些也编译过了啊?还有一个,就是编译gdb等海思芯片工具的时候,有些依赖库需要安装,但是并没有在文档里写,比如bison和gpref。
3. 基础的嵌入式Linux知识
这里需要对rootfs有一点点了解。
如上图(摘自海思文档Hi3516CV500╱Hi3516DV300╱Hi3516AV300 开发环境用户指南),rootfs包含的内容就是这样的。嵌入式Linux系统启动的时候,会从这个文件系统中,调用init这个程序,init作为Linux系统启动的第一个进程,会从这里fork出很多其他的进程来。Linux系统的启动过程可以分为5个阶段:内核的引导、运行 init、系统初始化、建立终端、用户登录系统。在嵌入式环境下,由uboot来加载内核,然后内核加载完成后,运行init,这样就完成了系统的启动。
在这里,我们是用busybox来完成rootfs的编译的。busybox是一个集成了一百多个最常用linux命令和工具的软件,它集成了init、shell,甚至还集成了一个http服务器和一个telnet服务器,而所有这一切功能却只占了很小的空间。
0x3. rootfs编译与打包
1. Makefile的全部编译目标
这里,我们看海思提供的Makefile,在osdrv下的这一个。我们可以看下,这个Makefile可以编译什么东西。在刚才的make all 的过程中,可以看到,整个系统的编译是分为几个部分的。这里,我们分别来看每个部分。
其中,红圈是我们需要的,看名字就可以知道它是干啥的了,通过hiboardtool、hipctool是编译我们需要板子、电脑上的工具,包括在板子上调试代码的gdb、电脑上打包rootfs用的mkyaffs2image100等;hibusybox是编译busybox的,hirootfs是打包编译完成的busybox到可用的rootfs镜像的。
2. 按照官方说明编译全部内容(可选)
这一步的目的是为了了解海思编译的过程,然后,后面编译工作可以更顺利,当然也可以手动执行这些操作。此外,通过make all可以检查依赖库是否安装正确,然后编译出我们需要的工具来,这样后面就不用再考虑工具的事情了。
网上有些博主说,直接在海思osdrv那个文件夹下执行make all,会有坑,容易编译不过。其实,这都是海思文档的锅,那个文档并不全面。在osdrv下的那个Makefile文件,里面内容非常多,不过有大坑,如果可以读懂那个Makefile,并做出合适的修改,后面构建rootfs等操作就会非常顺利。
这里,按照那个readme_cn.txt,下载了那些开源代码的源码文件,放到相应的位置,就可以开始编译了。直接执行make all就可以了,这样默认编译出来的是3516DV平台的文件。这里首先跑一遍make all,来编译全部的东西,包括电脑上用的那些工具,后面就可以只编译一部分了。我的电脑是AMD R5 2600X的CPU,编译一次大约10分钟。如果报错,那就是的问题。
完成编译后,可以看到已经按照默认配置生成了一些rootfs文件、uImage还有uboot文件。这个uImage和uboot是没修改过的,不一定可以用。这里只看它生成的rootfs文件。
此外,在osdrv/pub/bin下,会编译出板子和PC上用的工具,包括镜像制作工具、GDB调试工具等,在hi3516dv300_spi_smp_image_glibc下,是编译出的uboot、uImage和rootfs。
3. 修改rootfs然后编译
如果用make all编译出的rootfs镜像,是不方便的,因为这里没把NNIE之类的驱动打包进去,同时,并没有为板子上设置/tmp分区,而且,编译的busybox里面有相当多的冗余的东西。所以这里需要对编译的过程进行修改,然后手动编译并将驱动放进去。
我们的目标是build出带有驱动、相关配置的可以直接用的rootfs来。这里我们先看busybox的部分。
看红圈的部分,有没有感觉不太对?这是海思的另一个坑,编译完了,做出修改之后重新编译,相当于没修改。所以,我们手动编译这些东西。此外,使用这个编译出来的rootfs,busybox是有不少多余的东西的,通过手动配置,可以更加精简。这里,我们把红圈的内容都注释掉,然后在
osdrv/opensource/busybox/busybox-1.26.2
下,直接执行cp config_v200_a7_softfp_neon .config && make menuconfig
,然后进入配置界面。这里,我把Print Utilities下的所有内容关了,把shells下的hush关了,这些可以根据自己的想法来配置。配置完成后保存并退出,再执行
make -j && make install
来编译busybox,这时候,编译的结果在_install文件夹下,我们已经完成了busybox的编译。
4. 添加驱动、配置启动设置
系统在启动的时候,需要加载驱动,然后配置网络,所以需要修改配置项,然后让系统可以自动完成驱动加载等任务。
这里,我只需要开发NNIE的应用,可能用到TDE、IVE等模块。驱动在这里
海思提供了一个rootfs的框架(也可以看做是一个类似于模板的东西吧),我们可以在这个的基础上进行配置,把编译的busybox复制进去(实际上make all的时候,也是使用的这个模板)。这里,我们要在这个模板的基础上,把驱动和相关配置添加进去。
在hirootfs_prepare的这里,把
rootfs_scripts/rootfs.tgz
的内容解压到pub这里了,而这个rootfs.tgz,就是海思提供的rootfs的模板。海思提供的Makefile会把这个rootfs.tgz解压,然后复制到目标位置,那么我们就仿照着它,把驱动、配置也都复制到目标位置。把ko文件夹复制到osdrv/rootfs_scripts下。在makefile的第313行添加如下内容,这样,就可以在打包rootfs的时候把驱动打包进去。
有了驱动,还需要在系统启动的时候加载驱动。所以这里需要设置启动脚本。解压rootfs_scripts/rootfs.tgz,然后把其中的/etc文件夹移动到rootfs_scripts文件夹下。修改etc/init.d/rcS,在文件结尾添加3行,具体内容如下
#! /bin/sh
/bin/mount -a
echo "
_ _ _ _ _ _ _ _ _ _ _ _
\ _ _ _ _ _ ___
/ /__/ \ |_/
/ __ / - _ ___
/ / / / / /
_ _ _ _/ / / \_/ \_ ______
___________\___\__________________
"
for initscript in /etc/init.d/S[0-9][0-9]*
do
if [ -x $initscript ] ;
then
echo "[RCS]: $initscript"
$initscript
fi
done
cd /komod
./load3516dv300 -i -osmem 512 -total 1024
telnetd &
以上三行命令含义为,进入/komod加载驱动,加载时设置DV300板子有1G的内存,其中为系统分配512MB,剩余的为MMZ内存,最后启动telnetd服务,这样可以使用telnet来连接板子。
除了加载驱动,这时候板子是没有设置网络的,需要为板子设置一个网络连接。这里使用的是静态IP。在osdrv/rootfs_scripts文件夹下,修改etc/init.d/S80Network文件。打开这个文件,修改前面几行即可。
#!/bin/sh
ipaddr=192.168.1.123
bootp=
gateway=192.168.1.1
netmask=255.255.255.0
hostname=
netdev=eth0
autoconf=
如果想使用/tmp分区,则需要在etc/fstab中设置tmp挂载。在osdrv/etc/fstab中,添加这一行
tmpfs /tmp tmpfs defaults,size=20m 0 0
以上配置完成后,同样需要在构建rootfs镜像时把配置文件添加进去。编辑osdrv/Makefile,在第314行添加如下内容:
完成以上内容后,在make hirootfs_prepare的时候就会自动完成相关文件的添加。
5.打包rootfs
在上面,我们已经完成了busybox的修改与编译,也完成配置驱动、运行库、环境变量等工作了。这时,使用上面得到的内容来打包rootfs。
由于上面已经在makefile里把重新编译busybox的内容注释掉了,在make hibusybox的时候会跳过重新编译而只是把之前手动编译的结果复制到目标位置,所以这里我们可以直接使用这个makefile来完成打包。
在Makefile的第402行左右,可以找到打包的命令。
在这里,我们可以发现,海思提供的makefile编译rootfs,实际上分了5个步骤:准备、编译busybox、编译板子的工具、编译PC的工具、打包rootfs,这个hirootfs_notools_build里,就是调用mkyaffs2image100之类的工具来打包镜像。在上面make all的时候,已经完成了工具的编译,所以,我们在第445行附近,仿照hirootfs_build添加一行,让它不编译工具,只进行复制文件、打包的操作:
然后执行
make hirootfs_pack
,这样,就可以看到,我们需要的rootfs镜像已经生成了。在这一步中,是打包一个文件夹里的文件到rootfs镜像,然后这个文件夹里的内容又被压缩到pub/rootfs_glibc.tgz中,最后这个文件夹被删掉,可以解压这个压缩包看下里面有什么东西。
完成以上步骤后,就得到了海思DV300上使用的rootfs。最后一步,就是把它刷到板子上。
0x4. rootfs的刷写
我买的这个板子,配的是128MB的nand flash,型号为W25N01G,页大小为2K,ECC为4bit。
这个板子的rootfs使用的是yaffs2的分区格式,正好,海思的工具已经生成了2k4bit的yaffs格式的rootfs文件,直接把它刷进去就行。
刷固件需要使用tftp和串口。在Linux上,串口可以使用picocom或者minicom。这里我使用的是picocom。运行
sudo picocom /dev/ttyUSB0 -b 115200
即可打开串口。这里tftp使用的是tftpd-hpa。tftp服务器的配置参考这个文章http://blog.chinaunix.net/uid-23065002-id-5203089.html把上面的rootfs文件放到tftpd文件夹下,然后打开串口,给板子上电,随便按键进入uboot命令行。执行
printenv
,看下当前uboot的环境设置。可以看到,uboot的启动参数如图,这里有配置启动的时候nand flash的分区设置、内存划分、串口终端等信息,也配置了从flash加载数据内存的地址和大小。
参考nand命令,可以判断出,启动时从flash中,跳过1MB后读取了3MB的内容到内存的0x81000000,这就是加载内核。结合bootargs和bootcmd,我们知道了flash的rootfs写入偏移量为1MB+3MB,既0x400000。
现在可以开始刷写rootfs。首先从内存中清空一块空间,用于保存tftp下载的rootfs文件,这块空间一定要比下载的rootfs文件大。我这里rootfs是20.7MB,我准备了25MB来存rootfs。然后再写入,写入的时候也是先从nand flash中把相应的空间擦除,然后再写。命令如下:
mw.b 81000000 ff 2500000;tftp 0x81000000 rootfs_hi3516dv300_2k_4bit.yaffs2
nand erase 400000 2500000;nand write.yaffs 81000000 400000 14a8400 #注意:14a8400为rootfs文件实际大小(16进制)
红色框即为rootfs文件16进制的大小。
使用以上操作即可完成海思平台的rootfs编译、驱动的加载、IP配置等操作。有的时候,如果遇到找不到init导致的kernel panic,那么,可以把整个nand flash清空,然后重新写uboot、uImage、rootfs,当然,这个操作就比较危险了,如果真的把uboot写没了又没及时恢复进去,那么只能用HiTool的HiBurn来写了。
每个店家卖的板子都不一样,我的板子是这个配置的,当然也有emmc等存储芯片的板子,这个就需要参考海思的官方文档来写入了,当然,编译rootfs的方法都是一样的。
最后放两张重新编译打包的rootfs在板子上跑的截图