From efe84f03ea88db49ae9180a9ffe4634349333c4e Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Wed, 8 Dec 2021 14:03:47 +0100 Subject: [PATCH 01/16] qtest/libqos: add a function to initialize secondary PCI buses Scan the PCI devices to find bridge and set PCI_SECONDARY_BUS and PCI_SUBORDINATE_BUS (algorithm from seabios) Signed-off-by: Laurent Vivier Acked-by: Thomas Huth Message-Id: <20211208130350.10178-2-lvivier@redhat.com> Signed-off-by: Thomas Huth --- include/hw/pci/pci_bridge.h | 8 +++ tests/qtest/libqos/pci.c | 119 ++++++++++++++++++++++++++++++++++++ tests/qtest/libqos/pci.h | 1 + 3 files changed, 128 insertions(+) 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/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); From e32b96b559cefb8cbb7038e913b94567e1a18045 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Wed, 8 Dec 2021 14:03:48 +0100 Subject: [PATCH 02/16] tests/qtest: add some tests for virtio-net failover Add test cases to test several error cases that must be generated by invalid failover configuration. Add a combination of coldplug and hotplug test cases to be sure the primary is correctly managed according the presence or not of the STANDBY feature. Signed-off-by: Laurent Vivier Message-Id: <20211208130350.10178-3-lvivier@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/meson.build | 4 + tests/qtest/virtio-net-failover.c | 788 ++++++++++++++++++++++++++++++ 2 files changed, 792 insertions(+) create mode 100644 tests/qtest/virtio-net-failover.c diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index c9d8458062..975a0f2f5f 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', diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c new file mode 100644 index 0000000000..fd7821deaf --- /dev/null +++ b/tests/qtest/virtio-net-failover.c @@ -0,0 +1,788 @@ +/* + * 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 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" + +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); +} + +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); + + ret = g_test_run(); + + unlink(tmpfile); + g_free(tmpfile); + + return ret; +} From 1e2077e22332c4d2867043bbea2337d331106bfd Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Wed, 8 Dec 2021 14:03:49 +0100 Subject: [PATCH 03/16] tests/libqtest: add some virtio-net failover migration cancelling tests Add some tests to check the state of the machine if the migration is cancelled while we are using virtio-net failover. Signed-off-by: Laurent Vivier Acked-by: Thomas Huth Message-Id: <20211208130350.10178-4-lvivier@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/virtio-net-failover.c | 282 ++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c index fd7821deaf..e998a546b0 100644 --- a/tests/qtest/virtio-net-failover.c +++ b/tests/qtest/virtio-net-failover.c @@ -752,6 +752,280 @@ static void test_migrate_in(gconstpointer opaque) 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); +} + int main(int argc, char **argv) { const gchar *tmpdir = g_get_tmp_dir(); @@ -778,6 +1052,14 @@ int main(int argc, char **argv) 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); + } ret = g_test_run(); From e1e3d3211815c407cacb845fc0f6c2c328b2e684 Mon Sep 17 00:00:00 2001 From: Laurent Vivier Date: Wed, 8 Dec 2021 14:03:50 +0100 Subject: [PATCH 04/16] tests/libqtest: add a migration test with two couples of failover devices Signed-off-by: Laurent Vivier Acked-by: Thomas Huth Message-Id: <20211208130350.10178-5-lvivier@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/virtio-net-failover.c | 282 ++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) diff --git a/tests/qtest/virtio-net-failover.c b/tests/qtest/virtio-net-failover.c index e998a546b0..4b2ba8a106 100644 --- a/tests/qtest/virtio-net-failover.c +++ b/tests/qtest/virtio-net-failover.c @@ -20,6 +20,7 @@ #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 " \ @@ -27,6 +28,8 @@ #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; @@ -1026,6 +1029,281 @@ static void test_migrate_abort_timeout(gconstpointer opaque) 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(); @@ -1060,6 +1338,10 @@ int main(int argc, char **argv) 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(); From a9697d0947798ec9cc8387308e1d353123639ec3 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 1 Dec 2021 11:43:44 +0100 Subject: [PATCH 05/16] tests/qtest: Run the PPC 32-bit tests with the 64-bit target binary, too The ppc64 target is a superset of the 32-bit target, so we should include the tests here, too. This used to be done in the past already, but it got lost during the conversion to meson. Fixes: a2ce7dbd91 ("meson: convert tests/qtest to meson") Message-Id: <20211201104347.51922-2-thuth@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 975a0f2f5f..36ca175660 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -138,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'] : []) + \ From bf22f15114b2a1d5eefbcbc18f939d5006d77f72 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 1 Dec 2021 11:43:45 +0100 Subject: [PATCH 06/16] tests/qtest: Fence the tests that need xlnx-zcu102 with CONFIG_XLNX_ZYNQMP_ARM The 'xlnx-can-test' and the 'fuzz-xlnx-dp-test' need the "xlnx-zcu102" machine and thus should only be built and run if CONFIG_XLNX_ZYNQMP_ARM is enabled. Message-Id: <20211201104347.51922-3-thuth@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 36ca175660..9ff3eaf3d0 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -188,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 = \ From 5516a3b592def4de7fa78153912470f160814e5a Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 1 Dec 2021 11:43:46 +0100 Subject: [PATCH 07/16] tests/qtest: Add a function that gets a list with available machine types For the upcoming patches, we will need a way to gets a list with all available machine types. Refactor the qtest_cb_for_every_machine() to split the related code out into a separate new function, and gather the aliases of the various machine types, too. Message-Id: <20211201104347.51922-4-thuth@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/libqtest.c | 64 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index 25aeea385b..7ae2dc4e1d 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,54 @@ 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); - /* Ignore machines that cannot be used for qtests */ - if (!strncmp("xenfv", mname, 5) || g_str_equal("xenpv", mname)) { - continue; - } - if (!skip_old_versioned || !qtest_is_old_versioned_machine(mname)) { - cb(mname); + 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", machines[i].name, 5) || + g_str_equal("xenpv", machines[i].name)) { + continue; + } + if (!skip_old_versioned || + !qtest_is_old_versioned_machine(machines[i].name)) { + cb(machines[i].name); + } + } } /* From 719051ca3fd0ee765d8a80670df1a0292aa566f0 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 1 Dec 2021 11:43:47 +0100 Subject: [PATCH 08/16] tests/qtest: Add a function to check whether a machine is available It is nowadays possible to build QEMU with a reduced set of machines in each binary. However, the qtests still hard-code the expected machines and fail if the binary does not feature the required machine. Let's get a little bit more flexible here: Add a function that can be used to query whether a certain machine is available or not, and use it in some tests as an example (more work has to be done in other tests which will follow later). Message-Id: <20211201104347.51922-5-thuth@redhat.com> Acked-by: John Snow Signed-off-by: Thomas Huth --- tests/qtest/boot-serial-test.c | 3 ++- tests/qtest/cdrom-test.c | 8 +++++--- tests/qtest/libqos/libqtest.h | 8 ++++++++ tests/qtest/libqtest.c | 17 +++++++++++++++++ tests/qtest/prom-env-test.c | 8 +++++--- 5 files changed, 37 insertions(+), 7 deletions(-) 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 @@ -710,6 +710,14 @@ QDict *qmp_fd(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3); 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 diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index 7ae2dc4e1d..65ed949685 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -1401,6 +1401,23 @@ void qtest_cb_for_every_machine(void (*cb)(const char *machine), } } +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; +} + /* * Generic hot-plugging test via the device_add QMP commands. */ 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); + } } } From e6a52b3651a3b2be41ee3309bc05f9b1e2cf8b90 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 9 Dec 2021 15:48:01 +0100 Subject: [PATCH 09/16] Move the libssh setup from configure to meson.build It's easier to do this in meson.build now. Message-Id: <20211209144801.148388-1-thuth@redhat.com> Acked-by: Richard W.M. Jones Signed-off-by: Thomas Huth --- configure | 27 --------------------------- meson.build | 13 +++++++++---- meson_options.txt | 2 ++ scripts/meson-buildoptions.sh | 3 +++ 4 files changed, 14 insertions(+), 31 deletions(-) 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 @@ -2561,21 +2555,6 @@ if test "$modules" = yes; then fi 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 @@ -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/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/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 ;; From f462be4c062959b6c5bf78df235c16b6c09792ab Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 26 Nov 2021 17:27:24 +0100 Subject: [PATCH 10/16] gitlab-ci.d/buildtest: Add jobs that run the device-crash-test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The device-crash-test script has been quite neglected in the past, so that it bit-rot quite often. Let's add CI jobs that run this script for at least some targets, so that this script does not regress that easily anymore. Message-Id: <20211126162724.1162049-1-thuth@redhat.com> Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Thomas Huth --- .gitlab-ci.d/buildtest.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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: From f11b0a4892ae22b8729a75b2232073fb4090ff28 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 9 Dec 2021 11:31:24 +0100 Subject: [PATCH 11/16] gitlab-ci: Add cirrus-ci based tests for NetBSD and OpenBSD Cirrus-CI provides KVM in their Linux containers, so we can also run our VM-based NetBSD and OpenBSD build jobs there. Since the VM installation might take a while, we only run the "help" target on the first invocation to avoid timeouts, and then only check the build during the next run, once the base image has been cached. For the the build tests, we also only use very a limited set of target CPUs since compiling in these VMs is not very fast (especially the build on OpenBSD seems to be incredibly slow). The jobs are marked as "manual" only, since this double-indirect setup (with the cirrus-run script and VMs in the Cirrus-CI containers) might fail more often than the other jobs, and since we can trigger a limited amount of Cirrus-CI jobs at a time anyway (due to the restrictions in the free tier of Cirrus). Thus these jobs are rather added as convenience for contributors who would like to run the NetBSD/OpenBSD tests without the need of downloading and installing the corresponding VM images on their local machines. Message-Id: <20211209103124.121942-1-thuth@redhat.com> Signed-off-by: Thomas Huth --- .gitlab-ci.d/cirrus.yml | 35 +++++++++++++++++++++++++++++++ .gitlab-ci.d/cirrus/kvm-build.yml | 31 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 .gitlab-ci.d/cirrus/kvm-build.yml 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 From 7b140d23598650326b2bdefbf2b134eacf995d31 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Sat, 27 Nov 2021 08:29:07 +0100 Subject: [PATCH 12/16] virtio-iommu: Remove set_config callback The spec says "the driver must not write to device configuration fields". So remove the set_config() callback which anyway did not do anything. Signed-off-by: Eric Auger Reviewed-by: Jean-Philippe Brucker Message-Id: <20211127072910.1261824-2-eric.auger@redhat.com> Signed-off-by: Thomas Huth --- hw/virtio/trace-events | 1 - hw/virtio/virtio-iommu.c | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events index 650e521e35..54bd7da00c 100644 --- a/hw/virtio/trace-events +++ b/hw/virtio/trace-events @@ -92,7 +92,6 @@ 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_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..645c0aa399 100644 --- a/hw/virtio/virtio-iommu.c +++ b/hw/virtio/virtio-iommu.c @@ -832,19 +832,6 @@ static void virtio_iommu_get_config(VirtIODevice *vdev, uint8_t *config_data) 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); -} - static uint64_t virtio_iommu_get_features(VirtIODevice *vdev, uint64_t f, Error **errp) { @@ -1185,7 +1172,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; From 3a411b2d9660234c8b6855d1b2861085a4ee5943 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Sat, 27 Nov 2021 08:29:08 +0100 Subject: [PATCH 13/16] virtio-iommu: Fix endianness in get_config Endianess is not properly handled when populating the returned config. Use the cpu_to_le* primitives for each separate field. Also, while at it, trace the domain range start. Reported-by: Thomas Huth Signed-off-by: Eric Auger Reviewed-by: Jean-Philippe Brucker Message-Id: <20211127072910.1261824-3-eric.auger@redhat.com> Signed-off-by: Thomas Huth --- hw/virtio/trace-events | 2 +- hw/virtio/virtio-iommu.c | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events index 54bd7da00c..f7ad6be5fb 100644 --- a/hw/virtio/trace-events +++ b/hw/virtio/trace-events @@ -91,7 +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_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 645c0aa399..30ee09187b 100644 --- a/hw/virtio/virtio-iommu.c +++ b/hw/virtio/virtio-iommu.c @@ -822,14 +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; + struct virtio_iommu_config *dev_config = &dev->config; + struct virtio_iommu_config *out_config = (void *)config_data; - 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)); + 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, From 6b77ae0531d1a70a87c08408f6c9cd3389439686 Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Sat, 27 Nov 2021 08:29:09 +0100 Subject: [PATCH 14/16] virtio-iommu: Fix the domain_range end in old times the domain range was defined by a domain_bits le32. This was then converted into a domain_range struct. During the upgrade the original value of '32' (bits) has been kept while the end field now is the max value of the domain id (UINT32_MAX). Fix that and also use UINT64_MAX for the input_range.end. Reported-by: Jean-Philippe Brucker Signed-off-by: Eric Auger Reviewed-by: Jean-Philippe Brucker Message-Id: <20211127072910.1261824-4-eric.auger@redhat.com> Signed-off-by: Thomas Huth --- hw/virtio/virtio-iommu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/virtio/virtio-iommu.c b/hw/virtio/virtio-iommu.c index 30ee09187b..aa9c16a17b 100644 --- a/hw/virtio/virtio-iommu.c +++ b/hw/virtio/virtio-iommu.c @@ -978,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); From becf88730b86c94137cad20c98ef3b8d24559b9b Mon Sep 17 00:00:00 2001 From: Eric Auger Date: Sat, 27 Nov 2021 08:29:10 +0100 Subject: [PATCH 15/16] tests: qtest: Add virtio-iommu test Add the framework to test the virtio-iommu-pci device and tests exercising the attach/detach, map/unmap API. Signed-off-by: Eric Auger Tested-by: Jean-Philippe Brucker Reviewed-by: Jean-Philippe Brucker Acked-by: Thomas Huth Message-Id: <20211127072910.1261824-5-eric.auger@redhat.com> Signed-off-by: Thomas Huth --- tests/qtest/libqos/meson.build | 1 + tests/qtest/libqos/virtio-iommu.c | 126 ++++++++++++ tests/qtest/libqos/virtio-iommu.h | 40 ++++ tests/qtest/meson.build | 1 + tests/qtest/virtio-iommu-test.c | 326 ++++++++++++++++++++++++++++++ 5 files changed, 494 insertions(+) create mode 100644 tests/qtest/libqos/virtio-iommu.c create mode 100644 tests/qtest/libqos/virtio-iommu.h create mode 100644 tests/qtest/virtio-iommu-test.c 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/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 + * + * 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 + * + * 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/meson.build b/tests/qtest/meson.build index 9ff3eaf3d0..acc7de5067 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -234,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/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 + * + * 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); From 7876cba8fc0cab9a4c803a30f427d4b20b95a868 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 15 Nov 2021 15:06:23 +0100 Subject: [PATCH 16/16] gitlab-ci: Test compilation on Windows with MSYS2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gitlab also provides runners with Windows, we can use them to test compilation with MSYS2, in both, 64-bit and 32-bit. However, it takes quite a long time to set up the VM, so to stay in a reasonable time frame, we can only compile and check one target here. Message-Id: <20211115140623.104116-1-thuth@redhat.com> Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Thomas Huth --- .gitlab-ci.d/qemu-project.yml | 1 + .gitlab-ci.d/windows.yml | 98 +++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 .gitlab-ci.d/windows.yml 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'