В данной статье я постараюсь описать процесс создания кастомного образа 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 WrapperGenerate Output ProductsRun 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 (продолжение)
Скопируем содержимое 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 терминале
Теперь создадим загрузочный 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.
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/mmcblk1p2" rw rootwait
Этот файл хранит в себе настройки для U-Boot, по которым тот запускает систему.
Первый запуск
Вставляем карту в плату и запускаем. Видим меню U-Boot, который сканирует все носители на наличие инструкций для запуска, находит extlinux.conf и запускает ядро.
По окончанию загрузки заходим в систему под своим логином/паролем (root/root).
Подключим плату к роутеру и попробуем подключиться по SSH. Сначала узнаем свой IP адрес (пока ещё в UART терминале)
Видим, что роутер выдал нам адрес 192.168.3.122. Подключаемся по SSH с хоста
Напоследок протестируем скорость соединения между ПК и Zynqmp с помощью iperf/iperf3. Любопытные могут дополнительно глянуть статью про то как пользоваться iperf.
Возможные неполадки
ядро не хочет грузить 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
Видим сообщение от системы
Чтобы исправить это, находим в собранном 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-часть из запущенной ОС
На этом всё, увидимся в следующих статьях!
Заходите к нам в чатек: https://t.me/fpgasystems_embd
Комментарии (9)
AndroDaPooh
19.04.2024 09:22А не подскажите - пробовали ли вариант без sd карты? т.е. всё чисто на QSPI? Вроде как доки это позволяют просто интересно насколько это работоспособно.
lazbaphilipp Автор
19.04.2024 09:22Вполне работоспособно, разве что у вас FS будет readonly. Вернее, все изменения в рантайме вы в QSPI не запишете. А ещё на QSPI долго грузится прошивка. Прям сильно долго, я бы сказал. Думаю, полный образ (а это мегабайт 150) будет грузиться около часа
Demonter
19.04.2024 09:22На таких толстых флешках вполне себе можно развернуть полноценную rw файловую систему. Я периодически использую UBIFS - она еще дает неплохую экономию места засчет встроенного сжатия содержимого. При некоторых танцах с бубном можно эту фс сделать видимой и из u-boot.
almaz1c
19.04.2024 09:22Спасибо за статью. Жаль, что Zynq UltraScale фактически теперь недоступен. В отличии от того же Zynq-7000, который можно купить на Китайских стоках.
tntrol
Спасибо за статью. Особенно полезно для разворачивания файловой системы. Только недавно подобная задача появилась. Но там сборка uboot, linux своим комплектом собранным buildroot.
lazbaphilipp Автор
В следующей статье как раз буду писать, как разом собирать всё из buildroot. У него есть все настройки и для ATF и для U-Boot и для всего остального кроме FSBL и PMUFW.
tntrol
Просто когда задача заключается в том, чтобы работала в неизменяемой фс, то проблем нет. Cramfs в помощь. А вот когда у нас используется изменяем фс, то появляются проблемы с развертыванием
lazbaphilipp Автор
buildroot тоже не предполагает изменения собранного набора пакетов. Тем не менее, вы можете доставить туда opkg, настроить его, и устанавливать что душе угодно. Ну или там арч какой-нибудь поднять