aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2021-12-15 07:23:50 -0800
committerRichard Henderson <richard.henderson@linaro.org>2021-12-15 07:23:50 -0800
commit5d3da09e44a73b4dadf9f1a94828fef27bcb5588 (patch)
tree95395fee3bdbb28592edc66106437a742bb4fce7
parent76b56fdfc9fa43ec6e5986aee33f108c6c6a511e (diff)
parent7876cba8fc0cab9a4c803a30f427d4b20b95a868 (diff)
downloadqemu-5d3da09e44a73b4dadf9f1a94828fef27bcb5588.zip
qemu-5d3da09e44a73b4dadf9f1a94828fef27bcb5588.tar.gz
qemu-5d3da09e44a73b4dadf9f1a94828fef27bcb5588.tar.bz2
Merge tag 'pull-request-2021-12-15' of https://gitlab.com/thuth/qemu into staging
* Add virtio-net failover test * Make qtests a little bit more flexible with regards to reduced configs * Move libssh setup from configure to meson.build * Run device-crash-test in CI * Add jobs for NetBSD and OpenBSD to the CI * Test compilation with MSYS2 in the gitlab-ci, too * Add new virtio-iommu test # gpg: Signature made Tue 14 Dec 2021 11:11:54 PM PST # gpg: using RSA key 27B88847EEE0250118F3EAB92ED9D774FE702DB5 # gpg: issuer "thuth@redhat.com" # gpg: Good signature from "Thomas Huth <th.huth@gmx.de>" [full] # gpg: aka "Thomas Huth <thuth@redhat.com>" [full] # gpg: aka "Thomas Huth <th.huth@posteo.de>" [unknown] # gpg: aka "Thomas Huth <huth@tuxfamily.org>" [full] * tag 'pull-request-2021-12-15' of https://gitlab.com/thuth/qemu: gitlab-ci: Test compilation on Windows with MSYS2 tests: qtest: Add virtio-iommu test virtio-iommu: Fix the domain_range end virtio-iommu: Fix endianness in get_config virtio-iommu: Remove set_config callback gitlab-ci: Add cirrus-ci based tests for NetBSD and OpenBSD gitlab-ci.d/buildtest: Add jobs that run the device-crash-test Move the libssh setup from configure to meson.build tests/qtest: Add a function to check whether a machine is available tests/qtest: Add a function that gets a list with available machine types tests/qtest: Fence the tests that need xlnx-zcu102 with CONFIG_XLNX_ZYNQMP_ARM tests/qtest: Run the PPC 32-bit tests with the 64-bit target binary, too tests/libqtest: add a migration test with two couples of failover devices tests/libqtest: add some virtio-net failover migration cancelling tests tests/qtest: add some tests for virtio-net failover qtest/libqos: add a function to initialize secondary PCI buses Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
-rw-r--r--.gitlab-ci.d/buildtest.yml23
-rw-r--r--.gitlab-ci.d/cirrus.yml35
-rw-r--r--.gitlab-ci.d/cirrus/kvm-build.yml31
-rw-r--r--.gitlab-ci.d/qemu-project.yml1
-rw-r--r--.gitlab-ci.d/windows.yml98
-rwxr-xr-xconfigure27
-rw-r--r--hw/virtio/trace-events3
-rw-r--r--hw/virtio/virtio-iommu.c42
-rw-r--r--include/hw/pci/pci_bridge.h8
-rw-r--r--meson.build13
-rw-r--r--meson_options.txt2
-rw-r--r--scripts/meson-buildoptions.sh3
-rw-r--r--tests/qtest/boot-serial-test.c3
-rw-r--r--tests/qtest/cdrom-test.c8
-rw-r--r--tests/qtest/libqos/libqtest.h8
-rw-r--r--tests/qtest/libqos/meson.build1
-rw-r--r--tests/qtest/libqos/pci.c119
-rw-r--r--tests/qtest/libqos/pci.h1
-rw-r--r--tests/qtest/libqos/virtio-iommu.c126
-rw-r--r--tests/qtest/libqos/virtio-iommu.h40
-rw-r--r--tests/qtest/libqtest.c79
-rw-r--r--tests/qtest/meson.build9
-rw-r--r--tests/qtest/prom-env-test.c8
-rw-r--r--tests/qtest/virtio-iommu-test.c326
-rw-r--r--tests/qtest/virtio-net-failover.c1352
25 files changed, 2290 insertions, 76 deletions
diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml
index 71d0f40..7e1cb0b 100644
--- a/.gitlab-ci.d/buildtest.yml
+++ b/.gitlab-ci.d/buildtest.yml
@@ -100,6 +100,17 @@ avocado-system-debian:
IMAGE: debian-amd64
MAKE_CHECK_ARGS: check-avocado
+crash-test-debian:
+ extends: .native_test_job_template
+ needs:
+ - job: build-system-debian
+ artifacts: true
+ variables:
+ IMAGE: debian-amd64
+ script:
+ - cd build
+ - scripts/device-crash-test -q ./qemu-system-i386
+
build-system-fedora:
extends: .native_build_job_template
needs:
@@ -134,6 +145,18 @@ avocado-system-fedora:
IMAGE: fedora
MAKE_CHECK_ARGS: check-avocado
+crash-test-fedora:
+ extends: .native_test_job_template
+ needs:
+ - job: build-system-fedora
+ artifacts: true
+ variables:
+ IMAGE: fedora
+ script:
+ - cd build
+ - scripts/device-crash-test -q ./qemu-system-ppc
+ - scripts/device-crash-test -q ./qemu-system-riscv32
+
build-system-centos:
extends: .native_build_job_template
needs:
diff --git a/.gitlab-ci.d/cirrus.yml b/.gitlab-ci.d/cirrus.yml
index d273a9e..19e6c21 100644
--- a/.gitlab-ci.d/cirrus.yml
+++ b/.gitlab-ci.d/cirrus.yml
@@ -89,3 +89,38 @@ x64-macos-11-base-build:
PATH_EXTRA: /usr/local/opt/ccache/libexec:/usr/local/opt/gettext/bin
PKG_CONFIG_PATH: /usr/local/opt/curl/lib/pkgconfig:/usr/local/opt/ncurses/lib/pkgconfig:/usr/local/opt/readline/lib/pkgconfig
TEST_TARGETS: check-unit check-block check-qapi-schema check-softfloat check-qtest-x86_64
+
+
+# The following jobs run VM-based tests via KVM on a Linux-based Cirrus-CI job
+.cirrus_kvm_job:
+ stage: build
+ image: registry.gitlab.com/libvirt/libvirt-ci/cirrus-run:master
+ needs: []
+ timeout: 80m
+ allow_failure: true
+ script:
+ - sed -e "s|[@]CI_REPOSITORY_URL@|$CI_REPOSITORY_URL|g"
+ -e "s|[@]CI_COMMIT_REF_NAME@|$CI_COMMIT_REF_NAME|g"
+ -e "s|[@]CI_COMMIT_SHA@|$CI_COMMIT_SHA|g"
+ -e "s|[@]NAME@|$NAME|g"
+ -e "s|[@]CONFIGURE_ARGS@|$CONFIGURE_ARGS|g"
+ -e "s|[@]TEST_TARGETS@|$TEST_TARGETS|g"
+ <.gitlab-ci.d/cirrus/kvm-build.yml >.gitlab-ci.d/cirrus/$NAME.yml
+ - cat .gitlab-ci.d/cirrus/$NAME.yml
+ - cirrus-run -v --show-build-log always .gitlab-ci.d/cirrus/$NAME.yml
+ rules:
+ - when: manual
+
+x86-netbsd:
+ extends: .cirrus_kvm_job
+ variables:
+ NAME: netbsd
+ CONFIGURE_ARGS: --target-list=x86_64-softmmu,ppc64-softmmu,aarch64-softmmu
+ TEST_TARGETS: check
+
+x86-openbsd:
+ extends: .cirrus_kvm_job
+ variables:
+ NAME: openbsd
+ CONFIGURE_ARGS: --target-list=i386-softmmu,riscv64-softmmu,mips64-softmmu
+ TEST_TARGETS: check
diff --git a/.gitlab-ci.d/cirrus/kvm-build.yml b/.gitlab-ci.d/cirrus/kvm-build.yml
new file mode 100644
index 0000000..4334fab
--- /dev/null
+++ b/.gitlab-ci.d/cirrus/kvm-build.yml
@@ -0,0 +1,31 @@
+container:
+ image: fedora:35
+ cpu: 4
+ memory: 8Gb
+ kvm: true
+
+env:
+ CIRRUS_CLONE_DEPTH: 1
+ CI_REPOSITORY_URL: "@CI_REPOSITORY_URL@"
+ CI_COMMIT_REF_NAME: "@CI_COMMIT_REF_NAME@"
+ CI_COMMIT_SHA: "@CI_COMMIT_SHA@"
+
+@NAME@_task:
+ @NAME@_vm_cache:
+ folder: $HOME/.cache/qemu-vm
+ install_script:
+ - dnf update -y
+ - dnf install -y git make openssh-clients qemu-img qemu-system-x86 wget
+ clone_script:
+ - git clone --depth 100 "$CI_REPOSITORY_URL" .
+ - git fetch origin "$CI_COMMIT_REF_NAME"
+ - git reset --hard "$CI_COMMIT_SHA"
+ build_script:
+ - if [ -f $HOME/.cache/qemu-vm/images/@NAME@.img ]; then
+ make vm-build-@NAME@ J=$(getconf _NPROCESSORS_ONLN)
+ EXTRA_CONFIGURE_OPTS="@CONFIGURE_ARGS@"
+ BUILD_TARGET="@TEST_TARGETS@" ;
+ else
+ make vm-build-@NAME@ J=$(getconf _NPROCESSORS_ONLN) BUILD_TARGET=help
+ EXTRA_CONFIGURE_OPTS="--disable-system --disable-user --disable-tools" ;
+ fi
diff --git a/.gitlab-ci.d/qemu-project.yml b/.gitlab-ci.d/qemu-project.yml
index b3d79bc..871262f 100644
--- a/.gitlab-ci.d/qemu-project.yml
+++ b/.gitlab-ci.d/qemu-project.yml
@@ -11,3 +11,4 @@ include:
- local: '/.gitlab-ci.d/static_checks.yml'
- local: '/.gitlab-ci.d/custom-runners.yml'
- local: '/.gitlab-ci.d/cirrus.yml'
+ - local: '/.gitlab-ci.d/windows.yml'
diff --git a/.gitlab-ci.d/windows.yml b/.gitlab-ci.d/windows.yml
new file mode 100644
index 0000000..309f7e7
--- /dev/null
+++ b/.gitlab-ci.d/windows.yml
@@ -0,0 +1,98 @@
+.shared_msys2_builder:
+ tags:
+ - shared-windows
+ - windows
+ - windows-1809
+ cache:
+ key: "${CI_JOB_NAME}-cache"
+ paths:
+ - ${CI_PROJECT_DIR}/msys64/var/cache
+ needs: []
+ stage: build
+ timeout: 70m
+ before_script:
+ - If ( !(Test-Path -Path msys64\var\cache ) ) {
+ mkdir msys64\var\cache
+ }
+ - If ( !(Test-Path -Path msys64\var\cache\msys2.exe ) ) {
+ Invoke-WebRequest
+ "https://github.com/msys2/msys2-installer/releases/download/2021-07-25/msys2-base-x86_64-20210725.sfx.exe"
+ -outfile "msys64\var\cache\msys2.exe"
+ }
+ - msys64\var\cache\msys2.exe -y
+ - ((Get-Content -path .\msys64\etc\\post-install\\07-pacman-key.post -Raw)
+ -replace '--refresh-keys', '--version') |
+ Set-Content -Path ${CI_PROJECT_DIR}\msys64\etc\\post-install\\07-pacman-key.post
+ - .\msys64\usr\bin\bash -lc "sed -i 's/^CheckSpace/#CheckSpace/g' /etc/pacman.conf"
+ - .\msys64\usr\bin\bash -lc 'pacman --noconfirm -Syuu' # Core update
+ - .\msys64\usr\bin\bash -lc 'pacman --noconfirm -Syuu' # Normal update
+ - taskkill /F /FI "MODULES eq msys-2.0.dll"
+
+msys2-64bit:
+ extends: .shared_msys2_builder
+ script:
+ - .\msys64\usr\bin\bash -lc "pacman -Sy --noconfirm --needed
+ diffutils git grep make sed
+ mingw-w64-x86_64-capstone
+ mingw-w64-x86_64-curl
+ mingw-w64-x86_64-cyrus-sasl
+ mingw-w64-x86_64-gcc
+ mingw-w64-x86_64-glib2
+ mingw-w64-x86_64-gnutls
+ mingw-w64-x86_64-libnfs
+ mingw-w64-x86_64-libpng
+ mingw-w64-x86_64-libssh
+ mingw-w64-x86_64-libtasn1
+ mingw-w64-x86_64-libusb
+ mingw-w64-x86_64-libxml2
+ mingw-w64-x86_64-nettle
+ mingw-w64-x86_64-ninja
+ mingw-w64-x86_64-pixman
+ mingw-w64-x86_64-pkgconf
+ mingw-w64-x86_64-python
+ mingw-w64-x86_64-SDL2
+ mingw-w64-x86_64-SDL2_image
+ mingw-w64-x86_64-snappy
+ mingw-w64-x86_64-usbredir
+ mingw-w64-x86_64-zstd "
+ - $env:CHERE_INVOKING = 'yes' # Preserve the current working directory
+ - $env:MSYSTEM = 'MINGW64' # Start a 64 bit Mingw environment
+ - .\msys64\usr\bin\bash -lc './configure --target-list=x86_64-softmmu
+ --enable-capstone=system'
+ - .\msys64\usr\bin\bash -lc "sed -i '/^ROMS=/d' build/config-host.mak"
+ - .\msys64\usr\bin\bash -lc 'make -j2'
+ - .\msys64\usr\bin\bash -lc 'make check'
+
+msys2-32bit:
+ extends: .shared_msys2_builder
+ script:
+ - .\msys64\usr\bin\bash -lc "pacman -Sy --noconfirm --needed
+ diffutils git grep make sed
+ mingw-w64-i686-capstone
+ mingw-w64-i686-curl
+ mingw-w64-i686-cyrus-sasl
+ mingw-w64-i686-gcc
+ mingw-w64-i686-glib2
+ mingw-w64-i686-gnutls
+ mingw-w64-i686-gtk3
+ mingw-w64-i686-libgcrypt
+ mingw-w64-i686-libjpeg-turbo
+ mingw-w64-i686-libssh
+ mingw-w64-i686-libtasn1
+ mingw-w64-i686-libusb
+ mingw-w64-i686-libxml2
+ mingw-w64-i686-lzo2
+ mingw-w64-i686-ninja
+ mingw-w64-i686-pixman
+ mingw-w64-i686-pkgconf
+ mingw-w64-i686-python
+ mingw-w64-i686-snappy
+ mingw-w64-i686-usbredir "
+ - $env:CHERE_INVOKING = 'yes' # Preserve the current working directory
+ - $env:MSYSTEM = 'MINGW32' # Start a 32-bit MinG environment
+ - mkdir output
+ - cd output
+ - ..\msys64\usr\bin\bash -lc "../configure --target-list=ppc64-softmmu
+ --enable-capstone=system"
+ - ..\msys64\usr\bin\bash -lc 'make -j2'
+ - ..\msys64\usr\bin\bash -lc 'make check'
diff --git a/configure b/configure
index 48c2177..bb99a40 100755
--- a/configure
+++ b/configure
@@ -344,7 +344,6 @@ debug_stack_usage="no"
crypto_afalg="no"
tls_priority="NORMAL"
tpm="$default_feature"
-libssh="$default_feature"
live_block_migration=${default_feature:-yes}
numa="$default_feature"
replication=${default_feature:-yes}
@@ -1078,10 +1077,6 @@ for opt do
;;
--enable-tpm) tpm="yes"
;;
- --disable-libssh) libssh="no"
- ;;
- --enable-libssh) libssh="yes"
- ;;
--disable-live-block-migration) live_block_migration="no"
;;
--enable-live-block-migration) live_block_migration="yes"
@@ -1448,7 +1443,6 @@ cat << EOF
live-block-migration Block migration in the main migration stream
coroutine-pool coroutine freelist (better performance)
tpm TPM support
- libssh ssh block device support
numa libnuma support
avx2 AVX2 optimization support
avx512f AVX512F optimization support
@@ -2562,21 +2556,6 @@ if test "$modules" = yes; then
fi
##########################################
-# libssh probe
-if test "$libssh" != "no" ; then
- if $pkg_config --exists "libssh >= 0.8.7"; then
- libssh_cflags=$($pkg_config libssh --cflags)
- libssh_libs=$($pkg_config libssh --libs)
- libssh=yes
- else
- if test "$libssh" = "yes" ; then
- error_exit "libssh required for --enable-libssh"
- fi
- libssh=no
- fi
-fi
-
-##########################################
# TPM emulation is only on POSIX
if test "$tpm" = ""; then
@@ -3636,12 +3615,6 @@ if test "$cmpxchg128" = "yes" ; then
echo "CONFIG_CMPXCHG128=y" >> $config_host_mak
fi
-if test "$libssh" = "yes" ; then
- echo "CONFIG_LIBSSH=y" >> $config_host_mak
- echo "LIBSSH_CFLAGS=$libssh_cflags" >> $config_host_mak
- echo "LIBSSH_LIBS=$libssh_libs" >> $config_host_mak
-fi
-
if test "$live_block_migration" = "yes" ; then
echo "CONFIG_LIVE_BLOCK_MIGRATION=y" >> $config_host_mak
fi
diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events
index 650e521..f7ad6be 100644
--- a/hw/virtio/trace-events
+++ b/hw/virtio/trace-events
@@ -91,8 +91,7 @@ virtio_mmio_setting_irq(int level) "virtio_mmio setting IRQ %d"
virtio_iommu_device_reset(void) "reset!"
virtio_iommu_get_features(uint64_t features) "device supports features=0x%"PRIx64
virtio_iommu_device_status(uint8_t status) "driver status = %d"
-virtio_iommu_get_config(uint64_t page_size_mask, uint64_t start, uint64_t end, uint32_t domain_range, uint32_t probe_size) "page_size_mask=0x%"PRIx64" start=0x%"PRIx64" end=0x%"PRIx64" domain_range=%d probe_size=0x%x"
-virtio_iommu_set_config(uint64_t page_size_mask, uint64_t start, uint64_t end, uint32_t domain_range, uint32_t probe_size) "page_size_mask=0x%"PRIx64" start=0x%"PRIx64" end=0x%"PRIx64" domain_bits=%d probe_size=0x%x"
+virtio_iommu_get_config(uint64_t page_size_mask, uint64_t start, uint64_t end, uint32_t domain_start, uint32_t domain_end, uint32_t probe_size) "page_size_mask=0x%"PRIx64" input range start=0x%"PRIx64" input range end=0x%"PRIx64" domain range start=%d domain range end=%d probe_size=0x%x"
virtio_iommu_attach(uint32_t domain_id, uint32_t ep_id) "domain=%d endpoint=%d"
virtio_iommu_detach(uint32_t domain_id, uint32_t ep_id) "domain=%d endpoint=%d"
virtio_iommu_map(uint32_t domain_id, uint64_t virt_start, uint64_t virt_end, uint64_t phys_start, uint32_t flags) "domain=%d virt_start=0x%"PRIx64" virt_end=0x%"PRIx64 " phys_start=0x%"PRIx64" flags=%d"
diff --git a/hw/virtio/virtio-iommu.c b/hw/virtio/virtio-iommu.c
index 1b23e8e..aa9c16a 100644
--- a/hw/virtio/virtio-iommu.c
+++ b/hw/virtio/virtio-iommu.c
@@ -822,27 +822,22 @@ unlock:
static void virtio_iommu_get_config(VirtIODevice *vdev, uint8_t *config_data)
{
VirtIOIOMMU *dev = VIRTIO_IOMMU(vdev);
- struct virtio_iommu_config *config = &dev->config;
-
- trace_virtio_iommu_get_config(config->page_size_mask,
- config->input_range.start,
- config->input_range.end,
- config->domain_range.end,
- config->probe_size);
- memcpy(config_data, &dev->config, sizeof(struct virtio_iommu_config));
-}
-
-static void virtio_iommu_set_config(VirtIODevice *vdev,
- const uint8_t *config_data)
-{
- struct virtio_iommu_config config;
-
- memcpy(&config, config_data, sizeof(struct virtio_iommu_config));
- trace_virtio_iommu_set_config(config.page_size_mask,
- config.input_range.start,
- config.input_range.end,
- config.domain_range.end,
- config.probe_size);
+ struct virtio_iommu_config *dev_config = &dev->config;
+ struct virtio_iommu_config *out_config = (void *)config_data;
+
+ out_config->page_size_mask = cpu_to_le64(dev_config->page_size_mask);
+ out_config->input_range.start = cpu_to_le64(dev_config->input_range.start);
+ out_config->input_range.end = cpu_to_le64(dev_config->input_range.end);
+ out_config->domain_range.start = cpu_to_le32(dev_config->domain_range.start);
+ out_config->domain_range.end = cpu_to_le32(dev_config->domain_range.end);
+ out_config->probe_size = cpu_to_le32(dev_config->probe_size);
+
+ trace_virtio_iommu_get_config(dev_config->page_size_mask,
+ dev_config->input_range.start,
+ dev_config->input_range.end,
+ dev_config->domain_range.start,
+ dev_config->domain_range.end,
+ dev_config->probe_size);
}
static uint64_t virtio_iommu_get_features(VirtIODevice *vdev, uint64_t f,
@@ -983,8 +978,8 @@ static void virtio_iommu_device_realize(DeviceState *dev, Error **errp)
s->event_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE, NULL);
s->config.page_size_mask = TARGET_PAGE_MASK;
- s->config.input_range.end = -1UL;
- s->config.domain_range.end = 32;
+ s->config.input_range.end = UINT64_MAX;
+ s->config.domain_range.end = UINT32_MAX;
s->config.probe_size = VIOMMU_PROBE_SIZE;
virtio_add_feature(&s->features, VIRTIO_RING_F_EVENT_IDX);
@@ -1185,7 +1180,6 @@ static void virtio_iommu_class_init(ObjectClass *klass, void *data)
vdc->unrealize = virtio_iommu_device_unrealize;
vdc->reset = virtio_iommu_device_reset;
vdc->get_config = virtio_iommu_get_config;
- vdc->set_config = virtio_iommu_set_config;
vdc->get_features = virtio_iommu_get_features;
vdc->set_status = virtio_iommu_set_status;
vdc->vmsd = &vmstate_virtio_iommu_device;
diff --git a/include/hw/pci/pci_bridge.h b/include/hw/pci/pci_bridge.h
index a94d350..30691a6 100644
--- a/include/hw/pci/pci_bridge.h
+++ b/include/hw/pci/pci_bridge.h
@@ -138,6 +138,7 @@ typedef struct PCIBridgeQemuCap {
uint64_t mem_pref_64; /* Prefetchable memory to reserve (64-bit MMIO) */
} PCIBridgeQemuCap;
+#define REDHAT_PCI_CAP_TYPE_OFFSET 3
#define REDHAT_PCI_CAP_RESOURCE_RESERVE 1
/*
@@ -152,6 +153,13 @@ typedef struct PCIResReserve {
uint64_t mem_pref_64;
} PCIResReserve;
+#define REDHAT_PCI_CAP_RES_RESERVE_BUS_RES 4
+#define REDHAT_PCI_CAP_RES_RESERVE_IO 8
+#define REDHAT_PCI_CAP_RES_RESERVE_MEM 16
+#define REDHAT_PCI_CAP_RES_RESERVE_PREF_MEM_32 20
+#define REDHAT_PCI_CAP_RES_RESERVE_PREF_MEM_64 24
+#define REDHAT_PCI_CAP_RES_RESERVE_CAP_SIZE 32
+
int pci_bridge_qemu_reserve_cap_init(PCIDevice *dev, int cap_offset,
PCIResReserve res_reserve, Error **errp);
diff --git a/meson.build b/meson.build
index 96de1a6..ae67ca2 100644
--- a/meson.build
+++ b/meson.build
@@ -874,11 +874,15 @@ if not get_option('glusterfs').auto() or have_block
''', dependencies: glusterfs)
endif
endif
+
libssh = not_found
-if 'CONFIG_LIBSSH' in config_host
- libssh = declare_dependency(compile_args: config_host['LIBSSH_CFLAGS'].split(),
- link_args: config_host['LIBSSH_LIBS'].split())
+if not get_option('libssh').auto() or have_block
+ libssh = dependency('libssh', version: '>=0.8.7',
+ method: 'pkg-config',
+ required: get_option('libssh'),
+ kwargs: static_kwargs)
endif
+
libbzip2 = not_found
if not get_option('bzip2').auto() or have_block
libbzip2 = cc.find_library('bz2', has_headers: ['bzlib.h'],
@@ -1451,6 +1455,7 @@ config_host_data.set('CONFIG_EBPF', libbpf.found())
config_host_data.set('CONFIG_LIBDAXCTL', libdaxctl.found())
config_host_data.set('CONFIG_LIBISCSI', libiscsi.found())
config_host_data.set('CONFIG_LIBNFS', libnfs.found())
+config_host_data.set('CONFIG_LIBSSH', libssh.found())
config_host_data.set('CONFIG_LINUX_AIO', libaio.found())
config_host_data.set('CONFIG_LINUX_IO_URING', linux_io_uring.found())
config_host_data.set('CONFIG_LIBPMEM', libpmem.found())
@@ -3430,7 +3435,7 @@ endif
summary_info += {'seccomp support': seccomp}
summary_info += {'GlusterFS support': glusterfs}
summary_info += {'TPM support': config_host.has_key('CONFIG_TPM')}
-summary_info += {'libssh support': config_host.has_key('CONFIG_LIBSSH')}
+summary_info += {'libssh support': libssh}
summary_info += {'lzo support': lzo}
summary_info += {'snappy support': snappy}
summary_info += {'bzip2 support': libbzip2}
diff --git a/meson_options.txt b/meson_options.txt
index e392323..4114bfc 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -105,6 +105,8 @@ option('libdaxctl', type : 'feature', value : 'auto',
description: 'libdaxctl support')
option('libpmem', type : 'feature', value : 'auto',
description: 'libpmem support')
+option('libssh', type : 'feature', value : 'auto',
+ description: 'ssh block device support')
option('libudev', type : 'feature', value : 'auto',
description: 'Use libudev to enumerate host devices')
option('libusb', type : 'feature', value : 'auto',
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 7a17ff4..ae8f18e 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -53,6 +53,7 @@ meson_options_help() {
printf "%s\n" ' libiscsi libiscsi userspace initiator'
printf "%s\n" ' libnfs libnfs block device driver'
printf "%s\n" ' libpmem libpmem support'
+ printf "%s\n" ' libssh ssh block device support'
printf "%s\n" ' libudev Use libudev to enumerate host devices'
printf "%s\n" ' libusb libusb support for USB passthrough'
printf "%s\n" ' libxml2 libxml2 support for Parallels image format'
@@ -177,6 +178,8 @@ _meson_option_parse() {
--disable-libnfs) printf "%s" -Dlibnfs=disabled ;;
--enable-libpmem) printf "%s" -Dlibpmem=enabled ;;
--disable-libpmem) printf "%s" -Dlibpmem=disabled ;;
+ --enable-libssh) printf "%s" -Dlibssh=enabled ;;
+ --disable-libssh) printf "%s" -Dlibssh=disabled ;;
--enable-libudev) printf "%s" -Dlibudev=enabled ;;
--disable-libudev) printf "%s" -Dlibudev=disabled ;;
--enable-libusb) printf "%s" -Dlibusb=enabled ;;
diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
index 83828ba..4d8e134 100644
--- a/tests/qtest/boot-serial-test.c
+++ b/tests/qtest/boot-serial-test.c
@@ -285,7 +285,8 @@ int main(int argc, char *argv[])
g_test_init(&argc, &argv, NULL);
for (i = 0; tests[i].arch != NULL; i++) {
- if (strcmp(arch, tests[i].arch) == 0) {
+ if (g_str_equal(arch, tests[i].arch) &&
+ qtest_has_machine(tests[i].machine)) {
char *name = g_strdup_printf("boot-serial/%s", tests[i].machine);
qtest_add_data_func(name, &tests[i], test_machine);
g_free(name);
diff --git a/tests/qtest/cdrom-test.c b/tests/qtest/cdrom-test.c
index 5af944a..c1fcac5 100644
--- a/tests/qtest/cdrom-test.c
+++ b/tests/qtest/cdrom-test.c
@@ -109,9 +109,11 @@ static void test_cdrom_param(gconstpointer data)
static void add_cdrom_param_tests(const char **machines)
{
while (*machines) {
- char *testname = g_strdup_printf("cdrom/param/%s", *machines);
- qtest_add_data_func(testname, *machines, test_cdrom_param);
- g_free(testname);
+ if (qtest_has_machine(*machines)) {
+ char *testname = g_strdup_printf("cdrom/param/%s", *machines);
+ qtest_add_data_func(testname, *machines, test_cdrom_param);
+ g_free(testname);
+ }
machines++;
}
}
diff --git a/tests/qtest/libqos/libqtest.h b/tests/qtest/libqos/libqtest.h
index 59e9271..dff6b31 100644
--- a/tests/qtest/libqos/libqtest.h
+++ b/tests/qtest/libqos/libqtest.h
@@ -711,6 +711,14 @@ void qtest_cb_for_every_machine(void (*cb)(const char *machine),
bool skip_old_versioned);
/**
+ * qtest_has_machine:
+ * @machine: The machine to look for
+ *
+ * Returns: true if the machine is available in the target binary.
+ */
+bool qtest_has_machine(const char *machine);
+
+/**
* qtest_qmp_device_add_qdict:
* @qts: QTestState instance to operate on
* @drv: Name of the device that should be added
diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build
index 4af1f04..e988d15 100644
--- a/tests/qtest/libqos/meson.build
+++ b/tests/qtest/libqos/meson.build
@@ -41,6 +41,7 @@ libqos_srcs = files('../libqtest.c',
'virtio-rng.c',
'virtio-scsi.c',
'virtio-serial.c',
+ 'virtio-iommu.c',
# qgraph machines:
'aarch64-xlnx-zcu102-machine.c',
diff --git a/tests/qtest/libqos/pci.c b/tests/qtest/libqos/pci.c
index e1e9618..3a9076a 100644
--- a/tests/qtest/libqos/pci.c
+++ b/tests/qtest/libqos/pci.c
@@ -13,6 +13,8 @@
#include "qemu/osdep.h"
#include "pci.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
#include "hw/pci/pci_regs.h"
#include "qemu/host-utils.h"
#include "qgraph.h"
@@ -99,6 +101,123 @@ void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr)
g_assert(!addr->device_id || device_id == addr->device_id);
}
+static uint8_t qpci_find_resource_reserve_capability(QPCIDevice *dev)
+{
+ uint16_t device_id;
+ uint8_t cap = 0;
+
+ if (qpci_config_readw(dev, PCI_VENDOR_ID) != PCI_VENDOR_ID_REDHAT) {
+ return 0;
+ }
+
+ device_id = qpci_config_readw(dev, PCI_DEVICE_ID);
+
+ if (device_id != PCI_DEVICE_ID_REDHAT_PCIE_RP &&
+ device_id != PCI_DEVICE_ID_REDHAT_BRIDGE) {
+ return 0;
+ }
+
+ do {
+ cap = qpci_find_capability(dev, PCI_CAP_ID_VNDR, cap);
+ } while (cap &&
+ qpci_config_readb(dev, cap + REDHAT_PCI_CAP_TYPE_OFFSET) !=
+ REDHAT_PCI_CAP_RESOURCE_RESERVE);
+ if (cap) {
+ uint8_t cap_len = qpci_config_readb(dev, cap + PCI_CAP_FLAGS);
+ if (cap_len < REDHAT_PCI_CAP_RES_RESERVE_CAP_SIZE) {
+ return 0;
+ }
+ }
+ return cap;
+}
+
+static void qpci_secondary_buses_rec(QPCIBus *qbus, int bus, int *pci_bus)
+{
+ QPCIDevice *dev;
+ uint16_t class;
+ uint8_t pribus, secbus, subbus;
+ int index;
+
+ for (index = 0; index < 32; index++) {
+ dev = qpci_device_find(qbus, QPCI_DEVFN(bus + index, 0));
+ if (dev == NULL) {
+ continue;
+ }
+ class = qpci_config_readw(dev, PCI_CLASS_DEVICE);
+ if (class == PCI_CLASS_BRIDGE_PCI) {
+ qpci_config_writeb(dev, PCI_SECONDARY_BUS, 255);
+ qpci_config_writeb(dev, PCI_SUBORDINATE_BUS, 0);
+ }
+ g_free(dev);
+ }
+
+ for (index = 0; index < 32; index++) {
+ dev = qpci_device_find(qbus, QPCI_DEVFN(bus + index, 0));
+ if (dev == NULL) {
+ continue;
+ }
+ class = qpci_config_readw(dev, PCI_CLASS_DEVICE);
+ if (class != PCI_CLASS_BRIDGE_PCI) {
+ g_free(dev);
+ continue;
+ }
+
+ pribus = qpci_config_readb(dev, PCI_PRIMARY_BUS);
+ if (pribus != bus) {
+ qpci_config_writeb(dev, PCI_PRIMARY_BUS, bus);
+ }
+
+ secbus = qpci_config_readb(dev, PCI_SECONDARY_BUS);
+ (*pci_bus)++;
+ if (*pci_bus != secbus) {
+ secbus = *pci_bus;
+ qpci_config_writeb(dev, PCI_SECONDARY_BUS, secbus);
+ }
+
+ subbus = qpci_config_readb(dev, PCI_SUBORDINATE_BUS);
+ qpci_config_writeb(dev, PCI_SUBORDINATE_BUS, 255);
+
+ qpci_secondary_buses_rec(qbus, secbus << 5, pci_bus);
+
+ if (subbus != *pci_bus) {
+ uint8_t res_bus = *pci_bus;
+ uint8_t cap = qpci_find_resource_reserve_capability(dev);
+
+ if (cap) {
+ uint32_t tmp_res_bus;
+
+ tmp_res_bus = qpci_config_readl(dev, cap +
+ REDHAT_PCI_CAP_RES_RESERVE_BUS_RES);
+ if (tmp_res_bus != (uint32_t)-1) {
+ res_bus = tmp_res_bus & 0xFF;
+ if ((uint8_t)(res_bus + secbus) < secbus ||
+ (uint8_t)(res_bus + secbus) < res_bus) {
+ res_bus = 0;
+ }
+ if (secbus + res_bus > *pci_bus) {
+ res_bus = secbus + res_bus;
+ }
+ }
+ }
+ subbus = res_bus;
+ *pci_bus = res_bus;
+ }
+
+ qpci_config_writeb(dev, PCI_SUBORDINATE_BUS, subbus);
+ g_free(dev);
+ }
+}
+
+int qpci_secondary_buses_init(QPCIBus *bus)
+{
+ int last_bus = 0;
+
+ qpci_secondary_buses_rec(bus, 0, &last_bus);
+
+ return last_bus;
+}
+
+
void qpci_device_enable(QPCIDevice *dev)
{
uint16_t cmd;
diff --git a/tests/qtest/libqos/pci.h b/tests/qtest/libqos/pci.h
index ee64fde..becb800 100644
--- a/tests/qtest/libqos/pci.h
+++ b/tests/qtest/libqos/pci.h
@@ -81,6 +81,7 @@ void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
void *data);
QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn);
void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr);
+int qpci_secondary_buses_init(QPCIBus *bus);
bool qpci_has_buggy_msi(QPCIDevice *dev);
bool qpci_check_buggy_msi(QPCIDevice *dev);
diff --git a/tests/qtest/libqos/virtio-iommu.c b/tests/qtest/libqos/virtio-iommu.c
new file mode 100644
index 0000000..18cba4c
--- /dev/null
+++ b/tests/qtest/libqos/virtio-iommu.c
@@ -0,0 +1,126 @@
+/*
+ * libqos driver virtio-iommu-pci framework
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * Authors:
+ * Eric Auger <eric.auger@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "qgraph.h"
+#include "virtio-iommu.h"
+#include "hw/virtio/virtio-iommu.h"
+
+static QGuestAllocator *alloc;
+
+/* virtio-iommu-device */
+static void *qvirtio_iommu_get_driver(QVirtioIOMMU *v_iommu,
+ const char *interface)
+{
+ if (!g_strcmp0(interface, "virtio-iommu")) {
+ return v_iommu;
+ }
+ if (!g_strcmp0(interface, "virtio")) {
+ return v_iommu->vdev;
+ }
+
+ fprintf(stderr, "%s not present in virtio-iommu-device\n", interface);
+ g_assert_not_reached();
+}
+
+static void virtio_iommu_cleanup(QVirtioIOMMU *interface)
+{
+ qvirtqueue_cleanup(interface->vdev->bus, interface->vq, alloc);
+}
+
+static void virtio_iommu_setup(QVirtioIOMMU *interface)
+{
+ QVirtioDevice *vdev = interface->vdev;
+ uint64_t features;
+
+ features = qvirtio_get_features(vdev);
+ features &= ~(QVIRTIO_F_BAD_FEATURE |
+ (1ull << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1ull << VIRTIO_RING_F_EVENT_IDX) |
+ (1ull << VIRTIO_IOMMU_F_BYPASS));
+ qvirtio_set_features(vdev, features);
+ interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0);
+ qvirtio_set_driver_ok(interface->vdev);
+}
+
+/* virtio-iommu-pci */
+static void *qvirtio_iommu_pci_get_driver(void *object, const char *interface)
+{
+ QVirtioIOMMUPCI *v_iommu = object;
+ if (!g_strcmp0(interface, "pci-device")) {
+ return v_iommu->pci_vdev.pdev;
+ }
+ return qvirtio_iommu_get_driver(&v_iommu->iommu, interface);
+}
+
+static void qvirtio_iommu_pci_destructor(QOSGraphObject *obj)
+{
+ QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
+ QVirtioIOMMU *interface = &iommu_pci->iommu;
+ QOSGraphObject *pci_vobj = &iommu_pci->pci_vdev.obj;
+
+ virtio_iommu_cleanup(interface);
+ qvirtio_pci_destructor(pci_vobj);
+}
+
+static void qvirtio_iommu_pci_start_hw(QOSGraphObject *obj)
+{
+ QVirtioIOMMUPCI *iommu_pci = (QVirtioIOMMUPCI *) obj;
+ QVirtioIOMMU *interface = &iommu_pci->iommu;
+ QOSGraphObject *pci_vobj = &iommu_pci->pci_vdev.obj;
+
+ qvirtio_pci_start_hw(pci_vobj);
+ virtio_iommu_setup(interface);
+}
+
+
+static void *virtio_iommu_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+ void *addr)
+{
+ QVirtioIOMMUPCI *virtio_rpci = g_new0(QVirtioIOMMUPCI, 1);
+ QVirtioIOMMU *interface = &virtio_rpci->iommu;
+ QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj;
+
+ virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr);
+ interface->vdev = &virtio_rpci->pci_vdev.vdev;
+ alloc = t_alloc;
+
+ obj->get_driver = qvirtio_iommu_pci_get_driver;
+ obj->start_hw = qvirtio_iommu_pci_start_hw;
+ obj->destructor = qvirtio_iommu_pci_destructor;
+
+ return obj;
+}
+
+static void virtio_iommu_register_nodes(void)
+{
+ QPCIAddress addr = {
+ .devfn = QPCI_DEVFN(4, 0),
+ };
+
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+
+ /* virtio-iommu-pci */
+ add_qpci_address(&opts, &addr);
+ qos_node_create_driver("virtio-iommu-pci", virtio_iommu_pci_create);
+ qos_node_consumes("virtio-iommu-pci", "pci-bus", &opts);
+ qos_node_produces("virtio-iommu-pci", "pci-device");
+ qos_node_produces("virtio-iommu-pci", "virtio");
+ qos_node_produces("virtio-iommu-pci", "virtio-iommu");
+}
+
+libqos_init(virtio_iommu_register_nodes);
diff --git a/tests/qtest/libqos/virtio-iommu.h b/tests/qtest/libqos/virtio-iommu.h
new file mode 100644
index 0000000..d753761
--- /dev/null
+++ b/tests/qtest/libqos/virtio-iommu.h
@@ -0,0 +1,40 @@
+/*
+ * libqos driver virtio-iommu-pci framework
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * Authors:
+ * Eric Auger <eric.auger@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#ifndef TESTS_LIBQOS_VIRTIO_IOMMU_H
+#define TESTS_LIBQOS_VIRTIO_IOMMU_H
+
+#include "qgraph.h"
+#include "virtio.h"
+#include "virtio-pci.h"
+
+typedef struct QVirtioIOMMU QVirtioIOMMU;
+typedef struct QVirtioIOMMUPCI QVirtioIOMMUPCI;
+typedef struct QVirtioIOMMUDevice QVirtioIOMMUDevice;
+
+struct QVirtioIOMMU {
+ QVirtioDevice *vdev;
+ QVirtQueue *vq;
+};
+
+struct QVirtioIOMMUPCI {
+ QVirtioPCIDevice pci_vdev;
+ QVirtioIOMMU iommu;
+};
+
+struct QVirtioIOMMUDevice {
+ QOSGraphObject obj;
+ QVirtioIOMMU iommu;
+};
+
+#endif
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
index 25aeea3..65ed949 100644
--- a/tests/qtest/libqtest.c
+++ b/tests/qtest/libqtest.c
@@ -1321,16 +1321,29 @@ static bool qtest_is_old_versioned_machine(const char *mname)
return res;
}
-void qtest_cb_for_every_machine(void (*cb)(const char *machine),
- bool skip_old_versioned)
+struct MachInfo {
+ char *name;
+ char *alias;
+};
+
+/*
+ * Returns an array with pointers to the available machine names.
+ * The terminating entry has the name set to NULL.
+ */
+static struct MachInfo *qtest_get_machines(void)
{
+ static struct MachInfo *machines;
QDict *response, *minfo;
QList *list;
const QListEntry *p;
QObject *qobj;
QString *qstr;
- const char *mname;
QTestState *qts;
+ int idx;
+
+ if (machines) {
+ return machines;
+ }
qts = qtest_init("-machine none");
response = qtest_qmp(qts, "{ 'execute': 'query-machines' }");
@@ -1338,25 +1351,71 @@ void qtest_cb_for_every_machine(void (*cb)(const char *machine),
list = qdict_get_qlist(response, "return");
g_assert(list);
- for (p = qlist_first(list); p; p = qlist_next(p)) {
+ machines = g_new(struct MachInfo, qlist_size(list) + 1);
+
+ for (p = qlist_first(list), idx = 0; p; p = qlist_next(p), idx++) {
minfo = qobject_to(QDict, qlist_entry_obj(p));
g_assert(minfo);
+
qobj = qdict_get(minfo, "name");
g_assert(qobj);
qstr = qobject_to(QString, qobj);
g_assert(qstr);
- mname = qstring_get_str(qstr);
+ machines[idx].name = g_strdup(qstring_get_str(qstr));
+
+ qobj = qdict_get(minfo, "alias");
+ if (qobj) { /* The alias is optional */
+ qstr = qobject_to(QString, qobj);
+ g_assert(qstr);
+ machines[idx].alias = g_strdup(qstring_get_str(qstr));
+ } else {
+ machines[idx].alias = NULL;
+ }
+ }
+
+ qtest_quit(qts);
+ qobject_unref(response);
+
+ memset(&machines[idx], 0, sizeof(struct MachInfo)); /* Terminating entry */
+ return machines;
+}
+
+void qtest_cb_for_every_machine(void (*cb)(const char *machine),
+ bool skip_old_versioned)
+{
+ struct MachInfo *machines;
+ int i;
+
+ machines = qtest_get_machines();
+
+ for (i = 0; machines[i].name != NULL; i++) {
/* Ignore machines that cannot be used for qtests */
- if (!strncmp("xenfv", mname, 5) || g_str_equal("xenpv", mname)) {
+ if (!strncmp("xenfv", machines[i].name, 5) ||
+ g_str_equal("xenpv", machines[i].name)) {
continue;
}
- if (!skip_old_versioned || !qtest_is_old_versioned_machine(mname)) {
- cb(mname);
+ if (!skip_old_versioned ||
+ !qtest_is_old_versioned_machine(machines[i].name)) {
+ cb(machines[i].name);
}
}
+}
- qtest_quit(qts);
- qobject_unref(response);
+bool qtest_has_machine(const char *machine)
+{
+ struct MachInfo *machines;
+ int i;
+
+ machines = qtest_get_machines();
+
+ for (i = 0; machines[i].name != NULL; i++) {
+ if (g_str_equal(machine, machines[i].name) ||
+ (machines[i].alias && g_str_equal(machine, machines[i].alias))) {
+ return true;
+ }
+ }
+
+ return false;
}
/*
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c9d8458..acc7de5 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -68,6 +68,10 @@ qtests_i386 = \
(config_all_devices.has_key('CONFIG_RTL8139_PCI') ? ['rtl8139-test'] : []) + \
(config_all_devices.has_key('CONFIG_E1000E_PCI_EXPRESS') ? ['fuzz-e1000e-test'] : []) + \
(config_all_devices.has_key('CONFIG_ESP_PCI') ? ['am53c974-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_VIRTIO_NET') and \
+ config_all_devices.has_key('CONFIG_Q35') and \
+ config_all_devices.has_key('CONFIG_VIRTIO_PCI') and \
+ slirp.found() ? ['virtio-net-failover'] : []) + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
qtests_pci + \
['fdc-test',
@@ -134,6 +138,7 @@ qtests_ppc = \
['boot-order-test', 'prom-env-test', 'boot-serial-test'] \
qtests_ppc64 = \
+ qtests_ppc + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['device-plug-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-xscom-test'] : []) + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['rtas-test'] : []) + \
@@ -183,11 +188,10 @@ qtests_aarch64 = \
(cpu != 'arm' and unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
(config_all_devices.has_key('CONFIG_TPM_TIS_SYSBUS') ? ['tpm-tis-device-test'] : []) + \
(config_all_devices.has_key('CONFIG_TPM_TIS_SYSBUS') ? ['tpm-tis-device-swtpm-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 'fuzz-xlnx-dp-test'] : []) + \
['arm-cpu-features',
'numa-test',
'boot-serial-test',
- 'xlnx-can-test',
- 'fuzz-xlnx-dp-test',
'migration-test']
qtests_s390x = \
@@ -230,6 +234,7 @@ qos_test_ss.add(
'virtio-rng-test.c',
'virtio-scsi-test.c',
'virtio-serial-test.c',
+ 'virtio-iommu-test.c',
'vmxnet3-test.c',
)
if have_virtfs
diff --git a/tests/qtest/prom-env-test.c b/tests/qtest/prom-env-test.c
index f41d801..bdbb01d 100644
--- a/tests/qtest/prom-env-test.c
+++ b/tests/qtest/prom-env-test.c
@@ -71,9 +71,11 @@ static void add_tests(const char *machines[])
char *name;
for (i = 0; machines[i] != NULL; i++) {
- name = g_strdup_printf("prom-env/%s", machines[i]);
- qtest_add_data_func(name, machines[i], test_machine);
- g_free(name);
+ if (qtest_has_machine(machines[i])) {
+ name = g_strdup_printf("prom-env/%s", machines[i]);
+ qtest_add_data_func(name, machines[i], test_machine);
+ g_free(name);
+ }
}
}
diff --git a/tests/qtest/virtio-iommu-test.c b/tests/qtest/virtio-iommu-test.c
new file mode 100644
index 0000000..47e6838
--- /dev/null
+++ b/tests/qtest/virtio-iommu-test.c
@@ -0,0 +1,326 @@
+/*
+ * QTest testcase for VirtIO IOMMU
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * Authors:
+ * Eric Auger <eric.auger@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-iommu.h"
+#include "hw/virtio/virtio-iommu.h"
+
+#define PCI_SLOT_HP 0x06
+#define QVIRTIO_IOMMU_TIMEOUT_US (30 * 1000 * 1000)
+
+static QGuestAllocator *alloc;
+
+static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioIOMMU *v_iommu = obj;
+ QVirtioDevice *dev = v_iommu->vdev;
+ uint64_t input_range_start = qvirtio_config_readq(dev, 8);
+ uint64_t input_range_end = qvirtio_config_readq(dev, 16);
+ uint32_t domain_range_start = qvirtio_config_readl(dev, 24);
+ uint32_t domain_range_end = qvirtio_config_readl(dev, 28);
+
+ g_assert_cmpint(input_range_start, ==, 0);
+ g_assert_cmphex(input_range_end, ==, UINT64_MAX);
+ g_assert_cmpint(domain_range_start, ==, 0);
+ g_assert_cmpint(domain_range_end, ==, UINT32_MAX);
+}
+
+static int read_tail_status(struct virtio_iommu_req_tail *buffer)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ g_assert_cmpint(buffer->reserved[i], ==, 0);
+ }
+ return buffer->status;
+}
+
+/**
+ * send_attach_detach - Send an attach/detach command to the device
+ * @type: VIRTIO_IOMMU_T_ATTACH/VIRTIO_IOMMU_T_DETACH
+ * @domain: domain the endpoint is attached to
+ * @ep: endpoint
+ */
+static int send_attach_detach(QTestState *qts, QVirtioIOMMU *v_iommu,
+ uint8_t type, uint32_t domain, uint32_t ep)
+{
+ QVirtioDevice *dev = v_iommu->vdev;
+ QVirtQueue *vq = v_iommu->vq;
+ uint64_t ro_addr, wr_addr;
+ uint32_t free_head;
+ struct virtio_iommu_req_attach req = {}; /* same layout as detach */
+ size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
+ size_t wr_size = sizeof(struct virtio_iommu_req_tail);
+ struct virtio_iommu_req_tail buffer;
+ int ret;
+
+ req.head.type = type;
+ req.domain = cpu_to_le32(domain);
+ req.endpoint = cpu_to_le32(ep);
+
+ ro_addr = guest_alloc(alloc, ro_size);
+ wr_addr = guest_alloc(alloc, wr_size);
+
+ qtest_memwrite(qts, ro_addr, &req, ro_size);
+ free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
+ qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_IOMMU_TIMEOUT_US);
+ qtest_memread(qts, wr_addr, &buffer, wr_size);
+ ret = read_tail_status(&buffer);
+ guest_free(alloc, ro_addr);
+ guest_free(alloc, wr_addr);
+ return ret;
+}
+
+/**
+ * send_map - Send a map command to the device
+ * @domain: domain the new mapping is attached to
+ * @virt_start: iova start
+ * @virt_end: iova end
+ * @phys_start: base physical address
+ * @flags: mapping flags
+ */
+static int send_map(QTestState *qts, QVirtioIOMMU *v_iommu,
+ uint32_t domain, uint64_t virt_start, uint64_t virt_end,
+ uint64_t phys_start, uint32_t flags)
+{
+ QVirtioDevice *dev = v_iommu->vdev;
+ QVirtQueue *vq = v_iommu->vq;
+ uint64_t ro_addr, wr_addr;
+ uint32_t free_head;
+ struct virtio_iommu_req_map req;
+ size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
+ size_t wr_size = sizeof(struct virtio_iommu_req_tail);
+ struct virtio_iommu_req_tail buffer;
+ int ret;
+
+ req.head.type = VIRTIO_IOMMU_T_MAP;
+ req.domain = cpu_to_le32(domain);
+ req.virt_start = cpu_to_le64(virt_start);
+ req.virt_end = cpu_to_le64(virt_end);
+ req.phys_start = cpu_to_le64(phys_start);
+ req.flags = cpu_to_le32(flags);
+
+ ro_addr = guest_alloc(alloc, ro_size);
+ wr_addr = guest_alloc(alloc, wr_size);
+
+ qtest_memwrite(qts, ro_addr, &req, ro_size);
+ free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
+ qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_IOMMU_TIMEOUT_US);
+ qtest_memread(qts, wr_addr, &buffer, wr_size);
+ ret = read_tail_status(&buffer);
+ guest_free(alloc, ro_addr);
+ guest_free(alloc, wr_addr);
+ return ret;
+}
+
+/**
+ * send_unmap - Send an unmap command to the device
+ * @domain: domain the new binding is attached to
+ * @virt_start: iova start
+ * @virt_end: iova end
+ */
+static int send_unmap(QTestState *qts, QVirtioIOMMU *v_iommu,
+ uint32_t domain, uint64_t virt_start, uint64_t virt_end)
+{
+ QVirtioDevice *dev = v_iommu->vdev;
+ QVirtQueue *vq = v_iommu->vq;
+ uint64_t ro_addr, wr_addr;
+ uint32_t free_head;
+ struct virtio_iommu_req_unmap req;
+ size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
+ size_t wr_size = sizeof(struct virtio_iommu_req_tail);
+ struct virtio_iommu_req_tail buffer;
+ int ret;
+
+ req.head.type = VIRTIO_IOMMU_T_UNMAP;
+ req.domain = cpu_to_le32(domain);
+ req.virt_start = cpu_to_le64(virt_start);
+ req.virt_end = cpu_to_le64(virt_end);
+
+ ro_addr = guest_alloc(alloc, ro_size);
+ wr_addr = guest_alloc(alloc, wr_size);
+
+ qtest_memwrite(qts, ro_addr, &req, ro_size);
+ free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
+ qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_IOMMU_TIMEOUT_US);
+ qtest_memread(qts, wr_addr, &buffer, wr_size);
+ ret = read_tail_status(&buffer);
+ guest_free(alloc, ro_addr);
+ guest_free(alloc, wr_addr);
+ return ret;
+}
+
+static void test_attach_detach(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioIOMMU *v_iommu = obj;
+ QTestState *qts = global_qtest;
+ int ret;
+
+ alloc = t_alloc;
+
+ /* type, domain, ep */
+
+ /* attach ep0 to domain 0 */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 0);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* attach a non existing device */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 444);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+ /* detach a non existing device (1) */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 1);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+ /* move ep0 from domain 0 to domain 1 */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* detach ep0 from domain 0 */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 0);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
+
+ /* detach ep0 from domain 1 */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
+ g_assert_cmpint(ret, ==, 0);
+
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000,
+ VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_map(qts, v_iommu, 1, 0x2000, 0x2FFF, 0xb1000,
+ VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
+ g_assert_cmpint(ret, ==, 0);
+}
+
+/* Test map/unmap scenari documented in the spec */
+static void test_map_unmap(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioIOMMU *v_iommu = obj;
+ QTestState *qts = global_qtest;
+ int ret;
+
+ alloc = t_alloc;
+
+ /* attach ep0 to domain 1 */
+ ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
+ g_assert_cmpint(ret, ==, 0);
+
+ ret = send_map(qts, v_iommu, 0, 0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+ /* domain, virt start, virt end, phys start, flags */
+ ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* send a new mapping overlapping the previous one */
+ ret = send_map(qts, v_iommu, 1, 0, 0xFFFF, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
+
+ ret = send_unmap(qts, v_iommu, 4, 0x10, 0xFFF);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
+
+ ret = send_unmap(qts, v_iommu, 1, 0x10, 0xFFF);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE);
+
+ ret = send_unmap(qts, v_iommu, 1, 0, 0x1000);
+ g_assert_cmpint(ret, ==, 0); /* unmap everything */
+
+ /* Spec example sequence */
+
+ /* 1 */
+ ret = send_unmap(qts, v_iommu, 1, 0, 4);
+ g_assert_cmpint(ret, ==, 0); /* doesn't unmap anything */
+
+ /* 2 */
+ ret = send_map(qts, v_iommu, 1, 0, 9, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_unmap(qts, v_iommu, 1, 0, 9);
+ g_assert_cmpint(ret, ==, 0); /* unmaps [0,9] */
+
+ /* 3 */
+ ret = send_map(qts, v_iommu, 1, 0, 4, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_map(qts, v_iommu, 1, 5, 9, 0xb2000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_unmap(qts, v_iommu, 1, 0, 9);
+ g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [5,9] */
+
+ /* 4 */
+ ret = send_map(qts, v_iommu, 1, 0, 9, 0xc1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+
+ ret = send_unmap(qts, v_iommu, 1, 0, 4);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE); /* doesn't unmap anything */
+
+ ret = send_unmap(qts, v_iommu, 1, 0, 10);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* 5 */
+ ret = send_map(qts, v_iommu, 1, 0, 4, 0xd1000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_map(qts, v_iommu, 1, 5, 9, 0xd2000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_unmap(qts, v_iommu, 1, 0, 4);
+ g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
+
+ ret = send_unmap(qts, v_iommu, 1, 5, 9);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* 6 */
+ ret = send_map(qts, v_iommu, 1, 0, 4, 0xe2000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_unmap(qts, v_iommu, 1, 0, 9);
+ g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
+
+ /* 7 */
+ ret = send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_unmap(qts, v_iommu, 1, 0, 14);
+ g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
+
+ ret = send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, 0);
+ ret = send_unmap(qts, v_iommu, 1, 0, 4);
+ g_assert_cmpint(ret, ==, 0); /* only unmaps [0,4] */
+ ret = send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
+ g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL); /* 10-14 still is mapped */
+}
+
+static void register_virtio_iommu_test(void)
+{
+ qos_add_test("config", "virtio-iommu", pci_config, NULL);
+ qos_add_test("attach_detach", "virtio-iommu", test_attach_detach, NULL);
+ qos_add_test("map_unmap", "virtio-iommu", test_map_unmap, NULL);
+}
+
+libqos_init(register_virtio_iommu_test);
diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c
new file mode 100644
index 0000000..4b2ba8a
--- /dev/null
+++ b/tests/qtest/virtio-net-failover.c
@@ -0,0 +1,1352 @@
+/*
+ * QTest testcase for virtio-net failover
+ *
+ * See docs/system/virtio-net-failover.rst
+ *
+ * Copyright (c) 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "libqos/libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+#include "qapi/qmp/qjson.h"
+#include "libqos/malloc-pc.h"
+#include "libqos/virtio-pci.h"
+#include "hw/pci/pci.h"
+
+#define ACPI_PCIHP_ADDR_ICH9 0x0cc0
+#define PCI_EJ_BASE 0x0008
+#define PCI_SEL_BASE 0x0010
+
+#define BASE_MACHINE "-M q35 -nodefaults " \
+ "-device pcie-root-port,id=root0,addr=0x1,bus=pcie.0,chassis=1 " \
+ "-device pcie-root-port,id=root1,addr=0x2,bus=pcie.0,chassis=2 "
+
+#define MAC_PRIMARY0 "52:54:00:11:11:11"
+#define MAC_STANDBY0 "52:54:00:22:22:22"
+#define MAC_PRIMARY1 "52:54:00:33:33:33"
+#define MAC_STANDBY1 "52:54:00:44:44:44"
+
+static QGuestAllocator guest_malloc;
+static QPCIBus *pcibus;
+
+static QTestState *machine_start(const char *args, int numbus)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ int bus;
+
+ qts = qtest_init(args);
+
+ pc_alloc_init(&guest_malloc, qts, 0);
+ pcibus = qpci_new_pc(qts, &guest_malloc);
+ g_assert(qpci_secondary_buses_init(pcibus) == numbus);
+
+ for (bus = 1; bus <= numbus; bus++) {
+ dev = qpci_device_find(pcibus, QPCI_DEVFN(bus, 0));
+ g_assert_nonnull(dev);
+
+ qpci_device_enable(dev);
+ qpci_iomap(dev, 4, NULL);
+
+ g_free(dev);
+ }
+
+ return qts;
+}
+
+static void machine_stop(QTestState *qts)
+{
+ qpci_free_pc(pcibus);
+ alloc_destroy(&guest_malloc);
+ qtest_quit(qts);
+}
+
+static void test_error_id(void)
+{
+ QTestState *qts;
+ QDict *resp;
+ QDict *err;
+
+ qts = machine_start(BASE_MACHINE
+ "-device virtio-net,bus=root0,id=standby0,failover=on",
+ 2);
+
+ resp = qtest_qmp(qts, "{'execute': 'device_add',"
+ "'arguments': {"
+ "'driver': 'virtio-net',"
+ "'bus': 'root1',"
+ "'failover_pair_id': 'standby0'"
+ "} }");
+ g_assert(qdict_haskey(resp, "error"));
+
+ err = qdict_get_qdict(resp, "error");
+ g_assert(qdict_haskey(err, "desc"));
+
+ g_assert_cmpstr(qdict_get_str(err, "desc"), ==,
+ "Device with failover_pair_id needs to have id");
+
+ qobject_unref(resp);
+
+ machine_stop(qts);
+}
+
+static void test_error_pcie(void)
+{
+ QTestState *qts;
+ QDict *resp;
+ QDict *err;
+
+ qts = machine_start(BASE_MACHINE
+ "-device virtio-net,bus=root0,id=standby0,failover=on",
+ 2);
+
+ resp = qtest_qmp(qts, "{'execute': 'device_add',"
+ "'arguments': {"
+ "'driver': 'virtio-net',"
+ "'id': 'primary0',"
+ "'bus': 'pcie.0',"
+ "'failover_pair_id': 'standby0'"
+ "} }");
+ g_assert(qdict_haskey(resp, "error"));
+
+ err = qdict_get_qdict(resp, "error");
+ g_assert(qdict_haskey(err, "desc"));
+
+ g_assert_cmpstr(qdict_get_str(err, "desc"), ==,
+ "Bus 'pcie.0' does not support hotplugging");
+
+ qobject_unref(resp);
+
+ machine_stop(qts);
+}
+
+static QDict *find_device(QDict *bus, const char *name)
+{
+ const QObject *obj;
+ QList *devices;
+ QList *list;
+
+ devices = qdict_get_qlist(bus, "devices");
+ if (devices == NULL) {
+ return NULL;
+ }
+
+ list = qlist_copy(devices);
+ while ((obj = qlist_pop(list))) {
+ QDict *device;
+
+ device = qobject_to(QDict, obj);
+
+ if (qdict_haskey(device, "pci_bridge")) {
+ QDict *bridge;
+ QDict *bridge_device;
+
+ bridge = qdict_get_qdict(device, "pci_bridge");
+
+ if (qdict_haskey(bridge, "devices")) {
+ bridge_device = find_device(bridge, name);
+ if (bridge_device) {
+ qobject_unref(device);
+ qobject_unref(list);
+ return bridge_device;
+ }
+ }
+ }
+
+ if (!qdict_haskey(device, "qdev_id")) {
+ qobject_unref(device);
+ continue;
+ }
+
+ if (strcmp(qdict_get_str(device, "qdev_id"), name) == 0) {
+ qobject_unref(list);
+ return device;
+ }
+ qobject_unref(device);
+ }
+ qobject_unref(list);
+
+ return NULL;
+}
+
+static QDict *get_bus(QTestState *qts, int num)
+{
+ QObject *obj;
+ QDict *resp;
+ QList *ret;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-pci' }");
+ g_assert(qdict_haskey(resp, "return"));
+
+ ret = qdict_get_qlist(resp, "return");
+ g_assert_nonnull(ret);
+
+ while ((obj = qlist_pop(ret))) {
+ QDict *bus;
+
+ bus = qobject_to(QDict, obj);
+ if (!qdict_haskey(bus, "bus")) {
+ qobject_unref(bus);
+ continue;
+ }
+ if (qdict_get_int(bus, "bus") == num) {
+ qobject_unref(resp);
+ return bus;
+ }
+ qobject_ref(bus);
+ }
+ qobject_unref(resp);
+
+ return NULL;
+}
+
+static char *get_mac(QTestState *qts, const char *name)
+{
+ QDict *resp;
+ char *mac;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'qom-get', "
+ "'arguments': { "
+ "'path': %s, "
+ "'property': 'mac' } }", name);
+
+ g_assert(qdict_haskey(resp, "return"));
+
+ mac = g_strdup(qdict_get_str(resp, "return"));
+
+ qobject_unref(resp);
+
+ return mac;
+}
+
+static void check_one_card(QTestState *qts, bool present,
+ const char *id, const char *mac)
+{
+ QDict *device;
+ QDict *bus;
+ char *addr;
+
+ bus = get_bus(qts, 0);
+ device = find_device(bus, id);
+ if (present) {
+ char *path;
+
+ g_assert_nonnull(device);
+ qobject_unref(device);
+
+ path = g_strdup_printf("/machine/peripheral/%s", id);
+ addr = get_mac(qts, path);
+ g_free(path);
+ g_assert_cmpstr(mac, ==, addr);
+ g_free(addr);
+ } else {
+ g_assert_null(device);
+ }
+
+ qobject_unref(bus);
+}
+
+static void test_on(void)
+{
+ QTestState *qts;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-device virtio-net,bus=root0,id=standby0,"
+ "failover=on,netdev=hs0,mac="MAC_STANDBY0" "
+ "-device virtio-net,bus=root1,id=primary0,"
+ "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY0,
+ 2);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ machine_stop(qts);
+}
+
+static void test_on_mismatch(void)
+{
+ QTestState *qts;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-device virtio-net,bus=root0,id=standby0,"
+ "failover=on,netdev=hs0,mac="MAC_STANDBY0" "
+ "-netdev user,id=hs1 "
+ "-device virtio-net,bus=root1,id=primary0,"
+ "failover_pair_id=standby1,netdev=hs1,mac="MAC_PRIMARY0,
+ 2);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ machine_stop(qts);
+}
+
+static void test_off(void)
+{
+ QTestState *qts;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-device virtio-net,bus=root0,id=standby0,"
+ "failover=off,netdev=hs0,mac="MAC_STANDBY0" "
+ "-netdev user,id=hs1 "
+ "-device virtio-net,bus=root1,id=primary0,"
+ "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY0,
+ 2);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ machine_stop(qts);
+}
+
+static QDict *get_failover_negociated_event(QTestState *qts)
+{
+ QDict *resp;
+ QDict *data;
+
+ resp = qtest_qmp_eventwait_ref(qts, "FAILOVER_NEGOTIATED");
+ g_assert(qdict_haskey(resp, "data"));
+
+ data = qdict_get_qdict(resp, "data");
+ g_assert(qdict_haskey(data, "device-id"));
+ qobject_ref(data);
+ qobject_unref(resp);
+
+ return data;
+}
+
+static QVirtioPCIDevice *start_virtio_net(QTestState *qts, int bus, int slot,
+ const char *id)
+{
+ QVirtioPCIDevice *dev;
+ uint64_t features;
+ QPCIAddress addr;
+ QDict *resp;
+
+ addr.devfn = QPCI_DEVFN((bus << 5) + slot, 0);
+ dev = virtio_pci_new(pcibus, &addr);
+ g_assert_nonnull(dev);
+ qvirtio_pci_device_enable(dev);
+ qvirtio_start_device(&dev->vdev);
+ features = qvirtio_get_features(&dev->vdev);
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
+ (1ull << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1ull << VIRTIO_RING_F_EVENT_IDX));
+ qvirtio_set_features(&dev->vdev, features);
+ qvirtio_set_driver_ok(&dev->vdev);
+
+ resp = get_failover_negociated_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, id);
+ qobject_unref(resp);
+
+ return dev;
+}
+
+static void test_enabled(void)
+{
+ QTestState *qts;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-device virtio-net,bus=root0,id=standby0,"
+ "failover=on,netdev=hs0,mac="MAC_STANDBY0" "
+ "-netdev user,id=hs1 "
+ "-device virtio-net,bus=root1,id=primary0,"
+ "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY0" ",
+ 2);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_hotplug_1(void)
+{
+ QTestState *qts;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-device virtio-net,bus=root0,id=standby0,"
+ "failover=on,netdev=hs0,mac="MAC_STANDBY0" "
+ "-netdev user,id=hs1 ", 2);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_hotplug_1_reverse(void)
+{
+ QTestState *qts;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 "
+ "-device virtio-net,bus=root1,id=primary0,"
+ "failover_pair_id=standby0,netdev=hs1,mac="MAC_PRIMARY0" ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_hotplug_2(void)
+{
+ QTestState *qts;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_hotplug_2_reverse(void)
+{
+ QTestState *qts;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ /*
+ * XXX: sounds like a bug:
+ * The primary should be hidden until the virtio-net driver
+ * negotiates the VIRTIO_NET_F_STANDBY feature by start_virtio_net()
+ */
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static QDict *migrate_status(QTestState *qts)
+{
+ QDict *resp, *ret;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-migrate' }");
+ g_assert(qdict_haskey(resp, "return"));
+
+ ret = qdict_get_qdict(resp, "return");
+ g_assert(qdict_haskey(ret, "status"));
+ qobject_ref(ret);
+ qobject_unref(resp);
+
+ return ret;
+}
+
+static QDict *get_unplug_primary_event(QTestState *qts)
+{
+ QDict *resp;
+ QDict *data;
+
+ resp = qtest_qmp_eventwait_ref(qts, "UNPLUG_PRIMARY");
+ g_assert(qdict_haskey(resp, "data"));
+
+ data = qdict_get_qdict(resp, "data");
+ g_assert(qdict_haskey(data, "device-id"));
+ qobject_ref(data);
+ qobject_unref(resp);
+
+ return data;
+}
+
+static void test_migrate_out(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
+ const gchar *status;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* the event is sent when QEMU asks the OS to unplug the card */
+ resp = get_unplug_primary_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "primary0");
+ qobject_unref(resp);
+
+ /* wait the end of the migration setup phase */
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "wait-unplug") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+
+ /* The migration must not start if the card is not ejected */
+ g_assert_cmpstr(status, !=, "active");
+ g_assert_cmpstr(status, !=, "completed");
+ g_assert_cmpstr(status, !=, "failed");
+ g_assert_cmpstr(status, !=, "cancelling");
+ g_assert_cmpstr(status, !=, "cancelled");
+
+ qobject_unref(ret);
+ }
+
+ if (g_test_slow()) {
+ /* check we stay in wait-unplug while the card is not ejected */
+ for (int i = 0; i < 5; i++) {
+ sleep(1);
+ ret = migrate_status(qts);
+ status = qdict_get_str(ret, "status");
+ g_assert_cmpstr(status, ==, "wait-unplug");
+ qobject_unref(ret);
+ }
+ }
+
+ /* OS unplugs the cards, QEMU can move from wait-unplug state */
+ qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "completed") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+ g_assert_cmpstr(status, !=, "failed");
+ g_assert_cmpstr(status, !=, "cancelling");
+ g_assert_cmpstr(status, !=, "cancelled");
+ qobject_unref(ret);
+ }
+
+ qtest_qmp_eventwait(qts, "STOP");
+
+ /*
+ * in fact, the card is ejected from the point of view of kernel
+ * but not really from QEMU to be able to hotplug it back if
+ * migration fails. So we can't check that:
+ * check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ * check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ */
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static QDict *get_migration_event(QTestState *qts)
+{
+ QDict *resp;
+ QDict *data;
+
+ resp = qtest_qmp_eventwait_ref(qts, "MIGRATION");
+ g_assert(qdict_haskey(resp, "data"));
+
+ data = qdict_get_qdict(resp, "data");
+ g_assert(qdict_haskey(data, "status"));
+ qobject_ref(data);
+ qobject_unref(resp);
+
+ return data;
+}
+
+static void test_migrate_in(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 "
+ "-incoming defer ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
+ args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ resp = get_migration_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "status"), ==, "setup");
+ qobject_unref(resp);
+
+ resp = get_failover_negociated_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby0");
+ qobject_unref(resp);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_eventwait(qts, "RESUME");
+
+ ret = migrate_status(qts);
+ g_assert_cmpstr(qdict_get_str(ret, "status"), ==, "completed");
+ qobject_unref(ret);
+
+ machine_stop(qts);
+}
+
+static void test_migrate_abort_wait_unplug(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
+ const gchar *status;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* the event is sent when QEMU asks the OS to unplug the card */
+ resp = get_unplug_primary_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "primary0");
+ qobject_unref(resp);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate_cancel' }");
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* migration has been cancelled while the unplug was in progress */
+
+ /* while the card is not ejected, we must be in "cancelling" state */
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ g_assert_cmpstr(status, ==, "cancelling");
+ qobject_unref(ret);
+
+ /* OS unplugs the cards, QEMU can move from wait-unplug state */
+ qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "cancelled") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+ g_assert_cmpstr(status, !=, "failed");
+ g_assert_cmpstr(status, !=, "active");
+ qobject_unref(ret);
+ }
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_migrate_abort_active(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
+ const gchar *status;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* the event is sent when QEMU asks the OS to unplug the card */
+ resp = get_unplug_primary_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "primary0");
+ qobject_unref(resp);
+
+ /* OS unplugs the cards, QEMU can move from wait-unplug state */
+ qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "wait-unplug") != 0) {
+ qobject_unref(ret);
+ break;
+ }
+ g_assert_cmpstr(status, !=, "failed");
+ qobject_unref(ret);
+ }
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate_cancel' }");
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "cancelled") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+ g_assert_cmpstr(status, !=, "failed");
+ g_assert_cmpstr(status, !=, "active");
+ qobject_unref(ret);
+ }
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_migrate_abort_timeout(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
+ const gchar *status;
+ int total;
+ QVirtioPCIDevice *vdev;
+
+ qts = machine_start(BASE_MACHINE
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 ",
+ 2);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ vdev = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* the event is sent when QEMU asks the OS to unplug the card */
+ resp = get_unplug_primary_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "primary0");
+ qobject_unref(resp);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate_cancel' }");
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* migration has been cancelled while the unplug was in progress */
+
+ /* while the card is not ejected, we must be in "cancelling" state */
+
+ total = 0;
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "cancelled") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+ g_assert_cmpstr(status, ==, "cancelling");
+ g_assert(qdict_haskey(ret, "total-time"));
+ total = qdict_get_int(ret, "total-time");
+ qobject_unref(ret);
+ }
+
+ /*
+ * migration timeout in this case is 30 seconds
+ * check we exit on the timeout (ms)
+ */
+ g_assert_cmpint(total, >, 30000);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+
+ qos_object_destroy((QOSGraphObject *)vdev);
+ machine_stop(qts);
+}
+
+static void test_multi_out(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat > %s", (gchar *)opaque);
+ const gchar *status, *expected;
+ QVirtioPCIDevice *vdev0, *vdev1;
+
+ qts = machine_start(BASE_MACHINE
+ "-device pcie-root-port,id=root2,addr=0x3,bus=pcie.0,chassis=3 "
+ "-device pcie-root-port,id=root3,addr=0x4,bus=pcie.0,chassis=4 "
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 "
+ "-netdev user,id=hs2 "
+ "-netdev user,id=hs3 ",
+ 4);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ vdev0 = start_virtio_net(qts, 1, 0, "standby0");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby1",
+ "{'bus': 'root2',"
+ "'failover': 'on',"
+ "'netdev': 'hs2',"
+ "'mac': '"MAC_STANDBY1"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, true, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary1",
+ "{'bus': 'root3',"
+ "'failover_pair_id': 'standby1',"
+ "'netdev': 'hs3',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY1"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, true, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ vdev1 = start_virtio_net(qts, 3, 0, "standby1");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, true, "standby1", MAC_STANDBY1);
+ check_one_card(qts, true, "primary1", MAC_PRIMARY1);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate', 'arguments': %p}", args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /* the event is sent when QEMU asks the OS to unplug the card */
+ resp = get_unplug_primary_event(qts);
+ if (strcmp(qdict_get_str(resp, "device-id"), "primary0") == 0) {
+ expected = "primary1";
+ } else if (strcmp(qdict_get_str(resp, "device-id"), "primary1") == 0) {
+ expected = "primary0";
+ } else {
+ g_assert_not_reached();
+ }
+ qobject_unref(resp);
+
+ resp = get_unplug_primary_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, expected);
+ qobject_unref(resp);
+
+ /* wait the end of the migration setup phase */
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "wait-unplug") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+
+ /* The migration must not start if the card is not ejected */
+ g_assert_cmpstr(status, !=, "active");
+ g_assert_cmpstr(status, !=, "completed");
+ g_assert_cmpstr(status, !=, "failed");
+ g_assert_cmpstr(status, !=, "cancelling");
+ g_assert_cmpstr(status, !=, "cancelled");
+
+ qobject_unref(ret);
+ }
+
+ /* OS unplugs primary1, but we must wait the second */
+ qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+ ret = migrate_status(qts);
+ status = qdict_get_str(ret, "status");
+ g_assert_cmpstr(status, ==, "wait-unplug");
+ qobject_unref(ret);
+
+ if (g_test_slow()) {
+ /* check we stay in wait-unplug while the card is not ejected */
+ for (int i = 0; i < 5; i++) {
+ sleep(1);
+ ret = migrate_status(qts);
+ status = qdict_get_str(ret, "status");
+ g_assert_cmpstr(status, ==, "wait-unplug");
+ qobject_unref(ret);
+ }
+ }
+
+ /* OS unplugs primary0, QEMU can move from wait-unplug state */
+ qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_SEL_BASE, 2);
+ qtest_outl(qts, ACPI_PCIHP_ADDR_ICH9 + PCI_EJ_BASE, 1);
+
+ while (true) {
+ ret = migrate_status(qts);
+
+ status = qdict_get_str(ret, "status");
+ if (strcmp(status, "completed") == 0) {
+ qobject_unref(ret);
+ break;
+ }
+ g_assert_cmpstr(status, !=, "failed");
+ g_assert_cmpstr(status, !=, "cancelling");
+ g_assert_cmpstr(status, !=, "cancelled");
+ qobject_unref(ret);
+ }
+
+ qtest_qmp_eventwait(qts, "STOP");
+
+ qos_object_destroy((QOSGraphObject *)vdev0);
+ qos_object_destroy((QOSGraphObject *)vdev1);
+ machine_stop(qts);
+}
+
+static void test_multi_in(gconstpointer opaque)
+{
+ QTestState *qts;
+ QDict *resp, *args, *ret;
+ g_autofree gchar *uri = g_strdup_printf("exec: cat %s", (gchar *)opaque);
+
+ qts = machine_start(BASE_MACHINE
+ "-device pcie-root-port,id=root2,addr=0x3,bus=pcie.0,chassis=3 "
+ "-device pcie-root-port,id=root3,addr=0x4,bus=pcie.0,chassis=4 "
+ "-netdev user,id=hs0 "
+ "-netdev user,id=hs1 "
+ "-netdev user,id=hs2 "
+ "-netdev user,id=hs3 "
+ "-incoming defer ",
+ 4);
+
+ check_one_card(qts, false, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby0",
+ "{'bus': 'root0',"
+ "'failover': 'on',"
+ "'netdev': 'hs0',"
+ "'mac': '"MAC_STANDBY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary0",
+ "{'bus': 'root1',"
+ "'failover_pair_id': 'standby0',"
+ "'netdev': 'hs1',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY0"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, false, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "standby1",
+ "{'bus': 'root2',"
+ "'failover': 'on',"
+ "'netdev': 'hs2',"
+ "'mac': '"MAC_STANDBY1"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, true, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_device_add(qts, "virtio-net", "primary1",
+ "{'bus': 'root3',"
+ "'failover_pair_id': 'standby1',"
+ "'netdev': 'hs3',"
+ "'rombar': 0,"
+ "'romfile': '',"
+ "'mac': '"MAC_PRIMARY1"'}");
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, false, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, true, "standby1", MAC_STANDBY1);
+ check_one_card(qts, false, "primary1", MAC_PRIMARY1);
+
+ args = qdict_from_jsonf_nofail("{}");
+ g_assert_nonnull(args);
+ qdict_put_str(args, "uri", uri);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
+ args);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ resp = get_migration_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "status"), ==, "setup");
+ qobject_unref(resp);
+
+ resp = get_failover_negociated_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby0");
+ qobject_unref(resp);
+
+ resp = get_failover_negociated_event(qts);
+ g_assert_cmpstr(qdict_get_str(resp, "device-id"), ==, "standby1");
+ qobject_unref(resp);
+
+ check_one_card(qts, true, "standby0", MAC_STANDBY0);
+ check_one_card(qts, true, "primary0", MAC_PRIMARY0);
+ check_one_card(qts, true, "standby1", MAC_STANDBY1);
+ check_one_card(qts, true, "primary1", MAC_PRIMARY1);
+
+ qtest_qmp_eventwait(qts, "RESUME");
+
+ ret = migrate_status(qts);
+ g_assert_cmpstr(qdict_get_str(ret, "status"), ==, "completed");
+ qobject_unref(ret);
+
+ machine_stop(qts);
+}
+
+int main(int argc, char **argv)
+{
+ const gchar *tmpdir = g_get_tmp_dir();
+ gchar *tmpfile = g_strdup_printf("%s/failover_test_migrate-%u-%u",
+ tmpdir, getpid(), g_test_rand_int());
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("failover-virtio-net/params/error/id", test_error_id);
+ qtest_add_func("failover-virtio-net/params/error/pcie", test_error_pcie);
+ qtest_add_func("failover-virtio-net/params/on", test_on);
+ qtest_add_func("failover-virtio-net/params/on_mismatch",
+ test_on_mismatch);
+ qtest_add_func("failover-virtio-net/params/off", test_off);
+ qtest_add_func("failover-virtio-net/params/enabled", test_enabled);
+ qtest_add_func("failover-virtio-net/hotplug_1", test_hotplug_1);
+ qtest_add_func("failover-virtio-net/hotplug_1_reverse",
+ test_hotplug_1_reverse);
+ qtest_add_func("failover-virtio-net/hotplug_2", test_hotplug_2);
+ qtest_add_func("failover-virtio-net/hotplug_2_reverse",
+ test_hotplug_2_reverse);
+ qtest_add_data_func("failover-virtio-net/migrate/out", tmpfile,
+ test_migrate_out);
+ qtest_add_data_func("failover-virtio-net/migrate/in", tmpfile,
+ test_migrate_in);
+ qtest_add_data_func("failover-virtio-net/migrate/abort/wait-unplug",
+ tmpfile, test_migrate_abort_wait_unplug);
+ qtest_add_data_func("failover-virtio-net/migrate/abort/active", tmpfile,
+ test_migrate_abort_active);
+ if (g_test_slow()) {
+ qtest_add_data_func("failover-virtio-net/migrate/abort/timeout",
+ tmpfile, test_migrate_abort_timeout);
+ }
+ qtest_add_data_func("failover-virtio-net/multi/out",
+ tmpfile, test_multi_out);
+ qtest_add_data_func("failover-virtio-net/multi/in",
+ tmpfile, test_multi_in);
+
+ ret = g_test_run();
+
+ unlink(tmpfile);
+ g_free(tmpfile);
+
+ return ret;
+}