xref: /qemu/tests/functional/test_aarch64_device_passthrough.py (revision 7698afc42b5af9e55f12ab2236618e38e5a1c23f)
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