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 外设。
这一篇会从零走完整个流程,重点在三个真实会卡住你的地方:
- Ubuntu 22.04 上 PetaLinux 的依赖坑(特别是 dash → bash 这一步,官方文档提了但没说清楚后果)
- 内核配置里哪几个选项是 PL 驱动必须打开的,以及 CMA 大小该设多少
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。它做的事情是:
- 解析 XSA 里的 BD 设计,提取 CPU 频率、DDR 配置、外设地址映射
- 生成
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-config或petalinux-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";
};
几个关键点:
-
/include/ "system-conf.dtsi"这一行不能删。它引入了 PetaLinux 从 XSA 自动生成的 PS 配置,删掉会导致 DDR、时钟等基础配置丢失,系统不能启动。 -
中断号算法: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 基偏移),4是IRQ_TYPE_LEVEL_HIGH。具体可查 UG585 Table B-4 PL-PS Interrupt Sources。 -
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.ub | FIT 格式:Linux 内核 + 设备树 + ramdisk initramfs |
boot.scr | U-Boot 启动脚本(告诉 U-Boot 去哪加载 image.ub) |
rootfs.tar.gz | 根文件系统压缩包(EXT4 模式用来解压到第二分区) |
zynq_fsbl.elf | First Stage Bootloader ELF |
u-boot.elf | U-Boot ELF(打包 BOOT.BIN 时用) |
system.dtb | 编译好的设备树 blob |
Image | Linux 内核镜像(ARM zImage 格式) |
构建如果中途报错,先看
build/tmp/log/目录,特别是do_compile.log和do_fetch.log。最常见的失败原因:磁盘满了(build 目录会吃掉 50-80GB),或者首次构建时网络超时拉不到源码包。
7. BOOT.BIN 的结构与打包
这是最容易被讲错的地方。BOOT.BIN 不是单一的二进制,它是 Xilinx 自定义的 BootROM Image Format(BIF) 打包的容器,内部按顺序包含:
打包命令(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 实际拨码 | 典型用途 |
|---|---|---|---|
| JTAG | 00000 | JP4 = JTAG | 开发调试,直接从 PC 下载运行 |
| SD Card | 00110 | JP4 = SD | 最常用,从 SD 卡 FAT32 读 BOOT.BIN |
| QSPI | 00001 | JP4 = 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什么时候必须加
参考资料
| 文档号 | 名称 | 用途 |
|---|---|---|
| UG1144 | PetaLinux Tools Documentation: Reference Guide (2023.2) | PetaLinux 官方手册,Chapter 2 安装要求、Chapter 3 工程创建 |
| UG585 | Zynq-7000 SoC Technical Reference Manual | Table B-4 PL→PS 中断源列表;Chapter 6 PCAP 启动流程 |
| UG821 | Zynq-7000 SoC Software Developers Guide | FSBL 实现原理,Boot ROM 流程,OCM 地址布局 |
| UG1283 | Bootgen User Guide | BIF 语法、BOOT.BIN 打包参数详解 |
| devicetree spec | Device Tree Specification (devicetree.org) | DTS 中断 #interrupt-cells 格式规范 |
系列持续更新中。如果你在 PetaLinux 构建中卡住了,留言告诉我是哪一步——大概率我也踩过。