Linux From Scratch на Zynq UltraScale+ MPSoC

Kate

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

В следующих статьях попробуем собрать всю систему сразу с помощью buildroot и Yocto/petalinux.

a60b7372e8f1c2206f6e69ebbe831715.png

Источники для дополнительного изучения​

  1. Инструкция по сборке Linux from scratch на Xilinx Wiki
  2. Видеоинструкция по сборке Embedded Linux от Алексея Ростова для Advanced Engineering Radar Systems: часть 1, часть 2
  3. Плейлист FPGA Systems посвященный Zynq

Предварительные требования​

Для работы потребуются

  1. Xilinx Vivado
  2. Xilinx Vitis (я буду использовать Vitis Unified IDE из версии 2023.2.2, однако аналогичные действия можно повторить и в Vitis Classic из предыдущих версий)
  3. Машина с Linux (можно как поставить Xilinx приложения на Windows, а остальное запускать в VM/dualboot/etc, так и сразу работать из Linux)
  4. Xilinx Device Tree Generator (https://github.com/Xilinx/device-tree-xlnx)
  5. Xilinx ATF (https://github.com/Xilinx/arm-trusted-firmware)
  6. Xilinx U-boot (https://github.com/Xilinx/u-boot-xlnx)
  7. Xilinx Linux Kernel (https://github.com/Xilinx/linux-xlnx/)
  8. Buildroot (git://git.buildroot.net/buildroot)
  9. aarch64-linux-gnu-gcc
  10. u-boot tools

Этапы выполнения​

Для запуска Linux на zynqmp требуются следующие компоненты

  1. FSBL
  2. PMU frimware
  3. ARM trusted firmware
  4. Bitstream
  5. U-Boot
  6. Devicetree
  7. Linux Kernel
  8. 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. Окончательный вариант показан на рисунке

0c5f2a49d515f213730649ef86c0011c.png

Необходимая периферия​

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

ab753a50116f77279b609b3a5dd697fb.png
Для сборки данной схемы можно передать в 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 Sources
\rightarrow
Create HDL Wrapper
\rightarrow
Generate Output Products
\rightarrow
Run Synthesis
\rightarrow
Run Implementation
\rightarrow
Generate Bitstream) и экспоритируем его в XSA файл вместе с платформой (File
\rightarrow
Export
\rightarrow
Export Hardware
\rightarrow
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-файл.
На этапе выбора окружения выставляем следующие настройки:
2a067b0f9e6094da4ca2e6506f974087.png

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

Эти файлы пригодятся нам позже.
Генерация FSBL и PMU FW из XSCT
Примечание: если у вас не открывается XSCT из консоли, то необходимо добавить в PATH папку, в которую у вас установлен Vitis.
создадим файл gensoft.tcl
hsi::eek:pen_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
\rightarrow
Zynq MP DRAM tests
\rightarrow
Create Application Component from Template
909af2ab8f0953214a66782535232ce8.png

Build, Run
Для взаимодействия с платой потребуется также какая-нибудь утилита для просмотра COM-порта. Я буду использовать расширение Serial Monitor для VSCode, просто потому что оно у меня есть. Для Windows могу посоветовать Terminal 1.9b
Взаимодействие с устройством через UART-терминал

Взаимодействие с устройством через UART-терминал

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::eek:pen_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,- нужна вся папка.
161774c947b0373ff4f98327e5b61efc.png

Подготовка к дальнейшей работе​

Дальнейшая работа будет проходить в 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 (продолжение)​

Скопируем содержимое devictree (my_dts из xsct либо dts из Vitis) в папку ~/alynx-linux/devicetree
DTG сгенерировал несколько файлов, однако для дальнейшей работы необходимо собрать их все в один. Откроем папку alynx-linux в терминале
ставим опции: cd ~/alynx-linux/devicetree

gcc -I devicetree -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -o $HOME/alynx-linux/alynx-linux.dts my_dts/system-top.dts
dtc -I dts -O dtb -o $HOME/alynx-linux/output/alynx-linux.dtb $HOME/alynx-linux/output/alynx-linux.dts
Если в репозиториях нет DTC
git clone https://git.kernel.org/pub/scm/utils/dtc/dtc.git
cd dtc
make
export PATH=$PATH:$PWD

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 терминале
adacff877c510b59c14c6f17edea6c49.png

Теперь создадим загрузочный 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
\rightarrow
Create Boot Image
\rightarrow
Zynq Ultrascale+
\rightarrow
Import Existing BIF file
\rightarrow
Create Image
a7c8f6a8a489f4907c2a83c446809357.png

После чего заливаем программу в 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
\rightarrow
Program Flash
2560b846a06b98d72eb3d9bdd5226fd8.png

Теперь, если переключатели 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 из рантайма.
47a46d1f7700dba6315e2cf7c7b1a5b9.png

Скомпилированные файлы Image и Image.gz будут скопированы в папку output.

RootFS​

В корневой ФС содержатся файлы и программы, необходимы для запуска окружения.
В папке buildroot создадим скрипт: br_config для запуска конфигуратора buildroot.
br_config
#!/bin/sh

make -C /home/lazba/buildroot \
O=$PWD \
nconfig
Запустим конфигуратор и передём к настройкам желаемой ФС

Target Options​

Сменим архитектуру на aarch64 le, остальное оставим по умолчанию.
acd8bfd7e883510ff315754eb459ba6d.png

Toolchain​

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

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.
1e6fb266c6aeec88959e296fd58ff3ea.png

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 - записываем изменения на диск и закрываем программу
2f30c3e78dbef29704559b651faca076.png

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/mmcblk1p2" rw rootwait
Этот файл хранит в себе настройки для U-Boot, по которым тот запускает систему.

Первый запуск​

Вставляем карту в плату и запускаем. Видим меню U-Boot, который сканирует все носители на наличие инструкций для запуска, находит extlinux.conf и запускает ядро.
ffbb9079f561a53a5ceee05952966f79.png

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

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

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

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

Возможные неполадки​

ядро не хочет грузить rootfs с sd-карты
Если при запуске ядро выдаёт вам ошибку вроде такой:
mmcblk1: mmc1:b369 SDABC 29.5 GiB (ro)
mmc1: Tuning failed, falling back to fixed sampling clock
mmcblk1: p1 p2
/dev/root: Can't open blockdev
VFS: Cannot open root device "/dev/mmcblk1p2" or unknown-block(179,26): error -30
Please append a correct "root=" boot option; here are the available partitions:
То проблема в том, что карта подлючается в режиме write protect. Это можно проверить запустившись с ramdisk, который мы сгенерировали вместе с ext образом. Зальём на флешку файл rootfs.cpio.uboot. Перезапустим плату и прервём автозагрузку U-Boot. В терминале U-Boot пишем:
load mmc 1:1 0x00200000 Image
load mmc 1:1 0x00100000 alynx-linux.dtb
load mmc 1:1 0x04000000 rootfs.cpio.uboot
booti 0x00200000 0x04000000 0x00100000
Загружаемся в систему и пробуем примонтировать sd карту в систему:
mkdir -p /mnt/part2
mount /dev/mmcblk1p2 /mnt/part2
Видим сообщение от системы

Система сообщает о RO доступе

Система сообщает о RO доступе
Чтобы исправить это, находим в собранном dts файле (у нас это был alynx-linux.dts) раздел, который отвечает за SD:
sdhci1: mmc@ff170000 {
u-boot,dm-pre-reloc;
compatible = "xlnx,zynqmp-8.9a", "arasan,sdhci-8.9a";
status = "okay";
interrupt-parent = <&gic>;
interrupts = <0 49 4>;
reg = <0x0 0xff170000 0x0 0x1000>;
clock-names = "clk_xin", "clk_ahb";
iommus = <&smmu 0x871>;
power-domains = <&zynqmp_firmware 40>;
#clock-cells = <1>;
clock-output-names = "clk_out_sd1", "clk_in_sd1";
resets = <&zynqmp_reset 39>;
};
И добавляем туда строку, которая отключает WP:
sdhci1: mmc@ff170000 {
u-boot,dm-pre-reloc;
compatible = "xlnx,zynqmp-8.9a", "arasan,sdhci-8.9a";
status = "okay";
interrupt-parent = <&gic>;
interrupts = <0 49 4>;
reg = <0x0 0xff170000 0x0 0x1000>;
clock-names = "clk_xin", "clk_ahb";
iommus = <&smmu 0x871>;
power-domains = <&zynqmp_firmware 40>;
#clock-cells = <1>;
clock-output-names = "clk_out_sd1", "clk_in_sd1";
resets = <&zynqmp_reset 39>;
disable-wp;
};
Пересобираем dtb с помощью dtc, заливаем на флешку, включаемся

Запуск 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-часть из запущенной ОС

 
Сверху