Поднимаем Linux на плате Zynq RK-7020-F с помощью Buildroot и U-Boot SPL

Описание отладочной платы

На плате установлен чип XC7Z020-2CLG484I, содержащий PS-часть с двумя ядрами ARM Cortex-A9. Периферия включает:

  • CAN PS и RS-485
  • DDR3 1 GB
  • Отладка через FT2232HQ (JTAG + USB-UART)
  • EEPROM (AT24C256C)
  • 8 GB eMMC
  • Ethernet PS + PL (RTL8211F-CG)
  • FMC 40 pin
  • LCD
  • 256 Mbit QSPI NOR Flash
  • RTC PCF8563
  • USB 2.0 PS (USB3320)

Плата поставляется с предустановленным образом Linux в QSPI, однако необходимо научиться собирать кастомный образ с использованием Buildroot вместо PetaLinux.

Процесс загрузки платы RK-ZYNQ7020-F

Полная цепочка загрузки в режиме Non-secure:

BootROM → FSBL/U-Boot SPL → U-Boot → Linux kernel → rootfs

Этап 1 — BootROM

Неизменяемый код в кристалле, запускается первым после питания на CPU0. BootROM:

  1. Определяет источник загрузки по выводам MIO[8:2]
  2. Находит и проверяет заголовок boot.bin (идентификатор 0x584C4E58 — 'XLNX')
  3. Обрабатывает блок Register Initialization из Boot Header
  4. Копирует SPL во внутреннюю память OCM и передаёт управление

На этом этапе DDR не инициализирован — доступна только OCM.

Этап 2 — U-Boot SPL

Вместо традиционного FSBL используется U-Boot SPL, позволяющий собирать весь загрузочный процесс средствами Buildroot без Vitis. Новая последовательность:

BootROM → U-Boot SPL → U-Boot → Linux

Почему SPL необходим: полный U-Boot не помещается в OCM (256 КБ), а DDR ещё не инициализирован.

Функции SPL:

  • Инициализация PS: тактирование, MIO, UART
  • Настройка DDR по параметрам из ps7_init_gpl.c
  • Загрузка полного u-boot.img с SD-карты в DDR

Критический файл ps7_init_gpl.c экспортируется из Vivado и содержит тайминги DDR, частоты PLL и конфигурацию MIO.

Этап 3 — U-Boot

Полный U-Boot исполняется из DDR:

  1. Инициализирует MMC, Ethernet, UART и периферию
  2. Ищет /extlinux/extlinux.conf на первом разделе SD-карты
  3. Загружает uImage → 0x02000000, system.dtb → 0x01F00000
  4. При bootm: копирует ядро (0x02000000 → 0x00008000), перемещает DTB в конец DDR, переводит CPU в режим SVC, прыгает на 0x00008000

Этап 4 — Linux Kernel

uImage содержит zImage с заголовком U-Boot. При запуске на 0x00008000 запускается декомпрессор, который проверяет перекрытие образа с собой, распаковывает vmlinux и передаёт управление stext(). Первые 32 КБ зарезервированы под начальную таблицу страниц (swapper_pg_dir).

Параметры монтирования rootfs из /extlinux/extlinux.conf: root=/dev/mmcblk0p2 rw rootwait. Без rootwait ядро упадёт в панику до готовности контроллера MMC.

Подготовка XSA в Vivado

Создание проекта и Block Design

Указываем компонент XC7Z020-2CLG484I, тип RTL Project. Создаём Block Design с одним IP-ядром ZYNQ7 Processing System (PL не используем).

Конфигурация PS

PS-PL Configuration: отключаем M AXI GP0 interface.
DDR: MT41K256M16 RE-125, 32 Bit.
MIO: Bank 0 — LVCMOS 3.3V, Bank 1 — LVCMOS 1.8V. SD0: MIO40-45, UART0: MIO 10/11, ENET0: MIO16-27.

Синтез и экспорт XSA

Run Synthesis → Run Implementation
File → Export → Export Hardware → Pre-synthesis

Сохраняем design_1_wrapper.xsa. Из него нам нужны:

ФайлНазначениеИспользование
ps7_init_gpl.c / .hРанняя инициализация PSU-Boot SPL
*.hwhXML-описание аппаратурыxsct — генерация Device Tree
*.bitБитстрим PLПри экспорте «с битстримом»

Проверка конфигурации через Vitis

