← 返回博客
FPGAZynqPetaLinux嵌入式LinuxXilinx设备树U-BootYocto

Zynq 实战 08|PetaLinux 嵌入式 Linux 系统构建:从 XSA 到 BOOT.BIN 的完整踩坑指南

Zynq 实战 08|PetaLinux 嵌入式 Linux 系统构建

这是《Zynq FPGA 嵌入式系统设计实战》系列的第 8 篇。 板子:Pynq-Z2(XC7Z020-1CLG400C)。工具链:Vivado / Vitis / PetaLinux 2023.2。 上一篇:《Zynq 实战 07|AXI DMA 数据搬运》


0. 这一篇要解决什么问题

你手上已经有了一个 Vivado 工程,导出了 .xsa 文件。现在你想在 Pynq-Z2 上跑一个真正的 Linux——能 SSH 进去、能跑 Python、能通过 /dev/uio0 访问你自定义的 PL 外设。

这一篇会从零走完整个流程,重点在三个真实会卡住你的地方

  1. Ubuntu 22.04 上 PetaLinux 的依赖坑(特别是 dash → bash 这一步,官方文档提了但没说清楚后果)
  2. 内核配置里哪几个选项是 PL 驱动必须打开的,以及 CMA 大小该设多少
  3. system-user.dtsi 怎么写,给 UIO 驱动暴露 PL 外设的最小片段

本文不会讲:Yocto layer 的深度定制、SDK 工具链生成、QEMU 模拟(够独立开一篇)。


1. Ubuntu 22.04 依赖:一次装对,不留后患

PetaLinux 2023.2 的宿主机要求是 Ubuntu 22.04 LTS x86_64(UG1144 PetaLinux Tools Documentation: Reference Guide,章节 2.1 Installation Requirements)。其他发行版不在官方支持矩阵里,Ubuntu 20.04 也能凑合,但 22.04 是最省事的选择。

1.1 系统依赖包

以下是 PetaLinux 2023.2 在全新 Ubuntu 22.04 上必须安装的包(来自 UG1144 表 2-1,并结合实际构建补充):

sudo apt-get update
sudo apt-get install -y \
  gcc git make net-tools libncurses5-dev tftpd \
  zlib1g-dev libssl-dev flex bison libselinux1 \
  gnupg wget diffstat chrpath socat xterm \
  autoconf libtool tar unzip texinfo \
  gcc-multilib build-essential \
  libglib2.0-dev screen pax gzip gawk \
  python3 python3-pexpect python3-pip \
  python3-git python3-jinja2 \
  xz-utils debianutils iputils-ping \
  libegl1-mesa libgtk-3-0 \
  cpio rsync bc lz4 lzop \
  zlib1g:i386 libc6:i386 libncurses5:i386 \
  expect mtools mtd-utils isoinfo dos2unix \
  tofrodos cmake help2man

几个容易漏掉的包:

  • zlib1g:i386 / libc6:i386:32-bit 兼容库,Yocto 构建部分工具是 32-bit 的
  • tofrodos:Windows 换行符转换,.dtsi 文件从 Windows 上传过来会出问题
  • mtools / mtd-utils:打包 FAT 镜像时用到

1.2 ⚠️ bash/dash 陷阱——没做这一步就别往下走

Ubuntu 默认的 /bin/sh 指向 dash,而 PetaLinux 的构建脚本大量使用了 bash 专有语法(source、数组、[[ ]])。如果不切换,petalinux-build 会在一个看似不相关的地方报错,错误信息完全不提 dash,让你以为是包缺失或权限问题。

# 查看当前状态
ls -la /bin/sh    # → /bin/sh -> dash  (Ubuntu 默认)

# 切换为 bash
sudo dpkg-reconfigure dash
# 弹出提示框:Install dash as /bin/sh? → 选 <No>

# 验证
ls -la /bin/sh    # → /bin/sh -> bash  ✅

🚧 避坑:做这一步之前,检查你的系统里有没有脚本依赖 dash 的 POSIX 行为(比如 /etc/init.d/ 下的脚本)。如果是干净的开发机器,直接切 bash 没问题。如果是生产服务器,谨慎一点——把 dash 改回去:sudo dpkg-reconfigure dash → <Yes>

1.3 安装 PetaLinux

从 AMD 官网下载 petalinux-v2023.2-10141622-installer.run(需要 AMD 账号)。

