Не так давно у меня на руках появилась плата RK-ZYNQ7020-F REV 1.1.
В данной статье я хочу поделиться своим опытом, связанным с подготовкой buildroot для данной платы. Здесь мы разберем этапы от формирования xsa файла до запуска платы и первых логов linux, с объяснением что происходит в железе на каждом этапе.
Также в рамках этой статьи я не буду использовать FSBL, мы попробуем обойтись U-Boot SPL.
Итак, приступим к рассмотрению!
Описание отладочной платы
Документация от продавца:
https://disk.yandex.ru/d/S-ma8Wm3MY3ibQ
Базовый набор периферии на данной плате, заявленный в документации:
-
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)
-
XC7Z020-2CLG484I
На плате уже присутствует прошитый образ Linux в QSPI, который позволяет работать с PS частью нашего SoC, однако хочется научиться собирать linux для данной платы самостоятельно, как минимум для того, чтобы если мы захотели поэкспериментировать с драйверами — у нас уже были артефакты сборки ядра.
В качестве решения для подготовки образа продавец платы отправляет в архиве небольшую документацию, в которой объясняет, каким образом можно собрать petalinux под данную плату, какие исходные файлы из приложенного проекта для этого использовать. И сам проект, где все эти конфигурационные файлы petalinux присутствуют.
Мануал довольно подробный, понятный, несмотря на то, что большая часть — на китайском языке. Однако хочется иметь минимальный рабочий образ, который можно подготовить быстрее, и для этого мы будем использовать Buildroot.
На отладочной плате расположился XC7Z020-2CLG484I, в составе которого есть PS часть, содержащая в себе два ядра (ARM Cortex-A9). С ней мы и будем работать, PL в данной статье рассматриваться не будет. При помощи buildroot мы будем собирать U-Boot (включая SPL), Linux Kernel, rootfs, и впоследствии добавлять пакеты в наш образ, драйвера.
Перед тем, как продолжить дальше, предлагаю ознакомиться, что происходит при загрузке платы, и зачем нужны те или иные параметры.
Процесс загрузки платы RK-ZYNQ7020-F
Чтобы понять, что нужно сделать для запуска платы, разберём, как происходит загрузка системы.
Мы рассматриваем Non-secure mode.
Полная цепочка загрузки выглядит так:
BootROM → FSBL/U-Boot SPL → U-Boot → Linux kernel → rootfs
Каждый этап решает свою задачу и передаёт управление следующему. Разберём их по порядку.
Этап 1 — BootROM
BootROM — это неизменяемый код, зашитый в кристалл производителем. Он запускается первым сразу после подачи питания, исполняется на CPU0, а CPU1 в это время стоит в WFE, ожидая явного пробуждения.
Что делает BootROM:
-
Определяет источник загрузки по выводам
MIO[8:2], которые считываются один раз и сохраняются в регистреslcr.BOOT_MODE[6:0] -
Находит
boot.binна носителе и проверяет заголовок: поле идентификации должно содержать0x584C4E58('XLNX'), контрольная сумма должна совпадать -
Обрабатывает блок Register Initialization из Boot Header — до 256 пар «адрес/значение», которые записываются в регистры PS до начала копирования образа
-
Копирует SPL во внутреннюю память OCM (On-Chip Memory) и передаёт ему управление
На этом этапе DDR не инициализирован — работать можно только в пределах OCM.
Источник загрузки выбирается DIP-переключателем на плате. Соответствие состояния выводов и режима загрузки:
📄 Zynq-7000 TRM, Chapter 6 — Boot and Configuration (AMD UG585)
Этап 2 — U-Boot SPL
В типичной схеме Xilinx вторым этапом идёт FSBL (First Stage Boot Loader).
В этой статье вместо него используется U-Boot SPL — это позволяет собирать весь загрузочный процесс средствами Buildroot без использования Vitis, а сборку легко автоматизировать в CI/CD.
В большинстве гайдов, найденных на просторе интернета цепочка выглядит таким образом:
BootROM → FSBL → U-Boot → Linux
В нашем же случае мы откажемся от использования FSBL, и будем использовать U-Boot SPL. Последовательность загрузки тогда несколько изменится, и будет выглядеть так:
BootROM → U-Boot SPL → U-Boot → Linux
Почему нельзя запустить U-Boot напрямую из BootROM?
Полный U-Boot не помещается в OCM (256 КБ). А DDR на этом этапе ещё не инициализирован — U-Boot просто некуда загрузить. SPL решает именно эту проблему: он достаточно мал для OCM и умеет настроить DDR.
Что делает SPL:
-
Выполняет инициализацию PS: тактирование, MIO, UART
-
Настраивает DDR по параметрам из
ps7_init_gpl.c— файла, экспортированного из Vivado для конкретного дизайна -
Загружает полный
u-boot.imgс SD-карты в DDR и передаёт ему управлениеps7_init_gpl.c— ключевой файл, специфичный для каждой платы. В нём содержатся тайминги DDR, частоты PLL и конфигурация MIO. Без корректногоps7_initсистема не запустится.
Этап 3 — U-Boot
Полный U-Boot исполняется из DDR.
Здесь происходит инициализация всей периферии и передача управления ядру.
Что делает U-Boot:
-
Инициализирует MMC, Ethernet, UART и остальную периферию
-
Ищет
/extlinux/extlinux.confна первом разделе SD-карты и загружает по его инструкциям файлы в RAM память:-
uImage→0x02000000 -
system.dtb→0x01F00000
-
-
Запускает
bootm, который перед передачей управления ядру:-
Копирует ядро
0x02000000→0x00008000(требование ARM Linux) -
Перемещает DTB в безопасное место в конце DDR →
0x2fff9000 -
Останавливает все DMA-устройства
-
Переводит CPU в режим SVC, отключает MMU, кэши и прерывания
-
Устанавливает регистры:
r0=0,r1=~0,r2=адрес DTB
-
-
Прыгает на
0x00008000— U-Boot прекращает существование, управление передается ядру.
В логах это выглядит следующим образом
Booting kernel from Legacy Image at 02000000 ... Load Address: 00008000 Entry Point: 00008000Loading Kernel Image to 8000Loading Device Tree to 2fff9000 ...Starting kernel ...
Подробнее можно почитать здесь:
Этап 4 — Linux Kernel
4.1 — Декомпрессия
uImage — это zImage с заголовком U-Boot.
Поэтому по адресу 0x00008000 запускается не само ядро, а встроенный декомпрессор из arch/arm/boot/compressed/head.S, который:
-
Проверяет, не перекроет ли распакованный образ сам себя; если перекроет — перемещает сжатый образ дальше
-
Распаковывает
vmlinuxна адрес0x00008000 -
Сбрасывает кэши и передаёт управление на
stext()— настоящую точку входа ядра
Первые 32 КБ (0x0000–0x7FFF) зарезервированы под внутренние нужды ядра: сюда попадает начальная таблица страниц (swapper_pg_dir по адресу 0x00004000).
📄 How the ARM32 Linux kernel decompresses — Linus Walleij
📄 How the ARM32 kernel starts — Linus Walleij
4.2 — Ранняя инициализация
После декомпрессии ядро выполняет по порядку:
-
Настраивает временные page tables, размещает swapper_pg_dir
-
Включает MMU и кэши
-
Читает DTB по адресу из
r2, проверяет magic0xd00dfeed— из него ядро получает всю аппаратную конфигурацию -
Инициализирует GIC, планировщик, подсистему памяти
4.3 — Монтирование rootfs
Параметры передаются через /extlinux/extlinux.conf:
root=/dev/mmcblk0p2 rw rootwait
Рассмотрим, что они означают:
|
Параметр |
Назначение |
|---|---|
|
|
Корневая ФС — второй раздел SD-карты |
|
|
Смонтировать на запись |
|
|
Ждать появления устройства (MMC инициализируется асинхронно) |
Без rootwait ядро попытается смонтировать раздел до готовности MMC-контроллера и упадёт в панику:
VFS: Unable to mount root fs.
После успешного монтирования запускается /sbin/init, далее система готова к работе.
📄 bootparam(7) — Linux manual page, man7.org
📄 The kernel’s command-line parameters — kernel.org
Подготовка XSA в Vivado
Прежде чем переходить к Buildroot, нам нужно описать нашу аппаратную платформу в терминах, которые понимают U-Boot SPL и ядро Linux. Этим занимается Vivado.
На выходе этого этапа мы получим файл XSA (Xilinx Support Archive) — он нужен ровно для двух вещей:
-
ps7_init_gpl.c — код ранней инициализации PS (DDR, PLL, MIO), который встроится в U-Boot SPL
-
.hwh (Hardware Handoff) — XML-описание аппаратуры, из которого
xsctсгенерирует Device Tree
Если коротко: всё, что мы настраиваем в Vivado — это и есть описание нашего железа. Ошибка в таймингах DDR или неправильный MIO означает, что загрузчик не запустится или UART не заработает.
Шаг 1 — Создание проекта
File → Project → New
При выборе компонента указываем XC7Z020-2CLG484I — этот чип установлен на плате RK-ZYNQ7020-F. Тип проекта — RTL Project.
Шаг 2 — Block Design и добавление ZYNQ7 PS
В IP Integrator создаём новый Block Design и добавляем IP-ядро ZYNQ7 Processing System.
Вся конфигурация PS производится через двойной клик по блоку. Это единственный IP в нашем дизайне — PL мы не используем, поэтому никаких дополнительных блоков добавлять не нужно.
Шаг 3 — Конфигурация PS
PS-PL Configuration → AXI Non Secure Enablement
Отключаем M AXI GP0 interface. Этот интерфейс нужен для связи процессора с логикой PL через шину AXI. Раз мы не используем PL — интерфейс должен быть отключён.
DDR Configuration:
MT41K256M16 RE-125, Effective DRAM Bus Width 32 Bit
MIO Configuration:
Bank 0 I/O Voltage LVCMOS 3.3V
Bank 1 I/O Voltage LVCMOS 1.8V
SD0:
MIO40 — 45, а также card detect на MIO9.
UART0:
MIO 10, 11
ENET0:
MIO16-27, MDIO MIO 52-53
Шаг 4 — Финальные штрихи в Block Design
После конфигурации в Block Design должен остаться один блок без висящих портов. Создаём HDL-обёртку:
Sources → ПКМ на design → Create HDL Wrapper
Шаг 5 — Синтез, имплементация и экспорт XSA
Run Synthesis → Run Implementation
Затем экспортируем XSA:
File → Export → Export Hardware → Pre-synthesis (без bitstream)
Сохраняем файл — например, design_1_wrapper.xsa.
На этом работа в Vivado завершена, в ближайшее время он нам больше не понадобится.
Что внутри XSA и зачем он нужен дальше
XSA — это обычный архив. Посмотреть его содержимое можно довольно просто:
unzip design_1_wrapper.xsa -d xsa_contentsls xsa_contents/
|
Файл |
Назначение |
Используется |
|---|---|---|
|
|
Ранняя инициализация PS: DDR, PLL, MIO |
U-Boot SPL — до инициализации DDR |
|
|
XML-описание аппаратуры |
|
|
|
Битстрим для PL |
Только при экспорте «с битстримом» |
На следующем шаге XSA используется двумя путями, для U-Boot, и для генерации Device Tree:
Проверка минимальной конфигурации через Vitis
Этот шаг не обязателен, но крайне рекомендуется: перед тем как переходить к Buildroot, стоит убедиться, что UART работает корректно.
Отлаживать поведение U-Boot SPL без рабочего UART практически невозможно — вы не увидите ни вывода загрузчика, ни сообщений об ошибках.
Простой тест с hello_world занимает 5 минут и исключает целый класс проблем.
Шаги
1. Создание проекта в Vitis
Открываем Vitis, указываем путь до design_1_wrapper.xsa как платформу. Создаём новое приложение из шаблона — Hello World.
2. Перевод платы в режим JTAG
Перед прошивкой необходимо перевести плату в режим загрузки с JTAG — переключателем BOOT_MODE на плате. В этом режиме BootROM ожидает загрузки образа через JTAG, а не с SD-карты.
3. Сборка и прошивка
Собираем проект и заливаем на плату через Vitis (Run → Run Configurations → Single Application Debug).
4. Подключение к UART
Baudrate терминала должен совпадать с тем, что задан в конфигурации PS в Vivado. Посмотреть или изменить его можно там же:
После подключения к UART и запуска прошивки в терминале должно появиться:
Hello worldSuccessfully ran Hello World application
Если сообщение появилось — UART работает, конфигурация PS корректна, можно переходить к следующему этапу.
Какой порт использовать
Микросхема FT2232HQ на плате предоставляет два интерфейса одновременно: JTAG и USB-UART. После подключения в системе появляются два устройства:
/dev/ttyUSB0 ← JTAG/dev/ttyUSB1 ← UART (MIO10 / MIO11)
Для терминала нужен /dev/ttyUSB1.
Нумерация может отличаться в зависимости от других подключённых USB-устройств, можно посмотреть при помощиdmesg | grep ttyUSB после подключения платы.
Подготовка конфигурационных файлов для платы на Zynq-7000 с использованием Buildroot и BR2_EXTERNAL
В предыдущей части мы разобрались с процессом загрузки платы на базе Zynq-7000.
Теперь перейдём к практике: подготовим все необходимые конфигурационные файлы для сборки образа Linux с помощью Buildroot.
Все исходники для сборки, а также собранные артефакты доступны здесь:
Документация, на которую мы будем опираться:
Что такое BR2_EXTERNAL и зачем он нужен
Buildroot предоставляет механизм, позволяющий хранить board-специфичные файлы за пределами основного дерева Buildroot. Основная идея состоит в том, чтобы не изменять исходники самого Buildroot, а хранить всё, что относится к конкретной плате, во внешней директории.
Эти файлы включают:
-
defconfig-файлы Buildroot,
-
Device Tree,
-
конфиги Linux kernel и U-Boot,
-
патчи,
-
rootfs overlay,
-
собственные пакеты.
Такой подход позволяет безболезненно обновлять Buildroot, не поддерживать собственный fork, ну и удобно хранить поддержку нескольких плат под одним репозиторием.
В данном случае удобно использовать Buildroot как git-сабмодуль. Обновление Buildroot при этом сводится к переключению на новый релиз и обновлению сабмодуля — без ручного переноса изменений между версиями. Без BR2_EXTERNAL при каждом обновлении Buildroot пришлось бы вручную переносить собственные правки. Обычно это довольно скучное занятие, которого хочется всячески избежать.
Внешнее дерево активируется переменной BR2_EXTERNAL, передаваемой при первом вызове make:
make BR2_EXTERNAL=/path/to/buildroot_external zynq_rk7020f_defconfig
После первого вызова путь сохраняется в output/.br2-external.mk и указывать его повторно не нужно.
Структура проекта
buildroot_custom/├── buildroot/ # git-сабмодуль — сам Buildroot├── buildroot_external/ # BR2_EXTERNAL дерево└── README.md
Структура внешнего дерева buildroot_external/:
buildroot_external/├── external.desc # идентификатор дерева├── Config.in # точка входа config├── external.mk # включение пакетов├── configs/ # defconfig-файлы плат│ └── zynq_rk7020f_defconfig├── board/ # board-специфичные файлы│ └── zynq/│ ├── RK-ZYNQ7020-F/│ │ ├── dts/xilinx/│ │ │ ├── zynq-7000.dtsi│ │ │ └── zynq-rk7020f.dts│ │ ├── patches/│ │ │ ├── arm-trusted-firmware/│ │ │ ├── linux/│ │ │ ├── linux-headers/│ │ │ └── uboot/│ │ ├── ps7_init/│ │ │ ├── ps7_init_gpl.c│ │ │ └── ps7_init_gpl.h│ │ ├── rootfs_overlay/│ │ │ └── etc/│ │ │ ├── network/│ │ │ ├── ssh/│ │ │ └── sysctl.conf│ │ ├── linux-config.fragment│ │ ├── uboot-config.fragment│ ├── post-build.sh│ └── post-image.sh└── package/ # кастомные пакеты
Шаг 1: Обязательные файлы BR2_EXTERNAL
external.desc
Обязательный файл, который уникально идентифицирует внешнее дерево.
На основе поля name Buildroot формирует переменную BR2_EXTERNAL_<NAME>_PATH, доступную во всех файлах дерева.
name: FKAdesc: Buildroot
После обработки этого файла Buildroot экспортирует переменную BR2_EXTERNAL_FKA_PATH, указывающую на корень нашего дерева. Все пути внутри конфигов строятся на её основе.
Config.in
Точка входа для Kconfig-опций.
Buildroot автоматически включает этот файл в своё меню конфигурации:
menu "Custom Packages" source "$BR2_EXTERNAL_FKA_PATH/package/Config.in"endmenumenu "BootLoaders" source "$BR2_EXTERNAL_FKA_PATH/boot/Config.in"endmenumenu "Host Packages" source "$BR2_EXTERNAL_FKA_PATH/package/Config.in.host"endmenu
external.mk
Подключает .mk-файлы всех кастомных пакетов и загрузчиков через wildcard:
include $(sort $(wildcard $(BR2_EXTERNAL_FKA_PATH)/package/*/*.mk))include $(sort $(wildcard $(BR2_EXTERNAL_FKA_PATH)/boot/*/*.mk))
Шаг 2: Получение ps7_init из Vivado
Файлы ps7_init_gpl.c и ps7_init_gpl.h — это аппаратная конфигурация процессорной системы, сгенерированная Vivado на основе XSA-файла. Они содержат параметры ранней инициализации: настройки PLL, контроллера DDR, MIO-выводов и периферии PS.
U-Boot SPL использует эти файлы для инициализации системы до запуска полноценного U-Boot, то есть играет роль FSBL в нашей цепочке загрузки.
Файлы экспортируются из Vivado автоматически при генерации Hardware Platform (XSA). Найти их можно в директории проекта Vivado после генерации bitstream. Размещаем их в дереве:
board/zynq/RK-ZYNQ7020-F/ps7_init/├── ps7_init_gpl.c└── ps7_init_gpl.h
Важно: при изменении аппаратной конфигурации в Vivado (тактирование, DDR, MIO) необходимо повторно экспортировать эти файлы и обновить их в репозитории. Устаревшие
ps7_initприведут к нестабильной работе или отказу загрузки.
Шаг 3: Генерация Device Tree
Device Tree описывает ядру Linux и U-Boot всю аппаратную конфигурацию платы: процессорные ядра, контроллеры памяти, периферию, прерывания, параметры шин. Подробнее про дерево устройств можно почитать в Device Tree Specification.
Для генерации исходных файлов Device Tree используем инструменты Xilinx, описанные здесь: Generate DTS Files Using XSCT. Генерация выполняется на основе XSA-файла, экспортированного из Vivado.
Подготовка
Понадобится утилита xsct, входящая в состав Vitis (путь до директории установки у вас свой):
/home/fka/tools/Xilinx/2025.1/Vitis/bin/xsct
Также потребуется репозиторий от Xilinx — device-tree-xlnx. Клонируем его и переключаемся на ветку, соответствующую версии Vivado/Vitis:
git clone https://github.com/Xilinx/device-tree-xlnxcd device-tree-xlnxgit checkout <xilinx_rel_v20XX.X>
Использование одинаковых версий Vivado, Vitis и device-tree-xlnx желательно, поскольку между релизами структура генерируемого Device Tree периодически меняется.
TCL-скрипт для генерации Device Tree
Рядом с XSA-файлом создаём TCL-скрипт dts_gen.tcl:
hsi open_hw_design ./design_1_wrapper.xsahsi set_repo_path /home/fka/dev_linux/device-tree-xlnxhsi create_sw_design device-tree -os device_tree -proc ps7_cortexa9_0hsi set_property CONFIG.periph_type_overrides { {DEVICE_ID xc7z020} } [hsi get_os]hsi generate_target -dir ./dts_outputhsi close_hw_design [hsi current_hw_design]
Данный скрипт открывает XSA-файл, указывает репозиторий device-tree-xlnx, создаёт описание Device Tree для ps7_cortexa9_0 и генерирует DTS/DTSI-файлы в директорию dts_output.
Запускаем xsct и выполняем скрипт:
xsctsource dts_gen.tcl
После выполнения появится вывод вида:
INFO: [Hsi 55-2053] elapsed time for repository loading 0 secondsWARNING: The DTG tool is scheduled to be deprecated in a future release.WARNING: ps7_ethernet_0: No reset found
Предупреждение No reset found в данном случае можно игнорировать.
В директории dts_output появятся файлы:
dts_output/├── device-tree.mss├── include/│ └── dt-bindings/├── pcw.dtsi├── skeleton.dtsi├── system.dts├── system-top.dts└── zynq-7000.dtsi
Нас интересуют файлы с расширениями .dts и .dtsi.
Директорию include/dt-bindings копировать не нужно — нужные заголовки уже присутствуют в Buildroot.
Объединение DTS-файлов (опционально)
Сгенерированный Device Tree разбит на несколько файлов: system-top.dts, pcw.dtsi, zynq-7000.dtsi.
Можно оставить структуру как есть, либо объединить всё в один итоговый .dts через препроцессор GCC:
gcc -I my_dts \ -E \ -nostdinc \ -undef \ -D__DTS__ \ -x assembler-with-cpp \ -o system.dts \ system-top.dts
После этого получится единый system.dts со всем содержимым подключённых .dtsi.
Подготовка Device Tree для U-Boot SPL
U-Boot SPL использует сокращённую версию Device Tree, содержащую только устройства, необходимые на раннем этапе загрузки. Это связано с ограничением по размеру SPL и тем, что часть драйверов на данном этапе ещё недоступна.
Если необходимые узлы не помечены свойством bootph-all, U-Boot исключит их из Device Tree для SPL — и устройства окажутся недоступны при ранней инициализации. Поэтому до копирования файлов в buildroot_external добавляем bootph-all для UART и SD-контроллера:
&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>;};
Проверка корректности Device Tree
Перед добавлением файлов в buildroot_external рекомендуется проверить их компиляцию с помощью dtc (Device Tree Compiler) (его можно установить через apt):
dtc -@ -I dts -O dtb ./system.dts > ./system.dtb
При этом могут появиться предупреждения вида:
zynq-7000.dtsi:146: Warning (interrupt_provider): Missing #address-cells in interrupt provider
В данном случае Device Tree успешно собирается, а предупреждения не мешают дальнейшей работе. Полученный .dtb пока никуда копировать не нужно — Buildroot соберёт его самостоятельно. На данном этапе нам нужен именно исходный .dts или набор dts + dtsi.
После проверки копируем .dts и .dtsi файлы в директорию платы:
buildroot_external/board/zynq/RK-ZYNQ7020-F/dts/xilinx/├── zynq-7000.dtsi└── zynq-rk7020f.dts
Шаг 4: Патч для сборки DTS в U-Boot
U-Boot не знает о нашем Device Tree, поскольку он отсутствует в upstream arch/arm/dts/Makefile.
Необходимо сделать небольшой патч, который включит сборку нашего DTB. Создаём файл buildroot_external/board/zynq/RK-ZYNQ7020-F/patches/uboot/0001-add-rk7020f-dts.patch:
--- 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 dtb-$(CONFIG_ARCH_ZYNQMP) += \
Buildroot автоматически применит патч из директории patches/uboot/ при сборке U-Boot, если указана директория с патчами.
Шаг 5: Конфигурационные фрагменты
Вместо хранения полных .config-файлов для ядра и U-Boot используются конфигурационные фрагменты — файлы, содержащие только те опции, которые отличаются от базового defconfig. Это упрощает сопровождение при обновлении версий.
uboot-config.fragment
Фрагмент настраивает U-Boot SPL для нашей платы. Без него при первом запуске плата не подаёт признаков жизни: нет вывода в UART, нет возможности понять, на каком этапе остановилась загрузка.
Ключевые параметры:
# Ранний вывод через UART для отладки SPLCONFIG_DEBUG_UART=yCONFIG_DEBUG_UART_ZYNQ=yCONFIG_DEBUG_UART_BASE=0xE0000000CONFIG_DEBUG_UART_CLOCK=100000000# Поддержка serial в SPLCONFIG_SPL_SERIAL=yCONFIG_SPL_DM_SERIAL=yCONFIG_SPL_DM=yCONFIG_SPL_OF_CONTROL=y# Список DTB для SPL — наш DTB должен быть в этом списке,CONFIG_SPL_OF_LIST="zynq-rk7020f"
Адрес 0xE0000000 — физический адрес UART0 на Zynq-7000.
Частота 100000000 (100 МГц) должна соответствовать настройкам тактирования из ps7_init.
P.S. DEBUG опции можно не включать, они использовались мной при первоначальной отладке, когда плата не подавала признаки жизни.
linux-config.fragment
Фрагмент добавляет опции поверх базового xilinx_zynq defconfig:
# SPICONFIG_SPI=yCONFIG_SPI_MASTER=yCONFIG_SPI_XILINX=yCONFIG_SPI_SPIDEV=y# Device Tree overlaysCONFIG_OF_OVERLAY=yCONFIG_OF_DYNAMIC=y
P.S. Текущие опции не являются обязательными, их можно опустить. Мной они использовались для того, чтобы была возможность накладывать device tree overlay, и работать с SPI.
Шаг 6: rootfs overlay
Как это работает:
После того как Buildroot собрал все пакеты и сформировал output/target/ — но до упаковки в финальный образ — он выполняет rsync из директории overlay поверх целевой файловой системы:
rsync -a board/rk7020f/rootfs_overlay/ output/target/
Любой файл, помещённый в overlay, окажется в rootfs по тому же относительному пути. Существующие файлы перезаписываются, а новые добавляются.
Путь к overlay задаётся в buildroot_config.fragment:
BR2_ROOTFS_OVERLAY="board/rk7020f/rootfs_overlay"
📄 Buildroot manual — Customizing the generated target filesystem
Структура overlay в нашем случае
board/rk7020f/rootfs_overlay/├── etc/│ ├── network/│ │ └── interfaces ← статический IP│ ├── ssh/│ │ └── sshd_config ← настройки SSH
Содержимое моего overlay:
etc/network/interfaces — статический IP
Buildroot с busybox init поднимает сеть через скрипт S40network, который вызывает ifup по конфигурации из /etc/network/interfaces.
auto loiface lo inet loopbackauto eth0iface eth0 inet static address 192.168.1.100 netmask 255.255.255.0 gateway 192.168.1.1
Без этого файла в overlay Buildroot создаст /etc/network/interfaces со стандартным содержимым, где не будет прописан наш статический адрес.
etc/ssh/sshd_config — настройки SSH
По умолчанию Buildroot собирает dropbear или openssh с консервативными настройками.
Для разработки удобно разрешить вход по паролю и логин root:
PermitRootLogin yesPasswordAuthentication yes
Шаг 7: Итоговый defconfig
Собирая все описанные выше компоненты, zynq_rk7020f_defconfig выглядит следующим образом:
# АрхитектураBR2_arm=yBR2_cortex_a9=yBR2_ARM_ENABLE_NEON=yBR2_ARM_ENABLE_VFP=y# Внешний тулчейн от Bootlin (armv7-eabihf + glibc)BR2_TOOLCHAIN_EXTERNAL=yBR2_TOOLCHAIN_EXTERNAL_BOOTLIN=yBR2_TOOLCHAIN_EXTERNAL_BOOTLIN_ARMV7_EABIHF_GLIBC_STABLE=y# Файлы для конкретной платы (патчи, оверлей, post_build скрипты)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 (форк Xilinx, v6.12 LTS)BR2_LINUX_KERNEL=yBR2_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=yBR2_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 (форк Xilinx, v2025.01)BR2_TARGET_UBOOT=yBR2_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=yBR2_TARGET_UBOOT_SPL_NAME="spl/boot.bin"BR2_TARGET_UBOOT_NEEDS_DTC=yBR2_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"# rootfsBR2_TARGET_ROOTFS_EXT2=yBR2_TARGET_ROOTFS_EXT2_4=yBR2_TARGET_ROOTFS_EXT2_SIZE="256M"
Несколько важных моментов по параметрам:
В BR2_TARGET_UBOOT_ZYNQ_PS7_INIT_FILE указывается только .c-файл — соответствующий .h подтянется при сборке автоматически.
Шаг 8: Сборка
# Клонировать репозиторий с сабмодулямиgit clone --recurse-submodules https://github.com/FernandesKA/buildroot_customexport BR2_EXTERNAL_FKA_PATH=$(pwd)/buildroot_externalcd buildrootmake BR2_EXTERNAL=$BR2_EXTERNAL_FKA_PATH zynq_rk7020f_defconfigmake menuconfig #опционально, если хочется что - то поменятьmake
Результаты сборки окажутся в output/images/.
Подготовка SD-карты
После успешной сборки (make) в директории output/images/ появятся все необходимые файлы. Следующий шаг — подготовить SD-карту и правильно разложить эти файлы по разделам.
Important! В этом разделе показано, что необходимо сделать для подготовки образа на SD карточку. Не стоит повторять этот процесс самостоятельно, в следующем разделе будет описано, каким образом это было автоматизировано.
Что Buildroot собирает в output/images/
output/images/├── boot.bin ← U-Boot SPL (скопируется на boot-раздел)├── u-boot.img ← полный U-Boot (скопируется на boot-раздел)├── uImage ← ядро Linux (скопируется на boot-раздел)├── zynq-rk7020f.dtb ← Device Tree Blob (скопируется на boot-раздел)├── rootfs.ext4 ← корневая файловая система (второй раздел)└── rootfs.cpio.gz ← initramfs
Разметка карты
SD-карта должна иметь два раздела:
/dev/sdX1 FAT32 64 МБ boot ← загрузочные файлы, читаются U-Boot/dev/sdX2 ext4 остаток rootfs ← корневая файловая система Linux
Разметка через fdisk:
sudo fdisk /dev/sdX
Последовательность команд внутри fdisk:
o # создать новую таблицу разделов DOS (MBR)n # новый разделp # primary1 # номер раздела[Enter] # первый доступный сектор+64M # размер boot-разделаt # изменить тип разделаc # W95 FAT32 (LBA) — тип 0x0Ca # пометить раздел как bootablen # новый разделp # primary2 # номер раздела[Enter] # первый доступный сектор[Enter] # до конца картыw # записать изменения и выйти
Форматирование разделов:
sudo mkfs.vfat -F 32 -n BOOT /dev/sdX1sudo mkfs.ext4 -L rootfs /dev/sdX2> Флаг `bootable` на первом разделе обязателен — U-Boot SPL ищет загрузочный раздел именно по этому флагу.
Файлы на boot-разделе
Монтируем boot-раздел и копируем файлы из output/images/:
sudo mount /dev/sdX1 /mnt/bootsudo cp output/images/boot.bin /mnt/boot/sudo cp output/images/u-boot.img /mnt/boot/sudo cp output/images/uImage /mnt/boot/sudo cp output/images/zynq-rk7020f.dtb /mnt/boot/
Создаём директорию и файл конфигурации загрузки:
sudo mkdir -p /mnt/boot/extlinuxsudo nano /mnt/boot/extlinux/extlinux.conf
Содержимое extlinux.conf:
label Linux kernel /uImage fdt /zynq-rk7020f.dtb append root=/dev/mmcblk0p2 rw rootwait earlycon
|
Параметр |
Назначение |
|---|---|
|
|
Путь к ядру на FAT-разделе |
|
|
Путь к Device Tree Blob |
|
|
Корневая ФС — второй раздел SD-карты |
|
|
Смонтировать на запись |
|
|
Ждать инициализации MMC-контроллера |
|
|
Включить вывод в UART до инициализации tty |
sudo umount /mnt/boot
rootfs на второй раздел
Записываем rootfs.ext4 на второй раздел через dd:
sudo dd if=output/images/rootfs.ext4 of=/dev/sdX2 bs=1M status=progresssudo sync
После записи при желании можно расширить файловую систему на весь раздел:
sudo e2fsck -f /dev/sdX2sudo resize2fs /dev/sdX2
rootfs.ext4собирается с фиксированным размером 256 МБ (параметрBR2_TARGET_ROOTFS_EXT2_SIZE="256M"в defconfig).
Если вы используете карту на 4 ГБ и более —resize2fsосвободит всё оставшееся место под рабочие файлы.
Итоговая структура SD карты
После всех шагов карта должна выглядеть так:
/dev/sdX1 (FAT32, boot)├── boot.bin├── u-boot.img├── uImage├── zynq-rk7020f.dtb└── extlinux/ └── extlinux.conf/dev/sdX2 (ext4, rootfs)└── <корневая файловая система Linux> ├── bin/ ├── etc/ │ ├── network/interfaces ← статический IP из overlay │ ├── ssh/sshd_config ← конфиг SSH из overlay ├── sbin/ └── ...
Автоматизация через post-image.sh и genimage
Удобно ли каждый раз проделывать шаги, описанные выше? Я думаю, не очень. К тому же, это не слишком удобно воспроизводится.
В репозитории предусмотрена автоматизация через post-image.sh и genimage — именно для неё в defconfig включены host-пакеты:
BR2_PACKAGE_HOST_DOSFSTOOLS=y # mkfs.vfatBR2_PACKAGE_HOST_GENIMAGE=y # genimageBR2_PACKAGE_HOST_MTOOLS=y # работа с FAT без root
genimage создаёт готовый образ sdcard.img, который можно записать на карту одной командой:
sudo dd if=output/images/sdcard.img of=/dev/sdX bs=4M status=progresssudo sync
Для этого в директории платы должны быть два файла:
board/zynq/RK-ZYNQ7020-F/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 = 0xCbootable = "true"image = "boot.vfat"}partition rootfs {partition-type = 0x83image = "rootfs.ext4"}}
board/zynq/post-image.sh— запускает genimage после сборки:
#!/usr/bin/env bashset -eBOARD_DIR="$(dirname "$0")/RK-ZYNQ7020-F"GENIMAGE_CFG="${BOARD_DIR}/genimage.cfg"GENIMAGE_TMP="${BUILD_DIR}/genimage.tmp"# Создаём extlinux.conf прямо здесь, чтобы genimage его подхватилmkdir -p "${BINARIES_DIR}/extlinux"cat > "${BINARIES_DIR}/extlinux/extlinux.conf" << EOFlabel Linux kernel /uImage fdt /zynq-rk7020f.dtb append root=/dev/mmcblk0p2 rw rootwait earlyconEOFrm -rf "${GENIMAGE_TMP}"genimage \ --rootpath "${TARGET_DIR}" \ --tmppath "${GENIMAGE_TMP}" \ --inputpath "${BINARIES_DIR}" \ --outputpath "${BINARIES_DIR}" \ --config "${GENIMAGE_CFG}"
Первый запуск и отладка
Этот раздел описывает реальные проблемы, возникшие при отладке, и способы их устранения в том порядке, в котором они появлялись.
Проблема 1: SPL молчит после подачи питания
Симптом: После включения платы в UART-терминале — тишина. Никаких сообщений, включая U-Boot SPL.
Первое, что нужно проверить — правильный ли режим загрузки выбран DIP-переключателем. Для SD-карты MIO должны соответствовать режиму SD boot.
Если переключатель верный, но вывода всё равно нет, то проблема скорее всего в конфигурации UART для SPL. Давайте попробуем включить debug UART вывод.
SPL использует debug UART для раннего вывода — ещё до инициализации DM (Driver Model).
Он задаётся отдельно от основного UART через параметры в uboot-config.fragment:
CONFIG_DEBUG_UART=yCONFIG_DEBUG_UART_ZYNQ=yCONFIG_DEBUG_UART_BASE=0xE0000000 # физический адрес UART0 на Zynq-7000CONFIG_DEBUG_UART_CLOCK=100000000 # тактовая частота UART в ГцCONFIG_SPL_SERIAL=yCONFIG_SPL_DM_SERIAL=yCONFIG_SPL_DM=y
0xE0000000— физический адрес регистров UART0 на Zynq-7000. Если плата использует UART1 — адрес будет0xE0001000. Значение должно совпадать с тем, что задано в Vivado при конфигурации.
CONFIG_DEBUG_UART_CLOCKдолжен совпадать с тактовой частотой UART, заданной в Vivado. Несоответствие приведёт к тому, что реальное значение BAUDRATE может отличаться от ожидаемого.
Проблема 2: SPL стартовал, но не видит MMC
Симптом: В терминале появился вывод SPL, но загрузка останавливается:
U-Boot SPL 2025.01 (...)Trying to boot from MMC1spl: error reading image uImage, err - -22SPL: failed to boot from all boot devices### ERROR ### Please RESET the board ###
Код -22 в Linux/U-Boot — это EINVAL. SPL нашёл MMC-устройство, но не смог прочитать образ с файловой системы FAT.
Причина: В конфигурации SPL не были включены поддержка FAT и MMC:
CONFIG_SPL_FS_FAT=y # поддержка FAT-файловой системы в SPLCONFIG_SPL_MMC=y # поддержка MMC/SD в SPLCONFIG_SPL_OS_BOOT=n # отключить попытку прямой загрузки OS из SPL
Без CONFIG_SPL_FS_FAT и CONFIG_SPL_MMC SPL физически не умеет читать файлы с FAT-раздела SD-карты, хотя MMC-контроллер и инициализируется.
Проблема 3: sdhci0 и uart0 не инициализируются в SPL
Симптом: После добавления uboot-config.fragment с параметрами MMC и UART SPL всё равно не видит MMC. В выводе:
mmc0 not available
Причина: В новых версиях U-Boot (начиная с ~2022) устройства, которые должны быть доступны на этапе SPL, требуют явного указания в DTS через свойство bootph-all. Без него SPL-фаза игнорирует соответствующие узлы при инициализации DM.
Решение: Добавить bootph-all в узлы sdhci0 и uart0 в zynq-rk7020f.dts:
&sdhci0 { bootph-all; status = "okay"; xlnx,has-cd = <0x1>; ...};&uart0 { bootph-all; cts-override; device_type = "serial"; ...};
bootph-all— это замена устаревшегоu-boot,dm-pre-reloc. Означает: «инициализировать это устройство на всех фазах загрузки, включая SPL».
Проблема 4: U-Boot стартовал, но выводит Warning — bad CRC, using default environment
`
Симптом: U-Boot загружается, но при каждом старте выводит:
*** Warning - bad CRC, using default environment
И далее пытается прочитать переменные окружения из FAT (uboot.env), не находит их.
Причина: По умолчанию U-Boot для Zynq настроен искать переменные окружения в FAT-файле uboot.env на SD-карте. Файла нет — CRC не сходится — U-Boot падает на дефолтное окружение, которое может не содержать нужных bootcmd / bootargs.
Решение: Указать U-Boot не искать окружение во внешних источниках, а использовать встроенное:
CONFIG_ENV_IS_IN_FAT=n CONFIG_ENV_IS_NOWHERE=y
Проблема 5: Ядро загрузилось, но Ethernet берет рандомный MAC адрес
Симптом: Система загрузилась, сеть работает, но поднимается каждый раз с разным MAC-адресом (генерируется случайно).
Причина: В Device Tree для ethernet-узла не был задан local-mac-address. Без него драйвер macb генерирует случайный MAC при каждом старте.
Решение: Прописать фиксированный MAC-адрес в DTS:
&gem0 { phy-mode = "rgmii-id"; status = "okay"; xlnx,ptp-enet-clock = <0x69f6bcb>; local-mac-address = [02 4F A3 7C 1B E9]; /* добавлено */};
Радостно смотрим логи
После записи образа на SD-карту и подачи питания в терминале должен появиться вывод U-Boot SPL:
U-Boot SPL 2025.01 (May 20 2026 - 23:45:43 +0300)Silicon version: 3Trying to boot from MMC1
Затем управление передаётся полноценному U-Boot, который загружает ядро по конфигурации из extlinux.conf:
U-Boot 2025.01 (May 20 2026 - 23:45:43 +0300)CPU: Zynq 7z020Silicon: v3.1DRAM: ECC disabled 1 GiB...Found /extlinux/extlinux.confRetrieving file: /uImageRetrieving 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 ... OKLoading Device Tree to 2fff9000 ... OKStarting kernel ...
После загрузки ядра система готова к работе: сеть поднимается, плата пингуется, доступен SSH.
Посмотрим, что с нашей сеткой

Как видим, поднялась, проверим, что наша железка пингуется

Ну и наконец, зайдем по ssh на нее)

Как видим, все работает, можно переходить к более интересным вещам)
Итог
Мы прошли полный путь от чистого Vivado-проекта до работающей Linux-системы на плате RK-ZYNQ7020-F:
-
Описали аппаратную платформу в Vivado — сконфигурировали PS, экспортировали XSA с
ps7_init_gpl.cи.hwh -
Проверили базовую конфигурацию через Vitis, убедились что UART работает до перехода к Buildroot
-
Разобрали цепочку загрузки
BootROM → SPL → U-Boot → kernelи поняли, что происходит на каждом этапе -
Сгенерировали Device Tree через
xsctиdevice-tree-xlnx, подготовили его для U-Boot SPL -
Организовали
BR2_EXTERNALдерево, написали конфигурационные фрагменты для U-Boot и ядра -
Собрали образ через Buildroot и подготовили SD-карту с правильной разметкой и
extlinux.conf
Если вы портируете этот подход на другую плату на базе Zynq-7000, основные изменения сводятся к трём вещам: новый XSA из Vivado, обновлённый ps7_init_gpl.c и пересборка Device Tree. Остальная конфигурация Buildroot остаётся практически без изменений.
Все исходники доступны в репозитории:
https://github.com/FernandesKA/buildroot_custom
Документация от продавца:
https://disk.yandex.ru/d/S-ma8Wm3MY3ibQ
Буду рад вопросам, замечаниям, предложениям и конструктивной критике!
ссылка на оригинал статьи https://habr.com/ru/articles/1042798/