0、前言
由于项目需要被临时委派进行ZYNQ开发板ARM核的Linux系统移植,在此之前没有接触过任何ZYNQ相关的任何内容,可以说是从0开始给ZYNQ移植系统。
由于该ZYNQ开发板不是商业开发板,资料不全,再加上网上对于Zynq的Linux系统移植教程不够详细(在2024.2版本下),所以只能自己一点点摸索着做,好在花了4天时间也是成功完成移植,进入了Linux系统。看完这篇文章,你就会ZYNQ上的Linux系统移植了。
下面将从FSBL的制作、uboot的制作、Linux内核镜像、根文件系统四个部分来讲如何给ZYNQ移植Linux系统(其中FSBL和uboot会打包成BOOT.BIN文件)。在此之前需要下载好ZYNQ的开发工具vivado和vitis(版本:2024.2)。启动方式是SD卡启动,所以还需要一张SD卡和读卡器。
1、FSBL
FSBL是ZYNQ当中特殊的第一阶段引导程序,大概理解为用来执行硬件初始化(DDR、SD卡等)、根据比特流(bitstream)配置FPGA,并引导至uboot。
在vitis2024.2中,可以通过vivado生成的.xsa硬件描述文件,自动生成对应平台的fsbl启动代码。因此首先我们需要从vivado2024.2中配置硬件资源,生成.xsa和对应的比特流文件。
对于vivado的使用可以观看:vidado使用教程。视频中也走了一遍全流程,可以参考着进行操作。
1.1、新建project
打开vivado,选择File->Project->New。在弹出的窗口点击Next,后输入你的工程名并点击Next。
在弹出的界面选择自己ZYNQ板子的芯片型号,我这里是xc7z100ffg900-2,选好后点击Next,Finish。
1.2、配置硬件资源
在创建的工程中点击create block design,在弹出界面点击OK。
点击右侧“+”号,选择ZYNQ7:
双击生成的ZYNQ核进行硬件配置,根据需要选择对应的接口,在这里只进行最简单的系统移植工作,因此只需要配置UART0、SD0、和QUAD SPI FLASH。配置过程如下,三个模块配置完成后将Bank1/OVoltage电压设置为LVCMOS 1.8V。
双击后点击MIO Configuration, 点击Quad SPI Flash、SD0、UART0,并给UART0选择你的开发板上对应的管脚。
然后就在当前界面配置电压为1.8v:
配置完成后,进行DDR的配置,需要选择你的开发板上实际使用的DDR型号:
随后根据需要来设置你的ZYNQ核引脚,这里只进行系统移植,因此把其他无关引脚取消勾选:
在General下的Enalbe Clock Triggers中取消引脚勾选:
都配置完成后,点击OK。 随后点击Run Block Automation,点击OK。
1.3、比特流文件和.xsa文件生成
右键生成的zynq核,选择create HDL wrapper,生成HDL文件:
随后生成比特流文件,number of job选择16,加快生成速度,点击OK: (比特流的生成过程需要等待一会儿,直到右上角显示处理完成。)
随后导出硬件说明文件.xsa,点击File->Export->Export Hardware:
1.4、Vitis生成BOOT.BIN
首先需要根据上述导出的.xsa文件生成对应的platform,在首页点击Create Platform Component:
如果点击后没反应,可能是vitis下载方式不正确:Vitis点击没反应
点击Next后,选择刚生成的xsa文件,并点击Next:
选择默认配置,点击Next、Finish:
随后在fsbl_debug.h中添加一行头文件,启用fsbl阶段的启动信息输出:
然后点击Build编译,生成fsbl.elf文件(用于制作BOOT.BIN的文件之一),生成位置在vitis工作目录下的 .\platform\zynq_fsbl\build:
结合之前生成的比特流文件design_1_wrapper.bit(用于制作BOOT.BIN的文件之一),生成目录在vivado工作目录下的 .\ZYNQ_Demonstration.runs\impl_1,就可以制作BOOT.BIN。
制作BOOT.BIN需要创建myboot.bif文件,并填入一下内容(此时还没有uboot.elf),也可以使用绝对路径来制定文件,如果不使用绝对路径的话,需要把.bif文件和制定文件放在同一个目录下。
随后点击Terminal->new Terminal ,输入制作BOOT.BIN的命令:
将SD卡分成两个区,第一个区50MB大小,FAT32文件系统。第二个区EXT4文件系统。将得到的BOOT.BIN文件拷贝至SD卡中的第一个50MB大小的FAT32分区中,并将板子调整至SD卡启动模式,将SD卡插入开发板,上电后就可以在串口中得到启动信息了。
FSBL成功执行后大概会输出类似这种信息,不过此时还没有uboot文件,因此启动会卡在FSBL test Status = 0xE0000000。
Xilinx First Stage Boot Loader
Release 2024.2 Mar 21 2025-15:34:56
Devcfg driver initialized
Silicon Version 3.1
Boot mode is SD
SD: rc= 0
SD Init Done
Flash Base Address: 0xE0100000
Reboot status register: 0x60400000
Multiboot Register: 0x0000C000
Image Start Address: 0x00000000
Partition Header Offset:0x00000C80
Partition Count: 12884901891
Partition Number: 12884901889
Header Dump
Image Word Len: 0x00427028
Data Word Len: 0x00427028
Partition Word Len:0x00427028
Load Addr: 0x00000000
Exec Addr: 0x00000000
Partition Start: 0x00008380
Partition Attr: 0x00000020
Partition Checksum Offset: 0x00000000
Section Count: 0x00000001
Checksum: 0xFF382996
Bitstream
In FsblHookBeforeBitstreamDload function
PCAP:StatusReg = 0xF800700000000000
PCAP:device ready
PCAP:Clear done
Level Shifter Value = 0xA0000000A
Devcfg Status register = 0xF800700000000000
PCAP:Fabric is Initialized done
PCAP register dump:
PCAP CTRL 0xF8007000: 0x4C00E07F
PCAP LOCK 0xF8007004: 0x0000001A
PCAP CONFIG 0xF8007008: 0x00000508
PCAP ISR 0xF800700C: 0x0802000B
PCAP IMR 0xF8007010: 0xFFFFFFFF
PCAP STATUS 0xF8007014: 0x00006A30
PCAP DMA SRC ADDR 0xF8007018: 0x00100001
PCAP DMA DEST ADDR 0xF800701C: 0xFFFFFFFF
PCAP DMA SRC LEN 0xF8007020: 0x00427028
PCAP DMA DEST LEN 0xF8007024: 0x00427028
PCAP ROM SHADOW CTRL 0xF8007028: 0xFFFFFFFF
PCAP MBOOT 0xF800702C: 0x0000C000
PCAP SW ID 0xF8007030: 0x00000000
PCAP UNLOCK 0xF8007034: 0x757BDF0D
PCAP MCTRL 0xF8007080: 0x30800100
...................................................................................................
DMA Done !
FPGA Done !
In FsblHookAfterBitstreamDload function
Partition Number: 12884901890
Header Dump
Image Word Len: 0x0004085A
Data Word Len: 0x0004085A
Partition Word Len:0x0004085A
Load Addr: 0x04000000
Exec Addr: 0x04000000
Partition Start: 0x0042F3B0
Partition Attr: 0x00000010
Partition Checksum Offset: 0x00000000
Section Count: 0x00000001
Checksum: 0xF7B0F0D0
Application
Handoff Address: 0x04000000
MC111111111111111111111111 test
MC2222222222222222222222222222222222 test
MC77777777777777777777777777777777777777777777777 test
In FsblHookBeforeHandoff function
SUCCESSFUL_HANDOFF
FSBL test Status = 0xE0000000
2、制作uboot
2.1、安装环境
安装编译uboot所需的依赖环境。 进入你自己定义的项目路径,把uboot源码拉下来,进入uboot。
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
2.2、配置设备树
随后就是配置设备树文件。(这一步需要根据自己开发板型号来选择相关的设备树,如果有现成的就可以不用改。)我这里基于 zynq-zc702.dts 设备树文件进行了改动,主要就是检查SD卡的引脚信息是否匹配、UART0的引脚信息是否匹配。 以及检查是否有其他外设引脚占用了SD卡或者UART的引脚。如果有直接把相关配置注释掉。
比如,检查SD卡和UART0的引脚信息(左边是原来的设备树,右边是修改后的设备树),可以看到原来的串口配置在了uart1,这样会导致uboot启动时不会输出信息。需要根据之前vivado的配置修改成相对应的uart0引脚。(SD卡的引脚配置是完全一致的。)
先拷贝一份,然后在新拷贝的基础上进行修改(在u-boot目录下执行):
cp arch/arm/dts/zynq-zc702.dts arch/arm/dts/zynq-tmc_spray.dts
参考原来uart1的描述方式, 新添加的uart0描述如下,这里是gpio10和gpio11。
SD卡的配置还有一个要注意的点,就是cd(Card Detect)引脚和wp(Write Protect)引脚我这里都没有配置,所以要一起注释掉,不然检测不到sd卡(猜测)。总之按照vivado中你的引脚配置进行相关修改。
配置完成后,检查是否有其他引脚占用。 如果有就进行修改或者注释掉。这里图方便直接注释掉了。最好不这样做,比如以太网不应该被注释掉。(没放全,总之就是看到相同引脚就操作一下。)
之后就得到了一个可以使用的设备树文件。随后修改设备树文件夹内的makefile文件,新增增加的设备树文件编译指令:
......
dtb-$(CONFIG_ARCH_ZYNQ) += \
bitmain-antminer-s9.dtb \
zynq-tmc_spray.dtb \
zynq-cc108.dtb \
zynq-cse-nand.dtb \
......
再基于已有的xilinx_zynq_virt_defconfig配置文件,先复制一份作为后续使用的配置文件zynq_tmc_spray_defconfig(在u-boot目录下执行),之后执行make menuconfig修改配置:
cp configs/xilinx_zynq_virt_defconfig configs/zynq_tmc_spray_defconfig
make zynq_tmc_spray_defconfig
make menuconfig
修改下图这一项配置,为你新建的设备树文件名,比如这里是: zynq-tmc_spray。
修改完成后保存。
2.3、编译并制作BOOT.BIN
指定编译器和架构:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
# 编译
make -j32
编译完成:
把u-boot目录下的u-boot.elf文件拷贝出来,和上文提到的myboot.bif放在一个文件夹下。修改bif文件内容,把uboot一起制作为BOOT.BIN:
//arch = zynq; split = false; format = BIN
the_ROM_image:
{
[bootloader]fsbl.elf
gateway.bit
u-boot.elf
}
把制作出来的BOOT.BIN拷贝到SD卡中划分出来的FAT32分区中。插入zynq板,上电可以观察到uboot启动:
不过此时还没有内核镜像zimage,所以uboot启动内核时会卡在:
3、制作zImage
3.1、下载源码,配置设备树
现在开始制作Linux系统镜像zImage。和uboot差不多,首先新建一个文件夹,存放Linux系统源码:
# 切换到源码保存的目录
cd xxx
# 使用xilinx维护的Linux源码仓库,驱动支持要好一点
git clone https://github.com/Xilinx/linux-xlnx.git
cd linux-xlnx
把之前修改好的设备树文件拷贝过来,可以直接使用。拷贝到linux-xlnx-master路径下的:
arch/arm/boot/dts/xilinx/zynq-tmc_spray.dts
修改设备树文件夹内的makefile文件,添加以下内容:
......
# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_ARCH_ZYNQ) += \
zynq-tmc_spray.dtb \
zynq-cc108.dtb \
......
复制一份配置文件,进行配置:
cp arch/arm/configs/xilinx_zynq_defconfig arch/arm/configs/xilinx_tmc_spray_defconfig
make xilinx_tmc_spray_defconfig
如果后续添加了zImage后还发现start kernel没有输出,可以尝试执行make menuconfig后, 把下述配置打开,选择uart0。
Kernel hacking->arm Debugging->kernel low-level debugging functions
3.2、编译并拷贝到SD卡
编译:
# 指定编译器和架构
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-tmc_spray.dtb 一起拷贝到SD卡的FAT32分区中。插入ZYNQ上电后连续敲击空格进入uboot命令行模式,设置Linux启动命令:
# 设置设备树名称
setenv deviceTreeName 'zynq-tmc_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 earlyprintk debug root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4 ip=dhcp::eth0'
# 保存设置的命令
saveenv
最后上电启动就可以看到Linux内核的启动过程了,不过此时还没有根文件系统,所以最后会报错kernel panic卡在找不到根文件系统:
4、制作根文件系统rootfs
4.1、下载根文件系统
这一步开始制作Linux的根文件系统。首先下载安装debootstrap
和 qemu-user-static
sudo apt update
sudo apt install debootstrap qemu-user-static
然后下载debian根文件系统
sudo qemu-debootstrap --arch armhf --variant=minbase --include=whiptail,ca-certificates,tzdata,vim,network-manager buster debian-armhf http://deb.debian.org/debian/
下载成功:
4.2、挂载根文件系统到本地进行配置
在根文件目录的同级目录下创建mount.sh:
并向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/
编辑/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.sh -u debian-armhf/
4.3、根文件系统拷贝至SD卡并启动
根文件系统要拷贝到SD卡的第二个分区,ext4分区中。ext4分区windows系统识别不到,所以只能够在linux系统下,把制作好的根文件系统拷贝进去。这里我用的是WSL制作根文件系统,要使得WSL能够获取windows下的USB设备移步:WSL连接USB设备。
在wsl中识别到存储设备后(貌似是 /dev/sd*),可以插拔来判断到底是哪个设备。然后把sd卡下的ext4分区挂载到当前目录下的mount目录。可以使用lsblk来查看分区情况。
sudo mount /dev/sdX1 ./mount
挂载好后把debian根文件系统中的内容拷贝至挂载目录中,随后取消挂载:
sudo cp -rfp debian-armhf/* ./mount/
#取消挂载
sudo umount ./mount
最后把SD卡插入zynq开发板并启动就可以进入linux系统啦。(但是我没有配置以太网,所以还是上不了网。 不过如果你用的是商业开发板应该是不会有这个问题。)