# 给执行权限并安装到 /opt/petalinux/2023.2
chmod +x petalinux-v2023.2-10141622-installer.run
./petalinux-v2023.2-10141622-installer.run --dir /opt/petalinux/2023.2 \
  --platform "arm"   # Zynq-7000 用 arm;Zynq UltraScale+ 用 aarch64

# 安装完成后,source 环境(每次新终端都要做)
source /opt/petalinux/2023.2/settings.sh

# 验证
petalinux-util --webtalk off   # 关掉使用统计上报
echo $PETALINUX               # → /opt/petalinux/2023.2

安装大约需要 30 分钟,安装包本身 ~16GB,安装后目录 ~30GB,留好磁盘空间。


2. 创建工程并导入 XSA 硬件描述

# 创建 Zynq 工程(template 必须是 zynq,不是 zynqMP)
petalinux-create -t project --template zynq -n my_zynq
cd my_zynq

# 导入从 Vivado 2023.2 导出的 XSA 文件
petalinux-config --get-hw-description=/path/to/your_design_wrapper.xsa

最后一条命令会弹出 menuconfig 界面(基于 Kconfig,和 Linux 内核配置界面完全一样)。第一次进去不用改什么,直接 Exit → Save。它做的事情是:

  1. 解析 XSA 里的 BD 设计,提取 CPU 频率、DDR 配置、外设地址映射
  2. 生成 project-spec/configs/config 和初始设备树骨架

工程目录结构(值得记一下)

my_zynq/
├── project-spec/
│   ├── configs/           ← 工程级配置(config、rootfs_config)
│   ├── meta-user/         ← 你的自定义 Yocto layer(改这里!)
│   │   ├── conf/
│   │   └── recipes-bsp/
│   │       └── device-tree/
│   │           └── files/
│   │               └── system-user.dtsi  ← 自定义设备树
│   └── hw-description/    ← petalinux-config 拷入的 XSA
├── images/linux/          ← 构建产物(BOOT.BIN / image.ub / boot.scr)
├── build/                 ← Yocto 构建目录(不要手动改这里)
└── components/            ← 第三方组件 / plnx-workspace

🚧 避坑:所有自定义内容都应该放在 project-spec/meta-user/ 下,不要直接改 build/components/ 里的自动生成文件——下次 petalinux-configpetalinux-build 会把它们覆盖掉。


3. 内核配置:打开 PL 驱动必需的三个选项

petalinux-config -c kernel

这会启动标准的 Linux 内核 menuconfig。以下三个选项在做 PL 硬件驱动时几乎必打开

3.1 UIO_PDRV_GENIRQ(用户空间驱动 + 通用 IRQ)

导航路径:

Device Drivers
  └── Userspace I/O drivers
        ├── [*] Userspace I/O platform driver with generic IRQ handling
        └── [*] Userspace platform driver with generic irq and dynamic memory

勾选之后,内核会提供 uio_pdrv_genirq 驱动,配合设备树里的 compatible = "generic-uio" 节点,可以把任意 PL IP 的寄存器空间和中断暴露给用户空间 /dev/uio0/dev/uio1……

3.2 CMA 大小(连续内存分配器)

导航路径:

Library routines / Generic Driver Options
  └── DMA Contiguous Memory Allocator
        └── [*] DMA Contiguous Memory Allocator
              └── Size in Mega Bytes (256)  ← 默认 256,改成你需要的

CMA 是 Linux 为 DMA 设备预留的一块物理上连续的内存池。PetaLinux 默认 256 MB,对于 Pynq-Z2(512MB DDR)比较合理。但如果你跑多路视频(每帧 1080p YUV422 ≈ 4MB,几帧缓冲就 40-80MB),或者跑 VDMA,建议改到 512 MB(Pynq-Z2 总共 512MB,Linux 内核自用 ~200MB,所以 512 会很紧,实际建议 384 MB)。

设置太小的症状:dma_alloc_coherent 返回 NULL,VDMA / AXI DMA 初始化时打印 Failed to allocate memory

3.3 Xilinx AXI DMA 驱动

导航路径:

Device Drivers
  └── DMA Engine support
        ├── [*] Async TX: Redirect poll/test for DMA drivers
        └── Xilinx DMA Engines
              ├── [*] Xilinx AXI DMAS Engine
              └── [*] Xilinx AXI VDMA Engine  (视频 DMA 需要)

这两个驱动对应的是 drivers/dma/xilinx/xilinx_dma.c,内核里已经有,只是默认没编进来(或只编成 module)。建议编成内核内置([*] 而不是 [M]),避免 rootfs 挂载之前 DMA 驱动加载不到的时序问题。