Создаём проект с design_1_wrapper.xsa как платформой, переводим плату в JTAG режим (DIP-переключатель BOOT_MODE), собираем и прошиваем Hello World.

Интерфейсы FT2232HQ: /dev/ttyUSB0 ← JTAG, /dev/ttyUSB1 ← UART (MIO10/MIO11). При успехе в терминале появится "Hello world".

Структура BR2_EXTERNAL

BR2_EXTERNAL — механизм Buildroot для хранения board-специфичных файлов вне основного дерева. Активируется переменной при вызове make:

make BR2_EXTERNAL=/path/to/buildroot_external zynq_rk7020f_defconfig

Структура внешнего дерева:

buildroot_external/
├── external.desc
├── Config.in
├── external.mk
├── configs/
│   └── zynq_rk7020f_defconfig
├── board/
│   └── zynq/
│       ├── RK-ZYNQ7020-F/
│       │   ├── dts/xilinx/
│       │   │   ├── zynq-7000.dtsi
│       │   │   └── zynq-rk7020f.dts
│       │   ├── patches/
│       │   ├── ps7_init/
│       │   │   ├── ps7_init_gpl.c
│       │   │   └── ps7_init_gpl.h
│       │   ├── rootfs_overlay/
│       │   ├── linux-config.fragment
│       │   ├── uboot-config.fragment
│       │   └── genimage.cfg
│       ├── post-build.sh
│       └── post-image.sh
└── package/

external.desc, Config.in, external.mk

# external.desc
name: FKA
desc: Buildroot
# Config.in
menu "Custom Packages"
    source "$BR2_EXTERNAL_FKA_PATH/package/Config.in"
endmenu

menu "BootLoaders"
    source "$BR2_EXTERNAL_FKA_PATH/boot/Config.in"
endmenu
# external.mk
include $(sort $(wildcard $(BR2_EXTERNAL_FKA_PATH)/package/*/*.mk))
include $(sort $(wildcard $(BR2_EXTERNAL_FKA_PATH)/boot/*/*.mk))

Генерация Device Tree через xsct

# dts_gen.tcl
hsi open_hw_design ./design_1_wrapper.xsa
hsi set_repo_path /home/fka/dev_linux/device-tree-xlnx
hsi create_sw_design device-tree -os device_tree -proc ps7_cortexa9_0
hsi set_property CONFIG.periph_type_overrides {
    {DEVICE_ID xc7z020}
    } [hsi get_os]
hsi generate_target -dir ./dts_output
hsi close_hw_design [hsi current_hw_design]

Запуск: xsct, затем source dts_gen.tcl.

Для U-Boot SPL добавляем bootph-all в DTS:

&uart0 {
    bootph-all;
    cts-override;
    device_type = "serial";
    port-number = <0>;
    status = "okay";
};

&sdhci0 {
    bootph-all;
    status = "okay";
    xlnx,has-cd = <0x1>;
    xlnx,has-power = <0x0>;
    xlnx,has-wp = <0x0>;
};

Патч для добавления DTS в U-Boot

--- a/arch/arm/dts/Makefile
+++ b/arch/arm/dts/Makefile
@@ -251,7 +251,8 @@
 	zynq-zturn.dtb \
 	zynq-zturn-v5.dtb \
 	zynq-zybo.dtb \
-	zynq-zybo-z7.dtb
+	zynq-zybo-z7.dtb \
+	zynq-rk7020f.dtb

uboot-config.fragment

# Ранний вывод через UART
CONFIG_DEBUG_UART=y
CONFIG_DEBUG_UART_ZYNQ=y
CONFIG_DEBUG_UART_BASE=0xE0000000
CONFIG_DEBUG_UART_CLOCK=100000000

# Поддержка serial в SPL
CONFIG_SPL_SERIAL=y
CONFIG_SPL_DM_SERIAL=y
CONFIG_SPL_DM=y
CONFIG_SPL_OF_CONTROL=y
CONFIG_SPL_OF_LIST="zynq-rk7020f"

# FAT и MMC
CONFIG_SPL_FS_FAT=y
CONFIG_SPL_MMC=y
CONFIG_SPL_OS_BOOT=n

# Без внешнего окружения
CONFIG_ENV_IS_IN_FAT=n
CONFIG_ENV_IS_NOWHERE=y

Адрес 0xE0000000 — физический адрес UART0 на Zynq-7000. Частота 100 МГц должна совпадать с ps7_init.

linux-config.fragment

