Цели статьи

  • Автоматизировать скриптами сборку загрузчиков и ядра Linux с заданными конфигурационными файлами в автоматическом режиме без необходимости запуска gui Vivado;

  • Автоматизировать установку операционной системы Debian из бинарных файлов с заданными пакетами и первоначальными настройками;

  • Загружать образы ядра linux + dts по tftp, а корневую файловую систему по NFS для сохранения ресурса карточки и времени на копирование файлов с рабочей станции на устройство;

  • Добавить в U-Boot меню для быстрого выбора источника загрузки: всё с карточки, всё по сети и разные комбинации этих вариантов;

  • При необходимости данные скрипты можно адаптировать практически под любую встраиваемую платформу.

Введение

Операционная система Debian была выбрана по причине использования окружения для активной разработки, где основными критериями являются: стабильность, легковесность, а главное - возможность быстрой установки пакетов при необходимости. По завершению разработки в случаях, когда под хранение выделяется только nand или nor флешка ограниченного объёма, делаем сборку buildroot с уже определённым минимальным набором пакетов.

Установка необходимых программ

Вся работа проводилась на компьютере с операционной системой Debian, для установки используется стандартный менеджер пакетов. Для сборки fsbl и pmu необходимо установить Vivado 2020 и старше. Всё отлично работает с самой последней 2023.2

  • Клонируем репозиторий

    Все необходимые сборочные скрипты выложил на гитхаб:

git clone https://github.com/Igorbunow/build-uboot-linux-debian.git
  • Устанавливаем кросскомпилятор

apt install gcc-aarch64-linux-gnu

Если вы используете внешний toolchain, то его настройка будет описана ниже

  • Устанавливаем debootstrap, qemu-static, tftp и nfs сервер

apt install debootstrap atftpd nfs-kernel-server qemu-user-static fdisk
  • настраиваем tftpd

mkdir /srv
mkdir /srv/tftp
chown 777 /srv/tftp

vim  /etc/default/atftpd

## Options for atftpd:
USE_INETD=false
OPTIONS="--port 69 --tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /srv/tftp"

systemctl restart atftpd
  • настраиваем NFS

mkdir /srv/nfs
mkdir /srv/nfs/root

vim /etc/exports

#/srv/nfs   *(no_root_squash,no_subtree_check,rw)
/srv/nfs   192.168.10.1/24(no_root_squash,no_subtree_check,rw)
#/tmp/root *(no_root_squash,no_subtree_check,rw)

Крайне не рекомендуется давать всем хостам права (указывать *), т.к. кто угодно может из сети может положить исполняемый файл от локального администратора с установленным битом SUID с опцией no_root_squash, которая отключает понижение прав root при подключении к NFS, но она необходима для корректной работы подключенной папки файловой системы в качестве корневой.

Собираем загрузчик FSBL+U-Boot и ядро Linux

Все скрипты для сборки загрузчиков и ядра находятся в папке ./boot репозитория:

configs

папка с defconfig для linux и uboot

all.sh

Скрипт, включающий все скрипты ниже и делающий полную сборку

download_orig.sh

Скрипт, выкачивающий исходники из официальных репозиториев для сборки. При необходимости в нём можно зафиксировать интересующий коммит. Также при необходимости можно указать свои репозитории

fsbl_pmu_2020.sh

Скрипт, создающий проект по умолчанию в Vitis и собирающий первичный загрузчик и pmu (Требуется Vivado, Vitis от 2020)

dts.sh

Скрипт, собирающий утилиту для работы с деревом устройств для ядра linux и u-boot (для компьютера), необходимую для дальнейшей сборки

atf.sh

Скрипт для сборки ARM trusted firmware (требуется кросскомпилятор или тулчейн)

u-boot.sh

Скрипт для сборки загрузчика U-Boot

kernel.sh

Скрипт для сборки ядра Linux

bootbin.sh

Копирование собранного первичного загрузчика, U-Boot, pmu и bl31 с последующей сборкой готового загрузочного образа BOOT.bin

