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

от автора

Не так давно у меня на руках появилась плата 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

Периферия отладочной платы ZYNQ-RK7020-F Rev 1.1

Периферия отладочной платы ZYNQ-RK7020-F Rev 1.1

На плате уже присутствует прошитый образ 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:

  1. Определяет источник загрузки по выводам MIO[8:2], которые считываются один раз и сохраняются в регистре slcr.BOOT_MODE[6:0]

  2. Находит boot.bin на носителе и проверяет заголовок: поле идентификации должно содержать 0x584C4E58 ('XLNX'), контрольная сумма должна совпадать

  3. Обрабатывает блок Register Initialization из Boot Header — до 256 пар «адрес/значение», которые записываются в регистры PS до начала копирования образа

  4. Копирует 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:

  1. Выполняет инициализацию PS: тактирование, MIO, UART

  2. Настраивает DDR по параметрам из ps7_init_gpl.c — файла, экспортированного из Vivado для конкретного дизайна

  3. Загружает полный u-boot.img с SD-карты в DDR и передаёт ему управление

    ps7_init_gpl.c — ключевой файл, специфичный для каждой платы. В нём содержатся тайминги DDR, частоты PLL и конфигурация MIO. Без корректного ps7_init система не запустится.

📄 U-Boot Secondary Program Loader — Xilinx Wiki

Этап 3 — U-Boot

Полный U-Boot исполняется из DDR.
Здесь происходит инициализация всей периферии и передача управления ядру.

Что делает U-Boot:

  1. Инициализирует MMC, Ethernet, UART и остальную периферию

  2. Ищет /extlinux/extlinux.conf на первом разделе SD-карты и загружает по его инструкциям файлы в RAM память:

    • uImage0x02000000

    • system.dtb0x01F00000

  3. Запускает bootm, который перед передачей управления ядру:

    • Копирует ядро 0x020000000x00008000 (требование ARM Linux)

    • Перемещает DTB в безопасное место в конце DDR → 0x2fff9000

    • Останавливает все DMA-устройства

    • Переводит CPU в режим SVC, отключает MMU, кэши и прерывания

    • Устанавливает регистры: r0=0, r1=~0, r2=адрес DTB

  4. Прыгает на 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 ...

Подробнее можно почитать здесь:

📄 Booting ARM Linux — The Linux Kernel documentation

Этап 4 — Linux Kernel

4.1 — Декомпрессия

uImage — это zImage с заголовком U-Boot.
Поэтому по адресу 0x00008000 запускается не само ядро, а встроенный декомпрессор из arch/arm/boot/compressed/head.S, который:

  1. Проверяет, не перекроет ли распакованный образ сам себя; если перекроет — перемещает сжатый образ дальше

  2. Распаковывает vmlinux на адрес 0x00008000

  3. Сбрасывает кэши и передаёт управление на stext() — настоящую точку входа ядра

Первые 32 КБ (0x00000x7FFF) зарезервированы под внутренние нужды ядра: сюда попадает начальная таблица страниц (swapper_pg_dir по адресу 0x00004000).

📄 How the ARM32 Linux kernel decompresses — Linus Walleij
📄 How the ARM32 kernel starts — Linus Walleij

4.2 — Ранняя инициализация

После декомпрессии ядро выполняет по порядку:

  1. Настраивает временные page tables, размещает swapper_pg_dir

  2. Включает MMU и кэши

  3. Читает DTB по адресу из r2, проверяет magic 0xd00dfeed — из него ядро получает всю аппаратную конфигурацию

  4. Инициализирует GIC, планировщик, подсистему памяти

📄 Booting ARM Linux — The Linux Kernel documentation

4.3 — Монтирование rootfs

Параметры передаются через /extlinux/extlinux.conf:

root=/dev/mmcblk0p2 rw rootwait

Рассмотрим, что они означают:

Параметр

Назначение

root=/dev/mmcblk0p2

Корневая ФС — второй раздел SD-карты

rw

Смонтировать на запись

rootwait

Ждать появления устройства (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/

Файл

Назначение

Используется

ps7_init_gpl.c / .h

Ранняя инициализация PS: DDR, PLL, MIO

U-Boot SPL — до инициализации DDR

*.hwh (Hardware Handoff)

XML-описание аппаратуры

xsct — генерация Device Tree

*.bit

Битстрим для 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 Baudrate Processing System

Настройки UART Baudrate Processing System

После подключения к 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

Параметр

Назначение

kernel /uImage

Путь к ядру на FAT-разделе

fdt /zynq-rk7020f.dtb

Путь к Device Tree Blob

root=/dev/mmcblk0p2

Корневая ФС — второй раздел SD-карты

rw

Смонтировать на запись

rootwait

Ждать инициализации MMC-контроллера

earlycon

Включить вывод в 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:

  1. Описали аппаратную платформу в Vivado — сконфигурировали PS, экспортировали XSA с ps7_init_gpl.c и .hwh

  2. Проверили базовую конфигурацию через Vitis, убедились что UART работает до перехода к Buildroot

  3. Разобрали цепочку загрузки BootROM → SPL → U-Boot → kernel и поняли, что происходит на каждом этапе

  4. Сгенерировали Device Tree через xsct и device-tree-xlnx, подготовили его для U-Boot SPL

  5. Организовали BR2_EXTERNAL дерево, написали конфигурационные фрагменты для U-Boot и ядра

  6. Собрали образ через 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/