ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ZYNQ传统方式移植Linux:从FSBL到根文件系统的完整启动链

ZYNQ传统方式移植Linux:从FSBL到根文件系统的完整启动链 ZYNQ跑系统 系列一 传统方式移植linux拿到一块ZYNQ开发板很多人第一件事就是想把Linux跑起来。但真上手之后会发现网上教程要么直接甩给你一个PetaLinux的现成镜像要么就是把Vivado、SDK、设备树、U-Boot这些词堆在一起看得一头雾水。这篇系列文章第一篇我用传统手工方式把Linux搬到ZYNQ上——不依赖PetaLinux自动生成从FSBL到U-Boot再到内核和根文件系统每一步都手动编译、手动打包、手动烧写。这种方式虽然笨但能让你真正看清ZYNQ启动的每个环节后续不管是调驱动、改启动方式还是做双核应用心里都有底。先说明一下ZYNQ的架构和普通ARM单片机最大的不同在于它内部有PSProcessor System即ARM Cortex-A9双核和PLProgrammable Logic即FPGA逻辑两个世界。Linux跑在PS上但启动过程要先经过BootROM、FSBL再由U-Boot引导内核任何一个环节出错都起不来。所以本文的“移植”本质上是把启动链条上的每一段程序都手动构建一遍最终得到一个能从SD卡启动的完整Linux系统。1. 动手前先搞清楚ZYNQ的启动架构与传统移植的本质1.1 从BootROM到Linux的完整启动链条ZYNQ上电后的启动过程很多人第一次接触时容易懵因为它不像STM32那样直接从Flash里取代码执行。ZYNQ内部有一个固化的BootROM程序上电后它先根据模式引脚MIO[5:4]的电平决定从哪类介质读取启动镜像——QSPI Flash、SD卡、JTAG还是NAND。BootROM本身很小它只负责把启动镜像的头几个字节读进来做校验然后跳转执行。所谓的启动镜像就是BOOT.bin。这个文件里面至少包含两个东西FSBLFirst Stage Boot Loader和U-Boot。FSBL是芯片厂商固定的第一阶段引导程序它的核心职责有三个初始化DDR控制器、初始化PS端的时钟PLL、把第二阶段引导程序也就是U-Boot从启动介质拷贝到DDR里然后跳转过去。U-Boot起来之后它会读取设备树dtb文件和内核镜像uImage或zImage把内核加载到DDR指定地址设置好启动参数最终跳到内核入口。内核再挂载根文件系统执行init进程这才算“跑起了Linux”。这条链路上的每一步都对应一个需要你手工编译或者生成的产物。如果只做应用开发PetaLinux一条命令全包了但如果你想知道“系统是怎么长出来的”就必须把每个产物单独拿出来研究。1.2 传统手工方式 vs PetaLinux自动流程很多初学者一开始就投入PetaLinux的怀抱但PetaLinux本质上是个自动构建工具它把硬件描述文件hdf、FSBL、U-Boot、内核、设备树甚至根文件系统一股脑打包成镜像。好处是快坏处是你不知道里面发生了什么。对比一下两种方式的差异你就明白为什么要学传统方式了对比项PetaLinux自动方式传统手工方式FSBL工具自动生成SDK/XSCT里手动生成U-Boot工具自动拉取并配置手动下载源码、改配置、交叉编译内核自动裁剪配置手动make menuconfig选驱动设备树根据hdf自动生成手动编写并编译根文件系统工具自带或自动构建手动用BusyBox搭建可定制性受限于工具的配置项任意环节可深度定制学习收益基本学不到原理每一步都清楚我在实际项目中见过不少用PetaLinux的工程师遇到启动失败时完全没有排查思路因为镜像是一个黑盒。传统方式里每个文件都是你自己生成的哪个环节出问题日志一出来你基本能猜到是哪一步。1.3 传统移植的交付物清单在开始动手之前先把目标产物列清楚。本文最终要得到三个独立的东西BOOT.bin包含FSBL和U-Boot放在SD卡FAT分区里uImage或zImageLinux内核压缩镜像放在FAT分区devicetree.dtb设备树编译产物放在FAT分区根文件系统可以做成ext4分区里的目录也可以做成ramdisk镜像。如果你的板子和常用的黑金、米联客、正点原子ZYNQ开发板布局相似那么SD卡FAT分区启动是最省事的调试方式——不用反复烧写Flash文件直接拷进卡里就能改。本文就以SD卡启动为例来操作。2. 交叉编译环境版本匹配是第一个大坑2.1 工具链选择与安装ZYNQ的PS端是ARM Cortex-A932位处理器所以我们需要一个arm架构的交叉编译工具链。常见选择是arm-linux-gnueabihf-支持硬件浮点或者arm-none-eabi-裸机用。跑Linux必须用前者因为内核和用户态程序都依赖glibc之类的C库。这里有个容易踩的坑工具链的版本和你要编译的内核版本、U-Boot版本之间存在匹配关系。太老的工具链比如gcc 4.x编译新版内核可能会报错太新的工具链比如gcc 12.x也可能因为内核代码里的老旧语法产生warning甚至error。我个人的推荐组合是U-Boot 2020.04、Linux 4.19或5.4、gcc 7.x或8.x。这套组合在ZYNQ上非常成熟网上资料多踩坑也少。以Ubuntu 18.04/20.04为例直接安装sudo apt-get install gcc-arm-linux-gnueabihf装完验证一下arm-linux-gnueabihf-gcc --version如果系统自带的是gcc 9.x也没关系编译内核时可以接受少量warning只要没有致命error就行。但如果你是处女座性格想完全复现我这边零告警的环境可以直接到ARM官网下载gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf这个版本解压后把bin目录加入PATH即可。2.2 环境变量与目录规划传统移植过程中会反复用到交叉编译命令每次都写全路径很烦。这里建议把所有环境变量写进一个脚本每次打开终端先source一下export CROSS_COMPILEarm-linux-gnueabihf- export ARCHarm export UBOOT_DIR$HOME/zynq/uboot export KERNEL_DIR$HOME/zynq/kernel export ROOTFS_DIR$HOME/zynq/rootfs另外交叉编译时有个老生常谈但很多人真会犯的错CROSS_COMPILE和ARCH这两个变量缺一不可。ARCHarm告诉编译系统内核面向ARM架构CROSS_COMPILE告诉它用哪个前缀的编译器。少了任何一个要么编译出x86架构的镜像要么调用本机gcc导致一堆无法链接的错误。我把所有源码放在$HOME/zynq目录下大家也可以这么规划zynq/ ├── uboot/ # U-Boot源码 ├── kernel/ # Linux内核源码 ├── rootfs/ # 根文件系统工作区 ├── outputs/ # 最终产物BOOT.bin、uImage、dtb等 └── sdk/ # Xilinx SDK或Vitis安装目录规划目录的意义在于后续做多个版本迭代时你永远知道哪个文件在哪里不会出现“改了源码但没编译进去”“编译了但拷错镜像”这种低级事故。2.3 硬件导出文件hdf的准备传统方式虽然不用PetaLinux但依然需要Vivado生成硬件描述文件。因为在ZYNQ上PS的外设配置比如DDR型号、MIO分配、UART接口是由硬件工程决定的U-Boot的配置、设备树里的节点都要和这个硬件描述保持一致。操作步骤是这样的在Vivado里搭建好最小系统ZYNQ PS核、DDR、UART1一般是MIO48/49对应开发板上的USB转串口如果需要PL逻辑把PL的约束和比特流生成好File - Export - Export Hardware勾选Include bitstream如果PL有逻辑生成一个.hdf文件。这个hdf文件在PetaLinux里是输入但在传统方式里我们主要用它来获取两样东西FSBL工程需要的硬件信息以及设备树源文件的生成依据。如果把PL比作一块画布上已经画好的图案那PS就是画布本身hdf就是这张画布的详细规格书——尺寸、材质、哪些区域能画画都写在里面了。拿到hdf后其实可以用一个叫xsct的命令行工具从中提取FSBL源码和设备树这是Xilinx SDK底层做的事情。我们接下来看FSBL怎么生成。3. 从FSBL到U-Boot第一段代码如何跑起来3.1 用XSCT生成FSBLFSBL虽然是一个固定流程的引导程序但它不是一成不变的二进制。不同板卡的DDR型号、时钟频率都不同所以FSBL需要根据你的硬件配置重新编译一次。打开Xilinx SDK现在叫Vitis新建一个Application Project硬件平台选择刚才导出的hdf文件。模板里专门有一个Zynq FSBL的选项直接选它然后Build。编译出来的文件叫fsbl.elf。如果用命令行操作更顺手可以这么干xsct % hsi open_hw_design system.hdf % hsi generate_app -dir ./fsbl_src -app zynq_fsbl % hsi close_hw_design cd fsbl_src make编译完成后会得到一个fsbl.elf。这个文件需要和U-Boot一起打包进BOOT.bin。这里有个小知识点FSBL的源码实际上是一套汇编加C的混合工程核心逻辑在fsbl_handoff.S、fsbl_main.c这些文件里。它初始化DDR的时序参数在有些官方BSP里甚至能看出来它如何搬运U-Boot到指定地址。如果你后续想深入研究启动流程直接把FSBL源码打开通读一遍比看任何教程都有效。3.2 U-Boot源码获取与板级配置U-Boot的官方代码仓库在https://github.com/u-boot/u-boot但针对ZYNQ我建议直接用Xilinx维护的分支它是基于上游U-Boot加了很多ZYNQ特有补丁。拉取命令git clone https://github.com/Xilinx/u-boot-xlnx.git -b xilinx-v2020.2这里有个痛点——不同分支对应不同Vivado版本选不好会出现编译错误。如果你用的Vivado 2019.1那U-Boot选xilinx-v2019.1分支最稳用2020.2版本就选xilinx-v2020.2。版本号对齐能省掉很多莫名的兼容性问题。进入U-Boot目录后先配置ZYNQ的板级configmake zynq_zc706_defconfig如果你的开发板不是ZC706而是其他厂商的比如黑金ZYNQ开发板、正点原子启明星大概率也有对应的defconfig实在找不到用make zynq_common_defconfig然后通过menuconfig微调。一个很多新手会犯的错只跑make zynq_zc706_defconfig就急着make结果编译出来一个默认配置的U-BootDDR容量不对、外设没开启动到一半就卡死。所以编译前一定要进menuconfig检查make menuconfig重点检查两个地方Device Tree Control确保U-Boot自带设备树并且选中你的板型Serial配置默认ZYNQ的UART地址是0xE0001000对应UART1如果开发板用的是UART0就要在设备树里改。然后开始编译make -j4编译产物是u-boot.elf有的配置也会生成u-boot.bin。打包BOOT.bin时我们需要的是u-boot.elf。3.3 制作并验证BOOT.binBOOT.bin的生成工具是Xilinx的bootgen可以从SDK安装目录里找到也可以直接用Xilinx SDK的图形界面。最常见的方式是在SDK里双击创建Zynq Boot Image把fsbl.elf和u-boot.elf加进去设置分区属性FSBL对应的partition是bootloaderU-Boot对应partition的启动地址填0x8000000ZYNQ约定U-Boot加载到DDR的这个位置。命令行的bootgen方式也分享一下方便脚本化重复构建bootgen -image boot.bif -o BOOT.bin其中boot.bif内容如下the_ROM_image: { [bootloader]fsbl.elf u-boot.elf }生成BOOT.bin后把它拷到SD卡FAT分区插上开发板设置启动模式为SD启动打开串口终端波特率115200上电后如果能看到U-Boot的启动Logo和命令行提示符那说明第一个里程碑完成了。串口完全无输出的排查方法我放到后面专门章节这里先继续往下走。在做内核之前建议先把U-Boot环境变量和启动参数弄清楚因为U-Boot的bootcmd决定了它怎么去加载内核。4. 内核与设备树传统移植的核心环节4.1 内核源码选择与裁剪思路Linux内核的源码仓库极大但ZYNQ相关的代码集中在一块。和U-Boot一样我推荐用Xilinx维护的内核分支git clone https://github.com/Xilinx/linux-xlnx.git -b xlnx_rebase_v4.19或者直接用LTS主线内核4.19或5.4原因之前说过ZYNQ的BSP支持它们最完善。内核编译的第一步不是直接make而是先选配置。ZYNQ的in-tree默认配置文件在arch/arm/configs/xilinx_zynq_defconfigmake xilinx_zynq_defconfig但这仅仅是起点。因为这个defconfig里开了很多你可能用不到的驱动也关了一些你需要的驱动。此时跑一遍内核编译产物可以启动但你会发现镜像很大编译时间很长部分外设不受控。所以生产级项目一定会做进一步裁剪。我的习惯是执行完defconfig之后直接make menuconfig做以下几项操作关掉不需要的网络协议栈特性如果项目不跑复杂网络服务确认开启UART驱动Device Drivers - Character devices - Serial drivers - Xilinx UART/UARTLITE controller support确认开启SD卡驱动Device Drivers - MMC/SD/SDIO card support - Xilinx Zynq MMC controller文件系统支持把ext4编译进内核File systems - Ext4如果根文件系统是ramdisk需要开启initramfs支持确认开启CONFIG_ROOT_NFS如果网络调试需要。裁剪的核心逻辑是让内核只包含你硬件上确实存在的设备驱动少一个不必要的模块就少一份启动出错的可能。内核配置是门细活你可以先开个比较全的配置把系统跑通再逐步裁剪。不要一上来就追求极简——你还不知道哪些是你开发板上的“标配”驱动缺失会直接导致启动失败。4.2 编译uImage的完整命令配置完成后编译命令如下make -j4 UIMAGE_LOADADDR0x8000 uImage LOADADDR0x8000这里必须指定UIMAGE_LOADADDR0x8000因为ZYNQ里U-Boot把内核加载到DDR地址0x2080000附近而uImage头里有自解压地址信息如果内核镜像实际加载地址和自解压地址对不上内核会起不来或者跑到一半崩掉。这个0x8000实际是内核在DDR里的相对偏移配合U-Boot的bootm 0x2080000使用。编译正常的情况下arch/arm/boot/uImage会生成拷贝到SD卡前先看一下大小ls -lh arch/arm/boot/uImage一般裁剪后的ZYNQ内核镜像在3MB到6MB之间。如果超过8MB说明你没用的驱动开太多了建议回头重新裁剪。4.3 设备树ZYNQ硬件和Linux内核之间的翻译官设备树是整个传统移植里最容易让人一头雾水的东西。它的作用很简单用一棵描述性树状结构把硬件信息告诉内核。比如DDR大小、UART基地址、中断号、GPIO控制器等。获取设备树源文件dts通常有两种途径在SDK里展开hdf时有一个子目录包含生成的pl.dtsi和system.dts这个最准确因为它是Vivado根据你的硬件设计自动生成的手写。如果你不熟悉PS的寄存器地址手写很容易写错不建议刚开始就这么干。拿到dts后用设备树编译器编译dtc -I dts -O dtb -o devicetree.dtb system.dts如果你的dts文件里有#include之类的C语言预处理指令要先过一遍cppcpp -nostdinc -I include -undef -x assembler-with-cpp system.dts system.dts.preprocessed dtc -I dts -O dtb -o devicetree.dtb system.dts.preprocessed注意ZYNQ开发板的设备树中最关键的一个节点是memory节点——它的reg属性要和你DDR实际容量匹配。如果你的板子是512MB DDR但设备树里写的1GB内核启动后肯定panic因为U-Boot传递的r0-r3参数和DDR初始化不一致。这个我在后面的故障排查里会再提。4.4 内核启动进程与总线初始化顺序设备树编译好之后值得花几分钟理解一下内核启动过程中设备树是怎么被消费的。内核启动早期在setup_arch()阶段就会解析设备树建立struct device_node的树形结构。总线驱动注册时比如amba_pl_platform_driverZYNQ特有的AMBA总线会遍历设备树里的节点把节点匹配到的device和driver做绑定。所以你在设备树里写了一个GPIO的子节点Linux启动后/sys/class/gpio下面就会多出一个gpiochip。这也是为什么有些开发板教程里“改设备树就像改硬件一样”因为你实际上是在告诉内核“这里有这么个硬件”。有一个容易被忽略的点ZYNQ的PL侧中断设备树里如果配了interrupt-parent指向intc但PS端对应的中断号是固定的比如PL到PS的中断是ID 61写错就收不到PL传来的中断。这些细节后续系列文章我们单独讲PL驱动时再展开。5. 最小根文件系统与BusyBox让Linux真正“跑起来”5.1 先理解根文件系统在Linux里的角色内核起来了但内核最后一步一定是找根文件系统。所谓根文件系统就是一个包含/bin、/etc、/dev、/proc、/sys、/tmp、/usr、/var这些目录的完整目录树里面放着用户态的程序、库、配置文件和设备节点。内核启动时是根据bootargs里的root参数来找根文件系统的。root/dev/mmcblk0p2表示挂载SD卡第二个分区root/dev/ram0表示挂载一个内存中的ramdisk。如果你把根文件系统做到SD卡的ext4分区那么内核、U-Boot都放FAT分区根文件系统放ext4分区这是最常用的生产级布局。因为ext4支持日志、权限可以在系统运行中修改文件。5.2 交叉编译BusyBox最小根文件系统不用像发行版那样自带整套GNU工具链一个BusyBox就搞定了一百多个常用命令。BusyBox的策略是“一个二进制提供所有命令”通过符号链接/bin/ls - /bin/busybox来区分调用哪个功能。下载BusyBox源码git clone https://git.busybox.net/busybox cd busybox make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- defconfig make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- menuconfigmenuconfig里有一个必须开的选项BusyBox Settings - Build Options - Build static binary (no shared libs)。静态链接意味着BusyBox不依赖任何动态库文件和glibc拷贝到任何地方都能直接执行这对于嵌入式的根文件系统非常关键——因为你还没配置库文件路径呢动态链接很容易出现“找不到libc.so.6”的尴尬。编译安装到一个临时目录make install CONFIG_PREFIX$HOME/zynq/rootfs这条命令会在rootfs目录下生成bin、sbin、usr目录里面全是BusyBox的符号链接。5.3 搭建init进程与inittab的关系光有BusyBox还不够Linux内核启动后第一个用户态进程是/sbin/init这个init进程负责读取/etc/inittab启动系统初始化脚本和服务。所以根文件系统里要创建etc/inittab文件。最小化的inittab长这样::sysinit:/etc/init.d/rcS console::askfirst:-/bin/sh第一行表示系统启动时先执行/etc/init.d/rcS脚本第二行表示在console设备上启动一个交互Shell。askfirst的作用是每次启动时询问“Press Enter to activate this console”。rcS脚本是初始化脚本至少要做这么几件事#!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t tmpfs tmpfs /tmp mkdir -p /dev/pts mount -t devpts devpts /dev/pts mknod /dev/console c 5 1 2/dev/null mknod /dev/null c 1 3 2/dev/null注意一定保留mount /proc和mount /sys这两行否则ps命令和部分驱动接口根本用不了很多程序会直接报错。创建完rcS后记得加执行权限chmod x $ROOTFS_DIR/etc/init.d/rcS5.4 ramdisk方式与ext4方式的取舍根文件系统到底用哪一种启动方式我建议存储空间够就上ext4分区原因很简单修改起来方便掉电不丢数据。但如果你想把系统做成只读启动或者想把它和内核打包成一个镜像分发给别人ramdisk方式更合适。ramdisk的制作也很简单把rootfs目录打成镜像dd if/dev/zero oframdisk.image bs1M count16 mkfs.ext4 ramdisk.image mkdir /mnt/ramdisk mount -o loop ramdisk.image /mnt/ramdisk cp -a $ROOTFS_DIR/* /mnt/ramdisk/ umount /mnt/ramdisk gzip -9 ramdisk.image制作ramdisk时有个坑U-Boot的bootm ramdisk命令要求ramdisk镜像加载到DDR地址0x400000064MB处并且在bootargs里要带rdinit/sbin/init。如果加载地址和根文件系统预期不一致内核会直接panic日志会提示“RAMDISK: incomplete write”或者类似信息。对我来说调试Linux系统最顺手的还是ext4分区因为任何文件都可以直接在开发板上改、重启生效。ramdisk在需要只读出厂配置或批量部署场景再考虑。6. 打包、烧写与启动调试串口日志里看门道6.1 SD卡分区方案与文件摆放拿到SD卡后建议用GParted或fdisk分成两个分区第一个分区FAT32大小256MB到512MB存放BOOT.bin、uImage、devicetree.dtb第二个分区ext4剩余全部空间存放根文件系统。分区命令示例sudo fdisk /dev/sdb # 创建第一个分区类型cFAT32 LBA # 创建第二个分区类型83Linux sudo mkfs.vfat /dev/sdb1 sudo mkfs.ext4 /dev/sdb2SD卡做好后把根文件系统内容拷贝进ext4分区sudo mount /dev/sdb2 /mnt/sd sudo cp -a $ROOTFS_DIR/* /mnt/sd/ sudo umount /mnt/sdFAT分区里的BOOT.bin、uImage、devicetree.dtb直接拷贝即可。在Windows下往卡里拷文件时注意SD卡启动模式下不识别长文件名和特殊字符最好统一用小写字母命名。6.2 启动参数bootargs的完整配置与含义U-Boot环境变量bootargs是决定内核启动行为的关键传统方式里这个参数用setenv手动设置。我常用的完整配置setenv bootargs consolettyPS0,115200 root/dev/mmcblk0p2 rw rootwait earlyprintk saveenv逐段解释一下consolettyPS0,115200告诉内核用哪个串口作为控制台。ttyPS0对应PS端的UART0如果你的U-Boot初始化的是UART1这里要改成ttyPS1root/dev/mmcblk0p2根文件系统在SD卡的第二个分区。如果SD卡被识别成mmcblk1有些ZYNQ板子SD控制器挂在SD0上就要改成mmcblk1p2rw根文件系统以读写方式挂载rootwait等待SD卡设备节点出现。这个参数一定要有因为SD卡驱动初始化和内核挂载根文件系统是异步的不加rootwait有时会因为设备节点还没生成而挂载失败earlyprintk在内核早期启动阶段就输出日志。如果内核死在早期大概率要靠它才能看到最后一句打印。U-Boot还要设置好加载命令bootcmd手动输入也可以fatload mmc 0:1 0x2080000 uImage fatload mmc 0:1 0x2000000 devicetree.dtb bootm 0x2080000 - 0x2000000这条命令的含义是从mmc设备0的第一个分区把uImage读到0x2080000把设备树读到0x2000000然后bootm使用这两个地址启动内核。注意bootm参数中间有个-表示没有ramdisk直接启动。如果你把根文件系统做成了ramdisk那就需要加载第三个文件fatload mmc 0:1 0x4000000 ramdisk.image.gz bootm 0x2080000 0x4000000 0x2000000这些启动命令第一次配好之后用saveenv保存到U-Boot的环境分区后续重启会自动执行bootcmd就不用每次敲了。6.3 常见启动故障的完整排查链路这部分是最有价值的实战经验汇总我按故障现象从“完全没输出”到“内核panic”逐条梳理。故障一串口完全无输出排查路径检查开发板启动模式跳线是否真的拨到了SD启动检查串口波特率ZYNQ的U-Boot默认115200但有的开发板用了57600先盲试几种常见波特率拔掉SD卡如果U-Boot坏了串口会有BootROM的“U-Boot SPL”之类信息或者完全没有确认BOOT.bin是否真的生成了fsbl.elf是否编译成功。如果BootROM阶段都没有输出而JTAG连接正常往往是因为FSBL没有正确生成或者BOOT.bin内的分区顺序不对——FSBL必须是第一个分区且属性为bootloader。故障二U-Boot起来了但bootcmd报找不到uImage排查路径ls mmc 0:1看看FAT分区里到底是什么文件可能是文件名拼写错误确认是不是分区类型不是FAT而是其他格式mmcinfo确认U-Boot识别到的mmc设备枚举号有的是0有的是1。故障三内核加载后启动到一半panic日志停在“Kernel panic - not syncing: No init found”这是最常见的错误。原因几乎一定是根文件系统没挂上。继续看日志如果你能看到“VFS: Cannot open root device mmcblk0p2”那大概率是root参数里的分区号不对或者这个分区类型格式不对。检查一下SD卡是不是一个FAT一个ext4同时确认是否加了rootwait。故障四死机在“Starting kernel ...”之后只是省略号就再没下文这个问题比较隐蔽。常见原因有两个设备树里没有配置正确的stdout-path内核不知道控制台是哪个串口或者uart时钟不对——U-Boot初始化uart的时钟频率和内核驱动推算出的频率不一致导致串口输出的波特率漂移。排查方法给bootargs增加earlyprintk和clk_ignore_unused试试或者用JTAG连接看内核是否其实已经跑到了某个阶段。故障五启动时卡在“waiting for root device”这种往往是rootwait等待了很长时间说明内核的SD卡驱动根本没有把设备枚举出来。大概率是设备树里mmce0100000节点被禁用status disabled或者没有正确写SD卡控制器的时钟和管脚信息。此时在内核配置里确认SD卡主机控制器驱动已经编进去。故障六文件系统挂载后执行/bin/sh报Permission denied多半是rootfs里的文件属性或软链接权限有问题。BusyBox的符号链接如果是指向/bin/busybox那么busybox本身是可执行文件即可但如果你把整个rootfs是从Windows拷贝到SD卡上的可能权限全变成了rwxrwxrwx反而没事倒是ext4分区根目录的权限模式可能不对。给rootfs根目录设置chmod 755关键的bin目录设chmod 755 /bin/busybox。6.4 从串口日志中读取启动关键阶段无论故障排查还是正常验证学会看串口日志比什么都强。我把一次正常启动的日志关键行拆解成表方便你对照日志内容对应阶段发生什么U-Boot 2020.04 ...SPL/U-Boot加载完成FSBL已经跳转到U-BootDDR初始化成功mmc: 0MMC控制器枚举SD卡控制器初始化完成Reading uImage from mmc 0:1内核镜像加载fatload从SD卡读到DDRStarting kernel ...跳转内核U-Boot把控制权交给内核入口Booting Linux on physical CPU 0x0内核早期初始化内核vmlinux开始执行Kernel command line: consolettyPS0...bootargs解析内核收到启动参数Freeing unused kernel memory内核初始化完成init进程即将启动init started: BusyBox v1.36.0用户态开始根文件系统挂载成功init进程运行我在实际调试中养成了一个习惯把正常启动的串口日志完整保存一份命名为good_boot.txt出问题时的日志另存为bad_boot.txt两边做diff通常几秒钟就能定位到从哪个阶段开始分叉。这个方法在以传统方式反复调试Linux启动时效率极高比一条条看日志靠谱得多。6.5 烧写QSPI与后续方向预告如果你调试稳定了要把它固化到开发板里可以把BOOT.bin烧到QSPI Flash。用SD卡启动进入Linux后在PS侧通过flashcp命令或者直接用U-Boot的sf命令即可烧写。QSPI启动的优缺点对比SD卡启动速度更快工业环境抗震性好但烧写次数有限QSPI Flash擦写寿命一般10万次调试阶段频繁烧写不划算QSPI镜像可以包含完整的FSBLU-Boot内核设备树ramdisk这样bootargs里可以不用SD卡。建议先SD卡调通再固化到QSPI。因为SD卡修改方便调试成本低。等系统稳定了再烧Flash能避免“Flash写坏只能换片”的悲剧。到这里ZYNQ传统方式移植Linux的完整链路已经走完。我最终得到的其实不是一堆文件而是一条链条——从BootROM到FSBL、从U-Boot到内核、从设备树到根文件系统每个环节都清楚它在干什么。这种“系统是自己搭出来”的感觉是PetaLinux一键生成给不了的。后续系列里我会继续展开在PS上移植BusyBox的细节优化、PL读写PS外挂DDR的FrameBuffer方案、双核AMP生成bin文件的流程以及ZYNQ GPIO控制器编号在设备树里的映射关系。自己搭出来的系统后面加什么都顺理成章。
返回列表