bootscript.sh

Генерирование скрипта для загрузчика UBoot из boot.script

image.sh

Копирование образа загрузчика BOOT.bin, ядра linux, device-tree, скрипта UBoot, модулей ядра в виде, пригодном для переноса на загрузочный раздел SD карточки. Device tree файл при этом переименовываем в system.dtb. На этот же раздел кладём system.bit, собранный под вашу задачу. Модули ядра переносятся в rootfs

Настойка ядра Linux и UBoot

  • Конфигурации defconfig

При сборке система берет defconfig, расположенные в папках ./configs/linux/xilinx_ulrascale_debug_defconfig и ./configs/uboot/xilinx_ulrascale_debug_defconfig и создаёт из них соответствующие .config файлы. Если Вам необходимо сделать настройки под свою задачу, заходим в папку u-boot-xlnx или linux-xlnx, настраиваем:

make ARCH=arm64 menuconfig

И сохраняем. Затем создаём из полученного .config -> defconfig:

make ARCH=arm64 savedefconfig

Забираем из корня u-boot-xlnx или linux-xlnx defconfig, переименовываем в зависимости от того, что у нас и кладём соответственно в /xilinx_ulrascale_debug_defconfig или ./configs/uboot/xilinx_ulrascale_debug_defconfig.

  • Дерево устройств Device tree

Device tree files (dts) общие для ядра и UBoot с необходимыми overlay берутся из ./configs/dts, куда при необходимо положить свои, если требуется. Кроме этого, для сборке в файле ./configs/dts/Makefile.in надо добавить цель для сборки dtb:

dtb-$(CONFIG_ARCH_ZYNQMP) += zynqmp-zcu106-custom.dtb

Также проверяем, чтобы в defconfig UBoot ./configs/uboot/xilinx_ulrascale_debug_defconfig использовался требуемый нам dts, указанный без расширения:

CONFIG_DEFAULT_DEVICE_TREE="zynqmp-zcu106-custom"

Загрузочного скрипта UBoot

Данный скрипт выполняется UBoot после загрузки переменных окружения, которые можно хранить, например, на карточке SD. Скрипт поддерживает циклы и операторы условного перехода, синтаксис похож на синтаксис bash. С его помощью в зависимости от заданных переменных, в нашем случае ip адресов tftp или nfs серверов и их папок, сделано отображение пунктов меню с соответствующими вариантами загрузки:

boot.script
setenv bitsteam_name system.bit
setenv kernel_image_name Image
setenv dts_name system.dtb
setenv bitsteam_addr 0x20000000
setenv kernel_image_addr 0x10000000
setenv dts_addr 0x02a00000
setenv load_tftp_bitsteam 'dhcp ${bitsteam_addr} ${tftp_serverip}:${tftp_dir}${bitsteam_name}; fpga load 0 ${bitsteam_addr} $filesize;'
setenv load_tftp_kernel_image 'dhcp ${kernel_image_addr} ${tftp_serverip}:${tftp_dir}${kernel_image_name}'
setenv load_tftp_dts 'dhcp ${dts_addr} ${tftp_serverip}:${tftp_dir}${dts_name}'
setenv load_mmc_bitsteam 'fatload mmc 0:1 ${bitsteam_addr} ${bitsteam_name}; fpga load 0 ${bitsteam_addr} $filesize;'
setenv load_mmc_kernel_image 'fatload mmc 0 ${kernel_image_addr} ${kernel_image_name}'
setenv load_mmc_dts 'fatload mmc 0 ${dts_addr} ${dts_name}'
setenv boot_dev booti 0x10000000 - 0x02a00000
setenv bootargs_mmc 'earlycon clk_ignore_unused consoleblank=0 root=/dev/mmcblk0p2 rootfstype=btrfs rw rootwait uio_pdrv_genirq.of_id=generic-uio net.ifnames=0 biosdevname=0'
setenv bootargs_nfs 'earlycon clk_ignore_unused consoleblank=0 root=/dev/nfs ip=dhcp nfsroot='${nfs_serverip}':'${nfs_dir}',nolock,nfsvers=4 rootfstype=nfs rw rootwait uio_pdrv_genirq.of_id=generic-uio net.ifnames=0 biosdevname=0'

