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

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

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

  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. Окончательный вариант показан на рисунке

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

  • 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 Sources\rightarrowCreate HDL Wrapper\rightarrowGenerate Output Products\rightarrowRun 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-файл.

На этапе выбора окружения выставляем следующие настройки:

Собираем проект и в 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 \rightarrow Zynq MP DRAM tests \rightarrow Create Application Component from Template

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::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 \rightarrow Create Boot Image \rightarrow Zynq Ultrascale+ \rightarrow Import Existing BIF file \rightarrow 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 \rightarrow 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

Видим сообщение от системы

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

На этом всё, увидимся в следующих статьях!

Заходите к нам в чатек: https://t.me/fpgasystems_embd

Комментарии (9)


  1. tntrol
    19.04.2024 09:22

    Спасибо за статью. Особенно полезно для разворачивания файловой системы. Только недавно подобная задача появилась. Но там сборка uboot, linux своим комплектом собранным buildroot.


    1. lazbaphilipp Автор
      19.04.2024 09:22

      В следующей статье как раз буду писать, как разом собирать всё из buildroot. У него есть все настройки и для ATF и для U-Boot и для всего остального кроме FSBL и PMUFW.


      1. tntrol
        19.04.2024 09:22

        Просто когда задача заключается в том, чтобы работала в неизменяемой фс, то проблем нет. Cramfs в помощь. А вот когда у нас используется изменяем фс, то появляются проблемы с развертыванием


        1. lazbaphilipp Автор
          19.04.2024 09:22

          buildroot тоже не предполагает изменения собранного набора пакетов. Тем не менее, вы можете доставить туда opkg, настроить его, и устанавливать что душе угодно. Ну или там арч какой-нибудь поднять


  1. AndroDaPooh
    19.04.2024 09:22

    А не подскажите - пробовали ли вариант без sd карты? т.е. всё чисто на QSPI? Вроде как доки это позволяют просто интересно насколько это работоспособно.


    1. lazbaphilipp Автор
      19.04.2024 09:22

      Вполне работоспособно, разве что у вас FS будет readonly. Вернее, все изменения в рантайме вы в QSPI не запишете. А ещё на QSPI долго грузится прошивка. Прям сильно долго, я бы сказал. Думаю, полный образ (а это мегабайт 150) будет грузиться около часа


    1. Demonter
      19.04.2024 09:22

      На таких толстых флешках вполне себе можно развернуть полноценную rw файловую систему. Я периодически использую UBIFS - она еще дает неплохую экономию места засчет встроенного сжатия содержимого. При некоторых танцах с бубном можно эту фс сделать видимой и из u-boot.


  1. almaz1c
    19.04.2024 09:22

    Спасибо за статью. Жаль, что Zynq UltraScale фактически теперь недоступен. В отличии от того же Zynq-7000, который можно купить на Китайских стоках.


    1. lazbaphilipp Автор
      19.04.2024 09:22

      SoM и отладочные платы вполне присутствуют даже на алиэкспресс