CONFIG_SPI=y
CONFIG_SPI_MASTER=y
CONFIG_SPI_XILINX=y
CONFIG_SPI_SPIDEV=y

CONFIG_OF_OVERLAY=y
CONFIG_OF_DYNAMIC=y

rootfs overlay

Buildroot выполняет rsync из директории overlay поверх output/target/ после сборки. Здесь задаём статический IP и конфиг SSH:

# etc/network/interfaces
auto lo
	iface lo inet loopback
auto eth0
iface eth0 inet static
	address 192.168.1.100
	netmask 255.255.255.0
	gateway 192.168.1.1
# etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes

Итоговый defconfig

# Архитектура
BR2_arm=y
BR2_cortex_a9=y
BR2_ARM_ENABLE_NEON=y
BR2_ARM_ENABLE_VFP=y

# Внешний тулчейн от Bootlin
BR2_TOOLCHAIN_EXTERNAL=y
BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
BR2_TOOLCHAIN_EXTERNAL_BOOTLIN_ARMV7_EABIHF_GLIBC_STABLE=y

# Board-специфичные файлы
BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/patches"
BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/rootfs_overlay"
BR2_ROOTFS_POST_BUILD_SCRIPT="board/zynq/post-build.sh"
BR2_ROOTFS_POST_IMAGE_SCRIPT="board/zynq/post-image.sh"

# Linux kernel
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_DEFCONFIG="xilinx_zynq"
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/linux-config.fragment"
BR2_LINUX_KERNEL_UIMAGE=y
BR2_LINUX_KERNEL_UIMAGE_LOADADDR="0x8000"
BR2_LINUX_KERNEL_INTREE_DTS_NAME="xilinx/zynq-rk7020f"
BR2_LINUX_KERNEL_CUSTOM_DTS_DIR="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/dts"

