搭建ZYNQ Linux开发环境
搭建ZYNQ Linux开发环境
简介
ZYNQ Linux启动流程
-
片内ROM
功能:
第一阶段启动程序,是固化在芯片内部ROM的启动程序,主要负责加载第二阶段启动程序(FSBL)。在芯片复位后先采集启动模式管脚电平,根据启动模式从不同的物理接口尝试加载FSBL程序,如启动模式为SD卡启动,ROM程序就会去读取SD卡中第一个FAT分区中的BOOT.BIN文件到内存中,并开始执行FSBL;
涉及的程序:
无
-
FSBL
功能:
第二阶段启动程序,主要负责基本外设的初始化,如串口、内存DDR等,初始化完成后会加载下一阶段引导程序到内存并执行。FSBL可以由用户自定义修改,因此通常FSBL用来加载Uboot程序,也可以跳过Uboot阶段直接加载Linux内核并启动,减少启动时间。
涉及的程序:
FSBL程序通常有2种生成方式
- 在Vivado中调用PS IP并配置了各个接口后,导出硬件配置文件*.hdf或*.xsa,在SDK或Vitis开发软件中再根据上述导出的文件创建硬件平台工程和FSBL应用程序工程,最终生成FSBL.elf文件。如果启动模式为SD卡启动,后面还需要将FSBL.elf文件与其他文件合成BOOT.BIN文件放到SD卡FAT分区中,其他启动模式则需要将文件烧写到对于储存介质的起始分区中供片内ROM程序启动时读取。
- 在Vivado中调用PS IP并配置了各个接口后,导出硬件配置文件*.hdf或*.xsa,在SDK或Vitis开发软件再导入上述文件创建硬件平台工程,在平台工程中会生成ps7_init_gpl.c和ps7_init_gpl.h文件,这2个文件包含PS是的基本外设初始化程序。将这2个文件导入到Uboot源码中作为第一阶段启动代码,和Uboot源码一起编译得到一个包含FSBL的boot.bin和Uboot的uboot.img 2个程序的程序文件。
-
Uboot
功能:
通用引导加载程序,Uboot适配了各种SOC芯片的启动源代码,提供了一套标准的接口用于初始化芯片和板上外设,支持加载Linux、Android等操作系统内核。一般Uboot至少需要实现存储接口驱动用来加载Linux内核文件、实现串口驱动用来打印上电日志、实现网络驱动用来网络加载启动文件方便调试等。
涉及的程序:
Uboot源代码可以直接从Github获取,获取到源代码后需要修改源码配置和设备树适配自己的板子编译得到u-boot.img程序文件。Uboot源码主要由负责实现启动流程的代码和各种各样的驱动程序代码组成,驱动代码基本包含了所有常用的器件驱动或SOC片内外设驱动,移植的大部分工作主要就是根据实际板子的设计修改相关驱动的一些信息。比如在make menuconfig菜单中使能哪些驱动需要被编译进Uboot,在设备树文件中修改外设使用的GPIO、地址或使能等信息。
-
Linux
功能:
Linux只是一个操作系统内核程序,主要功能是负责上电后初始化外设(即使Uboot已经初始化了部分外设,Linux也会重新初始化一遍所有外设),然后开始运行根文件系统程序,为根文件系统程序提供一些必要的内核接口,用来操作底层硬件或进行任务调度。
涉及的程序:
Linux源码可以直接从Github获取,获取到源代码后需要修改源码配置和修改设备树以适配自己的板子,最终编译得到zImage或xxx.dtb文件。和Uboot一样,Linux源码中也包含了各种设备的驱动程序,移植主要工作还是修改源码配置哪些驱动被编译进内核,然后修改设备树文件调整外设设备的一些信息。
创建Vivado工程
-
打开Vivado2022.2创建新工程,ZYNQ建议使用Block Design的设计方式,在GUI中方便例化各种IP和调整模块之间的连接。如果使用到了PL部分的逻辑,需要生成比特流文件ps_axi_dma_wrapper.bit。
![image-20240817191319582]()
-
导出硬件配置文件ps_axi_dma_wrapper.xsa,注意老版本Vivado导出的文件可能叫xxx.hdf
![image-20240817191708973]()
生成FSBL
-
将上一步生成的xsa文件导入Vitis,创建硬件平台工程,生成ps7_init_gpl.c和ps7_init_gpl.h文件。
![image-20240817212457329]()
这里先不单独生成FSBL程序,只使用这里生成的初始化源文件合并到Uboot源代码中,在Uboot中生成boot.bin文件。
生成Uboot
编译ZYNQ的Uboot需要在Linux系统中使用跨编译器编译,首先需要有一个Linux环境,这里我是安装了一个UBUNTU20.04的虚拟机,后续操作都是在Linux中。
-
虚拟机环境配置
主要是安装源码管理、编译的依赖和跨编译器等安装包,使用以下命令安装:
sudo apt update sudo apt install git make gcc-arm-linux-gnueabihf flex bsion device-tree-compiler上面的软件包可能还不全,可以后面看编译时报错缺哪些软件包再安装。
-
获取源码
# 到想要保存的目录 cd xxx git clone https://github.com/u-boot/u-boot.git cd uboot -
将上一步生成的ps7_init_gpl.c、ps7_init_gpl.h文件添加到Uboot源码中
# 在 u-boot/board/xilinx/zynq文件夹下新建一个文件夹,这里我是zynq-zc701_spray mkdir board/xilinx/zynq/zynq-zc701_spray/ # 复制ps7_init_gpl.c到文件夹 cp xxx/ps7_init_gpl.c board/xilinx/zynq/zynq-zc701_spray cp xxx/ps7_init_gpl.h board/xilinx/zynq/zynq-zc701_spray -
新建设备树文件
基于已有的zynq-zc702.dts设备树文件,先复制一份作为后续使用的设备树,改名为zynq-zc701_spray.dts,注意设备树文件名需要和第3步新建的文件夹名一致
cp arch/arm/dts/zynq-zc702cp arch/arm/dts/zynq-zc702.dts arch/arm/dts/zynq-zc701_spray.dts修改新复制的设备树文件
修改设备树文件夹内的makefile文件,新增增加的设备树文件编译指令
...... dtb-$(CONFIG_ARCH_ZYNQ) += \ bitmain-antminer-s9.dtb \ zynq-zc701_spray.dtb \ zynq-cc108.dtb \ zynq-cse-nand.dtb \ ...... -
新建配置文件
基于已有的xilinx_zynq_virt_defconfig配置文件,先复制一份作为后续使用的配置文件zynq_zc701_spray_defconfig
cp configs/xilinx_zynq_virt_defconfig configs/zynq_zc701_spray_defconfig make zynq_zc701_spray_defconfig修改配置
make menuconfig修改设备树文件名为新增的设备树文件名,不带后缀
![image-20240817203636639]()
![image-20240817203755803]()
- 编译
# 指定编译器和架构 export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- # 编译 make -j32编译完成
![image-20240817212235593]()
在uboot/spl目录下生成了包含FSBL的文件boot.bin,在uboot目录下生成u-boot.img文件
-
烧写到SD卡
准备1张SD卡,格式化出一个FAT分区,分区大小建议尽量小一点,50MB就够了,这个分区主要存这里生成的boot.bin、u-boot.img和下一节生成的Linux内核镜像、Linux设备树几个文件。
将第6步生成的boot.bin和u-boot.img文件拷贝到SD卡FAT分区中,SD卡插入ZYNQ板子上,设置为SD卡启动模式,复位或重新上电就能看到FSBL(SPL)和Uboot的打印了
![image-20240817212953290]()
生成Linux和设备树
-
获取源码
# 切换到源码保存的目录 cd xxx # 使用xilinx维护的Linux源码仓库,驱动支持要好一点 git clone https://github.com/Xilinx/linux-xlnx.git cd linux-xlnx -
修改设备树
复制一份zynq-zc702.dts到zynq-zc701_spray.dts,后续修改基于这个设备树进行
cp arch/arm/boot/dts/xilinx/zynq-zc702.dts arch/arm/boot/dts/xilinx/zynq-zc701_spray.dts修改设备树文件夹下的makefile文件
...... # SPDX-License-Identifier: GPL-2.0 dtb-$(CONFIG_ARCH_ZYNQ) += \ zynq-zc701_spray.dtb \ zynq-cc108.dtb \ ...... -
修改驱动配置
复制一份xilinx_zynq_defconfig到xilinx_zynq7010_srpay_defconfig,后续配置使用这个配置文件,这儿先使用默认配置
cp arch/arm/configs/xilinx_zynq_defconfig arch/arm/configs/xilinx_zynq7010_srpay_defconfig make xilinx_zynq7010_srpay_defconfig -
编译
# 指定编译器和架构 export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- # 编译 make -j32编译完成后在linux-xlnx/arch/arm/boot目录下生成Linux系统文件zImage,在linux-xlnx/arch/arm/boot/dts/xilinx目录下生成设备树文件zynq-zc701_spray.dtb
![image-20240818010512370]()
-
烧写到SD卡
将上一步生成的Linux镜像文件和设备树文件复制到SD卡的FAT分区里,SD卡插入板子启动,启动时快速敲几个回车打断Uboot启动,进入Uboot命令行模式,在Uboot中设置启动Linux的命令
# 设置设备树名称 setenv deviceTreeName 'zynq-zc701_spray.dtb' # 设置从SD卡启动行为:先从SD卡FAT分区中加载zImage文件到0x1000000地址,再加载设备树文件到0x3000000地址,再使用bootz命令启动Linux setenv boot_sd 'fatload mmc 0:1 0x1000000 zImage; fatload mmc 0:1 0x3000000 $deviceTreeName; bootz 0x1000000 - 0x3000000' # bootcmd为uboot正常启动时执行的命令,设置为运行上面设置的sd卡启动命令 setenv bootcmd 'run boot_sd' # 设置启动命令参数,主要指定了打印串口为ttyPS0,根文件系统路径为SD卡第2个分区,IP通过DHCP分配 setenv bootargs 'console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4 ip=dhcp::eth0' # 保存设置的命令 saveenv -
重新复位或上电启动
Starting kernel 后续的打印就是Linux启动过程的打印了。
![image-20240818015241771]()
这里还只有Linux内核没有根文件系统,因此最后Linux启动最后会报错kernel panic卡在找不到根文件系统
![image-20240818020335654]()
创建根文件系统
根文件系统才是我们平常使用的Linux系统的主要部分,提供了Linux中的各种命令,如cd、ls、apt、图形界面等,常用的根文件系统有busybox、ubuntu、Debian等,busybox只提供了基本的命令,但体积很小,常用在储存空间较小和功能简单的地方。Ubuntu和Debian提供了apt命令,方便安装各种其他软件,这里我们就以Ubuntu文件系统为例,制作一个Ubuntu18.04根文件系统。
制作过程还是在虚拟机中进行
-
下载Ubuntu根文件系统
# 安装相关依赖 sudo apt-get install debian-archive-keyring gcc-aarch64-linux-gnu bison flex bc build-essential libncurses* libssl-dev sudo apt-get install qemu qemu-user-static binfmt-support debootstrap # 切换到根文件系统保存目录 cd xxxx # 使用qemu-debootstrap下载armhf架构的ubuntu18.04版本根文件系统,同时安装相关软件 sudo qemu-debootstrap --arch armhf --variant=minbase --include=whiptail,ca-certificates,tzdata,vim,network-manager bionic ubuntu-armhf http://mirrors.ustc.edu.cn/ubuntu-ports/执行完后会新增一个ubuntu-armhf文件夹,里面包含Ubuntu系统需要的所有文件。下一步就是定制化系统,包括设置用户名、安装其他需要的软件包等。
-
挂载下载的根文件系统到本地Linux,进行添加用户、修改密码等操作
在本地Linux下新建一个脚本方便挂载根文件系统到虚拟机,命名为mount.sh,脚本内容如下:
#!/bin/bash # function mnt() { echo "MOUNTING..." sudo mount -t proc /proc ${2}proc sudo mount -t sysfs /sys ${2}sys sudo mount -o bind /dev ${2}dev sudo mount -o bind /dev/pts ${2}dev/pts echo "CHROOT..." sudo chroot ${2} echo "Success!" } function umnt() { echo "UNMOUNTING" sudo umount ${2}proc sudo umount ${2}sys sudo umount ${2}dev/pts sudo umount ${2}dev } if [ "$1" == "-m" ] && [ -n "$2" ] ; then mnt $1 $2 elif [ "$1" == "-u" ] && [ -n "$2" ]; then umnt $1 $2 else echo "" echo "Either 1'st, 2'nd or both parameters were missing" echo "" echo "1'st parameter can be one of these: -m(mount) OR -u(umount)" echo "2'nd parameter is the full path of rootfs directory(with trailing '/')" echo "" echo "For example: ch-mount -m /media/sdcard/" echo "" echo 1st parameter : ${1} echo 2nd parameter : ${2} fi对脚本增加执行权限
sudo chmod +x mount.sh执行脚本,带参数-m,进入制作的根文件系统
sudo ./mount.sh -m ubuntu-armhf/![image-20240818023144710]()
设置更新源
编辑/etc/apt/source.list文件,修改为以下内容
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse # 以下安全更新软件源包含了官方源与镜像站配置,如有需要可自行修改注释切换 deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse # deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse # 预发布软件源,不建议启用 # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-proposed main restricted universe multiverse # # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-proposed main restricted universe multiverse安装软件
apt update apt install sudo vim iputils-ping net-tools network-manager添加用户
# 添加用户 adduser username # 添加用户到sudo用户组,可以使用sudo命令 usermod -a -G sudo username # 设置新增加的用户密码 passwd username # 设置root密码 passwd root修改/etc/resolv.conf文件
# 删除原来的文件 rm /etc/reslov.conf # 重新创建一个链接指向lib文件夹里的reslov.conf文件 ln -s /lib/system/resolv.conf /etc/resolv.conf # 修改/etc/reslov.conf文件,添加一行 nameserver 8.8.8.8完成后使用exit命令退出挂载,再使用mount.sh脚本退出
exit sudo ./mount -u ubuntu-armhf/ -
在之前的SD卡FAT分区之外再新增一个EXT4格式的分区,将之前做好的根文件系统所有文件复制到SD卡
# 复制到SD卡EXT4分区,sd_card_path替换为实际SD卡分区路径 sudo cp -rfp ubuntu-armhf/* sd_card_path/ # 卸载SD卡 sudo umount sd_card_path -
将SD卡插入ZYNQ板子并启动
因为增加了根文件系统,Linux启动时不再报错找不到根文件系统,继续向下执行进入Ubuntu系统
![image-20240818024220906]()
Ubuntu系统最后提示登录用户,输入之前设置的用户名和密码后成功进入系统
![image-20240818024428705]()
到这里整个流程就结束了,后面可以自己根据需求使用apt安装其他软件了。
制作完整SD卡烧写镜像
之前的步骤都是一步一步生成需要的文件并单独复制到SD卡,如果更换SD卡或复制系统到其他新板子上很不方便。可以把所有文件都打包到一个镜像文件中,则以后只需要烧写一个镜像文件到SD卡就行了。
-
先划分出一块空白镜像文件,大小比根文件系统总大小大一点就行,一般1GB就够了
sudo dd if=/dev/zero of=./zynq-ubuntu18.img bs=1M count=1024 -
对进行文件分区,创建一个FAT分区和EXT4分区
# 使用losetup命令把镜像文件挂载到系统 sudo losetup --partscan --find --show ./zynq-ubuntu18.img命令执行完会返回一个设备,我这里是/dev/loop15,后面使用fdisk命令对该设备分区
# /dev/loop15替换为实际返回的设备名字 sudo fdisk /dev/loop15 # 进入fdisk后使用m命令新建分区分区完成后如下,可以看到有2个分区,一个大小为49MB,另一个为973MB
![image-20240818025322016]()
分别格式化2个分区为FAT格式和EXT4格式
sudo mkfs.vfat /dev/loop15p1 sudo mkfs.ext4 /dev/loop15p2 -
将Uboot、Linux镜像和设备树复制到FAT分区,把根文件系统复制到EXT4分区
# 挂载FAT分区,把Uboot、Linux镜像和设备树复制到分区 sudo mount /dev/loop15p1 /media sudo cp boot.bin /media sudo cp zImage /media sudo cp zynq-zc701_spray.dtb /media sudo umount /media # 挂载ext4分区,把根文件系统文件复制到分区 sudo mount /dev/loop15p2 /media sudo cp -rfp ubuntu-armhf/* /media sudo umount /media卸载上面生成的loop设备
sudo losetup -d /dev/loop15通过上述步骤就生成了一个完整的SD卡启动镜像文件zynq-ubuntu18.img文件,可以用于直接烧写到其他SD卡
-
烧写到新SD卡
在Linux下可通过dd命令烧写。
# 把/dev/sdb换成实际SD卡对应的设备 sudo dd if=zynq-ubuntu18.img of=/dev/sdb bs=1M sync也可以在Windows下通过其他烧写软件写入到SD卡中。
调试环境搭建
上面的步骤每次都是编译好程序后手动复制到SD卡再启动,在调试程序时可能需要反复编译、烧写和启动比较麻烦,在开发调试时可以使用网络加载,让板子启动后直接从本地虚拟机里面加载各种文件甚至根文件系统。
网络加载Linux内核和设备树
-
首先需要在开发虚拟机中开启tftp服务器,服务器需要安装tftp-hpa软件
sudo apt install tftpd-hpa然后设置tftp服务器分享的目录,修改/etc/default/tftpd-hpa文件中的TFTP_DIRECTORY
# /etc/default/tftpd-hpa TFTP_USERNAME="tftp" TFTP_DIRECTORY="/var/lib/tftpboot" TFTP_ADDRESS=":69" TFTP_OPTIONS="--secure"启动tftpd服务
sudo service tftpd-hpa start # 使用如下命令查看服务状态 sudo service tftpd-hpa status![image-20240818134208685]()
出现running代表服务启动成功
将Linux内核文件zImage和设备树文件.dtb复制到前面指定的目录即可,也可以直接设置tftp目录为源码中arch/arm/boot目录,这样每次编译完后就不需要手动复制了。
-
在板子中设置从虚拟机加载linux内核和设备树的命令,主要是使用tftpboot命令从虚拟机获取文件
要使用tftpboot需要先配置板子和虚拟机的ip地址,通过设置环境变量来设置
# 板卡ip setenv ip 192.168.1.10 # 服务器ip setenv serverip 192.168.1.56 # tftp加载文件命令 setenv boot_tftp 'tftpboot 0x1000000 zImage; tftpboot 0x3000000 xxx.dtb; bootz 0x1000000 - 0x3000000' # 设置默认启动模式为tftp加载启动 setenv bootcmd 'run boot_tftp' # 保存命令 saveenv这样以后上电启动就会从tftp加载并启动Linux
![image-20240818135136623]()
使用网络根文件系统
之前制作的Ubuntu根文件系统需要手动复制到SD卡后才能使用,Linux也支持使用网络上的NFS文件系统。
-
设置虚拟机开启NFS服务器
sudo apt install nfs-kernel-server nfs-common -
设置网络共享文件夹,编辑文件/etc/exports,添加虚拟机中根文件系统的路径
# /etc/exports: the access control list for filesystems which may be exported # to NFS clients. See exports(5). # # Example for NFSv2 and NFSv3: # /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check) # # Example for NFSv4: # /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check) # /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check) /home/jch *(rw,sync,insecure,no_root_squash,no_subtree_check) -
启动nfs服务器
sudo service nfs-kernel-server start # 查看服务器状态 sudo service nfs-kernel-server status -
ZYNQ板卡设置环境变量,指定根文件系统路径为虚拟机中根文件系统文件夹的路径
setenv bootargs 'console=ttyPS0,115200 root=/dev/nfs rw rootwait nfsroot=192.168.1.56:/home/jch/Documents/Code/rootfs/ubuntu-armhf ip=dhcp::eth0' saveenv![image-20240818140318724]()
启动后可以看到根文件系统路径已变成虚拟机中的路径


















浙公网安备 33010602011771号