В данной статье я постараюсь описать процесс создания кастомного образа Linux на Zynq UltraScale+ MPSoCс. Каждый необходимый компонент будет собран отдельно с использованием соответствующих утилит. Статья разбита на разделы, которые шаг за шагом познакомят вас с процессом сборки и запуска системы на данной платформе.
В следующих статьях попробуем собрать всю систему сразу с помощью buildroot и Yocto/petalinux.
AXU9EGB User Manual
SCH для SoM ACU9EG_SCH
При настройке периферии буду ориентироваться на пример проекта, предоставленный производителем на GitHub.
Согласно схеме, на плате установлен XCZU9EG-FFVB1156-2-I. Соответственно проект будем создавать под неё. Добавим на схему Block Design блок Zynq UltraScale и перейдём к его настройкам.
Для сборки данной схемы можно передать в 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:iproc_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
Create HDL Wrapper
Generate Output Products
Run Synthesis
Run Implementation
Generate Bitstream) и экспоритируем его в XSA файл вместе с платформой (File
Export
Export Hardware
Include Bitstream). На этом работа в Vivado закончена, переходим в Vitis.
В папке 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: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
Examples
Zynq MP DRAM tests
Create Application Component from Template
Build, Run
Для взаимодействия с платой потребуется также какая-нибудь утилита для просмотра COM-порта. Я буду использовать расширение Serial Monitor для VSCode, просто потому что оно у меня есть. Для Windows могу посоветовать Terminal 1.9b
Взаимодействие с устройством через UART-терминал
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: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,- нужна вся папка.
В Home создадим директорию alynx-linux под наш проект. В ней создадим поддиректории
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.
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
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.
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.
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.
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.
В папке buildroot создадим скрипт: br_config для запуска конфигуратора buildroot.
br_config
#!/bin/sh
make -C /home/lazba/buildroot \
O=$PWD \
nconfig
Запустим конфигуратор и передём к настройкам желаемой ФС
Создадим и запустим файл 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.
Вставляем карту в ПК и смотрим, каким файлом её монтировать
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 следующие команды
P.S. Последняя часть, по изменению типа первого раздела на W95 FAT32 (LBA) необязательна - можно оба раздела оставить как Linux. Я поставил её такой, чтобы попробовать запуститься полностью с SD-карты. По той же причине была выбрана таблица разделов MBR - zynqmpus+ не поддерживает для запуска носители с разметкой GPT. В то же время, если u-boot у нас на QSPI, то можно смело форматировать всё в GPT и не париться о типа разделов.
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
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, по которым тот запускает систему.
По окончанию загрузки заходим в систему под своим логином/паролем (root/root).
Подключим плату к роутеру и попробуем подключиться по SSH. Сначала узнаем свой IP адрес (пока ещё в UART терминале)
Видим, что роутер выдал нам адрес 192.168.3.122. Подключаемся по SSH с хоста
Напоследок протестируем скорость соединения между ПК и Zynqmp с помощью iperf/iperf3. Любопытные могут дополнительно глянуть статью про то как пользоваться iperf.
Если при запуске ядро выдаёт вам ошибку вроде такой:
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 доступе
Чтобы исправить это, находим в собранном 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, заливаем на флешку, включаемся
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.
В следующих статьях попробуем собрать всю систему сразу с помощью 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.
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:iproc_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
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: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
Build, Run
Для взаимодействия с платой потребуется также какая-нибудь утилита для просмотра COM-порта. Я буду использовать расширение Serial Monitor для VSCode, просто потому что оно у меня есть. Для Windows могу посоветовать Terminal 1.9b
Взаимодействие с устройством через 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: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,- нужна вся папка.
Подготовка к дальнейшей работе
Дальнейшая работа будет проходить в 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
В Home создадим директорию alynx-linux под наш проект. В ней создадим поддиректории
- output - под итоговые бинарники и прочие файлы
- devicetree
- uboot - для сборки U-Boot
- kernel - для ядра
- buildroot - для RootFS
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/devicetreeDTG сгенерировал несколько файлов, однако для дальнейшей работы необходимо собрать их все в один. Откроем папку 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
После чего заливаем программу в 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
Теперь, если переключатели 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 - генератор случайных чисел, без которого много чего не работает
- dhcp
- dhcpd - получение IP адреса
- dropbear (SSH клиент)
- ifupdown - управление сетевым интерфейсом
- iperf, iperf3 - запустим в конце сетевой тест
- iptables, iputils
- lftp - кидаемся файлами
- openssh (его требует lftp, вроде как)
- 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 разделом.
Примонтируем bootsudo 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 доступе
Чтобы исправить это, находим в собранном 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-часть из запущенной ОС
Linux From Scratch на Zynq UltraScale+ MPSoC
В данной статье я постараюсь описать процесс создания кастомного образа Linux на Zynq UltraScale+ MPSoCс. Каждый необходимый компонент будет собран отдельно с использованием соответствующих утилит....
habr.com