# U-Boot SPL
BR2_TARGET_UBOOT=y
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="xilinx_zynq_virt"
BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/uboot-config.fragment"
BR2_TARGET_UBOOT_SPL=y
BR2_TARGET_UBOOT_SPL_NAME="spl/boot.bin"
BR2_TARGET_UBOOT_NEEDS_DTC=y
BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/dts/xilinx/zynq-rk7020f.dts \
  $(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/dts/xilinx/zynq-7000.dtsi"
BR2_TARGET_UBOOT_CUSTOM_MAKEOPTS="DEVICE_TREE=zynq-rk7020f"
BR2_TARGET_UBOOT_ZYNQ_PS7_INIT_FILE="$(BR2_EXTERNAL_FKA_PATH)/board/zynq/RK-ZYNQ7020-F/ps7_init/ps7_init_gpl.c"

# rootfs
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_SIZE="256M"

# Host инструменты
BR2_PACKAGE_HOST_DOSFSTOOLS=y
BR2_PACKAGE_HOST_GENIMAGE=y
BR2_PACKAGE_HOST_MTOOLS=y

Сборка

git clone --recurse-submodules https://github.com/FernandesKA/buildroot_custom
export BR2_EXTERNAL_FKA_PATH=$(pwd)/buildroot_external
cd buildroot
make BR2_EXTERNAL=$BR2_EXTERNAL_FKA_PATH zynq_rk7020f_defconfig
make

Подготовка SD-карты

Результат сборки в output/images/:

boot.bin          ← U-Boot SPL
u-boot.img        ← полный U-Boot
uImage            ← ядро Linux
zynq-rk7020f.dtb  ← Device Tree Blob
rootfs.ext4       ← корневая файловая система

Разметка карты через fdisk:

sudo fdisk /dev/sdX

o        # новая таблица MBR
n        # раздел 1, primary
[Enter]
+64M
t → c    # W95 FAT32 (LBA)
a        # bootable
n        # раздел 2 — до конца карты
w        # записать
sudo mkfs.vfat -F 32 -n BOOT /dev/sdX1
sudo mkfs.ext4 -L rootfs /dev/sdX2

Файлы на boot-разделе:

sudo mount /dev/sdX1 /mnt/boot
sudo cp output/images/{boot.bin,u-boot.img,uImage,zynq-rk7020f.dtb} /mnt/boot/
sudo mkdir -p /mnt/boot/extlinux

Содержимое /mnt/boot/extlinux/extlinux.conf:

label Linux
    kernel /uImage
    fdt /zynq-rk7020f.dtb
    append root=/dev/mmcblk0p2 rw rootwait earlycon
ПараметрНазначение
kernel /uImageПуть к ядру на FAT
fdt /zynq-rk7020f.dtbПуть к DTB
root=/dev/mmcblk0p2Корневая ФС — второй раздел
rwМонтировать на запись
rootwaitЖдать инициализации MMC
earlyconВывод в UART до инициализации tty
sudo dd if=output/images/rootfs.ext4 of=/dev/sdX2 bs=1M status=progress
sudo sync
sudo e2fsck -f /dev/sdX2 && sudo resize2fs /dev/sdX2

Автоматизация через genimage

# genimage.cfg
image boot.vfat {
	vfat {
		files = { "boot.bin", "u-boot.img", "system.dtb", "uImage" }
		file extlinux/extlinux.conf { image = extlinux.conf }
	}
	size = 32M
}

image sdcard.img {
	hdimage {}
	partition boot {
		partition-type = 0xC
		bootable = "true"
		image = "boot.vfat"
	}
	partition rootfs {
		partition-type = 0x83
		image = "rootfs.ext4"
	}
}
sudo dd if=output/images/sdcard.img of=/dev/sdX bs=4M status=progress && sudo sync

Первый запуск и отладка

Проблема 1: SPL молчит после подачи питания

Проверить режим загрузки DIP-переключателем (SD boot). Если верный — включить DEBUG UART в uboot-config.fragment:

CONFIG_DEBUG_UART=y
CONFIG_DEBUG_UART_ZYNQ=y
CONFIG_DEBUG_UART_BASE=0xE0000000
CONFIG_DEBUG_UART_CLOCK=100000000
CONFIG_SPL_SERIAL=y
CONFIG_SPL_DM_SERIAL=y

Проблема 2: SPL не видит MMC

U-Boot SPL 2025.01 (...)
Trying to boot from MMC1
spl: error reading image uImage, err - -22
SPL: failed to boot from all boot devices

Добавить в конфиг:

CONFIG_SPL_FS_FAT=y
CONFIG_SPL_MMC=y
CONFIG_SPL_OS_BOOT=n

Проблема 3: sdhci0 и uart0 не инициализируются в SPL

В новых версиях U-Boot устройства требуют свойства bootph-all в DTS — без него SPL не видит MMC несмотря на конфиг.

&sdhci0 { bootph-all; status = "okay"; ... };
&uart0  { bootph-all; ... };

Проблема 4: Bad CRC, using default environment

U-Boot ищет переменные окружения в FAT-файле uboot.env. Решение:

CONFIG_ENV_IS_IN_FAT=n
CONFIG_ENV_IS_NOWHERE=y

Проблема 5: Ethernet берёт случайный MAC

В Device Tree не задан local-mac-address. Добавить:

&gem0 {
	phy-mode = "rgmii-id";
	status = "okay";
	xlnx,ptp-enet-clock = <0x69f6bcb>;
	local-mac-address = [02 4F A3 7C 1B E9];
};

Успешная загрузка

U-Boot SPL 2025.01 (May 20 2026 - 23:45:43 +0300)
Silicon version:        3
Trying to boot from MMC1

U-Boot 2025.01 (May 20 2026 - 23:45:43 +0300)

CPU:   Zynq 7z020
Silicon: v3.1
DRAM:  ECC disabled 1 GiB
...
Found /extlinux/extlinux.conf
Retrieving file: /uImage
Retrieving file: /system.dtb
## Booting kernel from Legacy Image at 02000000 ...
   Image Name:   Linux-6.12.40-xilinx
   Data Size:    6857832 Bytes = 6.5 MiB
   Load Address: 00008000
   Verifying Checksum ... OK
Loading Device Tree to 2fff9000 ... OK

Starting kernel ...

Итог

Полный путь от Vivado-проекта до работающей Linux-системы:

  1. Описание аппаратной платформы в Vivado, экспорт XSA с ps7_init_gpl.c
  2. Проверка базовой конфигурации через Vitis (UART Hello World)
  3. Понимание цепочки: BootROM → SPL → U-Boot → kernel
  4. Генерация Device Tree через xsct и device-tree-xlnx
  5. Организация BR2_EXTERNAL дерева
  6. Написание конфигурационных фрагментов для U-Boot и ядра
  7. Сборка образа через Buildroot
  8. Подготовка SD-карты с правильной разметкой и extlinux.conf

Все исходники: github.com/FernandesKA/buildroot_custom