ZYNQ-7000芯片用u-boot启动linux系统方法
ZYNQ-7000芯片用u-boot启动linux系统方法
0. 芯片启动概述
- ZYNQ-7000系列芯片运行Linux操作系统需要BOOT.BIN文件、image.ub文件和rootfs。
- BOOT.BIN 文件由fsbl.elf、bitstream和u-boot.elf(裸机elf程序)文件组成。 fsbl.elf 是由xilinx设计的,由OCM加载执行,有两个主要功能。第一是用于加载bitstream到PL,第二是根据BOOT.BIN文件组成,执行u-boot或是裸机elf程序。 bitstream 文件是FPGA的配置文件。裸机elf程序用于在不启动操作系统的情况下运行一些软件。 u-boot.elf 是一种bootloader程序,可以根据不同的硬件情况,在不同的场景下引导操作系统镜像的执行。
- image.ub 文件是由操作系统的镜像文件uImage和设备树文件dtb组成。uImage由压缩过的操作系统镜像zImage和一段由u-boot在引导时会读取的头image_header_t(64B)组成,这个头用于储存u-boot在引导系统时所要获取的一些信息。zImage由未压缩的内核镜像vmlinux和解压代码组成。在u-boot引导内核时,会解压zImage到内存中执行。
- rootfs 是linux的根文件系统,在linux系统启动过程中,必须要挂载这样一个根文件系统。我们的可执行程序以及所用到的库文件、linux系统的配置文件等,都会存储在这样一个根文件系统中。
1. SD卡启动
SD卡启动的最大优点就是方便可携带, SD卡被分为两个分区。一个FAT32分区存放BOOT.BIN和image.ub文件,一个ext4分区存放rootfs。
zynq-7000有两个SD控制器,在blockdesign里配置zynq核时,根据硬件原理图显示(SD连到了PS端,电路接口与zynq的MIO40-45相连),选择SD0并选择正确的MIO口。
输入
petalinux-config --get-hw-description -p ./
配置硬件。选择SD0作为首选SDIO。
选择SD作为BOOT.BIN的存储介质。
默认FLASH作为u-boot环境变量的存储介质,u-boot会根据下面的FLASH分区表到指定的位置读取环境变量。忽略除bootenv外的其它部分,因为FLASH中根本就没有这些部分,它们都在SD卡上。在这里只会用到FLASH中的bootenv。
根据前面所分析,内核镜像当然在SD卡上喽。
下面还有能设置文件系统的存储介质,这里我们不管它,因为我们不从FALSH加载文件系统,不对它进行设置。那我们在哪设置呢?
在如下目录,设置文件系统的类型和位置,我们的文件系统在SD卡的第二分区,所以选择文件系统类型为SD,设备节点设置为
/dev/mmcblk0p2
。可以看到,有这么多种文件系统类型。从上到下依次为,内存文件系统、ramdisk文件系统、闪存文件系统、网络文件系统、SD文件系统。
我们的设备树和系统镜像是绑定的,所以如下设置设备树。
输入
petalinux-config -c u-boot
配置u-boot支持的启动内核方式,内核在SD卡中,所以这里是从SD启动。如上所示,从SD卡启动已配置完成,输入
petalinux-build
编译内核即可。我们用petalinux配置完后,petalinux会根据我们的配置生成u-boot的环境变量,进入u-boot后输入
printenv
查看环境变量。下面我简要分析一下u-boot的环境变量。
bootcmd=run default_bootcmd
bootcmd变量用于启动内核,在u-boot中输入run bootcmd
即可启动内核。default_bootcmd=run cp_kernel2ram && bootm ${netstart}
cp_kernel2ram=mmc dev 0 && mmcinfo && fatload mmc 0 ${netstart} ${kernel_img}
cp_kernel2ram变量用于将内核加载到内存中,mmc dev 0
用于切换到设备mmc0,mmcinfo
用于打印mmc的信息,fatload
用于从FAT32文件系统中加载文件到内存的给定位置。netstart=0x10000000
给定了加载到的内存位置,kernel_img=image.ub
指定了文件的名称。bootm
用于从指定的内存地址启动系统镜像,在这里即从加载到的位置启动镜像。在系统启动过程中我们看到,挂载了SD卡上的rootfs。
2. QSPI_FLASH 启动
QSPI_FLASH 启动时, FLASH被分为四个分区。partition0存放BOOT.BIN文件,partition1存放u-boot的环境变量,partition2存放image.ub,partition3存放文件系统。
一般情况下,当应用程序需要使用一些库,如Qt库时,文件系统所占空间会比较大。当FPGA逻辑部分比较复杂时,bitstream文件也会比较大,故BOOT.BIN文件会很大。我的硬件FLASH只有32MB,在这种情况下是肯定放不下的。所以一般不使用这种方式。
下面我简要介绍一下这种方式的实现。
QSPI_FLASH启动时,我们要用到FLASH芯片了,我们要在blockdesign里配置QSPI用于读写FLASH。我们使用的核心板上两块FLASH芯片并行连接成8bit宽,每个FLASH为4bit宽,即4线spi(QSPI)。
我们要根据每个文件的大小对FLASH的分区进行设置,一般情况下,FLASH是放不下这些文件的。每个分区大小设置如下,其实已大于FLASH的总大小32MB。下面估计每个文件的大小:
Boot.BIN 17.9MB 分配18MB 0x1200000
bootenv 分配128KB 0x20000
image.ub 3.7MB 分配4MB 0x400000
jffs2 分配8MB 0x800000 实际14.1MB
我们看到,32MB的FLASH根本放不下!!!!!
BOOT.BIN,linux内核及文件系统都在FLASH里。
文件系统类型选择jfss2闪存文件系统。
同样,U-boot选择从FLASH启动linux内核。
镜像编译完成后,我们反编译设备树,可以看到FLASH的分区如下所示,已经超出了FLASH的32MB存储范围。
用JTAG将BOOT.BIN下载至FLASH后,在u-boot启动后打印环境变量。
下面简要分析一下该环境变量。
bootcmd=run default_bootcmd
,default_bootcmd=run cp_kernel2ram && bootm ${netstart}
如前文所述,不再叙述。cp_kernel2ram=sf probe 0 && sf read ${netstart} ${kernelstart} ${kernelsize}
cp_kernel2ram
用于将内核镜像从存储介质复制到内存执行。sf probe 0
用于初始化与给定SPI总线相连接的FLASH设备。sf read
用于从给定的内存地址读取数据并保存到FLASH上。netstart=0x10000000
为镜像加载的内存地址,kernelstart=0x1220000
为给内核分配的FLASH起始地址,kernelsize=0x400000
为给内核分配的FLASH存储空间。如下图,设置板子和主机的ip地址,连接以太网并打开tftp服务器,通过通过tftp下载image.ub和jfss2到FLASH中,并启动内核。注意到,内核不一定能启动成功,因为FLASH放不下jffs2。
set serverip 192.168.10.124
set ipaddr 192.168.10.1
tftp 0x10000000 image.ub
sf probe 0
sf erase 0x01220000 0x400000
sf write 0x10000000 0x01220000 0x400000
tftp 0x10000000 rootfs.jffs2
sf erase 0x1620000 0xd6e770
sf write 0x10000000 0x1620000 0xd6e770
sf erase 0x1620000 0x800000
sf write 0x10000000 0x1620000 0x800000
可以看到,完整的jffs2根本就无法下载到FLASH中,只能将不完整的jffs2文件系统下载到FLASH中。
现在,FLASH中已经有BOOT.BIN,image.ub,jffs2了,重启后,u-boot会从FLASH中读取内核并挂载jffs2,可能启动失败,因为文件系统不完整!!
我们看到,在系统启动过程中,打印了许多jffs2错误和空间不足等信息,这是我们预料之中的。最神奇的是,我们竟然能登录进入系统。不得不说,Linux真的牛逼!!虽然系统能够进去,但使用肯定是不正常的,因为外存空间不足了。
大家最好还是别用这种方式了,如果有EMMC的话,它不香吗?
3. QSPI_FLASH + EMMC启动
在项目中,我一般使用这种方式,QSPI_FLASH + EMMC启动最大的优点是EMMC芯片稳定、可靠性高、能适用于震动等环境相对恶劣的场合。
QSPI_FLASH + EMMC启动时,BOOT.BIN文件存放在FLASH中,大容量(8GB)EMMC芯片被分为两个分区,第一个FAT32分区存放image.ub,第二个ext4分区存放rootfs。
我所使用的开发板EMMC接在了PL端,PS端通过EMIO接口路由到PL端,我们要在PL端进行约束,从而使得PS能与EMMC通信。(关于MIO与EMIO可见 )