1#!/usr/bin/env python3 2# 3# Boots a nested guest and compare content of a device (passthrough) to a 4# reference image. Both vfio group and iommufd passthrough methods are tested. 5# 6# Copyright (c) 2025 Linaro Ltd. 7# 8# Author: Pierrick Bouvier <pierrick.bouvier@linaro.org> 9# 10# SPDX-License-Identifier: GPL-2.0-or-later 11 12import os 13 14from qemu_test import QemuSystemTest, Asset 15from qemu_test import exec_command, wait_for_console_pattern 16from qemu_test import exec_command_and_wait_for_pattern 17from random import randbytes 18 19guest_script = ''' 20#!/usr/bin/env bash 21 22set -euo pipefail 23set -x 24 25# find disks from nvme serial 26dev_vfio=$(lsblk --nvme | grep vfio | cut -f 1 -d ' ') 27dev_iommufd=$(lsblk --nvme | grep iommufd | cut -f 1 -d ' ') 28pci_vfio=$(basename $(readlink -f /sys/block/$dev_vfio/../../../)) 29pci_iommufd=$(basename $(readlink -f /sys/block/$dev_iommufd/../../../)) 30 31# bind disks to vfio 32for p in "$pci_vfio" "$pci_iommufd"; do 33 if [ "$(cat /sys/bus/pci/devices/$p/driver_override)" == vfio-pci ]; then 34 continue 35 fi 36 echo $p > /sys/bus/pci/drivers/nvme/unbind 37 echo vfio-pci > /sys/bus/pci/devices/$p/driver_override 38 echo $p > /sys/bus/pci/drivers/vfio-pci/bind 39done 40 41# boot nested guest and execute /host/nested_guest.sh 42# one disk is passed through vfio group, the other, through iommufd 43qemu-system-aarch64 \ 44-M virt \ 45-display none \ 46-serial stdio \ 47-cpu host \ 48-enable-kvm \ 49-m 1G \ 50-kernel /host/Image.gz \ 51-drive format=raw,file=/host/guest.ext4,if=virtio \ 52-append "root=/dev/vda init=/init -- bash /host/nested_guest.sh" \ 53-virtfs local,path=/host,mount_tag=host,security_model=mapped,readonly=off \ 54-device vfio-pci,host=$pci_vfio \ 55-object iommufd,id=iommufd0 \ 56-device vfio-pci,host=$pci_iommufd,iommufd=iommufd0 57''' 58 59nested_guest_script = ''' 60#!/usr/bin/env bash 61 62set -euo pipefail 63set -x 64 65image_vfio=/host/disk_vfio 66image_iommufd=/host/disk_iommufd 67 68dev_vfio=$(lsblk --nvme | grep vfio | cut -f 1 -d ' ') 69dev_iommufd=$(lsblk --nvme | grep iommufd | cut -f 1 -d ' ') 70 71# compare if devices are identical to original images 72diff $image_vfio /dev/$dev_vfio 73diff $image_iommufd /dev/$dev_iommufd 74 75echo device_passthrough_test_ok 76''' 77 78class Aarch64DevicePassthrough(QemuSystemTest): 79 80 # https://github.com/pbo-linaro/qemu-linux-stack 81 # 82 # Linux kernel is compiled with defconfig + 83 # IOMMUFD + VFIO_DEVICE_CDEV + ARM_SMMU_V3_IOMMUFD 84 # https://docs.kernel.org/driver-api/vfio.html#vfio-device-cde 85 ASSET_DEVICE_PASSTHROUGH_STACK = Asset( 86 ('https://fileserver.linaro.org/s/fx5DXxBYme8dw2G/' 87 'download/device_passthrough.tar.xz'), 88 '812750b664d61c2986f2b149939ae28cafbd60d53e9c7e4b16e97143845e196d') 89 90 # This tests the device passthrough implementation, by booting a VM 91 # supporting it with two nvme disks attached, and launching a nested VM 92 # reading their content. 93 def test_aarch64_device_passthrough(self): 94 self.set_machine('virt') 95 self.require_accelerator('tcg') 96 97 self.vm.set_console() 98 99 stack_path_tar_gz = self.ASSET_DEVICE_PASSTHROUGH_STACK.fetch() 100 self.archive_extract(stack_path_tar_gz, format="tar") 101 102 stack = self.scratch_file('out') 103 kernel = os.path.join(stack, 'Image.gz') 104 rootfs_host = os.path.join(stack, 'host.ext4') 105 disk_vfio = os.path.join(stack, 'disk_vfio') 106 disk_iommufd = os.path.join(stack, 'disk_iommufd') 107 guest_cmd = os.path.join(stack, 'guest.sh') 108 nested_guest_cmd = os.path.join(stack, 'nested_guest.sh') 109 # we generate two random disks 110 with open(disk_vfio, "wb") as d: d.write(randbytes(512)) 111 with open(disk_iommufd, "wb") as d: d.write(randbytes(1024)) 112 with open(guest_cmd, 'w') as s: s.write(guest_script) 113 with open(nested_guest_cmd, 'w') as s: s.write(nested_guest_script) 114 115 self.vm.add_args('-cpu', 'max') 116 self.vm.add_args('-m', '2G') 117 self.vm.add_args('-M', 'virt,' 118 'virtualization=on,' 119 'gic-version=max,' 120 'iommu=smmuv3') 121 self.vm.add_args('-kernel', kernel) 122 self.vm.add_args('-drive', f'format=raw,file={rootfs_host}') 123 self.vm.add_args('-drive', 124 f'file={disk_vfio},if=none,id=vfio,format=raw') 125 self.vm.add_args('-device', 'nvme,serial=vfio,drive=vfio') 126 self.vm.add_args('-drive', 127 f'file={disk_iommufd},if=none,id=iommufd,format=raw') 128 self.vm.add_args('-device', 'nvme,serial=iommufd,drive=iommufd') 129 self.vm.add_args('-virtfs', 130 f'local,path={stack}/,mount_tag=host,' 131 'security_model=mapped,readonly=off') 132 # boot and execute guest script 133 # init will trigger a kernel panic if script fails 134 self.vm.add_args('-append', 135 'root=/dev/vda init=/init -- bash /host/guest.sh') 136 137 self.vm.launch() 138 wait_for_console_pattern(self, 'device_passthrough_test_ok', 139 failure_message='Kernel panic') 140 141if __name__ == '__main__': 142 QemuSystemTest.main() 143