setenv bootcmd bootmenu 10

setenv bootmenu_0 Load All from MMC='setenv bootargs '${bootargs_mmc}';run load_mmc_bitsteam; run load_mmc_kernel_image; run load_mmc_dts; run boot_dev;'


if test -n $tftp_serverip;
then
	if test -n $nfs_serverip;
	then
		setenv root_name NFS
		setenv bootargs ${bootargs_nfs}
	else
		setenv root_name SD_card
		setenv bootargs ${bootargs_mmc}
	fi
	setenv bootmenu_1 Load All via tftp ${tftp_serverip} root ${root_name}='run load_tftp_bitsteam; run load_tftp_kernel_image; run load_tftp_dts; run boot_dev;'
	setenv bootmenu_2 Load bitsteam via tftp ${tftp_serverip} kernel from SD card root ${root_name}='run load_tftp_bitsteam; run load_mmc_kernel_image; run load_mmc_dts; run boot_dev;'
	setenv bootmenu_3 Load bitsteam from SD Card  kernel via tftp ${tftp_serverip} root ${root_name}='run load_mmc_bitsteam; run load_tftp_kernel_image; run load_tftp_dts; run boot_dev;'
	setenv bootmenu_4 Reset board=reset
	setenv bootmenu_5 Test='ls'
else
	setenv bootmenu_1 Reset board=reset
fi

bootmenu 5

Переменные c ip адресами необходимо задать до загрузки скрипта. О том, как это сделать, будет описано ниже.

Также, в случае необходимости, можно требуемым образом изменить аргументы загрузки bootargs для bootargs_mmc и bootargs_nfs. `net.ifnames=0 biosdevname=0` необходимо для запрета переименования eth0 -> end0, т.к. при монтировании по nfs переименования не происходит и этот запрет позволяет избегать создания разных версий дополнительных конфигов rootfs.

Настраиваем кросскомпиляцию

Если вы используете внешний toolchain, добавляем в скриптах: fsbl_pmu_2020.sh, kernel.sh, u-boot.sh путь до его /bin, префикс кросскомпиляции и архитектуру:

export PATH=/tmp/build/buildroot/output/host/bin:${PATH}
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

Настраиваем остальные параметры скриптов

  • Путь к Vivado прописываем в скрипте all.sh:

export VIVADO=/opt/Electro/FPGA/Xilinx/Vivado/2023.1/settings64.sh
  • Копируемые в образ файлы и их имена - в скрипте image.sh, при этом не забывая проверить, какой device tree dtb файл мы истользуем:

cp ${WORK}/linux-xlnx/arch/arm64/boot/dts/xilinx/zynqmp-zcu106-custom.dtb ./system.dtb

Запускаем сборку

Запускаем скрипт ./all.sh и ждём завершения сборки. Если всё сделано правильно, то в папке ./image у нас будут следующие файлы, готовые для копирования на загрузочный раздел карты памяти за исключением модулей ядра - их копируем в rootfs

modules

Папка с модулями ядра Linux. Предназначена для копирования на rootfs раздел скриптами ниже

BOOT.bin

Образ загрузчика, содержащий fsbl, pmu, bl31, UBoot

boot.scr

Стартовый скрипт загрузчика UBoot

Image

Ядро Linux

system.dtb

Дерево устройств ядра linux

system.bit

Битстрим для fpga, подготавливается отдельно

Собираем Debian из бинарных пакетов

Сборка debian также полностью автоматизирована при помощи скриптов. RootFs debian собирается в папке ./root. Переходим в папку debian:

files

Файлы, которые необходимо поместить в целевую систему с заменой существующих

2_debian_bootstrap.sh

