В данной статье я постараюсь описать процесс создания кастомного образа Linux на Zynq UltraScale+ MPSoCс. Каждый необходимый компонент будет собран отдельно с использованием соответствующих утилит. Статья разбита на разделы, которые шаг за шагом познакомят вас с процессом сборки и запуска системы на данной платформе.
В следующих статьях попробуем собрать всю систему сразу с помощью buildroot и Yocto/petalinux.

Источники для дополнительного изучения
-
Инструкция по сборке Linux from scratch на Xilinx Wiki
-
Видеоинструкция по сборке Embedded Linux от Алексея Ростова для Advanced Engineering Radar Systems: часть 1, часть 2
-
Плейлист FPGA Systems посвященный Zynq
Предварительные требования
Для работы потребуются
-
Xilinx Vivado
-
Xilinx Vitis (я буду использовать Vitis Unified IDE из версии 2023.2.2, однако аналогичные действия можно повторить и в Vitis Classic из предыдущих версий)
-
Машина с Linux (можно как поставить Xilinx приложения на Windows, а остальное запускать в VM/dualboot/etc, так и сразу работать из Linux)
-
Xilinx Device Tree Generator (https://github.com/Xilinx/device-tree-xlnx)
-
Xilinx ATF (https://github.com/Xilinx/arm-trusted-firmware)
-
Xilinx U-boot (https://github.com/Xilinx/u-boot-xlnx)
-
Xilinx Linux Kernel (https://github.com/Xilinx/linux-xlnx/)
-
Buildroot (git://git.buildroot.net/buildroot)
-
aarch64-linux-gnu-gcc
-
u-boot tools
Этапы выполнения
Для запуска Linux на zynqmp требуются следующие компоненты
-
FSBL
-
PMU frimware
-
ARM trusted firmware
-
Bitstream
-
U-Boot
-
Devicetree
-
Linux Kernel
-
Linux RootFS
Создание проекта в Vivado
Я буду работать с платой Alinx AXU9EGB.
AXU9EGB User Manual
SCH для SoM ACU9EG_SCH
При настройке периферии буду ориентироваться на пример проекта, предоставленный производителем на GitHub.
Согласно схеме, на плате установлен XCZU9EG-FFVB1156-2-I. Соответственно проект будем создавать под неё. Добавим на схему Block Design блок Zynq UltraScale и перейдём к его настройкам.
Настройка памяти
На плате установлены 4 чипа MT40A512M16LY-062E от Micron, общим объёмом 4 ГБ. Однако ввиду возможностей Zynq использовать будем профиль от 083E, т.е. частоту 1200 МГц, Speed Bin 2400P и задержки 16-16-16. Устновим пресет MT40A256M16LY_083E, поменяем ёмкость DRAM на 8192 Мбит и количество бит в счётчике рядов на 16. Окончательный вариант показан на рисунке

Необходимая периферия
-
QSPI для FSBL, PMUFW, ATF и U-Boot
-
SD для Linux
-
UART нужен для взаиомдействия с U-Boot
-
GEM (Ethernet) для управления Linux через ssh
-
За необходимостью отключим сейчас AXI Master шину
-
Добавим в схему также блок Processing System Reset.
Итоговая схема

Для сборки данной схемы можно передать в tcl консоль следующие комманды
create_bd_cell -type ip -vlnv xilinx.com:ip:zynq_ultra_ps_e zynq_ultra_ps_e_0 apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset "1" } [get_bd_cells zynq_ultra_ps_e_0] set_property -dict [list \ CONFIG.SUBPRESET1 {DDR4_MICRON_MT40A256M16GE_083E} \ CONFIG.PSU__DDRC__DEVICE_CAPACITY {8192 MBits} \ CONFIG.PSU__DDRC__ROW_ADDR_COUNT {16} \ CONFIG.PSU__DDRC__CWL {16} \ CONFIG.PSU__UART0__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__UART0__PERIPHERAL__IO {MIO 42 .. 43} \ CONFIG.PSU__QSPI__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__QSPI__PERIPHERAL__DATA_MODE {x4} \ CONFIG.PSU__QSPI__PERIPHERAL__IO {MIO 0 .. 12} \ CONFIG.PSU__QSPI__PERIPHERAL__MODE {Dual Parallel} \ CONFIG.PSU__QSPI__GRP_FBCLK__ENABLE {1} \ CONFIG.PSU__SD0__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__SD0__DATA_TRANSFER_MODE {8Bit} \ CONFIG.PSU__SD0__PERIPHERAL__IO {MIO 13 .. 22} \ CONFIG.PSU__SD0__SLOT_TYPE {eMMC} \ CONFIG.PSU__SD0__RESET__ENABLE {1} \ CONFIG.PSU__SD1__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__SD1__PERIPHERAL__IO {MIO 46 .. 51} \ CONFIG.PSU__SD1__GRP_CD__ENABLE {1} \ CONFIG.PSU__SD1__SLOT_TYPE {SD 2.0} \ CONFIG.PSU__TTC0__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__TTC1__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__TTC2__PERIPHERAL__ENABLE {1} \ CONFIG.PSU__TTC3__PERIPHERAL__ENABLE {1} \ CONFIG.PSU_BANK_0_IO_STANDARD {LVCMOS18} \ CONFIG.PSU_BANK_1_IO_STANDARD {LVCMOS18} \ CONFIG.PSU_BANK_2_IO_STANDARD {LVCMOS18} \ CONFIG.PSU__USE__M_AXI_GP0 {0} \ CONFIG.PSU__USE__M_AXI_GP2 {0} \ \ ] [get_bd_cells zynq_ultra_ps_e_0] # Create instance: proc_sys_reset_0, and set properties set proc_sys_reset_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 proc_sys_reset_0 ] # Create port connections connect_bd_net -net zynq_ultra_ps_e_0_pl_clk0 [get_bd_pins zynq_ultra_ps_e_0/pl_clk0] [get_bd_pins proc_sys_reset_0/slowest_sync_clk] connect_bd_net -net zynq_ultra_ps_e_0_pl_resetn0 [get_bd_pins zynq_ultra_ps_e_0/pl_resetn0] [get_bd_pins proc_sys_reset_0/ext_reset_in] validate_bd_design save_bd_design
Сгенерируем bitstream (Design SourcesCreate HDL Wrapper
Generate Output Products
Run Synthesis
Run Implementation
Generate Bitstream) и экспоритируем его в XSA файл вместе с платформой (File
Export
Export Hardware
Include Bitstream). На этом работа в Vivado закончена, переходим в Vitis.
FSBL и PMUFW
Я буду работать в Vitis Unified IDE, инструкцию для Xilinx SDK можно найти в этой статье, а для Vitis Classic — в этом видео.
В папке Vivado проекта создадим директорию vitis_projects и скопируем в неё экспортированный .xsa файл, эта папка будет Workspace для Vitis. Запускаем Vitis и выбираем ранее созданную папку в команде Open Workspace.
Создадим новый проект по команде File->New Component->Platform. На этапе выбора платформы, выбираем Hardware Design и передаём наш xsa-файл.
На этапе выбора окружения выставляем следующие настройки:

Собираем проект и в Output->%project_name%->sw->boot будут лежать нужные нам fsbl.elf и pmufw.elf.

Эти файлы пригодятся нам позже.
Генерация FSBL и PMU FW из XSCT
Примечание: если у вас не открывается XSCT из консоли, то необходимо добавить в PATH папку, в которую у вас установлен Vitis.
создадим файл gensoft.tcl
hsi::open_hw_design -name alynx-linux simple_linux_wrapper.xsa set sw_pmufw [hsi::create_sw_design pmufw -app zynqmp_pmufw -proc psu_pmu_0] common::set_property -name APP_COMPILER_FLAGS -value "-DENABLE_EM" -objects $sw_pmufw hsi::generate_app -sw $sw_pmufw -compile -dir boot/pmufw -app zynqmp_pmufw set sw_fsbl [hsi::create_sw_design fsbl -app zynqmp_fsbl -proc psu_cortexa53_0] common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_NAND_EXCLUDE_VAL=1" -objects $sw_fsbl common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_FSBL_SECURE_EXCLUDE_VAL=1" -objects $sw_fsbl common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_FSBL_BS_EXCLUDE_VAL=1" -objects $sw_fsbl # common::set_property -name APP_COMPILER_FLAGS -value "-DFSBL_FSBL_DEBUG=1" -objects $sw_fsbl hsi::generate_app -sw $sw_fsbl -compile -dir boot/fsbl -app zynqmp_fsbl hsi::close_hw_design -name alynx-linux
в консоли
xsct -eval source gensoft.tcl
и копируем получившиеся в подпапках файлы «executable.elf» (по поводу выбранного пути пояснено далее)
find . -type f -name "*.elf" | grep pmufw | xargs -i cp {} ~/alynx-linux/output/pmufw.elf find . -type f -name "*.elf" | grep fsbl | xargs -i cp {} ~/alynx-linux/output/fsbl.elf
Zynq MP DRAM tests
Проверим верность настроек DDR. Создадим новый проект на основе шаблона тестов оперативной памяти.
Examples Zynq MP DRAM tests
Create Application Component from Template

Build, Run
Для взаимодействия с платой потребуется также какая-нибудь утилита для просмотра COM-порта. Я буду использовать расширение Serial Monitor для VSCode, просто потому что оно у меня есть. Для Windows могу посоветовать Terminal 1.9b
Device Tree
Склонируем репозиторий device-tree-xlnx на свой ПК и перейдём на ветку с релизом, соответствующим версии Xilinx IDE.
git clone https://github.com/Xilinx/device-tree-xlnx.git cd device-tree-xlnx git branch -r git checkout xlnx_rel_v2023.2
Скопируем xsa-файл в отдельную папку. В ней же создадим новый tcl-файл следующего содержания
hsi::open_hw_design simple_linux_wrapper.xsa hsi::set_repo_path /home/lazba/device-tree-xlnx/ hsi::create_sw_design device-tree -os device_tree -proc psu_cortexa53_0 hsi::generate_target -dir my_dts hsi::close_hw_design [hsi current_hw_design]
Запускаем терминал(cmd/powershell/bash/etc) в этой папке. Примечание: если у вас не открывается XSCT из консоли, то необходимо добавить в PATH папку, в которую у вас установлен Vitis.
xsct -eval source gendt.tcl
P.S. также devicetree можно взять из platform-проекта Vitis, оно находится по пути /export/platform/hw/sdt,- нужна вся папка.

Подготовка к дальнейшей работе
Дальнейшая работа будет проходить в Linux. Перечислю основные пакеты, которые вам понадобятся (однако возможно в вашем дистрибутиве не будет хватать и других, ориентируйтесь на ошибки в консоли)
-
git
-
base-devel (на Debian-based это build-essential)
-
gcc, g++
-
aarch64-linux-gnu-gcc (и прочие)
-
make
-
binutils
-
python, python-setuptools
-
bison
-
flex
-
swig
-
tar
-
cpio
-
zip, unzip
-
patch
-
dtc
Если в репозиториях нет dtc и aarc64-gcc, не переживайте, расскажу об этом далее.
В Home создадим директорию alynx-linux под наш проект. В ней создадим поддиректории
-
output — под итоговые бинарники и прочие файлы
-
devicetree
-
uboot — для сборки U-Boot
-
kernel — для ядра
-
buildroot — для RootFS
mkdir -p ~/alynx-linux/output mkdir -p ~/alynx-linux/uboot mkdir -p ~/alynx-linux/kernel mkdir -p ~/alynx-linux/buildroot mkdir -p ~/alynx-linux/devicetree
Скопируем .bit-файл в папку hw, его можно в папке с xsa-файлом (XSCT) либо в папке Output/platform/hw (Vitis).
В дальнейшем, когда будет идти работа с инструментами Xilinx SDK, если они установлены у вас в Windows, я буду предполагать, что вы скопировали на Windows-машину необходимые файлы, что перед тем были сгенерированы в Linux.
Device Tree (продолжение)
Xilinx DTG сгенерировал и положил в папку my_dts несколько файлов, из которых нам, однако, интересны лишь следующие
-
system.dts — файл, в котором хранятся некоторые настройки периферии, например, MAC-адрес для Ethernet
-
pcw.dtsi — файл с изменениями настроек периферии, сгенерированный на основе того, что мы сделали в Vivado
-
system-top.dts — файл верхнего уровня, который включает в себя все остальные
Файлы zynqmp.dtsi и zynqmp-clk-ccf.dtsi содержат описания исходных состояний периферии PS части, однако они нас не интересуют, так как в репозиториях U-Boot и Linux содержатся более правильные, или, скорее, более заточенные версии этих файлов.
Создадим новый файл alynx-linux.dts. В этот файл необходимо скопировать всё содержимое интересующих нас dts/dtsi файлов. При этом отмечу несколько отличий:
&sdhci1 { clock-frequency = <199998001>; status = "okay"; xlnx,mio-bank = <0x1>; disable-wp; no-1-8-v; u-boot,dm-pre-reloc; };
5, 6, 7 строки необходимо добавить в описание интерфейса SD, чтобы microSD карта не находилась в режиме read-only из-за неверного определения Write Protect, уровней напряжения логического сигнала; и была доступна из U-Boot.
Также можно добавить описание борды в раздел model. Пример минимальной конфигурации смотрим под катом
alynx-linux.dts
// SPDX-License-Identifier: GPL-2.0 /* * dts file for Alynx AXU9EG devmy */ /dts-v1/; #include "zynqmp.dtsi" #include "zynqmp-clk-ccf.dtsi" / { model = "Alynx AXU9EG"; compatible = "xlnx,zynqmp"; aliases { ethernet0 = &gem3; serial0 = &uart0; spi0 = &qspi; mmc0 = &sdhci1; }; chosen { bootargs = "earlycon"; stdout-path = "serial0:115200n8"; }; memory@0 { device_type = "memory"; reg = <0x0 0x0 0x0 0x7ff00000>, <0x00000008 0x00000000 0x0 0x80000000>; }; }; &gem3 { local-mac-address = [00 0a 35 00 00 00]; phy-mode = "rgmii-id"; status = "okay"; xlnx,ptp-enet-clock = <0x0>; }; &sdhci1 { clock-frequency = <199998001>; status = "okay"; xlnx,mio-bank = <0x1>; disable-wp; no-1-8-v; u-boot,dm-pre-reloc; }; &qspi { is-dual = <1>; num-cs = <2>; spi-rx-bus-width = <4>; spi-tx-bus-width = <4>; status = "okay"; }; &uart0 { cts-override ; device_type = "serial"; port-number = <0>; status = "okay"; u-boot,dm-pre-reloc ; };
Достаточно интересен раздел aliases. В нём можно назначить конкретным интерфейсам соответствующие номера. Например, uart1 записать как serial0, если вам удобнее выводить консоль в uart1; или sdhci1 как mmc0, чтобы разметить sd-карту как 0 устройство mmc в /dev/.
Поле chosen/bootargs содержит в себе команду, которая передаётся при инициализации ядра Linux. В stdout-path можно указать интерфейс, в который будет выводиться лог ядра и системная консоль, а также настройки этого интерфейса.
Положим файл alynx-linux.dts в папку output.
ARM Trusted Firmware
Выведем список тегов в удалённом репозитории, выберем и склонируем последний стабильный релиз. Параметр --depth 1 указывает, что необходимо скачать только файлы из указанного коммита без истории изменений, других веток и т.д., он сэкономит нам некоторое колимчество полимеров в дальнейшем.
cd ~/ git ls-remote -t https://github.com/Xilinx/arm-trusted-firmware git clone --depth 1 --branch xlnx_rebase_v2.8_2023.2 https://github.com/Xilinx/arm-trusted-firmware.git
Для сборки необходимо иметь установленный aarch64-linux-gnu-gcc. Если его нет в ваших репозиториях, то его можно скачать с сайта ARM.
Установка из tar
tar -xf %archive-name% -v
Переименуйте распакованную директорию до более простого имени, например aarch64-none-linux-gnu. Скопируйте в удобное место, например в home или /tools/.И добавьте директорию в path.
make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=aarch64 PLAT=zynqmp RESET_TO_BL31=1
Нужный нам файл лежит в …arm-trusted-firmware/build/zynqmp/release/bl31/bl31.elf . Скопируем его в рабочую директорию.
cp $HOME/arm-trusted-firmware/build/zynqmp/release/bl31/bl31.elf $HOME/alynx-linux/hw/bl31.elf
P.S. Если make будет выдавать `Error 1` после warning о правах доступа, то просто игнорируем, файл всё равно будет собран. Если хотите убрать эту ошибку, добавьте --no-warn-rwx-segments к флагам линкера в Makefile.
U-Boot
Склонируем на свой ПК репозиторий u-boot-xlnx и перейдём на коммит, соответствующий версии 2023.2.
cd ~/ git ls-remote -t https://github.com/Xilinx/u-boot-xlnx git clone --depth 1 --branch "xlnx_rebase_v2023.01_2023.2" https://github.com/Xilinx/u-boot-xlnx
Перенесём alynx-linux.dts в рабочую директорию u-boot
cd ~/alynx-linux mkdir -p uboot/arch/arm/dts/ cp output/alynx-linux.dts uboot/arch/arm/dts/alynx-linux.dts
В папке uboot создадим скрипт сборки build-uboot. Команда chmod+x выдаёт скрипту разрешение на исполнение.
cd uboot touch build_uboot ✔ chmod +x build_uboot
build-uboot
#!/bin/sh make distclean make -C $HOME/u-boot-xlnx distclean make -C $HOME/u-boot-xlnx \ O=$PWD \ xilinx_zynqmp_virt_defconfig sed -i 's/\(CONFIG_DEFAULT_DEVICE_TREE="\)[^"]*/\1'alynx-linux'/' .config sed -i 's/\(CONFIG_OF_LIST="\)\([^"]*\)/\1'alynx-linux\ '\2/' .config sed -i 's/\(CONFIG_SPL_OF_LIST="\)\([^"]*\)/\1'alynx-linux\ '\2/' .config make -j3 -C $HOME/u-boot-xlnx \ O=$PWD \ CROSS_COMPILE=aarch64-none-linux-gnu- \ DEVICE_TREE="alynx-linux" cp $HOME/alynx-linux/uboot/u-boot.elf $HOME/alynx-linux/output/u-boot.elf
Данный скрипт применяет стандартные настройки для ZynqMP, добавляет в него найстройки (sed) dts для нашего устройства, после чего запускается сборка с использованием до 3 параллельных потоков( -j3, если у вас больше ядер, можете заменить, например, на -j8 ). После сборки нужный нам elf-файл копируется в папку output.
Тестовый запуск
Проверим запуск U-Boot и загрузим его в QSPI, пока что без системы. Переведём ZynqMP в режим загрузки из JTAG и запустим xsct из папки с нашими elf-файлами (если Vivado у вас в Windows, то просто скопируйте файлы туда). Для того, чтобы загрузиться из JTAG, в соответствие с UG1137, необходимо разблокировать PMU. Подключим UART шину устройства к ПК (для мониторинга вывода с устройства). Включим устройство в режиме загрузки из JTAG. В папке alynx-linux/hw (где содержатся образы бутлоадера) создадим tcl-скрпит jtagload.tcl.
jtagload.tcl
#Disable Security gates to view PMU MB target targets -set -filter {name =~ "PSU"} #By default, JTAGsecurity gates are enabled #This disables security gates for DAP, PLTAP and PMU. mwr 0xffca0038 0x1ff after 500 #Load and run PMU FW targets -set -filter {name =~ "MicroBlaze PMU"} dow pmufw.elf con after 500 #Reset A53, load and run FSBL targets -set -filter {name =~ "Cortex-A53 #0"} rst -processor dow fsbl.elf con #Give FSBL time to run after 5000 stop #Other SW... dow u-boot.elf dow bl31.elf con #по желанию можно так же загрузить битстрим #Targets -set -nocase -filter {name =~ "*PL*"} #fpga simple-linux.bit
В терминале пишем
xsct -eval jtagload.tcl
После чего увидим следующую картину в UART терминале

Теперь создадим загрузочный boot.bin для запуска из QSPI/SD. В папке с elf-файлами создаём bif-файл, который будет хранить в себе описание последовательности загрузки файлов и опции.
//arch = zynqmp; split = false; format = BIN the_ROM_image: { [bootloader, destination_cpu = a53-0]fsbl.elf [pmufw_image]pmufw.elf [destination_cpu = a53-0, exception_level = el-3, trustzone]bl31.elf [destination_cpu = a53-0, exception_level = el-2]u-boot.elf }
И сгенерируем boot.bin
bootgen -image boot.bif -o boot.bin -arch zynqmp
Либо в Vitis: Vitis Create Boot Image
Zynq Ultrascale+
Import Existing BIF file
Create Image

После чего заливаем программу в QSPI FLASH (удостоверьтесь, что Zynq переведён в режим загрузки с JTAG)
xsct connect exec program_flash -f boot.bin -flash_type qspi-x8-dual_parallel -fsbl fsbl.elf -blank_check -verify exit
Либо в Vitis: Vitis Program Flash

Теперь, если переключатели BOOT переведены в загрузку из QSPI, автоматически будет стартовать FSBL->PMUFW->ATF->U-Boot. Bitstream будем загружать не из FSBL, а из Linux из SD.
Linux Kernel
Склонируем репозиторий на нужной нам ветке
git ls-remote -h https://github.com/Xilinx/linux-xlnx git clone --depth 1 --branch "xlnx_rebase_v6.6_LTS" https://github.com/Xilinx/linux-xlnx
В рабочей директории создадим файл kernel_build, который будет компилировать ядро в своей подпапке и копировать результат в output.
kernel_build
#!/bin/sh make -C $HOME/linux-xlnx \ O=$PWD \ ARCH=arm64 \ xilinx_zynqmp_defconfig make -C $HOME/linux-xlnx \ O=$PWD \ ARCH=arm64 \ LOCALVERSION= \ CROSS_COMPILE=aarch64-none-linux-gnu- \ nconfig make -C $HOME/linux-xlnx -j4 \ O=$PWD \ ARCH=arm64 \ LOCALVERSION= \ CROSS_COMPILE=aarch64-none-linux-gnu- cp $HOME/alynx-linux/kernel/arch/arm64/boot/Image $HOME/alynx-linux/output/Image cp $HOME/alynx-linux/kernel/arch/arm64/boot/Image.gz $HOME/alynx-linux/output/Image.gz
В конфигураторе включаем overlay filesystem support (как на картинке) — это нам нужно для загрузки bitstream из рантайма.

Скомпилированные файлы Image и Image.gz будут скопированы в папку output.
Device Tree Blob
Для дальнейшей работы вам потребуются gcc (возможно он предустановлен в вашем дистрибутиве) и dtc (в репозиториях он может быть обозван как dtc либо device-tree-compiler)
Если в репозиториях нет DTC
git clone https://git.kernel.org/pub/scm/utils/dtc/dtc.git cd dtc make export PATH=$PATH:$PWD
DTB передаётся в ядро линукс для указания, какая периферия доступна на устройстве. Формат device-tree не допускает #include, однако во включениях содержатся необходимые определения и макросы; чтобы привести его к стандартному виду используем препроцессор Си ( cpp либо gcc -E). Необходимые корректные определения для генерации DTB возьмём из репозитория linux-xlnx. Полученный объединённый dts файл скомпилируем с помощью DTC. Создадим в папке с alynx-linux.dts файлом скрипт dtb_compile.sh.
#!/bin/sh INCDIR=$HOME/linux-xlnx/include DTSHDIR=$HOME/linux-xlnx/arch/arm64/boot/dts/ DTSDIR=$HOME/linux-xlnx/arch/arm64/boot/dts/xilinx/ cpp -nostdinc -I${INCDIR} -I${DTSHDIR} -I${DTSDIR} \ -undef -x assembler-with-cpp -o alynx-linux-pre.dts alynx-linux.dts dtc -I dts -O dtb --symbols -i${DTSHDIR} -i${DTSDIR} -o alynx-linux.dtb alynx-linux-pre.dts
Получившийся файл alynx-linux.dtb скопируем в папку output.
RootFS
В корневой ФС содержатся файлы и программы, необходимы для запуска окружения.
В папке buildroot создадим скрипт: br_config для запуска конфигуратора buildroot.
br_config
#!/bin/sh make -C /home/lazba/buildroot \ O=$PWD \ nconfig
Запустим конфигуратор и передём к настройкам желаемой ФС
Target Options
Сменим архитектуру на aarch64 le, остальное оставим по умолчанию.

Toolchain
У нас уже установлен aarch64 gcc, потому в настройках выбираем
-
Toolchain type (External toolchain)
-
Toolchain (Arm AArch64 **)
-
Toolchain origin (Pre-installed toolchain)

System configuration
-
System hostname root
-
Enable root login with password
-
Root password — root
-
-
Init system — systemd (можно оставить busybox по умолчанию)
-
/bin/sh (default shell) — bash (можно оставить busybox по умолчанию)
Target packages
Miscellaneous:
-
haveged — генератор случайных чисел, без которого много чего не работает
Networking applications:
-
dhcp
-
dhcpd — получение IP адреса
-
dropbear (SSH клиент)
-
ifupdown — управление сетевым интерфейсом
-
iperf, iperf3 — запустим в конце сетевой тест
-
iptables, iputils
-
lftp — кидаемся файлами
-
openssh (его требует lftp, вроде как)
Text editors and viewers
-
mc — так файлы смотреть удобнее
-
nano, vim
Filesystem Images
Создадим образы в форматах ext4, который будем заливать на флешку; и в формате cpio, который понадобится, если решим запускать систему из ramdisk, заодно подпишем образ для U-Boot.

Host Utilities
-
host dosfstools
-
host genimage
-
host mtools
Запуск сборки
Сохраняем конфигурацию по F6 и закрываем конфигуратор F9.
Создадим и запустим файл br_build.
br_build
#!/bin/sh make -C /home/lazba/buildroot \ O=$PWD \ BR2_JLEVEL="$(($(nproc)))" cp $PWD/images/rootfs.cpio.uboot $PWD/../output/rootfs.cpio.uboot cp $PWD/images/rootfs.ext2 $PWD/../output/rootfs.ext2 cp $PWD/images/rootfs.ext4 $PWD/../output/rootfs.ext4
По окончанию компиляции образа ФС, нужные нам файлы будут скопированы в папку output.
Подготовка SD карты
Разметка носителя
Необходимо создать на карте два раздела — fat32 для ядра и dt, ext4 для rootfs. В Linux это можно сделать с помощью GUI утилит, таких как Gparted или KDE Partition Manager, либо в терминале, как я покажу далее.
Вставляем карту в ПК и смотрим, каким файлом её монтировать
sudo dmesg
На выходе увидим что-то вроде такого:
usb-storage 1-6.2.1:1.0: USB Mass Storage device detected scsi host6: usb-storage 1-6.2.1:1.0 scsi 6:0:0:0: Direct-Access Mass Storage Device 1.00 PQ: 0 ANSI: 0 CCS sd 6:0:0:0: [sdb] 61945856 512-byte logical blocks: (31.7 GB/29.5 GiB) sd 6:0:0:0: [sdb] Write Protect is off sd 6:0:0:0: [sdb] Mode Sense: 03 00 00 00 sd 6:0:0:0: [sdb] No Caching mode page found sd 6:0:0:0: [sdb] Assuming drive cache: write through sdb: sdb1 sdb2 sd 6:0:0:0: [sdb] Attached SCSI removable disk
Понимаем, что карта находится в /dev/sdb. Очистим таблицу разделов с помощью dd.
sudo dd if=/dev/zero of=/dev/sdb bs=1024 count=1
Проверим
sudo fdisk -l /dev/sdb # ответ Disk /dev/sdb: 29,54 GiB, 31716278272 bytes, 61945856 sectors Disk model: Storage Device Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes
Информация о разделах не была выведена, соответственно таблица затёрта. Создадим новые разделы — 1 Гб под BOOT раздел, и всё остальное — под ROOTFS. Подробнее про паботу с fdisk можно почитать здесь.
sudo fdisk /dev/sdb
Далее последовательно передаём в fdisk следующие команды
-
o — создать таблицу MBR
-
n — создать новый раздел
-
тип раздела по умолчанию primary (просто Enter)
-
номер раздела оставляем по умолчанию (просто Enter)
-
первый сектор оставляем по умолчанию (далее в качестве значения по умолчанию буду писать прочерк)
-
Последний сектор выберем с помощью указания размера, пишем: +1G
Первый раздел создан, идём дальше
-
n
-
—
-
—
-
—
Второй раздел тоже создан
-
t — изменить тип раздела
-
1 — первый раздел
-
c — W95 FAT32 (LBA)
-
p — посмотреть, что получилось
-
w — записываем изменения на диск и закрываем программу

P.S. Последняя часть, по изменению типа первого раздела на W95 FAT32 (LBA) необязательна — можно оба раздела оставить как Linux. Я поставил её такой, чтобы попробовать запуститься полностью с SD-карты. По той же причине была выбрана таблица разделов MBR — zynqmpus+ не поддерживает для запуска носители с разметкой GPT. В то же время, если u-boot у нас на QSPI, то можно смело форматировать всё в GPT и не париться о типа разделов.
Инициализация файловых систем в разделах и запись RootFS
Создадим ФС на нашем диске
sudo mkfs.vfat -F 32 -n BOOT /dev/sdb1 sudo mkfs.ext4 -L ROOTFS /dev/sdb2
Запишем образ rootfs на второй раздел. После работы команды dd, label второго раздела может измениться на rootfs, я лично проблем не замечал, однако считается, что имена в нижнем регистре могут некорректно отрабатывать на некоторых системах, поэтому лучше это исправить.
sudo dd if=/home/lazba/alynx-linux/output/rootfs.ext4 \ of=/dev/sdb2 status=progress sudo e2label /dev/sdb2 ROOTFS
Работа с BOOT разделом.
Примонтируем boot
sudo mkdir -p /mnt/boot sudo mount /dev/sdb1 /mnt/boot
Перенесём туда образ ядра, а также DTB
sudo cp $HOME/alynx-linux/output/Image /mnt/boot/Image sudo cp $HOME/alynx-linux/output/alynx-linux.dtb /mnt/boot/alynx-linux.dtb
Также необходимо создать на карте файл extlinux/extlinux.conf следующего содержания.
label linux kernel /Image devicetree /alynx-linux.dtb append console="ttyPS0,115200" root="/dev/mmcblk0p2" rw rootwait
Этот файл хранит в себе настройки для U-Boot, по которым тот запускает систему.
Первый запуск
Вставляем карту в плату и запускаем. Видим меню U-Boot, который сканирует все носители на наличие инструкций для запуска, находит extlinux.conf и запускает ядро.

По окончанию загрузки заходим в систему под своим логином/паролем (root/root).

Подключим плату к роутеру и попробуем подключиться по SSH. Сначала узнаем свой IP адрес (пока ещё в UART терминале)

Видим, что роутер выдал нам адрес 192.168.3.122. Подключаемся по SSH с хоста

Напоследок протестируем скорость соединения между ПК и Zynqmp с помощью iperf/iperf3. Любопытные могут дополнительно глянуть статью про то как пользоваться iperf.

Запуск bitstream из linux
Напишем простенькую мигалку светодиодом для проверки
module led_blink( input sys_clk_p, input sys_clk_n, output reg led, output reg led2 ); IBUFDS #( .DIFF_TERM("FALSE") ) ibufds_inst ( .I(sys_clk_p), .IB(sys_clk_n), .O(internal_clk) ); reg [31:0]count; always @(posedge internal_clk) begin if(count == 200000000) begin //Time is up count <= 0; //Reset count register led <= ~led; //Toggle led (in each second) led2 <= ~led2; //Toggle led (in each second) end else begin count <= count + 1; //Counts 200MHz clock end end endmodule
Constraints при этом следующие
#pl led set_property -dict { PACKAGE_PIN AM13 IOSTANDARD LVCMOS33 } [get_ports { led }]; set_property -dict { PACKAGE_PIN AP12 IOSTANDARD LVCMOS33 } [get_ports { led2 }]; # pl clock set_property IOSTANDARD DIFF_SSTL12 [get_ports sys_clk_p] set_property PACKAGE_PIN AL8 [get_ports sys_clk_p] set_property PACKAGE_PIN AL7 [get_ports sys_clk_n] set_property IOSTANDARD DIFF_SSTL12 [get_ports sys_clk_n] create_clock -period 5.000 -name sys_clk_clk_p -waveform {0.000 2.500} [get_ports sys_clk_p]
Генерируем битстрим, прошиваем ПЛИС, смотрим — мигает, красивое (но только показывают).
Скопируем полученный битстрим в папку alynx-linux/output с именем firmware.bit. Подключаем флешку к ПК и копируем наш файл в /lib/firmware
sudo mkdir -p /run/media/lazba/ROOTFS/lib/firmware sudo cp $HOME/alynx-linux/output/firmware.bit /run/media/lazba/ROOTFS/lib/firmware/firmware.bit
Запускаем плату и в консоли прописываем загрузку bit файла из fpga-manager
echo 0 > /sys/class/fpga_manager/fpga0/flags echo firmware.bit > /sys/class/fpga_manager/fpga0/firmware
Наблюдаем мигающий светодиод! 🙂
P.S. это не самый правильный способ запуска PL-части в рантайме, более подробно об этом написано в этой статье на Xilinx Wiki.
Подведём итоги
В этой статье мы
-
Создали минимальный дизайн Zynqmp US+ в Vivado
-
Запустили тест памяти
-
Сгенерировали FSBL и PMU Firmware
-
Сгенерировали и скомпилировали device-tree устройства
-
Собрали ARM Trusted Firmware
-
Собрали U-Boot
-
Скомпилировали и запустили с платы boot.bin с fsbl+pmufw+atf+uboot
-
Собрали ядро Linux
-
Собрали образ RootFS
-
Подготовили SD-карту для запуска ОС
-
Запустили из системы несколько приложений
-
Залили bitstream в PL-часть из запущенной ОС
На этом всё, увидимся в следующих статьях!
Заходите к нам в чатек: https://t.me/fpgasystems_embd

ссылка на оригинал статьи https://habr.com/ru/articles/805171/
Добавить комментарий