1. 为什么都2025年了,还要用“传统方式”移植Linux
先说个背景,免得有人觉得我在开倒车。ZYNQ这类SoC平台跑Linux,现在的主流路线基本已经被PetaLinux、Yocto这类自动化工具吃掉了,点几个按钮、跑几条命令,系统就出来了。但如果你真的想把ZYNQ从里到外玩明白,或者你手里的板子恰好是颗老芯片、旧工具链,又或者你需要定制一部分启动逻辑,那“传统方式”依然是绕不开的基本功。所谓传统方式,说人话就是:手动把FSBL、bitstream、U-Boot、内核、设备树、根文件系统逐个编译、打包、烧写,自己控制整条启动链路。这篇文章就从零把它走一遍,并且尽量把每个环节背后的“为什么”讲清楚。
很多人学ZYNQ的时候上来就折腾PetaLinux,结果遇到问题根本不知道卡在哪层。我个人的观点是:传统方式移植一次,你对启动流程的理解深度能顶得上用PetaLinux成功十次。它适合谁?适合已经在Vivado/Vitis里点亮过LED、跑过Hello World、但还没把系统层面的东西串起来的开发者。你看完这一篇,应该能把手上的ZYNQ板卡跑进Linux命令行,并且知道自己每一步在干什么。
先强调一点:这套流程有非常多的版本组合讲究,不可能一篇覆盖所有板卡。我用的组合是Vivado 2019.2 + U-Boot xilinx-v2019.2 + Linux内核 xlnx-2019.2,板子是自己画的ZYNQ-7020核心板,DDR3两颗256MB共512MB,启动方式选了SD卡启动。这个版本组合比较经典,资料多,也干净,用来理解原理很合适。你手里的板卡如果芯片不同、DDR型号不同,后面编译配置的地方就需要对应调整。
2. 先把启动流程和硬件基础盘明白
2.1 ZYNQ异构结构对Linux意味着什么
ZYNQ全称是Zynq-7000 AP SoC,它身上带着一个双核ARM Cortex-A9处理器(PS端,Processing System)和一块基于Artix-7/Kintex-7架构的可编程逻辑(PL端,Programmable Logic)。Linux跑在PS端的ARM核上,PL端更像是Linux的一堆“自定义外设”。
这个结构和普通的ARM9、Cortex-A8单芯片有本质区别。普通ARM芯片,内存控制器、外设控制器都是固定的,你只要把内核编好、设备树写对,Linux就能跑。但ZYNQ的PS端虽然集成了DDR控制器、UART、SDIO、GPIO等外设,这些外设的配置却要在上电早期由一段叫做FSBL(First Stage Boot Loader)的程序初始化。FSBL本身是用Vivado生成的硬件信息构建出来的,它知道你的PS端配置了什么、DDR是什么型号、MIO引脚怎么分配的。传统方式移植Linux,第一步其实不是在编译内核,而是在把FSBL先搞定。
另外,PL端配合Linux有两种常见玩法:一种是PL里跑一个固定的bitstream,把PL端的IP映射成内存地址或挂到AXI总线上,Linux通过/dev/mem或者内核驱动去访问;另一种是全国可重构,运行中动态加载bitstream。传统方式第一篇文章我建议先把固定bitstream这条路走通,也就是把bitstream打进启动镜像里,上电自动加载,再进Linux,后面再玩动态加载。
2.2 从BootROM到Linux的全部启动环节
ZYNQ启动流程是一条严格串行执行的链路,每一环都有固定职责:
- BootROM:芯片出厂固化的代码,上电最先执行。它根据Boot Mode引脚的电平状态决定从哪里读取下一级启动代码。我们这篇配置成SD启动,所以它会去读取SD卡FAT分区里的BOOT.BIN。
- FSBL:BOOT.BIN里的第一段用户代码。职责是初始化PS端最基本的时钟、DDR控制器、MIO引脚配置,可选地把PL bitstream加载进去,然后把U-Boot从启动介质拷贝到DDR里指定地址,最后跳转过去。
- U-Boot:二级引导程序,也是传统方式里最灵活的一环。它初始化串口、SD控制器、网络等外设,然后从SD卡读取内核和设备树到内存,按bootargs环境变量设置内核启动参数,最后跳转进内核。
- Linux内核:启动后先自解压,然后初始化各个子系统,挂载根文件系统,执行/sbin/init,最终进入shell。
这个链路里,最容易被忽视的是每一级跳转时的地址关系。FSBL会把U-Boot加载到DDR的某个地址,U-Boot的链接地址必须与之匹配;U-Boot又会把内核加载到另一个内存地址,内核的编译配置也要考虑到内存布局。后面等你踩过几次“启动到一半卡死”的坑就会明白,这种地址错位是经典问题。
2.3 硬件准备清单和开发环境搭建
硬件方面,你需要准备:
- 一块ZYNQ开发板(或者核心板加底板的组合),确认上面有USB转串口芯片或者外接串口线,用于查看启动日志
- 一张Micro SD卡,建议容量8-32GB,Class 10速度等级起步,启动过程中如果SD卡速度太慢,U-Boot加载内核会明显卡顿
- 一个5V/2A以上供电的电源适配器,ZYNQ跑满负载时电流不小,劣质USB供电容易导致启动过程随机复位
- 一根USB线用于JTAG调试(非必需,但出了问题 JTAG能看到底层)
软件方面,在Ubuntu 18.04(或者你顺手的任何Linux发行版)里安装:
- Vivado 2019.2 完整版或者WebPACK版,用于生成硬件描述文件(HDF)和FSBL源码
- Vitis/SDK(2019.2及以前叫Xilinx SDK),用于编译FSBL
- U-Boot源码,git clone git://git.denx.de/u-boot.git,然后切换到xilinx-v2019.2分支
- 内核源码,Linux内核xlnx版本的分支
- Arm交叉编译工具链,直接apt install gcc-arm-linux-gnueabihf
这里插一句版本匹配的教训:我见过很多朋友图新鲜用了2022、2023的Vivado,结果配老版本U-Boot和内核源码,编译各种报错,最后查下来是binutils版本和旧代码不兼容。如果你想走传统方式,第一优先是找齐一套彼此匹配的版本组合,而不是追求新。Xilinx官方其实每个Vivado版本会同步打一个对应的U-Boot分支和内核分支tag,这套组合是最稳的。
3. 在Vivado里生成硬件工程并导出FSBL
3.1 硬件工程里需要提前确认的PS配置
传统方式的起点还是Vivado里的Block Design。如果你已经建过ZYNQ的硬件工程,重点关注以下几个配置,它们会直接影响后面Linux能不能起来:
DDR配置。在ZYNQ7 Processing System的配置界面里,DDR型号必须和板卡上的实际颗粒完全一致,包括品牌、容量、位宽、速度等级。这里错了最典型的现象是U-Boot阶段能跑,一进内核就疯狂报错或者直接死机。DDR参数是由Vivado根据颗粒型号查表生成初始化代码的,FSBL就是靠这部分代码把内存训练出来的。我自己遇到过DDR3实际上只有256MB但配置写成512MB的事,结果内核起来之后内存检测信息里有一半是乱码,malloc一多就挂。
UART选择。ZYNQ的PS端有UART0和UART1,注意看你的板卡原理图串口接在哪个上,MIO引脚对不对。这个配置会同步生成到FSBL和设备树里,如果串口接的是UART1但你设备树写UART0,内核启动时console信息就会消失,给你一种“启动失败了”的错觉。
SDIO配置。SD卡启动自然需要SDIO控制器打开,工作模式选SD。还要检查MIO分配,SD卡的数据线、时钟线不能和别的功能冲突。
时钟配置。PS端输入时钟一般是33.333MHz,来自板上有源晶振。Vivado里会据此生成各种外设的工作时钟,尤其注意CPU时钟频率,默认可能跑到667MHz或433MHz,如果你的板子对散热没信心,可以适当降频,但Linux跑起来的性能会受影响。这块不建议乱改,保持默认就好。
3.2 导出硬件描述文件和FSBL工程
Block Design完成后,先在Sources面板里右键你的block设计,选择Create HDL Wrapper,让Vivado生成一个顶层HDL文件。然后跑综合、实现、生成比特流,这一步会产出system.bit,也就是PL端的bitstream文件。注意,这里我强烈建议在Generate Bitstream之前,先到File菜单里确认一下Project Settings的Bitstream设置,把-bin_file勾上,这样你会额外得到一个system.bit.bin文件,后面可以直接打进启动镜像。
硬件导出选择File菜单下的Export Hardware,勾选Include bitstream,指定输出目录,你会拿到一个system_wrapper.xsa文件(2019.2版本叫hdf文件)。这个文件是这个硬件工程的完整描述文件,FSBL、设备树的生成都要从它开始。
接下来打开Vitis(也就是Xilinx SDK 2019.2),新建一个Platform Project,选择刚才导出的xsa文件,然后新建一个Application Project,模板选择Zynq FSBL。平台工程编译完成后,你会得到一个fsbl.elf文件。有的新手朋友喜欢用现成的、从别处拷贝的fsbl.elf,我劝你千万别干这事。FSBL里记录的DDR、MIO、时钟参数是针对特定板卡的,用别人的FSBL等于把你的硬件参数全丢了,芯片能不能起来全靠运气。
3.3 顺手生成一份配套设备树
传统方式里设备树有两种来源,一种是从xsa文件里让Vitis自动生成,另一种是手写。第一篇文章我建议先自动生成,省得在DDR大小、中断号这些容易写错的地方出问题。Vitis里选择Xilinx菜单下的Generate Device Tree Source,软件会从xsa里提取PS端外设信息,生成pl.dtsi和配套的.dts文件。
有一点要特别提醒:自动生成的设备树里PL部分只会给每个IP分配地址和中断资源,不会自动生成对应驱动节点所需的属性。比如你在PL里放了一个AXI-GPIO,生成的设备树里可能只有一个gpio控制器节点,但没有定义具体让哪几个引脚作为输入输出、默认电平是多少。这些需要你自己往dts里加。这部分属于设备树定制,后面内核烧写时再细说。
4. 编译U-Boot并制作BOOT.BIN启动镜像
4.1 U-Boot版本选择和配置修改
U-Boot是传统方式里最容易出错也最有意思的环节。先拉源码:
git clone git://git.denx.de/u-boot.git cd u-boot git checkout xilinx-v2019.2然后配置编译。ZYNQ-7000系列在U-Boot里自带多块开发板的默认配置,比如zc702、zc706。如果你用的是官方开发板,直接:
make zynq_zc706_defconfig如果你用的是自己画的板子,建议先找一个最接近的defconfig,然后通过menuconfig微调。最常见要改的几项是:
- Device Tree Control——U-Boot本身也有一套设备树,用来描述它自己需要的串口、SD、DDR等初始化信息。如果串口引脚和开发板默认不一致,就要在这里改。
- Environment的大小和位置——这个牵扯到U-Boot环境变量保存区域,后面改bootargs全靠它。
- 启动介质的支持——如果你是SD卡启动,确认CONFIG_MMC和SD相关选项已经打开,并且默认启动设备是SD。
交叉编译时注意指定ARCH和CROSS_COMPILE:
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- make zynq_zc706_defconfig make -j4编译完成会生成u-boot、u-boot.elf、u-boot.bin等文件。传统方式最终打进启动镜像用的是u-boot.elf,因为FSBL跳转它的方式是按ELF格式解析的。
4.2 用Bootgen打包FSBL、bitstream和U-Boot
Xilinx提供了一个叫Bootgen的工具,用来把fsbl.elf、system.bit或者system.bit.bin、u-boot.elf打包成一个BOOT.BIN二进制文件。Bootgen通常位于Vivado安装目录下的bin文件夹里,装完Vivado后命令行可以直接执行。
启动镜像的格式由BIF文件描述。创建一个boot.bif,内容类似:
the_ROM_image: { [bootloader] fsbl.elf system.bit u-boot.elf }然后执行:
bootgen -image boot.bif -o i BOOT.BIN -w on如果你之前只生成了system.bit而没有生成bin文件,Bootgen能自动把bit转换成bin分区的格式。这里有个细节要说明:bitstream在启动镜像里不是必须的。如果你的PL逻辑需要上电跑,就放进去;如果暂时不用PL,可以不放,FSBL照样跳转U-Boot。
打包完之后,把BOOT.BIN拷贝到SD卡的FAT32分区里。这里特别提醒一下SD卡处理:用fdisk或者图形工具把SD卡分成两个区,第一个分区格式化为FAT32,大小建议不少于500MB,用来放BOOT.BIN、内核镜像uImage和设备树dtb文件。第二个分区格式化为ext4,用来放根文件系统。分区格式就是Linux系统能识别的文件系统格式。如果你在Windows下格式化成exFAT,UBoot根本不认,启动会直接卡在读取BOOT.BIN。
4.3 U-Boot编译常见坑
我列几个实测高频问题:
一是编译时提示缺少openssl头文件。老版本U-Boot编译过程中需要openssl库生成一些辅助工具,Ubuntu上执行sudo apt install libssl-dev就能解决。Fedora系则用openssl-devel。
二是mkimage工具缺失。mkimage用来给内核镜像加上U-Boot格式的头部,制作uImage时必须用到。它在u-boot的tools目录下,编译U-Boot时会一并生成。注意随便从网上下载别人编好的mkimage会存在版本不兼容问题,建议用你自己源码编出来的,扔到/usr/local/bin里方便后续使用。
三是CONFIG_ENV_IS_IN_FAT和CONFIG_ENV_IS_IN_MMC的差异。如果你的FAT分区上还想保存U-Boot的环境变量文件,要开启FAT方式的环境保存;如果希望环境变量直接写在MMC的特定扇区,要开另一个选项。新手经常两个都开了,结果环境变量读写异常混乱。第一篇文章建议用FAT方式,文件叫uboot.env,直观且容易排查。
5. 编译内核和根文件系统,把整个系统串起来
5.1 配置内核并编译uImage
内核源码用Xilinx维护的版本,因为ZYNQ平台很多外设驱动(比如xuartps串口驱动、xdevcfg设备配置驱动)不在主线内核里,或者说主线支持不够完善。拉源码的方式:
git clone https://github.com/Xilinx/linux-xlnx.git cd linux-xlnx git checkout xlnx-2019.2配置内核用ZYNQ默认配置:
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- make xilinx_zynq_defconfig make menuconfig如果只是想让系统跑起来,默认配置就够了。但有几个选项建议手动确认:
在Device Drivers里的Character devices下,确认CONFIG_DEVPORT和CONFIG_DEVKMEM是开启或模块状态,这对后面用devmem访问PL空间有帮助。
File systems里,ext4要编译进内核或者做成模块。如果你根文件系统打算用BusyBox的最小rootfs,那你还需要在内核里打开ramfs的支持。如果你打算把根文件系统直接放在SD卡第二个ext4分区,那FAT和ext4相关选项都要注意。
编译内核:
make -j4 uImage这里有个大坑,很多新手不知道:uImage并不是简单的把zImage换个扩展名。mkimage会把zImage加上一个64字节的U-Boot头,头部信息里包含了内核加载地址、入口地址、镜像类型等。U-Boot在启动时通过bootm命令解析这个头部,然后根据头部信息决定把内核放到哪个内存地址。
ZYNQ平台内核加载地址常见设置是0x2080000或者是0x8000,不同U-Boot版本默认值不一样。你可以在menuconfig里看到CONFIG_SYS_LOAD_ADDR的默认值,也可以直接通过U-Boot环境变量来控制。如果你的uImage头部信息和U-Boot传入的加载地址不一致,最典型的现象是bootm之后内核能解压但跑几步就死,或者直接提示Bad Magic Number。解决思路就是让mkimage的-a和-e参数与U-Boot的bootm行为对齐。常规操作:
make -j4 uImage LOADADDR=0x2080000这样生成的uImage头部会记录加载地址为0x2080000。后面在U-Boot环境变量里用bootm 0x2080000启动即可。这里每个平台的习惯可能不同,但读写逻辑是一致的:头部说放哪,U-Boot就放哪。
5.2 设备树的编译与适配
设备树在启动过程中的作用是让内核知道:这块板子有哪些外设、分别在什么地址、需要什么驱动、中断怎么用。它不是可选的,U-Boot启动内核时会单独把dtb文件加载到内存里指定地址,并把这个地址传给内核。
自动生成的dts文件通常放在内核源码的arch/arm/boot/dts/目录下,也可能是从Vitis导出的文件。编译设备树的方法:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs生成的dtb文件名跟dts对应。如果你手动改过dts,重新编译即可。
手动改dts最常见的是补PL外设信息。比如你在PL里放了AXI UART16550,自动生成的设备树可能只有地址和中断号,还需要加时钟频率、寄存器宽度等属性。不加上这些,对应驱动可能会初始化失败。此外,如果你用了自定义的GPIO编号映射,也要在dts里通过gpio-controller属性去描述。
还有一类高频需求是修改内存大小。自动生成的dts里memory节点的大小是从xsa里读出来的,如果你的DDR实际容量和xsa定义不一致,一定要手动改。我在3.1节提到的DDR容量配错案例,设备树里体现为:
memory@0 { device_type = "memory"; reg = <0x0 0x40000000>; };如果实际只有256MB,16进制就是0x10000000。不改的话,内核会认为有1GB内存并尝试访问不存在的地址空间,直接导致系统卡死。这种问题的排查思路就是先看启动日志里内核打印的Memory Available,对照实物去改。
5.3 根文件系统的三种方案
传统方式里根文件系统最常见的三种来源:一是直接下载别人打包好的arm rootfs归档;二是用BusyBox自己搭一个最小rootfs;三是用Ubuntu/Debian的arm base rootfs来支撑更完整的应用环境。
第一篇文章建议用BusyBox,因为体积小、编译快、依赖少,启动后能进shell做基本验证就够了。BusyBox编译过程:
git clone git://busybox.net/busybox.git cd busybox export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- make defconfig make menuconfig在menuconfig里注意选上静态编译。进入Settings选项,找到Build static binary(no shared libs),把它勾上。这样生成的busybox不依赖动态库,复制到rootfs里直接就能跑,省去拷贝一堆.so的麻烦。接着:
make -j4 make install默认会安装到源码目录下的_install文件夹里。把这个文件夹里的内容复制到一个rootfs目录中,然后手动创建必要的目录:
mkdir -p rootfs/{proc,sys,tmp,dev,etc,root,home}此外还需要在rootfs/etc目录下创建一个inittab文件和一个fstab文件,用来说明init进程如何启动shell:
inittab内容:
::sysinit:/etc/init.d/rcS console::askfirst:-/bin/sh这个文件的含义是系统初始化后自动执行rcS脚本,然后在console设备上启动一个shell,方便我们通过串口登录。
5.4 从U-Boot到内核的启动参数
现在SD卡里已经有BOOT.BIN、uImage、dtb文件和rootfs分区,但U-Boot还需要一点环境变量才知道该干什么。U-Boot启动时没有环境变量文件也可以跑,用默认配置也行,但要指定加载地址和bootargs。
进入U-Boot命令行后,手动敲一串命令:
setenv bootargs 'console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait' setenv bootcmd 'fatload mmc 0 0x2080000 uImage; fatload mmc 0 0x2100000 devicetree.dtb; bootm 0x2080000 - 0x2100000' saveenv boot我来逐个解释这串命令里的关键点:
- console=ttyPS0:这是ZYNQ PS端UART的设备名,不是ttyS0,很多从普通ARM平台转过来的朋友在这里写错,导致启动后串口完全无输出。
- root=/dev/mmcblk0p2:根文件系统在SD卡第二个分区。
- rootwait:等待SD卡设备就绪后再挂载,避免启动过快导致设备节点还没出现就挂载失败。
- fatload mmc 0:从第0个MMC设备的FAT分区读文件。这里如果你板子上还有eMMC或者其他存储设备,数字可能不是0,用mmc list确认。
- bootm 0x2080000 - 0x2100000:中间那个短横线表示没有ramdisk,第三个参数指定dtb在内存中的地址。这里地址可以随意指定,但一定要避开内核镜像占用的区段,不然解压时互相覆盖,系统起不来。
如果你打算让U-Boot自动启动,不用每次手动敲命令,就把bootcmd设成上面一长串。这样上电后U-Boot自动找到uImage和dtb并启动。我把bootcmd设计成直接在U-Boot里写死,避免依赖什么脚本环境,排障也直观。
6. 首次启动实录与问题排查
6.1 一次正常启动的日志长什么样
把SD卡插进板子,连接串口线,打开串口终端,波特率设置115200,8位数据,1位停止位,无校验。上电之后你应该看到类似这样的输出流程:
第一步,BootROM阶段看不到任何输出,因为此时串口还没初始化。
第二步,FSBL阶段开始有输出,比如:
Xilinx Zynq ZC706 U-Boot 2019.07-gc5b0348c4e-dirty实际上FSBL阶段通常只有少量打印,比如U-Boot版本信息前有一个U-Boot SPL或者直接跳转。不同的FSBL实现打印不同,但只要能看到U-Boot的欢迎信息,说明FSBL已经成功初始化DDR和串口。
第三步,U-Boot输出板卡内存信息、启动设备信息。然后执行你设置的bootcmd,加载内核和dtb,跳转。
第四步,内核启动,打印大量初始化日志,最后挂载rootfs并执行init进程。如果一切正常,你会看到:
Please press Enter to activate this console.回车之后就进入了BusyBox的shell。到这一步,传统方式移植Linux的第一阶段就算彻底完成了。
6.2 串口无输出,别急着怀疑软件
遇到串口无输出时,我的排查顺序是:硬件先于软件,软件前先查Boot Mode。
第一步拿万用表量板子上的电源,确认各路电压正常。第二步检查Boot Mode引脚电平,这个是最容易被忽略的。ZYNQ的启动模式由MIO[4:8]几个引脚在复位时的上下拉状态决定,如果跳线帽错了,BootROM会去QSPI或者JTAG找启动镜像,SD卡里的BOOT.BIN根本不会被读取。
确认启动模式正确后,再用JTAG连上板子,读一下PC指针在哪。如果PC停在0xFFFF0000这种BootROM地址附近,说明启动模式可能又不对;如果PC停在DDR里某个地址乱跳,那可能是FSBL崩溃了。
还有一个小概率但真实发生的坑:你的USB转串口芯片是3.3V电平的,而ZYNQ的PS端UART走的是MIO引脚,有些底板的串口线路经过了电平转换芯片。如果转换芯片供电不稳定或者方向配置错误,同样会表现为无输出。这时候用示波器在UART_TX引脚上量,看有没有跳变信号,就能定位是板子没发数据还是串口芯片没转出来。
6.3 U-Boot能启动但内核起不来
U-Boot正常打印,booting Linux字样也出来了,然后内核没有后续输出,或者输出几行就停了,这种情况大多数是设备树问题和内核配置问题。
先看dtb加载地址。内核在启动早期会打印机器型号和内存大小,如果你一个字符都看不到,大概率是dtb地址不对或者dtb文件损坏。可以用U-Boot的md命令检查内存里的数据是否和dtb内容一致:
md 0x2100000 40如果能看到dtb二进制数据的前几行,说明文件确实加载进来了。如果全是0或者乱码,说明fatload的地址和bootm参数不一致。
如果能打印机器型号,但后面挂死,那基本就是设备树里的外设节点和实际硬件不匹配,或者某个驱动在初始化的时候访问了不存在的硬件寄存器。这种问题只能回头慢慢比对dts文件和原理图。我常用的方法是先把内核config里CONFIG_DEBUG_LL和EARLY_PRINTK打开,这样能在串口初始化之前输出更早期的调试信息,对定位非常有帮助。
6.4 内核起来但根文件系统挂载失败
经典错误信息:
VFS: Unable to mount root fs on unknown-block(179,2)看到这个先确认:root=/dev/mmcblk0p2有没有写错,SD卡是不是第二分区为ext4,以及内核里ext4功能是否编进去了。如果内核里EXT4是模块而没被编入initramfs,那你就会陷入先有鸡还是先有蛋的困境:要挂载根文件系统需要ext4支持,但ext4模块又存放在根文件系统里。传统方式下建议直接把ext4编进内核,不要做成模块。
还有一种隐蔽情况是SD卡第二个分区的几何信息异常。有些朋友喜欢在Windows下用第三方分区工具,分出来的ext4分区在Linux下fdisk检查发现起始扇区对不齐,内核读写时就会出现IO错误。排查时先把SD卡插到Linux电脑上,用dmesg看内核识别分区的情况,再用fsck.ext4检查文件系统完整性。如果文件系统本身有错误,就重新格式化,或者换一个用Linux版fdisk分区的SD卡。
6.5 设备树里PL外设的地址分配问题
ZYNQ的PL端外设挂在AXI总线上,地址范围由Vivado里的Address Editor决定,通常是0x40000000到0x7FFFFFFF这段。自动生成的dtsi里会把每个IP的reg属性和interrupt属性列出来,但有时候不够完整。
比如PL里放了一个AXI-GPIO,你想在Linux里用gpio子系统控制它。自动生成的设备树可能只给了GPIO控制器的reg地址和中断号,但没指定gpio-controller和#gpio-cells属性,也没有指明每个bank的引脚数。这个时候就需要手动改dts:
axi_gpio_0: gpio@40000000 { compatible = "xlnx,axi-gpio-1.00.b"; reg = <0x40000000 0x10000>; #gpio-cells = <2>; gpio-controller; xlnx,gpio-width = <8>; };另外在实际使用中,PL外设的中断号需要加上GIC(通用中断控制器)的偏移量。ZYNQ的GIC中断号从32开始,硬件中断号在设备树里要注意这个偏移。自动生成的dtsi里通常会处理好,但如果你自己写PL节点的interrupt属性,一定要查清楚。
7. 这块还能往下扩展什么
最后分享一个我在多次移植里的个人感受。传统方式移植Linux,真正花时间的不是编译,而是排障。每遇到一个错误,你都要判断它发生在启动链路的哪一层:是BootROM没读到东西?FSBL初始化失败?U-Boot没适配硬件?内核配置不对?还是设备树和实际外设不符?这种分层排查的能力,用PetaLinux很难锻炼出来。
现在你手里的这套BOOT.BIN和rootfs已经能跑起来了,后续可以尝试的方向有很多。比如把bitstream动态加载放到Linux启动之后,用设备覆盖(Device Tree Overlay)的方式在运行时给PL外设加载驱动;再比如把U-Boot环境变量从FAT文件改成独立分区存放,提升可靠性;或者给系统换成更完整的Debian rootfs,在上面部署你的应用。第二篇我可以接着写U-Boot的环境变量定制和启动脚本改造,走传统的三段式启动也能玩出很多花样。到时候见。