Скрипт, выполняющий скачивание пакетов debian и производящий их первичную настройку при помощи debootstrap

3_copy_kernel_modules.sh

Скрипт, выполняющий копирование собранных выше модулей ядра в rootfs debian

4_copy_and_config.sh

Завершающие настройки системы: задание пароля root, создание пользователя, настройки монтирования и сети

  • Добавляем дополнительные пакеты, убираем существующие в зависимости от вашей задачи. Для этого в скрипте 2_debian_bootstrap.sh задаём переменной PACKAGES список соответствующих пакетов. При необходимости можно изменить версию систему, разрядность, источник пакетов. А также, при необходимости, можно вместо debian собрать ubuntu

  • Проводим окончательную настройку системы. Для этого в скрипте 4_copy_and_config.sh задаём требуемый пароль root, создание дополнительного пользователя, настройку сети, монтирования по-умолчанию fstab, копирование требуемых файлов из папки files, очистку кэша пакетов и логов и любые другие требуемые дополнительные настройки

  • По завершению настройки последовательно выполняем скрипты:

./2_debian_bootstrap.sh
./3_copy_kernel_modules.sh
./4_copy_and_config.sh

и получаем в папке ./root полностью готовую rootfs с debian, содержащую модули ядра для дальнейшего разворачивания на устройстве.

Работа c rootfs

Представим следующую ситуацию. Дождавшись, наконец, завершения сборки debian вы вдруг обнаруживаете, что данный образ не вписался в заданный объём, или вы забыли установить какой-то пакет. Что делать в таком случае? К счастью, заново сборку запускать не нужно, нас выручает qemu-static, производящий эмуляцию пользовательского режима. В этом режиме QEMU может запускать процессы Linux, скомпилированные для одного ЦП, на другом ЦП. Этот режим можно использовать как один из способов кроссборки с некоторыми ограничениями. Самым быстрым, при этом является вариант с использованием нативного кроскомпилятора напрямую, а в данном случае всё же работает через прослойку эмуляции

Например, нам надо добавить пакеты для возможности сборки приложений прямо на устройстве или через эмулятор. Для этого:

  • Проверить наличие в ./root символической ссылки на qemu-arm64-static и при её отстутствии создать:

ln -s "$(which qemu-arm64-static)" ./root/qemu-arm64-static
  • Монтирум системные устройства в rootfs целевого устройства (если хотите просто доустановить пакеты, то данный шаг можно пропустить):

mount --bind /dev ./root/dev/
mount --bind /sys ./root/sys/
mount --bind /proc ./root/proc/
mount --bind /dev/pts ./root/dev/pts
  • Переходим в систему, делая туда chroot

chroot ./root
  • Устанавливаем требуемые пакеты:

PACKAGES="valgrind build-essential clang u-boot-tools cmake git subversion \
          tcpdump kbuild nmap wget libboost-all-dev linux-headers-arm64 \
          minicom picocom p7zip-full gzip zstd bzip2 sysfsutils"
apt update
apt install ${PACKAGES}
echo "packages: ${PACKAGES}" >> /info
apt clean
  • Далее, можно зайти под user через login или `sudo -u user bash`, собрать, требуемые программы и выйти `exit`

  • Выходим из chroot:

exit
  • Если монтировали устройства с локальной машины в rootfs устройства, отмонтируем:

umount ./root/sys
umount ./root/proc
umount ./root/dev/pts
umount ./root/dev
  • В случае известных багов, мешающих запуску некоторых служб, в частности binfmt.service чиним это:

rm -rf ./root/usr/lib/binfmt.d/python3.11.conf 
rm -rf ./root/usr/lib/binfmt.d/llvm-14-runtime.binfmt.conf
  • Очищаем логи установки:

