This article describes how to install CentOS or Fedora on a disk image from an ISO installer image using Qemu. This disk image can be copied to an SD card to boot the ZCU102.
Linux distros generally distribute ISO files for installation. This works well for PC and server platforms, but generally not for embedded platforms. In this article we will use QEMU with a generic ARM64 model to install CentOS and Fedora from an ISO onto a raw disk image. We will install CentOS 8 and Fedora 29 as both of these ship with 4.18 kernel which has enough upstreamed driver support for Zynq Ultrascale+.
Installing a CentOS or Fedora system for Zynq UltraScale+ is currently a proof-of-concept (POC). You should not assume this builds a Xilinx supported, production-ready system. Please do your due diligence before deploying CentOS or Fedora and regression test against your system requirements. |
While this flow targets CentOS and Fedora because they are close relatives, this flow can be extended to support Ubuntu, Debian and openSUSE.
While this flow targets CentOS and Fedora because they are close relatives, this flow can be extended to support Ubuntu, Debian and openSUSE.
Linux VM or bare-metal machine
Sudo privileges
Terminal emulator
Internet access
ZCU102 evaluation board
8 GB (minimum)/16 GB (recommended) SD card
$ sudo dnf group install "Virtualization Host" $ sudo dnf install wget kpartx epel-release |
$ sudo apt-get install qemu wget kpartx |
$ sudo dnf install qemu-system-aarch64 |
$ sudo apt-get install qemu-system-arm |
If the install fails you may need to build QEMU from source. If the install succeeds, you may skip to “Prepare Workspace.” |
$ sudo dnf group install "Development Tools" $ sudo dnf install glib2-devel pixman-devel |
$ sudo apt-get install pkg-config libglib2.0-dev libpixman-1-dev git-core build-essential |
$ git clone https://git.qemu.org/git/qemu.git $ cd qemu $ git checkout -b v4.1.1.xlnx v4.1.1 # or later stable tag $ git submodule update --init --recursive $ ./configure --target-list=aarch64-softmmu,microblazeel-softmmu --disable-kvm --disable-xen $ make |
Recommend adding the path for the newly built aarch64 Qemu to your PATH variable in “.bashrc”, which should be $HOME/qemu/aarch64-softmmu.
Recommend adding the path for the newly built aarch64 Qemu to your PATH variable in “.bashrc”, which should be $HOME/qemu/aarch64-softmmu.
Create a directory structure for your workspace.
$ export COS_BUILD=$HOME/centos8-dev $ mkdir -p $COS_BUILD/rpmbuild $COS_BUILD/images/{efi,boot} $COS_BUILD/deploy/{efi/dtb/xilinx,boot} $ export IMG_ROOT= $COS_BUILD/images $ cd $IMG_ROOT |
Download the UEFI boot image for the QEMU generic ARM model.
$ wget https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.img.gz $ gunzip QEMU_EFI.img.gz |
Create the QEMU script to run the ISO installer.
These scripts use the QEMU generic ARM model vs. the ZCU102 model. This allows us to use a full-blown UEFI bootloader from Linaro and does not rely on a properly configured kernel for Zynq UltraScale+.
These scripts use the QEMU generic ARM model vs. the ZCU102 model. This allows us to use a full-blown UEFI bootloader from Linaro and does not rely on a properly configured kernel for Zynq UltraScale+.
#!/bin/bash # Install distro from an ISO onto a virtual disk image # . runqemu-iso <distro>.iso iso=$1 os=${iso%.iso} size=7G disk_img=$os.qcow2 efi_img=$os.efi qemu-img create -f qcow2 $disk_img ${size} qemu-img create -f qcow2 $efi_img 64M qemu-system-aarch64 \ -cpu cortex-a53 -M virt -m 4096 -nographic -smp $(nproc) -boot d \ -drive if=pflash,format=raw,file=QEMU_EFI.img \ -drive if=pflash,format=qcow2,file=${efi_img} \ -device virtio-scsi-device \ -device scsi-cd,drive=cdrom \ -device virtio-blk-device,drive=hd0 \ -drive file=${iso},id=cdrom,media=cdrom,if=none \ -drive file=${disk_img},id=hd0,if=none |
The actual storage space on your SD card is less than the size on the label. This script creates a 7 GB image targeting an 8 GB SD card.
The actual storage space on your SD card is less than the size on the label. This script creates a 7 GB image targeting an 8 GB SD card.
Create the QEMU script to boot from a QCOW2 or raw image.
#!/bin/bash # Boot distro from a QCOW2 or raw image # . runqemu-img <distro>.qcow2 disk_img=$1 efi_img=${1%.*}.efi qemu-system-aarch64 \ -cpu cortex-a53 -M virt -m 4096 -nographic -smp $(nproc) \ -drive if=pflash,format=raw,file=QEMU_EFI.img \ -drive if=pflash,format=qcow2,file=${efi_img} \ -device virtio-blk-device,drive=hd0 \ -drive file=${disk_img},id=hd0,if=none |
Download an ISO from one of mirrors for aarch64 CentOS 8.
$ cd $IMG_ROOT $ wget http://mirrors.usc.edu/pub/linux/distributions/centos/8.0.1905/isos/aarch64/CentOS-8-aarch64-1905-dvd1.iso $ wget http://mirrors.usc.edu/pub/linux/distributions/centos/8.0.1905/isos/aarch64/CHECKSUM $ sha256sum -c CHECKSUM CentOS-8-aarch64-1905-boot.iso: OK |
Depending on the number of ISOs included in the CHECKSUM file, you may get failures for files you have not downloaded. This is OK.
Depending on the number of ISOs included in the CHECKSUM file, you may get failures for files you have not downloaded. This is OK.
$ . runqemu-iso CentOS-8-aarch64-1905-dvd1.iso |
At the Grub menu, select install Linux and follow the installation menus. Configure as you see fit and let the VM reboot.
Login as root or switch to superuser (su). Since the initramfs is built with “hostonly” support, we will force dracut to add the MMC and SDHCI drivers.
# echo "add_drivers+=\"mmc_block sdhci-of-arasan\"" > /etc/dracut.conf.d/zcu102.conf # dracut --force |
If your ZCU102 fails to boot, you can try adding all the kernel modules to the initramfs with dracut “no-hostonly” option. |
# dracut --force --no-hostonly |
Power down the VM.
# systemctl poweroff |
Convert the QCOW2 image to a raw disk image.
$ qemu-img convert CentOS-8-aarch64-1905-dvd1.qcow2 CentOS-8-aarch64-1905-dvd1.raw |
I recommend archiving the QCOW2 image if you ever need to refer back to a clean installation. |
Create loop devices for the raw disk image. Mount the 2nd partition onto the “boot” directory.
$ sudo kpartx -va CentOS-8-aarch64-1905-dvd1.raw add map loop0p1 (253:3): 0 1228800 linear 7:1 2048 add map loop0p2 (253:4): 0 2097152 linear 7:1 1230848 add map loop0p3 (253:5): 0 11350016 linear 7:1 3328000 $ sudo mount /dev/mapper/loop0p2 boot |
Next grep the kernel configuration looking for a couple key options to see if we at least have a console and SDHCI support.
$ grep 'ARCH_ZYNQMP\|SERIAL_XILINX\|SDHCI_OF_ARASAN' boot/config-4.18.0-80.el8.aarch64 # CONFIG_ARCH_ZYNQMP is not set # CONFIG_SERIAL_XILINX_PS_UART is not set # CONFIG_MMC_SDHCI_OF_ARASAN is not set |
The default CentOS 8 kernel may not boot on the ZCU102, so you may need to build and install a new kernel. Please see Build a CentOS 8 System for Zynq UltraScale+ to generate the kernel RPMS if the CONFIG_* requirements above are not met. |
$ sudo cp ../rpmbuild/kernel/RPMS/aarch64/kernel-4.18.0-80.el8.xlnx.aarch64.rpm \ ../rpmbuild/kernel/RPMS/aarch64/kernel-core-4.18.0-80.el8.xlnx.aarch64.rpm \ ../rpmbuild/kernel/RPMS/aarch64/kernel-modules-4.18.0-80.el8.xlnx.aarch64.rpm \ ../rpmbuild/kernel/RPMS/aarch64/kernel-headers-4.18.0-80.el8.xlnx.aarch64.rpm \ boot/ |
Unmount the boot directory and cleanup the mappings.
$ sudo umount boot $ sudo kpartx -d CentOS-8-aarch64-1905-dvd1.raw loop deleted : /dev/loop0 |
Boot the system with the raw disk image and we will install the kernel RPM packages in the QEMU VM.
$ . runqemu-img CentOS-8-aarch64-1905-dvd1.raw |
Login as root and install the kernel.
zcu102 login: root Password: [root@localhost ~]# rpm -ivh kernel-*.rpm |
Once the kernel installation is complete, power-off the VM.
[root@localhost ~]# systemctl poweroff |
Next lets check the partition scheme on the raw disk image. The installer should have used a GPT scheme.
fdisk -l CentOS-8-aarch64-1905-dvd1.raw Disk CentOS-8-aarch64-1905-dvd1.raw: 7 GiB, 7516192768 bytes, 14680064 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: A01B0908-71D5-4058-A016-AE386F645BD9 Device Start End Sectors Size Type CentOS-8-aarch64-1905-dvd1.raw1 2048 1230847 1228800 600M EFI System CentOS-8-aarch64-1905-dvd1.raw2 1230848 3327999 2097152 1G Linux filesystem CentOS-8-aarch64-1905-dvd1.raw3 3328000 14678015 11350016 5.4G Linux LVM |
However, the Zynq UltraScale+ ROM requires an MBR scheme. So let’s convert the GPT to MBR.
$ sgdisk -m 1:2:3 CentOS-8-aarch64-1905-dvd1.raw Warning: The kernel is still using the old partition table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8) GPT data structures destroyed! You may now partition the disk using fdisk or other utilities. |
Mark the 1st partition with the boot flag.
$ sfdisk -A CentOS-8-aarch64-1905-dvd1.raw 1 The bootable flag on partition 1 is enabled now. The partition table has been altered. Syncing disks. |
Lets check to make sure the partition scheme converted correctly. You should see “Disklabel type: dos” and the Boot flag marked.
$ fdisk -l CentOS-8-aarch64-1905-dvd1.raw Disk CentOS-8-aarch64-1905-dvd1.raw: 7 GiB, 7516192768 bytes, 14680064 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00000000 Device Boot Start End Sectors Size Id Type CentOS-8-aarch64-1905-dvd1.raw1 * 2048 1230847 1228800 600M ef EFI (FAT CentOS-8-aarch64-1905-dvd1.raw2 1230848 3327999 2097152 1G 83 Linux CentOS-8-aarch64-1905-dvd1.raw3 3328000 14678015 11350016 5.4G 8e Linux LV |
Now lets create mappings for the raw image and mount the 1st partition on “efi.”
$ sudo kpartx -va CentOS-8-aarch64-1905-dvd1.raw add map loop0p1 (253:5): 0 1228800 linear 7:2 2048 add map loop0p2 (253:6): 0 2097152 linear 7:2 1230848 add map loop0p3 (253:7): 0 11350016 linear 7:2 3328000 $ sudo mount /dev/mapper/loop0p1 efi |
Install UEFI (boot.bin), DTB and replace shim with GRUB since we are not using PC secure boot.
$ sudo cp boot.bin efi/ $ mkdir -p efi/dtb/xilinx/ $ sudo cp zynqmp-zcu102-rev1.0.dtb efi/dtb/xilinx/ $ sudo cp efi/EFI/centos/grubaa64.efi efi/EFI/BOOT/BOOTAA64.EFI |
Unmount the “efi” partition and delete the mappings.
$ sudo umount efi $ sudo kpartx -d CentOS-8-aarch64-1905-dvd1.raw loop deleted : /dev/loop0 |
Finally copy the raw disk image to the SD card and boot the ZCU102.
$ sudo dd if=CentOS-8-aarch64-1905-dvd1.raw of=/dev/sdX bs=4M iflag=fullblock oflag=direct status=progress; sync |
$ cd $IMG_ROOT $ wget https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Server/aarch64/iso/Fedora-Server-dvd-aarch64-29-1.2.iso $ wget https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Server/aarch64/iso/Fedora-Server-29-1.2-aarch64-CHECKSUM $ sha256sum -c Fedora-Server-29-1.2-aarch64-CHECKSUM Fedora-Server-dvd-aarch64-29-1.2.iso: OK |
$ . install-iso Fedora-Server-dvd-aarch64-29-1.2.iso |
At the Grub menu, select Install Linux and follow the installation menus. Configure as you see fit and let the VM reboot.
Login as root or switch to superuser (su). Since the initramfs is built with “hostonly” support, we will force dracut to add the MMC and SDHCI drivers. You can verify the presence of these modules in the initramfs using “lsinitrd” before and after running dracut.
# echo "add_drivers+=\"mmc_block sdhci-of-arasan\"" > /etc/dracut.conf.d/zcu102.conf # dracut --force |
If your ZCU102 fails to boot, you can try adding all the kernel modules to the initramfs with dracut “no-hostonly” option.
If your ZCU102 fails to boot, you can try adding all the kernel modules to the initramfs with dracut “no-hostonly” option.
# dracut --force --no-hostonly |
Power down the VM.
# systemctl poweroff |
First you will need to first convert the QCOW2 image to a raw disk image.
$ qemu-img convert Fedora-Server-dvd-aarch64-29-1.2.qcow2 Fedora-Server-dvd-aarch64-29-1.2.raw |
I recommend archiving the QCOW2 image if you ever need to refer back to a virgin installation. |
Create loop devices for the raw disk image. Mount the 2nd partition onto the “boot” directory.
$ sudo kpartx -va Fedora-Server-dvd-aarch64-29-1.2.raw add map loop0p1 (253:3): 0 409600 linear 7:0 2048 add map loop0p2 (253:4): 0 2097152 linear 7:0 411648 add map loop0p3 (253:5): 0 12169216 linear 7:0 2508800 $ sudo mount /dev/mapper/loop0p2 boot |
Next check the kernel to see if we believe it will boot the ZCU102. Let’s grep the configuration looking for a couple key options.
$ grep 'ARCH_ZYNQMP\|SERIAL_XILINX\|SDHCI_OF_ARASAN' boot/config-4.18.16-300.fc29.aarch64 CONFIG_ARCH_ZYNQMP=y CONFIG_SERIAL_XILINX_PS_UART=y CONFIG_SERIAL_XILINX_PS_UART_CONSOLE=y CONFIG_MMC_SDHCI_OF_ARASAN=m |
It appears there are enough configuration options to at least get a console and mount the SD card.
Unmount the boot directory and cleanup the mappings.
$ sudo umount boot $ sudo kpartx -d Fedora-Server-dvd-aarch64-29-1.2.raw loop deleted : /dev/loop0 |
Next lets check the partition scheme on the raw disk image. The installer should have used a GPT scheme.
$ fdisk -l Fedora-Server-dvd-aarch64-29-1.2.raw Disk Fedora-Server-dvd-aarch64-29-1.2.raw: 7 GiB, 7516192768 bytes, 14680064 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: BA9EE671-D41D-4199-B3FF-4F4A54B09E79 Device Start End Sectors Size Type Fedora-Server-dvd-aarch64-29-1.2.raw1 2048 411647 409600 200M EFI System Fedora-Server-dvd-aarch64-29-1.2.raw2 411648 2508799 2097152 1G Linux file Fedora-Server-dvd-aarch64-29-1.2.raw3 2508800 14678015 12169216 5.8G Linux LVM |
However, the Zynq UltraScale+ ROM requires an MBR scheme. So let’s convert the GPT to MBR.
$ sgdisk -m 1:2:3 Fedora-Server-dvd-aarch64-29-1.2.raw Warning: The kernel is still using the old partition table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8) GPT data structures destroyed! You may now partition the disk using fdisk or other utilities. |
Also, mark the 1st partition with the boot flag.
$ sfdisk -A Fedora-Server-dvd-aarch64-29-1.2.raw 1 The bootable flag on partition 1 is enabled now. The partition table has been altered. Syncing disks. |
Now lets check to make sure the partition scheme converted correctly. You should see “Disklabel type: dos” and the Boot flag marked.
$ fdisk -l Fedora-Server-dvd-aarch64-29-1.2.raw Disk Fedora-Server-dvd-aarch64-29-1.2.raw: 7 GiB, 7516192768 bytes, 14680064 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00000000 Device Boot Start End Sectors Size Id Type Fedora-Server-dvd-aarch64-29-1.2.raw1 * 2048 411647 409600 200M ef EFI Fedora-Server-dvd-aarch64-29-1.2.raw2 411648 2508799 2097152 1G 83 Linu Fedora-Server-dvd-aarch64-29-1.2.raw3 2508800 14678015 12169216 5.8G 8e Linu |
Now lets create mappings for the raw image and mount the 1st partition on “efi” and the 2nd partition on “boot”.
$ sudo kpartx -va Fedora-Server-dvd-aarch64-29-1.2.raw add map loop0p1 (253:3): 0 409600 linear 7:0 2048 add map loop0p2 (253:4): 0 2097152 linear 7:0 411648 add map loop0p3 (253:5): 0 12169216 linear 7:0 2508800 $ sudo mount /dev/mapper/loop0p1 efi $ sudo mount /dev/mapper/loop0p2 boot |
Install UEFI (boot.bin), DTB and replace shim with GRUB since we are not using PC secure boot.
$ sudo cp boot.bin efi/ $ mkdir -p efi/dtb/xilinx/ $ sudo cp boot/dtb-4.18.16-300.fc29.aarch64/xilinx/zynqmp-zcu102-rev1.0.dtb efi/dtb/xilinx/ $ sudo cp efi/EFI/fedora/grubaa64.efi efi/EFI/BOOT/BOOTAA64.EFI |
Unmount the “efi” and “boot” partitions. Then delete the mappings.
$ sudo umount efi $ sudo umount boot $ sudo kpartx -d Fedora-Server-dvd-aarch64-29-1.2.raw loop deleted : /dev/loop0 |
Finally copy the raw disk image to the SD card and boot the ZCU102.
$ sudo dd if=Fedora-Server-dvd-aarch64-29-1.2.raw of=/dev/sdX bs=4M iflag=fullblock oflag=direct status=progress; sync |
This quick install leverages a preinstalled RPI ARMv8 disk image.
Fedora also provides raw disk images. You may download the raw disk image for a quicker install.
Fedora also provides raw disk images. You may download the raw disk image for a quicker install.
$ wget https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Server/aarch64/images/Fedora-Server-29-1.2.aarch64.raw.xz $ wget https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Server/aarch64/images/Fedora-Server-29-1.2-aarch64-CHECKSUM $ sha256sum -c Fedora-Server-29-1.2-aarch64-CHECKSUM Fedora-Server-29-1.2.aarch64.raw.xz: OK $ xz -d Fedora-Server-29-1.2.aarch64.raw.xz |
The raw image is already formatted as MBR, so we don’t need to modify the partitioning scheme.
$ fdisk -l Fedora-Server-29-1.2.aarch64.raw Disk Fedora-Server-29-1.2.aarch64.raw: 7 GiB, 7516192768 bytes, 14680064 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x180b287d Device Boot Start End Sectors Size Id Type Fedora-Server-29-1.2.aarch64.raw1 * 2048 411647 409600 200M 6 FAT16 Fedora-Server-29-1.2.aarch64.raw2 411648 2508799 2097152 1G 83 Linux Fedora-Server-29-1.2.aarch64.raw3 2508800 14680063 12171264 5.8G 8e Linux |
Now lets create mappings for the raw image and mount the 1st partition on “efi” and the 2nd partition on “boot”.
$ sudo kpartx -va Fedora-Server-29-1.2.aarch64.raw [sudo] password for emil: add map loop0p1 (253:3): 0 409600 linear 7:0 2048 add map loop0p2 (253:4): 0 2097152 linear 7:0 411648 add map loop0p3 (253:5): 0 12171264 linear 7:0 2508800 $ sudo mount /dev/mapper/loop0p1 efi $ sudo mount /dev/mapper/loop0p2 boot |
Install UEFI (boot.bin), DTB and replace shim with GRUB since we are not using PC secure boot.
$ sudo cp boot.bin efi/ $ mkdir -p efi/dtb/xilinx/ $ sudo cp boot/dtb/xilinx/zynqmp-zcu102-rev1.0.dtb efi/dtb/xilinx/ $ sudo cp efi/EFI/fedora/grubaa64.efi efi/EFI/BOOT/BOOTAA64.EFI |
The “efi” directory also contains RPI support images and files. You may delete them or leave them as they are benign. |
Next check the kernel to see if we believe it will boot the ZCU102. Let’s grep the configuration looking for a couple key options.
$ grep 'ARCH_ZYNQMP\|SERIAL_XILINX\|SDHCI_OF_ARASAN' boot/config-4.18.16-300.fc29.aarch64 CONFIG_ARCH_ZYNQMP=y CONFIG_SERIAL_XILINX_PS_UART=y CONFIG_SERIAL_XILINX_PS_UART_CONSOLE=y CONFIG_MMC_SDHCI_OF_ARASAN=m |
It appears there are enough configuration options to at least get a console and mount the SD card.
Check the initramfs for mmc_block and sdhci-of-arasan kernel modules.
$ sudo lsinitrd boot/initramfs-4.18.16-300.fc29.aarch64.img | grep 'mmc_block\|sdhci-of-arasan' -rw-r--r-- 1 root root 18740 Jul 26 2018 usr/lib/modules/4.18.16-300.fc29.aarch64/kernel/drivers/mmc/core/mmc_block.ko.xz -rw-r--r-- 1 root root 5800 Jul 26 2018 usr/lib/modules/4.18.16-300.fc29.aarch64/kernel/drivers/mmc/host/sdhci-of-arasan.ko.xz |
These modules are installed in the initramfs, so we should be able to mount a block device on the SD card.
Unmount the “efi” and “boot” partitions and delete the mappings.
$ sudo umount efi $ sudo umount boot $ sudo kpartx -d Fedora-Server-29-1.2.aarch64.raw loop deleted : /dev/loop0 |
Finally copy the raw disk image to the SD card and boot the ZCU102.
$ sudo dd if=Fedora-Server-29-1.2.aarch64.raw of=/dev/sdX bs=4M iflag=fullblock oflag=direct status=progress; sync |