配置完成后:Exit → Yes (Save)

🚧 避坑petalinux-config -c kernel 每次打开都会重新从 Yocto 拉内核源码(第一次),不是直接改 .config。如果你发现改了选项保存退出后下次重新打开发现没了,是因为 Yocto 的 config fragment 机制在 do_configure 阶段覆盖了你的选项。正确方式是把持久化配置写到 project-spec/meta-user/recipes-kernel/linux/linux-xlnx_%.bbappend 里——但这是进阶话题,初期直接 menuconfig 改就够。


4. 设备树定制:system-user.dtsi

PetaLinux 会从 XSA 自动生成大部分设备树(system-top.dts / pl.dtsi 等)。我们不改自动生成的文件,只改这一个:

project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

下面是一个完整的真实片段,给一个 PL 中的自定义 IP(基地址 0x43C0_0000,大小 0x10000,连了一根中断线到 PS GIC)暴露 UIO 接口,同时调整 CMA 参数:

/include/ "system-conf.dtsi"

/ {
	/* 把 CMA 内存池固定在高地址,避免和内核争低 128MB */
	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		linux,cma {
			compatible = "shared-dma-pool";
			reusable;
			size = <0x18000000>;   /* 384 MB */
			alignment = <0x2000>;
			linux,cma-default;
		};
	};

	/* 自定义 PL IP:通过 UIO 暴露给用户空间 */
	my_pl_ip@43c00000 {
		compatible = "generic-uio";
		reg = <0x43c00000 0x10000>;
		interrupt-parent = <&intc>;
		/* IRQ 61 = PL→PS 中断 0(fabric_irq[0]),高电平触发 */
		interrupts = <0 29 4>;   /* SPI 29 = IRQ 61,4 = IRQ_TYPE_LEVEL_HIGH */
		interrupt-names = "ip2intc_irpt";
	};
};

/* 调整 PS UART1 波特率上限(Pynq-Z2 的调试 UART 是 UART1) */
&uart1 {
	status = "okay";
};

几个关键点:

  1. /include/ "system-conf.dtsi" 这一行不能删。它引入了 PetaLinux 从 XSA 自动生成的 PS 配置,删掉会导致 DDR、时钟等基础配置丢失,系统不能启动。

  2. 中断号算法:Zynq PS 的 GIC 用的是 SPI(Shared Peripheral Interrupt)编号体系。PL→PS 的第 0 根中断线(IRQ_F2P[0])对应 SPI ID = 61,在设备树里写 <0 29 4>——其中 0 是 SPI 类型,29 = 61 - 32(GIC SPI 基偏移),4IRQ_TYPE_LEVEL_HIGH。具体可查 UG585 Table B-4 PL-PS Interrupt Sources

  3. reserved-memory 里的 CMA 节点:这和 kernel menuconfig 里的 CMA size两套机制,以 DTS 为准。设备树里写了 CMA 节点,kernel 里的编译时默认值就不生效了。建议统一用 DTS 控制,更灵活。


5. RootFS 配置:装上你真正需要的东西

petalinux-config -c rootfs

导航到需要的包:

Filesystem Packages
  ├── misc
  │   ├── [*] packagegroup-core-ssh-dropbear    ← SSH 服务(轻量)
  │   └── [*] python3                            ← Python 3.x
  ├── base
  │   ├── [*] dnf                               ← 运行时包管理(可选)
  │   └── [*] resize-part                       ← 启动后自动扩展 rootfs 分区
  └── Image Features
        └── [*] debug-tweaks                    ← 允许 root 空密码登录(开发期用)

对于 Pynq-Z2 上的典型嵌入式 Linux 开发场景,我会额外勾选:

Filesystem Packages → misc:
  [*] i2c-tools          ← 调试 PS I2C
  [*] ethtool            ← 调试网口
  [*] lsof               ← 查设备文件占用
  [*] usbutils           ← lsusb

Filesystem Packages → base:
  [*] tcf-agent          ← Vitis 远程调试需要

🚧 避坑debug-tweaks 这个选项勾上之后,rootfs 里的 root 账户没有密码,任何能 SSH 进来的人都是 root。这在局域网开发环境完全没问题,但如果你的板子接了能访问外网的网络,必须在第一次登录后立刻 passwd root 改密码,或者换掉 debug-tweaks 改用 extrausers 配置固定密码。


6. petalinux-build:漫长的等待

# 在工程根目录下
petalinux-build

第一次构建会从网络拉 Yocto 的 BSP 包,时间取决于网速,通常 1-3 小时。后续增量构建快很多(15-30 分钟)。