rm -rf ./root/var/log/apt/*
echo -n > ./root/var/log/alternatives.log
echo -n > ./root/var/log/dpkg.log
echo -n > ./root/root/.bash_history
rm ./root/var/cache/apt/archives/lock
  • Дописываем в ./root/info дополнительную иформацию о системе, помимо сгенерированной системое, например, расшифрем, что stable (12.0 bookworm), чтобы не вспоминать через несколько лет, что за система лежит в образе

Создаём образ для карты SD памяти

Для передачи образа карты памяти и его последующего хранения, создадим минимально необходимый образ системы, который, при разворачивании на карту памяти будет расширяться до его максимального размера. В качестве файловой системы для корневой файловой системы мы выбрали btrfs - система, поддерживающая прозрачное сжатие и копирование при записи, что продлевает жизнь карточкам памяти либо иному флеш носителю, записывая меньше информации и благодаря тому, что дважды в одно место данные не пишутся, производится выравнивание ресурса. Если по каким-то причинам необходимо использовать ext2, ext3, ext4 системы - как это сделать, тоже будет описано ниже.

Оцениваем необходимый размер образа

  • Загрузочный размер может иметь размер от 32Mb до 4Гб. 32Мб будет совсем впритык, а 4Гб - слишком много. Размер выбирается в зависимости от задач и размещаемых на нём файлах. Выберем 128Мб

  • Размер корневой файловой системы. Смотрим, сколько у нас получилось в образе, который мы собираемся записывать. В качестве примера, расмотрим минимальный образ без дополнительных пакектов. Смотрим:

du -h -d 0
517M

Соответственно, выбираем, 640Мб под корневой раздел

  • Итоговый размер нашего образа 640Мб + 128Мб = 768Мб

Создаём пустой образ необходимого размера

Выше мы определили, что размер нашего образа составляет 768Мб. Создадим пустой файл такого размера:

dd if=/dev/zero bs=1M count=768 of=ultrascale_short.img
sync;

и дожидаемся успешного создания:

768+0 records in
768+0 records out
805306368 bytes (805 MB, 768 MiB) copied, 0.396911 s, 2.0 GB/s

Монтируем и размечаем образ

  • размечаем образ утилитой fdisk:

fdisk -c=dos ultrascale_short.img

Затем последовательно выполняем команды. Размер указываем в секторах по 512 байт:

n

создаём новый раздел (загрузочный)

p

указываем тип MBR раздела - первичный

1

Указываем номер загрузочного раздела (строго 1-ый для загрузочного)

63

1-ый сектор загрузочного раздела, оставляем по-умолчанию

262144

Последний сектор раздела, вычисляем для 128Мб: 128*2^20/512

a

Делаем раздел загрузочным

n

создаём новый раздел (rootfs)

p

указываем тип MBR раздела - первичный

2

Указываем номер раздела rootfs

262145

1-ый сектор раздела rootfs, оставляем по-умолчанию

1572863

Последний сектор раздела rootfs

w

Записываем изменения в файл и выходим

лог выполнения fdisk
fdisk -c=dos ultrascale_short.img

Welcome to fdisk (util-linux 2.39.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
DOS-compatible mode is deprecated.

Created a new DOS (MBR) disklabel with disk identifier 0xb315f728.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1): 1
First sector (63-1572863, default 63): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (63-1572863, default 1572863): 262144

Created a new partition 1 of type 'Linux' and of size 128 MiB.

Command (m for help): a
Selected partition 1
The bootable flag on partition 1 is enabled now.

Command (m for help): n
Partition type
   p   primary (1 primary, 0 extended, 3 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (2-4, default 2): 2
First sector (262145-1572863, default 262145): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (262145-1572863, default 1572863): 

Created a new partition 2 of type 'Linux' and of size 640 MiB.

Command (m for help): w
The partition table has been altered.
Syncing disks.

  • Принудительно сбрасываем данные из буфера на диск:

sync;
  • Монтируем наш файл в виртуальное устройство:

losetup --partscan --find --show ultrascale_short.img

и дожидаемся ответа, каким устройством смонтировалось:

/dev/loop0

Для избежания проблем, т.к. у вас могут определиться по-другому, будем обозначать loop0 как loopX. При необдуманном копировании возможна потеря данных. Особенно славится в этом отношении команда dd, гарантированно затирающая данные на диски, если ошибиться с буквой диска и ещё подождать при этом...

Смотрим определившиеся разделы:

ls /dev | grep loopX

и видим наши 2 раздела:

loop0
loop0p1
loop0p2
  • Очищаем полученные разделы:

dd if=/dev/zero of=/dev/loopXp1 bs=4M
dd if=/dev/zero of=/dev/loopXp2 bs=4M
sync;
Лог очистки разделов loopXp1 и loopXp2
dd if=/dev/zero of=/dev/loop0p1 bs=4M
dd if=/dev/zero of=/dev/loop0p2 bs=4M
sync;
dd: error writing '/dev/loop0p1': No space left on device
32+0 records in
31+0 records out
134185984 bytes (134 MB, 128 MiB) copied, 0.617296 s, 217 MB/s
dd: error writing '/dev/loop0p2': No space left on device
160+0 records in
159+0 records out
671088128 bytes (671 MB, 640 MiB) copied, 4.38618 s, 153 MB/s

Появление ошибок о том, что закончилось место на диске вполне нормально и объяснимо, тк dd не знает размер диска и пишет до тех пор, пока не закончится место на разделе.

Форматируем загрузочный раздел в fat, а rootfs в файловую систему btrfs:

mkfs.vfat -F 32 -n "BOOT" /dev/loopXp1
mkfs.btrfs /dev/loopXp2 -L "root"
sync;
Лог форматирования разделов:
mkfs.vfat -F 32 -n "BOOT" /dev/loop0p1
mkfs.btrfs /dev/loop0p2 -L "root"
sync;
mkfs.fat 4.2 (2021-01-31)
btrfs-progs v6.3.2
See https://btrfs.readthedocs.io for more information.

Performing full device TRIM /dev/loop0p2 (640.00MiB) ...
NOTE: several default settings have changed in version 5.15, please make sure
      this does not affect your deployments:
      - DUP for metadata (-m dup)
      - enabled no-holes (-O no-holes)
      - enabled free-space-tree (-R free-space-tree)

Label:              root
UUID:               553f75fd-bc80-4553-8c7c-279c4e38a6a6
Node size:          16384
Sector size:        4096
Filesystem size:    640.00MiB
Block group profiles:
  Data:             single            8.00MiB
  Metadata:         DUP              32.00MiB
  System:           DUP               8.00MiB
SSD detected:       yes
Zoned device:       no
Incompat features:  extref, skinny-metadata, no-holes, free-space-tree
Runtime features:   free-space-tree
Checksum:           crc32c
Number of devices:  1
Devices:
   ID        SIZE  PATH
    1   640.00MiB  /dev/loop0p2

Если всё же выбрали ext4

Форматируем следующим образом (не забывая при этом поправить в boot.script в bootargs_mmc тип корневой файловой системы на rootfstype=ext4):

mkfs.vfat -F 32 -n "BOOT" /dev/loopXp1
mkfs.ext4 /dev/loopXp2 -L "root"
sync;

  • Монтируем полученные разделы. Для btrfs указываем сжатие. Алгоритм lzo наименее ресурсо-затратный и при этом неплохо сжимает:

mkdir /tmp/root
mkdir /tmp/boot
mount -o compress=lzo /dev/loopXp2 /tmp/root
mount  /dev/loopXp1 /tmp/boot

Копируем собранные загрузчики и rootfs в примонтированные разделы образа

  • Копируем bitstream system.bit в /tmp/boot

  • Копируем загрузчик, ядро linux, dts и скрипт U-Boot

cp ./../boot/image/BOOT.bin  /tmp/boot
cp ./../boot/image/boot.scr  /tmp/boot
cp ./../boot/image/Image /tmp/boot
cp ./../boot/image/system.dtb /tmp/boot
chmod -R 777 /tmp/boot
sync;
  • Копируем rootfs -> /tmp/root, не забывая при этом удалить оттуда qemu-arm64-static

cp -rpna  ./root/*  /tmp/root/
rm /tmp/root/qemu-arm64-static
sync;

Если наша корневая системы была заархивирована:

tar -C /tmp/root -xjf images/rootfs.tar.bz2
sync;
  • Отсоединяем примонтированные разделы нашего образа:

umount /tmp/boot
umount /tmp/root
sync;
  • Отсоединяем наше виртуальное устройство:

losetup -d /dev/loopX
sync;

Сжимаем полученный образ и записываем его на SD карту

  • сжимаем образ

    Для дальнейшего хранения образа и передачи его другим разработчикам, удобнее всего его будет сжать. Для сжатия выберем формат gzip, xz, bz2, тк сжатый им диск понимают многие утилиты для записи образов дисков, в том числе и популярный rufus. В нашел случае, наилучшего сжатия удалось добиться с форматом xz. Другой формат zstd позволяет в некоторых случаях сжать лучше, но применяйте его, если уверены, что кому его передаёте будет его удобно использовать

gzip -9cvf ultrascale_short.img > ultrascale_short.img.gz
xz -9cvf ultrascale_short.img > ultrascale_short.img.xz
bzip2 -9cvf ultrascale_short.img > ultrascale_short.img.bz2
zstd -16v --ultra --format=zstd ultrascale_short.img -o ultrascale_short.img.zst

В нашем случае получилось следующее:

ultrascale_short.img

640Мб

ultrascale_short.img.gz

206Мб

ultrascale_short.img.bz2

189Мб

ultrascale_short.img.zst

170Мб

ultrascale_short.img.xz

143Мб

  • Записываем полученный образ на SD карту и передаём другим разработчикам

zcat ultrascale_short.img.gz | dd bs=8M iflag=fullblock of=/dev/sdxxx status=progress; sync;
xzcat ultrascale_short.img.xz | dd bs=8M iflag=fullblock of=/dev/sdxxx status=progress; sync;
bzcat ultrascale_short.img.bz | dd bs=8M iflag=fullblock of=/dev/sdxxx status=progress; sync;
zstdcat ultrascale_short.img.zst | dd bs=8M iflag=fullblock of=/dev/sdxxx status=progress; sync;
Лог записи образа sd карты
zcat ultrascale_short.img.gz | dd bs=8M iflag=fullblock of=/dev/sdf status=progress; sync;
754974720 bytes (755 MB, 720 MiB) copied, 5 s, 150 MB/s
96+0 records in
96+0 records out
805306368 bytes (805 MB, 768 MiB) copied, 129.194 s, 6.2 MB/s

  • Если лицо, которому передаёте образ, использует windows, то gzip образ можно записать напрямую через rufus без распаковки:

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

Устанавливаем карту памяти в устроуйство, переключатель режимов запуска sw6 должен быть установлен в положение 1000.

  • Подключаемся к uart на устройстве через microusb кабель. На компьютере открываем порт /dev/ttyUSB0 и включаем устройство:

  • разворачиваем образ rootfs на всё свободное пространство карточки памяти:

  • для brtfs:

growpart /dev/mmcblk0 2
btrfs filesystem resize max /
sync;
для других файловых систем
  • для ext2, ext3, ext4:

growpart /dev/mmcblk0 2
resize2fs /dev/mmcblk0p2
sync;
  • для xfs:

growpart /dev/mmcblk0 2
xfs_growfs /
sync;

и перезагружаем устройство

  • Для того, чтобы не пропала возможность входить по uart без пороля (если используете):

chattr +i /lib/systemd/system/serial-getty@.service

Загрузка ядра Linux, dts, bitstream по tftp, rootfs debian по NFS

  • Настраиваем tftp и NFS как было описано выше

  • Не дожидаясь выполнения загрузочного скрипта uboot, сразу при включении устройства при появлении обратного отсчёта прерываем загрузку устройства клавишей enter:

  • и попадаем в терминал U-Boot, в котором указываем ip fttp сервера и путь к файлам, относительно корня сервера:

setenv tftp_serverip '192.168.4.12'
setenv tftp_dir /zsu106/
saveenv
reset

Перезагружаемся и видим, что появились пункты меню, позволяющие загрузить ядро Linux, dts, bitstream по tftp, а rootFS debian - с карты памяти:

  • Загружаем все по сети: ядро Linux, dts, bitstream по tftp, а rootFS debian - по NFC

    Прерываем загрузку UBoot как в случае с tftp и прописываем ip и путь к папке с rootfs debian:

setenv nfs_serverip '192.168.4.12'
setenv nfs_dir /tmp/root
saveenv
reset
  • После перезагрузки должно получиться как такое меню:

Весь процесс загрузки подробнее на видео:

  • Если необходимо отключить какой-либо из режимов - задайте и сохраните без значения

setenv tftp_serverip
setenv nfs_serverip
  • Никогда не сохраняйте переменные окружения после загрузки скрипта, иначе меню загрузки будет отображаться некорректно! Если всё же это сделали - то извлеките карту памяти и удалите с загрузочного раздела сохранённые файлы окружения: uboot-redund.env и uboot.env

Материалы к статье

1) Репозиторий со скриптами

2) Минималистичный загрузочный образ

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


  1. monah_tuk
    26.10.2023 00:01

    О, я правильно попял, что vitis тут не используется от слова совсем, т.е. на скриптовом уровне тоже? В таком варианте, получается, что при обновлении xsa с переферией для которой нужен драйвер, нужно руками делать запись в device tree?

    У себя я оставлял окружение чисто для генерации dt и ещё по мелочи, что бы не лезть туда руками. Ядро и rootfs тоже строились отдельно.


    1. nerudo
      26.10.2023 00:01

      fsbl_pmu_2020.sh Скрипт, создающий проект по умолчанию в Vitis и собирающий первичный загрузчик и pmu (Требуется Vivado, Vitis от 2020)

      Но если не используется petalinux - уже праздник.


      1. monah_tuk
        26.10.2023 00:01

        О да... пока всё запустится... добавлю в заметки, если опять потребуется запускать linux у меня на 104, покурить. Пока baremetal обходимся


    1. K0shi Автор
      26.10.2023 00:01

      Спасибо! Всё сразу не учёл, в ручную тоже делали.

      Подредактировал скрипты и обновил статью, сейчас dt c overlay берутся из ./configs/dts для ядра и UBoot. Как правило, из сгенерированных dt периферии можно сделать dtsi либо, в зависимости от того, что получилось - скопировать нагенерированное целиком.

      Сейчас для использования своих dt требуется, например, для zynqmp-zcu106-custom:

      • Поместить их исходники (dts, dtsi и если необходимо - .h) в ./configs/dts

      • Добавить dtb в цели /configs/dts/Makefile.in

      dtb-$(CONFIG_ARCH_ZYNQMP) += zynqmp-zcu106-custom.dtb
      • Указать в defconfig UBoot (./configs/uboot/xilinx_ulrascale_debug_defconfig) :

      CONFIG_DEFAULT_DEVICE_TREE="zynqmp-zcu106-custom"
      • В файле формирования образа указать копируемый dtb:

      cp ${WORK}/linux-xlnx/arch/arm64/boot/dts/xilinx/zynqmp-zcu106-custom.dtb ./system.dtb


  1. roman_ag
    26.10.2023 00:01

    Заголовочек поправьте. Архитектура не соответствует;)


    1. K0shi Автор
      26.10.2023 00:01

      Спасибо, опечатку поправил в заголовке


  1. pcbteach
    26.10.2023 00:01

    Благодарю за статью!


  1. gosha-z
    26.10.2023 00:01
    +3

    1. monah_tuk
      26.10.2023 00:01

      Ох... стримы... 4 часа... но на статью, да, уйдёт больше времени.

      Но:

      PS: "Petalinux должен сдохнуть" - ведущий

      согласен.