diff options
35 files changed, 2421 insertions, 202 deletions
diff --git a/.gitlab-ci.d/buildtest.yml b/.gitlab-ci.d/buildtest.yml index 71d0f407ad..7e1cb0b3c2 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 d273a9e713..19e6c21401 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 0000000000..4334fabf39 --- /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 b3d79bc429..871262fe0e 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 0000000000..309f7e7fb8 --- /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 48c21775f3..bb99a40ed0 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/dump/dump.c b/dump/dump.c index 662d0a62cd..a84d8b1598 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -1293,14 +1293,6 @@ static size_t get_len_buf_out(size_t page_size, uint32_t flag_compress) return 0; } -/* - * check if the page is all 0 - */ -static inline bool is_zero_page(const uint8_t *buf, size_t page_size) -{ - return buffer_is_zero(buf, page_size); -} - static void write_dump_pages(DumpState *s, Error **errp) { int ret = 0; @@ -1357,7 +1349,7 @@ static void write_dump_pages(DumpState *s, Error **errp) */ while (get_next_page(&block_iter, &pfn_iter, &buf, s)) { /* check zero page */ - if (is_zero_page(buf, s->dump_info.page_size)) { + if (buffer_is_zero(buf, s->dump_info.page_size)) { ret = write_cache(&page_desc, &pd_zero, sizeof(PageDescriptor), false); if (ret < 0) { diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events index 650e521e35..f7ad6be5fb 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 1b23e8e18c..aa9c16a17b 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 a94d350034..30691a6e57 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/include/migration/colo.h b/include/migration/colo.h index 768e1f04c3..5fbe1a6d5d 100644 --- a/include/migration/colo.h +++ b/include/migration/colo.h @@ -37,4 +37,5 @@ COLOMode get_colo_mode(void); void colo_do_failover(void); void colo_checkpoint_notify(void *opaque); +void colo_shutdown(void); #endif diff --git a/meson.build b/meson.build index 96de1a6ef9..ae67ca28ab 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 e392323732..4114bfcaa4 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/migration/colo.c b/migration/colo.c index 2415325262..5f7071b3cd 100644 --- a/migration/colo.c +++ b/migration/colo.c @@ -530,7 +530,6 @@ static void colo_process_checkpoint(MigrationState *s) { QIOChannelBuffer *bioc; QEMUFile *fb = NULL; - int64_t current_time = qemu_clock_get_ms(QEMU_CLOCK_HOST); Error *local_err = NULL; int ret; @@ -578,8 +577,8 @@ static void colo_process_checkpoint(MigrationState *s) qemu_mutex_unlock_iothread(); trace_colo_vm_state_change("stop", "run"); - timer_mod(s->colo_delay_timer, - current_time + s->parameters.x_checkpoint_delay); + timer_mod(s->colo_delay_timer, qemu_clock_get_ms(QEMU_CLOCK_HOST) + + s->parameters.x_checkpoint_delay); while (s->state == MIGRATION_STATUS_COLO) { if (failover_get_state() != FAILOVER_STATUS_NONE) { @@ -667,8 +666,6 @@ void migrate_start_colo_process(MigrationState *s) colo_checkpoint_notify, s); qemu_sem_init(&s->colo_exit_sem, 0); - migrate_set_state(&s->state, MIGRATION_STATUS_ACTIVE, - MIGRATION_STATUS_COLO); colo_process_checkpoint(s); qemu_mutex_lock_iothread(); } @@ -683,8 +680,8 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis, qemu_mutex_lock_iothread(); vm_stop_force_state(RUN_STATE_COLO); - trace_colo_vm_state_change("run", "stop"); qemu_mutex_unlock_iothread(); + trace_colo_vm_state_change("run", "stop"); /* FIXME: This is unnecessary for periodic checkpoint mode */ colo_send_message(mis->to_src_file, COLO_MESSAGE_CHECKPOINT_REPLY, @@ -786,8 +783,8 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis, vmstate_loading = false; vm_start(); - trace_colo_vm_state_change("stop", "run"); qemu_mutex_unlock_iothread(); + trace_colo_vm_state_change("stop", "run"); if (failover_get_state() == FAILOVER_STATUS_RELAUNCH) { return; @@ -820,6 +817,26 @@ static void colo_wait_handle_message(MigrationIncomingState *mis, } } +void colo_shutdown(void) +{ + MigrationIncomingState *mis = NULL; + MigrationState *s = NULL; + + switch (get_colo_mode()) { + case COLO_MODE_PRIMARY: + s = migrate_get_current(); + qemu_event_set(&s->colo_checkpoint_event); + qemu_sem_post(&s->colo_exit_sem); + break; + case COLO_MODE_SECONDARY: + mis = migration_incoming_get_current(); + qemu_sem_post(&mis->colo_incoming_sem); + break; + default: + break; + } +} + void *colo_process_incoming_thread(void *opaque) { MigrationIncomingState *mis = opaque; @@ -870,8 +887,8 @@ void *colo_process_incoming_thread(void *opaque) abort(); #endif vm_start(); - trace_colo_vm_state_change("stop", "run"); qemu_mutex_unlock_iothread(); + trace_colo_vm_state_change("stop", "run"); colo_send_message(mis->to_src_file, COLO_MESSAGE_CHECKPOINT_READY, &local_err); diff --git a/migration/migration.c b/migration/migration.c index abaf6f9e3d..3de11ae921 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -226,6 +226,12 @@ void migration_cancel(const Error *error) void migration_shutdown(void) { /* + * When the QEMU main thread exit, the COLO thread + * may wait a semaphore. So, we should wakeup the + * COLO thread before migration shutdown. + */ + colo_shutdown(); + /* * Cancel the current migration - that will (eventually) * stop the migration using this structure */ @@ -990,6 +996,8 @@ static void populate_time_info(MigrationInfo *info, MigrationState *s) static void populate_ram_info(MigrationInfo *info, MigrationState *s) { + size_t page_size = qemu_target_page_size(); + info->has_ram = true; info->ram = g_malloc0(sizeof(*info->ram)); info->ram->transferred = ram_counters.transferred; @@ -998,12 +1006,11 @@ static void populate_ram_info(MigrationInfo *info, MigrationState *s) /* legacy value. It is not used anymore */ info->ram->skipped = 0; info->ram->normal = ram_counters.normal; - info->ram->normal_bytes = ram_counters.normal * - qemu_target_page_size(); + info->ram->normal_bytes = ram_counters.normal * page_size; info->ram->mbps = s->mbps; info->ram->dirty_sync_count = ram_counters.dirty_sync_count; info->ram->postcopy_requests = ram_counters.postcopy_requests; - info->ram->page_size = qemu_target_page_size(); + info->ram->page_size = page_size; info->ram->multifd_bytes = ram_counters.multifd_bytes; info->ram->pages_per_second = s->pages_per_second; @@ -3607,12 +3614,7 @@ static void migration_iteration_finish(MigrationState *s) migration_calculate_complete(s); runstate_set(RUN_STATE_POSTMIGRATE); break; - - case MIGRATION_STATUS_ACTIVE: - /* - * We should really assert here, but since it's during - * migration, let's try to reduce the usage of assertions. - */ + case MIGRATION_STATUS_COLO: if (!migrate_colo_enabled()) { error_report("%s: critical error: calling COLO code without " "COLO enabled", __func__); @@ -3622,6 +3624,12 @@ static void migration_iteration_finish(MigrationState *s) * Fixme: we will run VM in COLO no matter its old running state. * After exited COLO, we will keep running. */ + /* Fallthrough */ + case MIGRATION_STATUS_ACTIVE: + /* + * We should really assert here, but since it's during + * migration, let's try to reduce the usage of assertions. + */ s->vm_was_running = true; /* Fallthrough */ case MIGRATION_STATUS_FAILED: diff --git a/migration/multifd-zlib.c b/migration/multifd-zlib.c index ab4ba75d75..da6201704c 100644 --- a/migration/multifd-zlib.c +++ b/migration/multifd-zlib.c @@ -13,6 +13,7 @@ #include "qemu/osdep.h" #include <zlib.h> #include "qemu/rcu.h" +#include "exec/ramblock.h" #include "exec/target_page.h" #include "qapi/error.h" #include "migration.h" @@ -42,7 +43,6 @@ struct zlib_data { */ static int zlib_send_setup(MultiFDSendParams *p, Error **errp) { - uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size(); struct zlib_data *z = g_malloc0(sizeof(struct zlib_data)); z_stream *zs = &z->zs; @@ -54,9 +54,8 @@ static int zlib_send_setup(MultiFDSendParams *p, Error **errp) error_setg(errp, "multifd %d: deflate init failed", p->id); return -1; } - /* We will never have more than page_count pages */ - z->zbuff_len = page_count * qemu_target_page_size(); - z->zbuff_len *= 2; + /* To be safe, we reserve twice the size of the packet */ + z->zbuff_len = MULTIFD_PACKET_SIZE * 2; z->zbuff = g_try_malloc(z->zbuff_len); if (!z->zbuff) { deflateEnd(&z->zs); @@ -74,6 +73,7 @@ static int zlib_send_setup(MultiFDSendParams *p, Error **errp) * Close the channel and return memory. * * @p: Params for the channel that we are using + * @errp: pointer to an error */ static void zlib_send_cleanup(MultiFDSendParams *p, Error **errp) { @@ -95,27 +95,27 @@ static void zlib_send_cleanup(MultiFDSendParams *p, Error **errp) * Returns 0 for success or -1 for error * * @p: Params for the channel that we are using - * @used: number of pages used + * @errp: pointer to an error */ -static int zlib_send_prepare(MultiFDSendParams *p, uint32_t used, Error **errp) +static int zlib_send_prepare(MultiFDSendParams *p, Error **errp) { - struct iovec *iov = p->pages->iov; struct zlib_data *z = p->data; + size_t page_size = qemu_target_page_size(); z_stream *zs = &z->zs; uint32_t out_size = 0; int ret; uint32_t i; - for (i = 0; i < used; i++) { + for (i = 0; i < p->pages->num; i++) { uint32_t available = z->zbuff_len - out_size; int flush = Z_NO_FLUSH; - if (i == used - 1) { + if (i == p->pages->num - 1) { flush = Z_SYNC_FLUSH; } - zs->avail_in = iov[i].iov_len; - zs->next_in = iov[i].iov_base; + zs->avail_in = page_size; + zs->next_in = p->pages->block->host + p->pages->offset[i]; zs->avail_out = available; zs->next_out = z->zbuff + out_size; @@ -180,7 +180,6 @@ static int zlib_send_write(MultiFDSendParams *p, uint32_t used, Error **errp) */ static int zlib_recv_setup(MultiFDRecvParams *p, Error **errp) { - uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size(); struct zlib_data *z = g_malloc0(sizeof(struct zlib_data)); z_stream *zs = &z->zs; @@ -194,10 +193,8 @@ static int zlib_recv_setup(MultiFDRecvParams *p, Error **errp) error_setg(errp, "multifd %d: inflate init failed", p->id); return -1; } - /* We will never have more than page_count pages */ - z->zbuff_len = page_count * qemu_target_page_size(); - /* We know compression "could" use more space */ - z->zbuff_len *= 2; + /* To be safe, we reserve twice the size of the packet */ + z->zbuff_len = MULTIFD_PACKET_SIZE * 2; z->zbuff = g_try_malloc(z->zbuff_len); if (!z->zbuff) { inflateEnd(zs); @@ -234,17 +231,17 @@ static void zlib_recv_cleanup(MultiFDRecvParams *p) * Returns 0 for success or -1 for error * * @p: Params for the channel that we are using - * @used: number of pages used * @errp: pointer to an error */ -static int zlib_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) +static int zlib_recv_pages(MultiFDRecvParams *p, Error **errp) { struct zlib_data *z = p->data; + size_t page_size = qemu_target_page_size(); z_stream *zs = &z->zs; uint32_t in_size = p->next_packet_size; /* we measure the change of total_out */ uint32_t out_size = zs->total_out; - uint32_t expected_size = used * qemu_target_page_size(); + uint32_t expected_size = p->pages->num * qemu_target_page_size(); uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK; int ret; int i; @@ -263,17 +260,16 @@ static int zlib_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) zs->avail_in = in_size; zs->next_in = z->zbuff; - for (i = 0; i < used; i++) { - struct iovec *iov = &p->pages->iov[i]; + for (i = 0; i < p->pages->num; i++) { int flush = Z_NO_FLUSH; unsigned long start = zs->total_out; - if (i == used - 1) { + if (i == p->pages->num - 1) { flush = Z_SYNC_FLUSH; } - zs->avail_out = iov->iov_len; - zs->next_out = iov->iov_base; + zs->avail_out = page_size; + zs->next_out = p->pages->block->host + p->pages->offset[i]; /* * Welcome to inflate semantics @@ -286,8 +282,8 @@ static int zlib_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) do { ret = inflate(zs, flush); } while (ret == Z_OK && zs->avail_in - && (zs->total_out - start) < iov->iov_len); - if (ret == Z_OK && (zs->total_out - start) < iov->iov_len) { + && (zs->total_out - start) < page_size); + if (ret == Z_OK && (zs->total_out - start) < page_size) { error_setg(errp, "multifd %d: inflate generated too few output", p->id); return -1; diff --git a/migration/multifd-zstd.c b/migration/multifd-zstd.c index 693bddf8c9..2d5b61106c 100644 --- a/migration/multifd-zstd.c +++ b/migration/multifd-zstd.c @@ -13,6 +13,7 @@ #include "qemu/osdep.h" #include <zstd.h> #include "qemu/rcu.h" +#include "exec/ramblock.h" #include "exec/target_page.h" #include "qapi/error.h" #include "migration.h" @@ -47,7 +48,6 @@ struct zstd_data { */ static int zstd_send_setup(MultiFDSendParams *p, Error **errp) { - uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size(); struct zstd_data *z = g_new0(struct zstd_data, 1); int res; @@ -67,9 +67,8 @@ static int zstd_send_setup(MultiFDSendParams *p, Error **errp) p->id, ZSTD_getErrorName(res)); return -1; } - /* We will never have more than page_count pages */ - z->zbuff_len = page_count * qemu_target_page_size(); - z->zbuff_len *= 2; + /* To be safe, we reserve twice the size of the packet */ + z->zbuff_len = MULTIFD_PACKET_SIZE * 2; z->zbuff = g_try_malloc(z->zbuff_len); if (!z->zbuff) { ZSTD_freeCStream(z->zcs); @@ -86,6 +85,7 @@ static int zstd_send_setup(MultiFDSendParams *p, Error **errp) * Close the channel and return memory. * * @p: Params for the channel that we are using + * @errp: pointer to an error */ static void zstd_send_cleanup(MultiFDSendParams *p, Error **errp) { @@ -108,12 +108,12 @@ static void zstd_send_cleanup(MultiFDSendParams *p, Error **errp) * Returns 0 for success or -1 for error * * @p: Params for the channel that we are using - * @used: number of pages used + * @errp: pointer to an error */ -static int zstd_send_prepare(MultiFDSendParams *p, uint32_t used, Error **errp) +static int zstd_send_prepare(MultiFDSendParams *p, Error **errp) { - struct iovec *iov = p->pages->iov; struct zstd_data *z = p->data; + size_t page_size = qemu_target_page_size(); int ret; uint32_t i; @@ -121,14 +121,14 @@ static int zstd_send_prepare(MultiFDSendParams *p, uint32_t used, Error **errp) z->out.size = z->zbuff_len; z->out.pos = 0; - for (i = 0; i < used; i++) { + for (i = 0; i < p->pages->num; i++) { ZSTD_EndDirective flush = ZSTD_e_continue; - if (i == used - 1) { + if (i == p->pages->num - 1) { flush = ZSTD_e_flush; } - z->in.src = iov[i].iov_base; - z->in.size = iov[i].iov_len; + z->in.src = p->pages->block->host + p->pages->offset[i]; + z->in.size = page_size; z->in.pos = 0; /* @@ -191,7 +191,6 @@ static int zstd_send_write(MultiFDSendParams *p, uint32_t used, Error **errp) */ static int zstd_recv_setup(MultiFDRecvParams *p, Error **errp) { - uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size(); struct zstd_data *z = g_new0(struct zstd_data, 1); int ret; @@ -212,10 +211,8 @@ static int zstd_recv_setup(MultiFDRecvParams *p, Error **errp) return -1; } - /* We will never have more than page_count pages */ - z->zbuff_len = page_count * qemu_target_page_size(); - /* We know compression "could" use more space */ - z->zbuff_len *= 2; + /* To be safe, we reserve twice the size of the packet */ + z->zbuff_len = MULTIFD_PACKET_SIZE * 2; z->zbuff = g_try_malloc(z->zbuff_len); if (!z->zbuff) { ZSTD_freeDStream(z->zds); @@ -254,14 +251,14 @@ static void zstd_recv_cleanup(MultiFDRecvParams *p) * Returns 0 for success or -1 for error * * @p: Params for the channel that we are using - * @used: number of pages used * @errp: pointer to an error */ -static int zstd_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) +static int zstd_recv_pages(MultiFDRecvParams *p, Error **errp) { uint32_t in_size = p->next_packet_size; uint32_t out_size = 0; - uint32_t expected_size = used * qemu_target_page_size(); + size_t page_size = qemu_target_page_size(); + uint32_t expected_size = p->pages->num * page_size; uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK; struct zstd_data *z = p->data; int ret; @@ -282,11 +279,9 @@ static int zstd_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) z->in.size = in_size; z->in.pos = 0; - for (i = 0; i < used; i++) { - struct iovec *iov = &p->pages->iov[i]; - - z->out.dst = iov->iov_base; - z->out.size = iov->iov_len; + for (i = 0; i < p->pages->num; i++) { + z->out.dst = p->pages->block->host + p->pages->offset[i]; + z->out.size = page_size; z->out.pos = 0; /* @@ -300,8 +295,8 @@ static int zstd_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) do { ret = ZSTD_decompressStream(z->zds, &z->out, &z->in); } while (ret > 0 && (z->in.size - z->in.pos > 0) - && (z->out.pos < iov->iov_len)); - if (ret > 0 && (z->out.pos < iov->iov_len)) { + && (z->out.pos < page_size)); + if (ret > 0 && (z->out.pos < page_size)) { error_setg(errp, "multifd %d: decompressStream buffer too small", p->id); return -1; diff --git a/migration/multifd.c b/migration/multifd.c index 7c9deb1921..3242f688e5 100644 --- a/migration/multifd.c +++ b/migration/multifd.c @@ -66,6 +66,7 @@ static int nocomp_send_setup(MultiFDSendParams *p, Error **errp) * For no compression this function does nothing. * * @p: Params for the channel that we are using + * @errp: pointer to an error */ static void nocomp_send_cleanup(MultiFDSendParams *p, Error **errp) { @@ -81,13 +82,11 @@ static void nocomp_send_cleanup(MultiFDSendParams *p, Error **errp) * Returns 0 for success or -1 for error * * @p: Params for the channel that we are using - * @used: number of pages used * @errp: pointer to an error */ -static int nocomp_send_prepare(MultiFDSendParams *p, uint32_t used, - Error **errp) +static int nocomp_send_prepare(MultiFDSendParams *p, Error **errp) { - p->next_packet_size = used * qemu_target_page_size(); + p->next_packet_size = p->pages->num * qemu_target_page_size(); p->flags |= MULTIFD_FLAG_NOCOMP; return 0; } @@ -142,10 +141,9 @@ static void nocomp_recv_cleanup(MultiFDRecvParams *p) * Returns 0 for success or -1 for error * * @p: Params for the channel that we are using - * @used: number of pages used * @errp: pointer to an error */ -static int nocomp_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) +static int nocomp_recv_pages(MultiFDRecvParams *p, Error **errp) { uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK; @@ -154,7 +152,7 @@ static int nocomp_recv_pages(MultiFDRecvParams *p, uint32_t used, Error **errp) p->id, flags, MULTIFD_FLAG_NOCOMP); return -1; } - return qio_channel_readv_all(p->c, p->pages->iov, used, errp); + return qio_channel_readv_all(p->c, p->pages->iov, p->pages->num, errp); } static MultiFDMethods multifd_nocomp_ops = { @@ -252,7 +250,7 @@ static MultiFDPages_t *multifd_pages_init(size_t size) static void multifd_pages_clear(MultiFDPages_t *pages) { - pages->used = 0; + pages->num = 0; pages->allocated = 0; pages->packet_num = 0; pages->block = NULL; @@ -270,7 +268,7 @@ static void multifd_send_fill_packet(MultiFDSendParams *p) packet->flags = cpu_to_be32(p->flags); packet->pages_alloc = cpu_to_be32(p->pages->allocated); - packet->pages_used = cpu_to_be32(p->pages->used); + packet->pages_used = cpu_to_be32(p->pages->num); packet->next_packet_size = cpu_to_be32(p->next_packet_size); packet->packet_num = cpu_to_be64(p->packet_num); @@ -278,7 +276,7 @@ static void multifd_send_fill_packet(MultiFDSendParams *p) strncpy(packet->ramblock, p->pages->block->idstr, 256); } - for (i = 0; i < p->pages->used; i++) { + for (i = 0; i < p->pages->num; i++) { /* there are architectures where ram_addr_t is 32 bit */ uint64_t temp = p->pages->offset[i]; @@ -289,7 +287,8 @@ static void multifd_send_fill_packet(MultiFDSendParams *p) static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) { MultiFDPacket_t *packet = p->packet; - uint32_t pages_max = MULTIFD_PACKET_SIZE / qemu_target_page_size(); + size_t page_size = qemu_target_page_size(); + uint32_t pages_max = MULTIFD_PACKET_SIZE / page_size; RAMBlock *block; int i; @@ -331,18 +330,18 @@ static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) p->pages = multifd_pages_init(packet->pages_alloc); } - p->pages->used = be32_to_cpu(packet->pages_used); - if (p->pages->used > packet->pages_alloc) { + p->pages->num = be32_to_cpu(packet->pages_used); + if (p->pages->num > packet->pages_alloc) { error_setg(errp, "multifd: received packet " "with %d pages and expected maximum pages are %d", - p->pages->used, packet->pages_alloc) ; + p->pages->num, packet->pages_alloc) ; return -1; } p->next_packet_size = be32_to_cpu(packet->next_packet_size); p->packet_num = be64_to_cpu(packet->packet_num); - if (p->pages->used == 0) { + if (p->pages->num == 0) { return 0; } @@ -355,17 +354,19 @@ static int multifd_recv_unfill_packet(MultiFDRecvParams *p, Error **errp) return -1; } - for (i = 0; i < p->pages->used; i++) { + p->pages->block = block; + for (i = 0; i < p->pages->num; i++) { uint64_t offset = be64_to_cpu(packet->offset[i]); - if (offset > (block->used_length - qemu_target_page_size())) { + if (offset > (block->used_length - page_size)) { error_setg(errp, "multifd: offset too long %" PRIu64 " (max " RAM_ADDR_FMT ")", offset, block->used_length); return -1; } + p->pages->offset[i] = offset; p->pages->iov[i].iov_base = block->host + offset; - p->pages->iov[i].iov_len = qemu_target_page_size(); + p->pages->iov[i].iov_len = page_size; } return 0; @@ -442,13 +443,13 @@ static int multifd_send_pages(QEMUFile *f) } qemu_mutex_unlock(&p->mutex); } - assert(!p->pages->used); + assert(!p->pages->num); assert(!p->pages->block); p->packet_num = multifd_send_state->packet_num++; multifd_send_state->pages = p->pages; p->pages = pages; - transferred = ((uint64_t) pages->used) * qemu_target_page_size() + transferred = ((uint64_t) pages->num) * qemu_target_page_size() + p->packet_len; qemu_file_update_transfer(f, transferred); ram_counters.multifd_bytes += transferred; @@ -468,12 +469,12 @@ int multifd_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset) } if (pages->block == block) { - pages->offset[pages->used] = offset; - pages->iov[pages->used].iov_base = block->host + offset; - pages->iov[pages->used].iov_len = qemu_target_page_size(); - pages->used++; + pages->offset[pages->num] = offset; + pages->iov[pages->num].iov_base = block->host + offset; + pages->iov[pages->num].iov_len = qemu_target_page_size(); + pages->num++; - if (pages->used < pages->allocated) { + if (pages->num < pages->allocated) { return 1; } } @@ -523,6 +524,9 @@ static void multifd_send_terminate_threads(Error *err) qemu_mutex_lock(&p->mutex); p->quit = true; qemu_sem_post(&p->sem); + if (p->c) { + qio_channel_shutdown(p->c, QIO_CHANNEL_SHUTDOWN_BOTH, NULL); + } qemu_mutex_unlock(&p->mutex); } } @@ -585,7 +589,7 @@ void multifd_send_sync_main(QEMUFile *f) if (!migrate_use_multifd()) { return; } - if (multifd_send_state->pages->used) { + if (multifd_send_state->pages->num) { if (multifd_send_pages(f) < 0) { error_report("%s: multifd_send_pages fail", __func__); return; @@ -627,7 +631,6 @@ static void *multifd_send_thread(void *opaque) MultiFDSendParams *p = opaque; Error *local_err = NULL; int ret = 0; - uint32_t flags = 0; trace_multifd_send_thread_start(p->id); rcu_register_thread(); @@ -648,13 +651,12 @@ static void *multifd_send_thread(void *opaque) qemu_mutex_lock(&p->mutex); if (p->pending_job) { - uint32_t used = p->pages->used; + uint32_t used = p->pages->num; uint64_t packet_num = p->packet_num; - flags = p->flags; + uint32_t flags = p->flags; if (used) { - ret = multifd_send_state->ops->send_prepare(p, used, - &local_err); + ret = multifd_send_state->ops->send_prepare(p, &local_err); if (ret != 0) { qemu_mutex_unlock(&p->mutex); break; @@ -664,7 +666,7 @@ static void *multifd_send_thread(void *opaque) p->flags = 0; p->num_packets++; p->num_pages += used; - p->pages->used = 0; + p->pages->num = 0; p->pages->block = NULL; qemu_mutex_unlock(&p->mutex); @@ -1090,7 +1092,7 @@ static void *multifd_recv_thread(void *opaque) break; } - used = p->pages->used; + used = p->pages->num; flags = p->flags; /* recv methods don't know how to handle the SYNC flag */ p->flags &= ~MULTIFD_FLAG_SYNC; @@ -1101,7 +1103,7 @@ static void *multifd_recv_thread(void *opaque) qemu_mutex_unlock(&p->mutex); if (used) { - ret = multifd_recv_state->ops->recv_pages(p, used, &local_err); + ret = multifd_recv_state->ops->recv_pages(p, &local_err); if (ret != 0) { break; } diff --git a/migration/multifd.h b/migration/multifd.h index 15c50ca0b2..e57adc783b 100644 --- a/migration/multifd.h +++ b/migration/multifd.h @@ -55,7 +55,7 @@ typedef struct { typedef struct { /* number of used pages */ - uint32_t used; + uint32_t num; /* number of allocated pages */ uint32_t allocated; /* global number of generated multifd packets */ @@ -159,7 +159,7 @@ typedef struct { /* Cleanup for sending side */ void (*send_cleanup)(MultiFDSendParams *p, Error **errp); /* Prepare the send packet */ - int (*send_prepare)(MultiFDSendParams *p, uint32_t used, Error **errp); + int (*send_prepare)(MultiFDSendParams *p, Error **errp); /* Write the send packet */ int (*send_write)(MultiFDSendParams *p, uint32_t used, Error **errp); /* Setup for receiving side */ @@ -167,7 +167,7 @@ typedef struct { /* Cleanup for receiving side */ void (*recv_cleanup)(MultiFDRecvParams *p); /* Read all pages */ - int (*recv_pages)(MultiFDRecvParams *p, uint32_t used, Error **errp); + int (*recv_pages)(MultiFDRecvParams *p, Error **errp); } MultiFDMethods; void multifd_register_ops(int method, MultiFDMethods *ops); diff --git a/migration/ram.c b/migration/ram.c index 863035d235..57efa67f20 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -81,11 +81,6 @@ /* 0x80 is reserved in migration.h start with 0x100 next */ #define RAM_SAVE_FLAG_COMPRESS_PAGE 0x100 -static inline bool is_zero_range(uint8_t *p, uint64_t size) -{ - return buffer_is_zero(p, size); -} - XBZRLECacheStats xbzrle_counters; /* struct contains XBZRLE cache and a static page @@ -1180,7 +1175,7 @@ static int save_zero_page_to_file(RAMState *rs, QEMUFile *file, uint8_t *p = block->host + offset; int len = 0; - if (is_zero_range(p, TARGET_PAGE_SIZE)) { + if (buffer_is_zero(p, TARGET_PAGE_SIZE)) { len += save_page_header(rs, file, block, offset | RAM_SAVE_FLAG_ZERO); qemu_put_byte(file, 0); len += 1; @@ -3367,7 +3362,7 @@ static inline void *colo_cache_from_block_offset(RAMBlock *block, */ void ram_handle_compressed(void *host, uint8_t ch, uint64_t size) { - if (ch != 0 || !is_zero_range(host, size)) { + if (ch != 0 || !buffer_is_zero(host, size)) { memset(host, ch, size); } } @@ -3918,7 +3913,6 @@ void colo_flush_ram_cache(void) unsigned long offset = 0; memory_global_dirty_log_sync(); - qemu_mutex_lock(&ram_state->bitmap_mutex); WITH_RCU_READ_LOCK_GUARD() { RAMBLOCK_FOREACH_NOT_IGNORED(block) { ramblock_sync_dirty_bitmap(ram_state, block); @@ -3954,7 +3948,6 @@ void colo_flush_ram_cache(void) } } trace_colo_flush_ram_cache_end(); - qemu_mutex_unlock(&ram_state->bitmap_mutex); } /** diff --git a/migration/savevm.c b/migration/savevm.c index d59e976d50..0bef031acb 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -1685,6 +1685,7 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis, { PostcopyState ps = postcopy_state_set(POSTCOPY_INCOMING_ADVISE); uint64_t remote_pagesize_summary, local_pagesize_summary, remote_tps; + size_t page_size = qemu_target_page_size(); Error *local_err = NULL; trace_loadvm_postcopy_handle_advise(); @@ -1741,13 +1742,13 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis, } remote_tps = qemu_get_be64(mis->from_src_file); - if (remote_tps != qemu_target_page_size()) { + if (remote_tps != page_size) { /* * Again, some differences could be dealt with, but for now keep it * simple. */ error_report("Postcopy needs matching target page sizes (s=%d d=%zd)", - (int)remote_tps, qemu_target_page_size()); + (int)remote_tps, page_size); return -1; } diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 7a17ff4218..ae8f18edc2 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 83828ba270..4d8e1343bd 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 5af944a5fb..c1fcac5c45 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 59e9271195..dff6b31cf0 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 4af1f04787..e988d15791 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 e1e96189c8..3a9076ae58 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 ee64fdecbd..becb800f9e 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 0000000000..18cba4ca36 --- /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 0000000000..d753761958 --- /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 25aeea385b..65ed949685 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 c9d8458062..acc7de5067 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 f41d80154a..bdbb01d8e5 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 0000000000..47e68388a0 --- /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 0000000000..4b2ba8a106 --- /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; +} |