构建成功后,images/linux/ 目录下会有这些东西:

文件说明
BOOT.BIN启动镜像(FSBL + bitstream + U-Boot),下文解释结构
image.ubFIT 格式:Linux 内核 + 设备树 + ramdisk initramfs
boot.scrU-Boot 启动脚本(告诉 U-Boot 去哪加载 image.ub
rootfs.tar.gz根文件系统压缩包(EXT4 模式用来解压到第二分区)
zynq_fsbl.elfFirst Stage Bootloader ELF
u-boot.elfU-Boot ELF(打包 BOOT.BIN 时用)
system.dtb编译好的设备树 blob
ImageLinux 内核镜像(ARM zImage 格式)

构建如果中途报错,先看 build/tmp/log/ 目录,特别是 do_compile.logdo_fetch.log。最常见的失败原因:磁盘满了(build 目录会吃掉 50-80GB),或者首次构建时网络超时拉不到源码包


7. BOOT.BIN 的结构与打包

这是最容易被讲错的地方。BOOT.BIN 不是单一的二进制,它是 Xilinx 自定义的 BootROM Image Format(BIF) 打包的容器,内部按顺序包含:

BOOT.BIN 内部结构 BootROM Image Format (BIF) — Zynq-7000 三段式启动 ① FSBL First Stage Bootloader zynq_fsbl.elf • 运行在 OCM(256KB) • 初始化 DDR 控制器 • 通过 PCAP 配置 PL • 加载并跳转到 U-Boot ② Bitstream PL 配置数据 system.bit • FPGA 逻辑配置流 • 约 2-8 MB(XC7Z020) • 可选(不含则 PL 不配置) • FSBL 负责写入 PCAP ③ U-Boot Second Stage Bootloader u-boot.elf • 加载到 DDR 0x0400_0000 • 读取 boot.scr 启动脚本 • 加载 image.ub 到 DDR • 解压并跳转 Linux 内核 BootROM(固化在芯片内,不可修改)读取 SD/QSPI 第一个扇区的引导头,找到并验证 FSBL,加载到 OCM 0xFFFC_0000 执行
图 1. BOOT.BIN 内部三段结构:FSBL → Bitstream → U-Boot

打包命令(Pynq-Z2 标准流程)

# 在工程根目录执行
petalinux-package --boot \
  --fsbl   images/linux/zynq_fsbl.elf \
  --fpga   images/linux/system.bit \
  --u-boot images/linux/u-boot.elf \
  --force

# 生成文件:images/linux/BOOT.BIN

--fpga 是可选的。如果你的工程没有 PL 逻辑(纯 PS 跑 Linux),可以省略 --fpga,FSBL 会跳过 PCAP 配置直接加载 U-Boot。但 Pynq-Z2 这种板子,你多半有自定义 IP,建议总是把 bitstream 打进去

🚧 避坑:网上有些 Zynq 教程写的打包命令里加了 --pmufw images/linux/pmufw.elf这是 Zynq UltraScale+ 的参数,Zynq-7000(包括 Pynq-Z2 的 7Z020)没有 PMU,加这个参数会报错。Zynq-7000 的 petalinux-package --boot 只需要 --fsbl--fpga(可选)、--u-boot


8. 启动模式选择

Zynq 的启动模式由板子上的 Mode pins(JP4/JP5 或 Boot Mode Switch) 决定,BootROM 在上电时采样。

启动模式Mode 引脚设置(MIO[6:2])Pynq-Z2 实际拨码典型用途
JTAG00000JP4 = JTAG开发调试,直接从 PC 下载运行
SD Card00110JP4 = SD最常用,从 SD 卡 FAT32 读 BOOT.BIN
QSPI00001JP4 = QSPI量产/固化,从板载 QSPI Flash 启动

Pynq-Z2 板上有一个 2-pin 的跳线 JP4,短接选项:

  • JP4 短接 1-2:SD 卡启动
  • JP4 短接 2-3(或 JTAG 位置):JTAG 启动

具体标注看板子丝印,和官方 Pynq-Z2 原理图(TUL PYNQ-Z2 Schematic)。

SD 卡分区和拷贝

# 假设 SD 卡是 /dev/sdb(换成你的实际设备)
# 分区 1:FAT32,至少 100MB,存 BOOT.BIN / image.ub / boot.scr
# 分区 2:EXT4(可选),存根文件系统

# 格式化(gdisk 或 fdisk 分区后)
sudo mkfs.vfat -F 32 /dev/sdb1
sudo mkfs.ext4 /dev/sdb2   # 如果用 EXT4 rootfs

# 挂载并拷贝启动文件
sudo mount /dev/sdb1 /mnt/boot
sudo cp images/linux/BOOT.BIN /mnt/boot/
sudo cp images/linux/image.ub /mnt/boot/
sudo cp images/linux/boot.scr /mnt/boot/
sudo umount /mnt/boot

# 如果是 EXT4 rootfs,解压 tarball 到第二分区
sudo mount /dev/sdb2 /mnt/root
sudo tar -xzf images/linux/rootfs.tar.gz -C /mnt/root
sudo umount /mnt/root

SD 卡插入 Pynq-Z2,把 JP4 拨到 SD 模式,上电。


9. 串口连接与第一次登录

Pynq-Z2 的调试 UART(USB-UART 桥)用 Micro-USB 线接到 PC 后,会出现一个 /dev/ttyUSB* 设备。

# Linux 主机连接(使用 picocom,比 minicom 更轻量)
sudo picocom -b 115200 /dev/ttyUSB1

# 或者 minicom
sudo minicom -D /dev/ttyUSB1 -b 115200
# minicom 里:Ctrl+A → Z 打开菜单;Ctrl+A → Q 退出

串口参数:115200 8N1(115200 波特率,8 数据位,无奇偶校验,1 停止位)。

上电后你会看到 FSBL 输出、U-Boot 倒计时(3 秒,按任意键可打断进 U-Boot shell)、然后内核启动 log,最后到登录提示:

Pynq-Z2 login: root
Password: root

默认用户名:root,密码:root(来自 PetaLinux debug-tweaks 选项)。

如果你要用 SSH(确认 dropbear 已勾选并且网口连通):

# 先找板子 IP
ssh root@<board-ip>
# 密码:root

🚧 避坑:Pynq-Z2 的 PS UART 是 UART1(对应 MIO 48/49),不是 UART0。PetaLinux 从 XSA 导入时会自动配置正确的控制台 UART,但如果你手工改了 bootargs,确保 console=ttyPS0,115200 里的 ttyPS0 对应的是实际接了 USB 转串口的那根 UART。Pynq-Z2 板上接出来的是 ttyPS0(映射到 UART1),没问题。


10. 本篇你应该带走的几个判断

完成这篇的操作后,你应该能判断:

  • Ubuntu 22.04 上 PetaLinux 安装失败,先查 bash/dash 有没有切换,这是 90% 奇怪报错的根源
  • 内核配置里 UIO_PDRV_GENIRQ 没打开 → /dev/uio* 设备不会出现
  • CMA 大小不够 的症状:DMA 分配失败,不是 OOM,而是 dma_alloc_coherent 返回 NULL
  • system-user.dtsi 里的中断号:用 <0 SPI_ID-32 IRQ_TYPE> 格式,SPI 29 = IRQ 61
  • BOOT.BIN 里不能有 --pmufw(那是 UltraScale+ 的,7Z020 会报错)
  • SD 卡 FAT32 分区放 BOOT.BIN + image.ub + boot.scr;第一次登录是 root/root

11. 下一篇预告

下一篇 《Zynq 实战 09|从 Linux 用户空间控制 PL:UIO 驱动 + mmap 访问寄存器》,我们会:

  • 在 Linux 里用 open("/dev/uio0") + mmap() 直接读写 PL 寄存器,不写任何内核驱动
  • 中断处理:read() 阻塞等待中断,比轮询省 CPU
  • 和 Python ctypes 结合,写出不用 PYNQ 框架的纯 Python PL 控制代码
  • 踩坑:为什么 mmap 必须对齐 pagesize,以及 O_SYNC 什么时候必须加

参考资料

文档号名称用途
UG1144PetaLinux Tools Documentation: Reference Guide (2023.2)PetaLinux 官方手册,Chapter 2 安装要求、Chapter 3 工程创建
UG585Zynq-7000 SoC Technical Reference ManualTable B-4 PL→PS 中断源列表;Chapter 6 PCAP 启动流程
UG821Zynq-7000 SoC Software Developers GuideFSBL 实现原理,Boot ROM 流程,OCM 地址布局
UG1283Bootgen User GuideBIF 语法、BOOT.BIN 打包参数详解
devicetree specDevice Tree Specification (devicetree.org)DTS 中断 #interrupt-cells 格式规范

系列持续更新中。如果你在 PetaLinux 构建中卡住了,留言告诉我是哪一步——大概率我也踩过。