aboutsummaryrefslogtreecommitdiff
path: root/tests/qtest
diff options
context:
space:
mode:
Diffstat (limited to 'tests/qtest')
-rw-r--r--tests/qtest/ac97-test.c57
-rw-r--r--tests/qtest/acpi-utils.c147
-rw-r--r--tests/qtest/acpi-utils.h56
-rw-r--r--tests/qtest/ahci-test.c1954
-rw-r--r--tests/qtest/arm-cpu-features.c559
-rw-r--r--tests/qtest/bios-tables-test-allowed-diff.h1
-rw-r--r--tests/qtest/bios-tables-test.c1046
-rw-r--r--tests/qtest/boot-order-test.c205
-rw-r--r--tests/qtest/boot-sector.c168
-rw-r--r--tests/qtest/boot-sector.h28
-rw-r--r--tests/qtest/boot-serial-test.c254
-rw-r--r--tests/qtest/cdrom-test.c228
-rw-r--r--tests/qtest/cpu-plug-test.c257
-rw-r--r--tests/qtest/dbus-vmstate-test.c382
-rw-r--r--tests/qtest/dbus-vmstate1.xml12
-rw-r--r--tests/qtest/device-introspect-test.c323
-rw-r--r--tests/qtest/device-plug-test.c178
-rw-r--r--tests/qtest/display-vga-test.c69
-rw-r--r--tests/qtest/drive_del-test.c154
-rw-r--r--tests/qtest/ds1338-test.c58
-rw-r--r--tests/qtest/e1000-test.c68
-rw-r--r--tests/qtest/e1000e-test.c279
-rw-r--r--tests/qtest/eepro100-test.c77
-rw-r--r--tests/qtest/endianness-test.c306
-rw-r--r--tests/qtest/es1370-test.c58
-rw-r--r--tests/qtest/fdc-test.c587
-rw-r--r--tests/qtest/fw_cfg-test.c260
-rw-r--r--tests/qtest/hd-geo-test.c988
-rw-r--r--tests/qtest/hexloader-test.c45
-rw-r--r--tests/qtest/i440fx-test.c413
-rw-r--r--tests/qtest/i82801b11-test.c31
-rw-r--r--tests/qtest/ide-test.c1092
-rw-r--r--tests/qtest/intel-hda-test.c39
-rw-r--r--tests/qtest/ioh3420-test.c32
-rw-r--r--tests/qtest/ipmi-bt-test.c425
-rw-r--r--tests/qtest/ipmi-kcs-test.c285
-rw-r--r--tests/qtest/ipoctal232-test.c49
-rw-r--r--tests/qtest/ivshmem-test.c500
-rw-r--r--tests/qtest/libqtest-single.h315
-rw-r--r--tests/qtest/libqtest.c1339
-rw-r--r--tests/qtest/libqtest.h732
-rw-r--r--tests/qtest/m25p80-test.c382
-rw-r--r--tests/qtest/m48t59-test.c269
-rw-r--r--tests/qtest/machine-none-test.c103
-rw-r--r--tests/qtest/megasas-test.c91
-rw-r--r--tests/qtest/microbit-test.c507
-rw-r--r--tests/qtest/migration-helpers.c167
-rw-r--r--tests/qtest/migration-helpers.h37
-rw-r--r--tests/qtest/migration-test.c1281
-rw-r--r--tests/qtest/modules-test.c74
-rw-r--r--tests/qtest/ne2000-test.c58
-rw-r--r--tests/qtest/numa-test.c574
-rw-r--r--tests/qtest/nvme-test.c88
-rw-r--r--tests/qtest/pca9552-test.c93
-rw-r--r--tests/qtest/pci-test.c26
-rw-r--r--tests/qtest/pcnet-test.c58
-rw-r--r--tests/qtest/pflash-cfi02-test.c681
-rw-r--r--tests/qtest/pnv-xscom-test.c153
-rw-r--r--tests/qtest/prom-env-test.c104
-rw-r--r--tests/qtest/pvpanic-test.c49
-rw-r--r--tests/qtest/pxe-test.c152
-rw-r--r--tests/qtest/q35-test.c201
-rw-r--r--tests/qtest/qmp-cmd-test.c234
-rw-r--r--tests/qtest/qmp-test.c344
-rw-r--r--tests/qtest/qom-test.c127
-rw-r--r--tests/qtest/qos-test.c449
-rw-r--r--tests/qtest/rtas-test.c40
-rw-r--r--tests/qtest/rtc-test.c720
-rw-r--r--tests/qtest/rtl8139-test.c211
-rw-r--r--tests/qtest/sdhci-test.c111
-rw-r--r--tests/qtest/spapr-phb-test.c32
-rw-r--r--tests/qtest/tco-test.c469
-rw-r--r--tests/qtest/test-arm-mptimer.c1090
-rw-r--r--tests/qtest/test-filter-mirror.c94
-rw-r--r--tests/qtest/test-filter-redirector.c219
-rw-r--r--tests/qtest/test-hmp.c171
-rw-r--r--tests/qtest/test-netfilter.c210
-rw-r--r--tests/qtest/test-x86-cpuid-compat.c381
-rw-r--r--tests/qtest/tmp105-test.c120
-rw-r--r--tests/qtest/tpm-crb-swtpm-test.c67
-rw-r--r--tests/qtest/tpm-crb-test.c179
-rw-r--r--tests/qtest/tpm-emu.c183
-rw-r--r--tests/qtest/tpm-emu.h39
-rw-r--r--tests/qtest/tpm-tests.c136
-rw-r--r--tests/qtest/tpm-tests.h26
-rw-r--r--tests/qtest/tpm-tis-swtpm-test.c67
-rw-r--r--tests/qtest/tpm-tis-test.c488
-rw-r--r--tests/qtest/tpm-util.c285
-rw-r--r--tests/qtest/tpm-util.h51
-rw-r--r--tests/qtest/usb-hcd-ehci-test.c178
-rw-r--r--tests/qtest/usb-hcd-ohci-test.c68
-rw-r--r--tests/qtest/usb-hcd-uhci-test.c88
-rw-r--r--tests/qtest/usb-hcd-xhci-test.c69
-rw-r--r--tests/qtest/vhost-user-test.c967
-rw-r--r--tests/qtest/virtio-9p-test.c662
-rw-r--r--tests/qtest/virtio-blk-test.c802
-rw-r--r--tests/qtest/virtio-ccw-test.c115
-rw-r--r--tests/qtest/virtio-net-test.c337
-rw-r--r--tests/qtest/virtio-rng-test.c38
-rw-r--r--tests/qtest/virtio-scsi-test.c298
-rw-r--r--tests/qtest/virtio-serial-test.c39
-rw-r--r--tests/qtest/virtio-test.c26
-rw-r--r--tests/qtest/vmgenid-test.c185
-rw-r--r--tests/qtest/vmxnet3-test.c58
-rw-r--r--tests/qtest/wdt_ib700-test.c118
105 files changed, 29795 insertions, 0 deletions
diff --git a/tests/qtest/ac97-test.c b/tests/qtest/ac97-test.c
new file mode 100644
index 0000000000..b084e31bff
--- /dev/null
+++ b/tests/qtest/ac97-test.c
@@ -0,0 +1,57 @@
+/*
+ * QTest testcase for AC97
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QAC97 QAC97;
+
+struct QAC97 {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *ac97_get_driver(void *obj, const char *interface)
+{
+ QAC97 *ac97 = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &ac97->dev;
+ }
+
+ fprintf(stderr, "%s not present in e1000e\n", interface);
+ g_assert_not_reached();
+}
+
+static void *ac97_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QAC97 *ac97 = g_new0(QAC97, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&ac97->dev, bus, addr);
+ ac97->obj.get_driver = ac97_get_driver;
+ return &ac97->obj;
+}
+
+static void ac97_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("AC97", ac97_create);
+ qos_node_produces("AC97", "pci-device");
+ qos_node_consumes("AC97", "pci-bus", &opts);
+}
+
+libqos_init(ac97_register_nodes);
diff --git a/tests/qtest/acpi-utils.c b/tests/qtest/acpi-utils.c
new file mode 100644
index 0000000000..d2a202efca
--- /dev/null
+++ b/tests/qtest/acpi-utils.c
@@ -0,0 +1,147 @@
+/*
+ * ACPI Utility Functions
+ *
+ * Copyright (c) 2013 Red Hat Inc.
+ * Copyright (c) 2017 Skyport Systems
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>,
+ * Ben Warren <ben@skyportsystems.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "qemu-common.h"
+#include "qemu/bitmap.h"
+#include "acpi-utils.h"
+#include "boot-sector.h"
+
+uint8_t acpi_calc_checksum(const uint8_t *data, int len)
+{
+ int i;
+ uint8_t sum = 0;
+
+ for (i = 0; i < len; i++) {
+ sum += data[i];
+ }
+
+ return sum;
+}
+
+uint32_t acpi_find_rsdp_address(QTestState *qts)
+{
+ uint32_t off;
+
+ /* RSDP location can vary across a narrow range */
+ for (off = 0xf0000; off < 0x100000; off += 0x10) {
+ uint8_t sig[] = "RSD PTR ";
+ int i;
+
+ for (i = 0; i < sizeof sig - 1; ++i) {
+ sig[i] = qtest_readb(qts, off + i);
+ }
+
+ if (!memcmp(sig, "RSD PTR ", sizeof sig)) {
+ break;
+ }
+ }
+ return off;
+}
+
+void acpi_fetch_rsdp_table(QTestState *qts, uint64_t addr, uint8_t *rsdp_table)
+{
+ uint8_t revision;
+
+ /* Read mandatory revision 0 table data (20 bytes) first */
+ qtest_memread(qts, addr, rsdp_table, 20);
+ revision = rsdp_table[15 /* Revision offset */];
+
+ switch (revision) {
+ case 0: /* ACPI 1.0 RSDP */
+ break;
+ case 2: /* ACPI 2.0+ RSDP */
+ /* Read the rest of the RSDP table */
+ qtest_memread(qts, addr + 20, rsdp_table + 20, 16);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ ACPI_ASSERT_CMP64(*((uint64_t *)(rsdp_table)), "RSD PTR ");
+}
+
+/** acpi_fetch_table
+ * load ACPI table at @addr_ptr offset pointer into buffer and return it in
+ * @aml, its length in @aml_len and check that signature/checksum matches
+ * actual one.
+ */
+void acpi_fetch_table(QTestState *qts, uint8_t **aml, uint32_t *aml_len,
+ const uint8_t *addr_ptr, int addr_size, const char *sig,
+ bool verify_checksum)
+{
+ uint32_t len;
+ uint64_t addr = 0;
+
+ g_assert(addr_size == 4 || addr_size == 8);
+ memcpy(&addr, addr_ptr , addr_size);
+ addr = le64_to_cpu(addr);
+ qtest_memread(qts, addr + 4, &len, 4); /* Length of ACPI table */
+ *aml_len = le32_to_cpu(len);
+ *aml = g_malloc0(*aml_len);
+ /* get whole table */
+ qtest_memread(qts, addr, *aml, *aml_len);
+
+ if (sig) {
+ ACPI_ASSERT_CMP(**aml, sig);
+ }
+ if (verify_checksum) {
+ g_assert(!acpi_calc_checksum(*aml, *aml_len));
+ }
+}
+
+#define GUID_SIZE 16
+static const uint8_t AcpiTestSupportGuid[GUID_SIZE] = {
+ 0xb1, 0xa6, 0x87, 0xab,
+ 0x34, 0x20,
+ 0xa0, 0xbd,
+ 0x71, 0xbd, 0x37, 0x50, 0x07, 0x75, 0x77, 0x85 };
+
+typedef struct {
+ uint8_t signature_guid[GUID_SIZE];
+ uint64_t rsdp10;
+ uint64_t rsdp20;
+} __attribute__((packed)) UefiTestSupport;
+
+/* Wait at most 600 seconds (test is slow with TCG and --enable-debug) */
+#define TEST_DELAY (1 * G_USEC_PER_SEC / 10)
+#define TEST_CYCLES MAX((600 * G_USEC_PER_SEC / TEST_DELAY), 1)
+#define MB 0x100000ULL
+uint64_t acpi_find_rsdp_address_uefi(QTestState *qts, uint64_t start,
+ uint64_t size)
+{
+ int i, j;
+ uint8_t data[GUID_SIZE];
+
+ for (i = 0; i < TEST_CYCLES; ++i) {
+ for (j = 0; j < size / MB; j++) {
+ /* look for GUID at every 1Mb block */
+ uint64_t addr = start + j * MB;
+
+ qtest_memread(qts, addr, data, sizeof(data));
+ if (!memcmp(AcpiTestSupportGuid, data, sizeof(data))) {
+ UefiTestSupport ret;
+
+ qtest_memread(qts, addr, &ret, sizeof(ret));
+ ret.rsdp10 = le64_to_cpu(ret.rsdp10);
+ ret.rsdp20 = le64_to_cpu(ret.rsdp20);
+ return ret.rsdp20 ? ret.rsdp20 : ret.rsdp10;
+ }
+ }
+ g_usleep(TEST_DELAY);
+ }
+ g_assert_not_reached();
+ return 0;
+}
diff --git a/tests/qtest/acpi-utils.h b/tests/qtest/acpi-utils.h
new file mode 100644
index 0000000000..0c86780689
--- /dev/null
+++ b/tests/qtest/acpi-utils.h
@@ -0,0 +1,56 @@
+/*
+ * Utilities for working with ACPI tables
+ *
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef TEST_ACPI_UTILS_H
+#define TEST_ACPI_UTILS_H
+
+#include "libqtest.h"
+
+/* DSDT and SSDTs format */
+typedef struct {
+ uint8_t *aml; /* aml bytecode from guest */
+ uint32_t aml_len;
+ gchar *aml_file;
+ gchar *asl; /* asl code generated from aml */
+ gsize asl_len;
+ gchar *asl_file;
+ bool tmp_files_retain; /* do not delete the temp asl/aml */
+} AcpiSdtTable;
+
+#define ACPI_ASSERT_CMP(actual, expected) do { \
+ char ACPI_ASSERT_CMP_str[5] = {}; \
+ memcpy(ACPI_ASSERT_CMP_str, &actual, 4); \
+ g_assert_cmpstr(ACPI_ASSERT_CMP_str, ==, expected); \
+} while (0)
+
+#define ACPI_ASSERT_CMP64(actual, expected) do { \
+ char ACPI_ASSERT_CMP_str[9] = {}; \
+ memcpy(ACPI_ASSERT_CMP_str, &actual, 8); \
+ g_assert_cmpstr(ACPI_ASSERT_CMP_str, ==, expected); \
+} while (0)
+
+
+#define ACPI_FOREACH_RSDT_ENTRY(table, table_len, entry_ptr, entry_size) \
+ for (entry_ptr = table + 36 /* 1st Entry */; \
+ entry_ptr < table + table_len; \
+ entry_ptr += entry_size)
+
+uint8_t acpi_calc_checksum(const uint8_t *data, int len);
+uint32_t acpi_find_rsdp_address(QTestState *qts);
+uint64_t acpi_find_rsdp_address_uefi(QTestState *qts, uint64_t start,
+ uint64_t size);
+void acpi_fetch_rsdp_table(QTestState *qts, uint64_t addr, uint8_t *rsdp_table);
+void acpi_fetch_table(QTestState *qts, uint8_t **aml, uint32_t *aml_len,
+ const uint8_t *addr_ptr, int addr_size, const char *sig,
+ bool verify_checksum);
+
+#endif /* TEST_ACPI_UTILS_H */
diff --git a/tests/qtest/ahci-test.c b/tests/qtest/ahci-test.c
new file mode 100644
index 0000000000..c8d42ceea0
--- /dev/null
+++ b/tests/qtest/ahci-test.c
@@ -0,0 +1,1954 @@
+/*
+ * AHCI test cases
+ *
+ * Copyright (c) 2014 John Snow <jsnow@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include <getopt.h>
+
+#include "libqtest.h"
+#include "libqos/libqos-pc.h"
+#include "libqos/ahci.h"
+#include "libqos/pci-pc.h"
+
+#include "qemu-common.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/host-utils.h"
+
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/pci_regs.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(s, ...) qobject_unref(qtest_qmp(s, __VA_ARGS__))
+
+/* Test images sizes in MB */
+#define TEST_IMAGE_SIZE_MB_LARGE (200 * 1024)
+#define TEST_IMAGE_SIZE_MB_SMALL 64
+
+/*** Globals ***/
+static char tmp_path[] = "/tmp/qtest.XXXXXX";
+static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX";
+static char mig_socket[] = "/tmp/qtest-migration.XXXXXX";
+static bool ahci_pedantic;
+static const char *imgfmt;
+static unsigned test_image_size_mb;
+
+/*** Function Declarations ***/
+static void ahci_test_port_spec(AHCIQState *ahci, uint8_t port);
+static void ahci_test_pci_spec(AHCIQState *ahci);
+static void ahci_test_pci_caps(AHCIQState *ahci, uint16_t header,
+ uint8_t offset);
+static void ahci_test_satacap(AHCIQState *ahci, uint8_t offset);
+static void ahci_test_msicap(AHCIQState *ahci, uint8_t offset);
+static void ahci_test_pmcap(AHCIQState *ahci, uint8_t offset);
+
+/*** Utilities ***/
+
+static uint64_t mb_to_sectors(uint64_t image_size_mb)
+{
+ return (image_size_mb * 1024 * 1024) / AHCI_SECTOR_SIZE;
+}
+
+static void string_bswap16(uint16_t *s, size_t bytes)
+{
+ g_assert_cmphex((bytes & 1), ==, 0);
+ bytes /= 2;
+
+ while (bytes--) {
+ *s = bswap16(*s);
+ s++;
+ }
+}
+
+/**
+ * Verify that the transfer did not corrupt our state at all.
+ */
+static void verify_state(AHCIQState *ahci, uint64_t hba_old)
+{
+ int i, j;
+ uint32_t ahci_fingerprint;
+ uint64_t hba_base;
+ AHCICommandHeader cmd;
+
+ ahci_fingerprint = qpci_config_readl(ahci->dev, PCI_VENDOR_ID);
+ g_assert_cmphex(ahci_fingerprint, ==, ahci->fingerprint);
+
+ /* If we haven't initialized, this is as much as can be validated. */
+ if (!ahci->enabled) {
+ return;
+ }
+
+ hba_base = (uint64_t)qpci_config_readl(ahci->dev, PCI_BASE_ADDRESS_5);
+ g_assert_cmphex(hba_base, ==, hba_old);
+
+ g_assert_cmphex(ahci_rreg(ahci, AHCI_CAP), ==, ahci->cap);
+ g_assert_cmphex(ahci_rreg(ahci, AHCI_CAP2), ==, ahci->cap2);
+
+ for (i = 0; i < 32; i++) {
+ g_assert_cmphex(ahci_px_rreg(ahci, i, AHCI_PX_FB), ==,
+ ahci->port[i].fb);
+ g_assert_cmphex(ahci_px_rreg(ahci, i, AHCI_PX_CLB), ==,
+ ahci->port[i].clb);
+ for (j = 0; j < 32; j++) {
+ ahci_get_command_header(ahci, i, j, &cmd);
+ g_assert_cmphex(cmd.prdtl, ==, ahci->port[i].prdtl[j]);
+ g_assert_cmphex(cmd.ctba, ==, ahci->port[i].ctba[j]);
+ }
+ }
+}
+
+static void ahci_migrate(AHCIQState *from, AHCIQState *to, const char *uri)
+{
+ QOSState *tmp = to->parent;
+ QPCIDevice *dev = to->dev;
+ char *uri_local = NULL;
+ uint64_t hba_old;
+
+ if (uri == NULL) {
+ uri_local = g_strdup_printf("%s%s", "unix:", mig_socket);
+ uri = uri_local;
+ }
+
+ hba_old = (uint64_t)qpci_config_readl(from->dev, PCI_BASE_ADDRESS_5);
+
+ /* context will be 'to' after completion. */
+ migrate(from->parent, to->parent, uri);
+
+ /* We'd like for the AHCIState objects to still point
+ * to information specific to its specific parent
+ * instance, but otherwise just inherit the new data. */
+ memcpy(to, from, sizeof(AHCIQState));
+ to->parent = tmp;
+ to->dev = dev;
+
+ tmp = from->parent;
+ dev = from->dev;
+ memset(from, 0x00, sizeof(AHCIQState));
+ from->parent = tmp;
+ from->dev = dev;
+
+ verify_state(to, hba_old);
+ g_free(uri_local);
+}
+
+/*** Test Setup & Teardown ***/
+
+/**
+ * Start a Q35 machine and bookmark a handle to the AHCI device.
+ */
+static AHCIQState *ahci_vboot(const char *cli, va_list ap)
+{
+ AHCIQState *s;
+
+ s = g_new0(AHCIQState, 1);
+ s->parent = qtest_pc_vboot(cli, ap);
+ alloc_set_flags(&s->parent->alloc, ALLOC_LEAK_ASSERT);
+
+ /* Verify that we have an AHCI device present. */
+ s->dev = get_ahci_device(s->parent->qts, &s->fingerprint);
+
+ return s;
+}
+
+/**
+ * Start a Q35 machine and bookmark a handle to the AHCI device.
+ */
+static AHCIQState *ahci_boot(const char *cli, ...)
+{
+ AHCIQState *s;
+ va_list ap;
+
+ if (cli) {
+ va_start(ap, cli);
+ s = ahci_vboot(cli, ap);
+ va_end(ap);
+ } else {
+ cli = "-drive if=none,id=drive0,file=%s,cache=writeback,format=%s"
+ " -M q35 "
+ "-device ide-hd,drive=drive0 "
+ "-global ide-hd.serial=%s "
+ "-global ide-hd.ver=%s";
+ s = ahci_boot(cli, tmp_path, imgfmt, "testdisk", "version");
+ }
+
+ return s;
+}
+
+/**
+ * Clean up the PCI device, then terminate the QEMU instance.
+ */
+static void ahci_shutdown(AHCIQState *ahci)
+{
+ QOSState *qs = ahci->parent;
+
+ ahci_clean_mem(ahci);
+ free_ahci_device(ahci->dev);
+ g_free(ahci);
+ qtest_shutdown(qs);
+}
+
+/**
+ * Boot and fully enable the HBA device.
+ * @see ahci_boot, ahci_pci_enable and ahci_hba_enable.
+ */
+static AHCIQState *ahci_boot_and_enable(const char *cli, ...)
+{
+ AHCIQState *ahci;
+ va_list ap;
+ uint16_t buff[256];
+ uint8_t port;
+ uint8_t hello;
+
+ if (cli) {
+ va_start(ap, cli);
+ ahci = ahci_vboot(cli, ap);
+ va_end(ap);
+ } else {
+ ahci = ahci_boot(NULL);
+ }
+
+ ahci_pci_enable(ahci);
+ ahci_hba_enable(ahci);
+ /* Initialize test device */
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+ if (is_atapi(ahci, port)) {
+ hello = CMD_PACKET_ID;
+ } else {
+ hello = CMD_IDENTIFY;
+ }
+ ahci_io(ahci, port, hello, &buff, sizeof(buff), 0);
+
+ return ahci;
+}
+
+/*** Specification Adherence Tests ***/
+
+/**
+ * Implementation for test_pci_spec. Ensures PCI configuration space is sane.
+ */
+static void ahci_test_pci_spec(AHCIQState *ahci)
+{
+ uint8_t datab;
+ uint16_t data;
+ uint32_t datal;
+
+ /* Most of these bits should start cleared until we turn them on. */
+ data = qpci_config_readw(ahci->dev, PCI_COMMAND);
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_MEMORY);
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_MASTER);
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_SPECIAL); /* Reserved */
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_VGA_PALETTE); /* Reserved */
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_PARITY);
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_WAIT); /* Reserved */
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_SERR);
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_FAST_BACK);
+ ASSERT_BIT_CLEAR(data, PCI_COMMAND_INTX_DISABLE);
+ ASSERT_BIT_CLEAR(data, 0xF800); /* Reserved */
+
+ data = qpci_config_readw(ahci->dev, PCI_STATUS);
+ ASSERT_BIT_CLEAR(data, 0x01 | 0x02 | 0x04); /* Reserved */
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_INTERRUPT);
+ ASSERT_BIT_SET(data, PCI_STATUS_CAP_LIST); /* must be set */
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_UDF); /* Reserved */
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_PARITY);
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_SIG_TARGET_ABORT);
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_REC_TARGET_ABORT);
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_REC_MASTER_ABORT);
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_SIG_SYSTEM_ERROR);
+ ASSERT_BIT_CLEAR(data, PCI_STATUS_DETECTED_PARITY);
+
+ /* RID occupies the low byte, CCs occupy the high three. */
+ datal = qpci_config_readl(ahci->dev, PCI_CLASS_REVISION);
+ if (ahci_pedantic) {
+ /* AHCI 1.3 specifies that at-boot, the RID should reset to 0x00,
+ * Though in practice this is likely seldom true. */
+ ASSERT_BIT_CLEAR(datal, 0xFF);
+ }
+
+ /* BCC *must* equal 0x01. */
+ g_assert_cmphex(PCI_BCC(datal), ==, 0x01);
+ if (PCI_SCC(datal) == 0x01) {
+ /* IDE */
+ ASSERT_BIT_SET(0x80000000, datal);
+ ASSERT_BIT_CLEAR(0x60000000, datal);
+ } else if (PCI_SCC(datal) == 0x04) {
+ /* RAID */
+ g_assert_cmphex(PCI_PI(datal), ==, 0);
+ } else if (PCI_SCC(datal) == 0x06) {
+ /* AHCI */
+ g_assert_cmphex(PCI_PI(datal), ==, 0x01);
+ } else {
+ g_assert_not_reached();
+ }
+
+ datab = qpci_config_readb(ahci->dev, PCI_CACHE_LINE_SIZE);
+ g_assert_cmphex(datab, ==, 0);
+
+ datab = qpci_config_readb(ahci->dev, PCI_LATENCY_TIMER);
+ g_assert_cmphex(datab, ==, 0);
+
+ /* Only the bottom 7 bits must be off. */
+ datab = qpci_config_readb(ahci->dev, PCI_HEADER_TYPE);
+ ASSERT_BIT_CLEAR(datab, 0x7F);
+
+ /* BIST is optional, but the low 7 bits must always start off regardless. */
+ datab = qpci_config_readb(ahci->dev, PCI_BIST);
+ ASSERT_BIT_CLEAR(datab, 0x7F);
+
+ /* BARS 0-4 do not have a boot spec, but ABAR/BAR5 must be clean. */
+ datal = qpci_config_readl(ahci->dev, PCI_BASE_ADDRESS_5);
+ g_assert_cmphex(datal, ==, 0);
+
+ qpci_config_writel(ahci->dev, PCI_BASE_ADDRESS_5, 0xFFFFFFFF);
+ datal = qpci_config_readl(ahci->dev, PCI_BASE_ADDRESS_5);
+ /* ABAR must be 32-bit, memory mapped, non-prefetchable and
+ * must be >= 512 bytes. To that end, bits 0-8 must be off. */
+ ASSERT_BIT_CLEAR(datal, 0xFF);
+
+ /* Capability list MUST be present, */
+ datal = qpci_config_readl(ahci->dev, PCI_CAPABILITY_LIST);
+ /* But these bits are reserved. */
+ ASSERT_BIT_CLEAR(datal, ~0xFF);
+ g_assert_cmphex(datal, !=, 0);
+
+ /* Check specification adherence for capability extenstions. */
+ data = qpci_config_readw(ahci->dev, datal);
+
+ switch (ahci->fingerprint) {
+ case AHCI_INTEL_ICH9:
+ /* Intel ICH9 Family Datasheet 14.1.19 p.550 */
+ g_assert_cmphex((data & 0xFF), ==, PCI_CAP_ID_MSI);
+ break;
+ default:
+ /* AHCI 1.3, Section 2.1.14 -- CAP must point to PMCAP. */
+ g_assert_cmphex((data & 0xFF), ==, PCI_CAP_ID_PM);
+ }
+
+ ahci_test_pci_caps(ahci, data, (uint8_t)datal);
+
+ /* Reserved. */
+ datal = qpci_config_readl(ahci->dev, PCI_CAPABILITY_LIST + 4);
+ g_assert_cmphex(datal, ==, 0);
+
+ /* IPIN might vary, but ILINE must be off. */
+ datab = qpci_config_readb(ahci->dev, PCI_INTERRUPT_LINE);
+ g_assert_cmphex(datab, ==, 0);
+}
+
+/**
+ * Test PCI capabilities for AHCI specification adherence.
+ */
+static void ahci_test_pci_caps(AHCIQState *ahci, uint16_t header,
+ uint8_t offset)
+{
+ uint8_t cid = header & 0xFF;
+ uint8_t next = header >> 8;
+
+ g_test_message("CID: %02x; next: %02x", cid, next);
+
+ switch (cid) {
+ case PCI_CAP_ID_PM:
+ ahci_test_pmcap(ahci, offset);
+ break;
+ case PCI_CAP_ID_MSI:
+ ahci_test_msicap(ahci, offset);
+ break;
+ case PCI_CAP_ID_SATA:
+ ahci_test_satacap(ahci, offset);
+ break;
+
+ default:
+ g_test_message("Unknown CAP 0x%02x", cid);
+ }
+
+ if (next) {
+ ahci_test_pci_caps(ahci, qpci_config_readw(ahci->dev, next), next);
+ }
+}
+
+/**
+ * Test SATA PCI capabilitity for AHCI specification adherence.
+ */
+static void ahci_test_satacap(AHCIQState *ahci, uint8_t offset)
+{
+ uint16_t dataw;
+ uint32_t datal;
+
+ g_test_message("Verifying SATACAP");
+
+ /* Assert that the SATACAP version is 1.0, And reserved bits are empty. */
+ dataw = qpci_config_readw(ahci->dev, offset + 2);
+ g_assert_cmphex(dataw, ==, 0x10);
+
+ /* Grab the SATACR1 register. */
+ datal = qpci_config_readw(ahci->dev, offset + 4);
+
+ switch (datal & 0x0F) {
+ case 0x04: /* BAR0 */
+ case 0x05: /* BAR1 */
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09: /* BAR5 */
+ case 0x0F: /* Immediately following SATACR1 in PCI config space. */
+ break;
+ default:
+ /* Invalid BARLOC for the Index Data Pair. */
+ g_assert_not_reached();
+ }
+
+ /* Reserved. */
+ g_assert_cmphex((datal >> 24), ==, 0x00);
+}
+
+/**
+ * Test MSI PCI capability for AHCI specification adherence.
+ */
+static void ahci_test_msicap(AHCIQState *ahci, uint8_t offset)
+{
+ uint16_t dataw;
+ uint32_t datal;
+
+ g_test_message("Verifying MSICAP");
+
+ dataw = qpci_config_readw(ahci->dev, offset + PCI_MSI_FLAGS);
+ ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_ENABLE);
+ ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_QSIZE);
+ ASSERT_BIT_CLEAR(dataw, PCI_MSI_FLAGS_RESERVED);
+
+ datal = qpci_config_readl(ahci->dev, offset + PCI_MSI_ADDRESS_LO);
+ g_assert_cmphex(datal, ==, 0);
+
+ if (dataw & PCI_MSI_FLAGS_64BIT) {
+ g_test_message("MSICAP is 64bit");
+ datal = qpci_config_readl(ahci->dev, offset + PCI_MSI_ADDRESS_HI);
+ g_assert_cmphex(datal, ==, 0);
+ dataw = qpci_config_readw(ahci->dev, offset + PCI_MSI_DATA_64);
+ g_assert_cmphex(dataw, ==, 0);
+ } else {
+ g_test_message("MSICAP is 32bit");
+ dataw = qpci_config_readw(ahci->dev, offset + PCI_MSI_DATA_32);
+ g_assert_cmphex(dataw, ==, 0);
+ }
+}
+
+/**
+ * Test Power Management PCI capability for AHCI specification adherence.
+ */
+static void ahci_test_pmcap(AHCIQState *ahci, uint8_t offset)
+{
+ uint16_t dataw;
+
+ g_test_message("Verifying PMCAP");
+
+ dataw = qpci_config_readw(ahci->dev, offset + PCI_PM_PMC);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_PME_CLOCK);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_RESERVED);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_D1);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CAP_D2);
+
+ dataw = qpci_config_readw(ahci->dev, offset + PCI_PM_CTRL);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_STATE_MASK);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_RESERVED);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_DATA_SEL_MASK);
+ ASSERT_BIT_CLEAR(dataw, PCI_PM_CTRL_DATA_SCALE_MASK);
+}
+
+static void ahci_test_hba_spec(AHCIQState *ahci)
+{
+ unsigned i;
+ uint32_t reg;
+ uint32_t ports;
+ uint8_t nports_impl;
+ uint8_t maxports;
+
+ g_assert(ahci != NULL);
+
+ /*
+ * Note that the AHCI spec does expect the BIOS to set up a few things:
+ * CAP.SSS - Support for staggered spin-up (t/f)
+ * CAP.SMPS - Support for mechanical presence switches (t/f)
+ * PI - Ports Implemented (1-32)
+ * PxCMD.HPCP - Hot Plug Capable Port
+ * PxCMD.MPSP - Mechanical Presence Switch Present
+ * PxCMD.CPD - Cold Presence Detection support
+ *
+ * Additional items are touched if CAP.SSS is on, see AHCI 10.1.1 p.97:
+ * Foreach Port Implemented:
+ * -PxCMD.ST, PxCMD.CR, PxCMD.FRE, PxCMD.FR, PxSCTL.DET are 0
+ * -PxCLB/U and PxFB/U are set to valid regions in memory
+ * -PxSUD is set to 1.
+ * -PxSSTS.DET is polled for presence; if detected, we continue:
+ * -PxSERR is cleared with 1's.
+ * -If PxTFD.STS.BSY, PxTFD.STS.DRQ, and PxTFD.STS.ERR are all zero,
+ * the device is ready.
+ */
+
+ /* 1 CAP - Capabilities Register */
+ ahci->cap = ahci_rreg(ahci, AHCI_CAP);
+ ASSERT_BIT_CLEAR(ahci->cap, AHCI_CAP_RESERVED);
+
+ /* 2 GHC - Global Host Control */
+ reg = ahci_rreg(ahci, AHCI_GHC);
+ ASSERT_BIT_CLEAR(reg, AHCI_GHC_HR);
+ ASSERT_BIT_CLEAR(reg, AHCI_GHC_IE);
+ ASSERT_BIT_CLEAR(reg, AHCI_GHC_MRSM);
+ if (BITSET(ahci->cap, AHCI_CAP_SAM)) {
+ g_test_message("Supports AHCI-Only Mode: GHC_AE is Read-Only.");
+ ASSERT_BIT_SET(reg, AHCI_GHC_AE);
+ } else {
+ g_test_message("Supports AHCI/Legacy mix.");
+ ASSERT_BIT_CLEAR(reg, AHCI_GHC_AE);
+ }
+
+ /* 3 IS - Interrupt Status */
+ reg = ahci_rreg(ahci, AHCI_IS);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* 4 PI - Ports Implemented */
+ ports = ahci_rreg(ahci, AHCI_PI);
+ /* Ports Implemented must be non-zero. */
+ g_assert_cmphex(ports, !=, 0);
+ /* Ports Implemented must be <= Number of Ports. */
+ nports_impl = ctpopl(ports);
+ g_assert_cmpuint(((AHCI_CAP_NP & ahci->cap) + 1), >=, nports_impl);
+
+ /* Ports must be within the proper range. Given a mapping of SIZE,
+ * 256 bytes are used for global HBA control, and the rest is used
+ * for ports data, at 0x80 bytes each. */
+ g_assert_cmphex(ahci->barsize, >, 0);
+ maxports = (ahci->barsize - HBA_DATA_REGION_SIZE) / HBA_PORT_DATA_SIZE;
+ /* e.g, 30 ports for 4K of memory. (4096 - 256) / 128 = 30 */
+ g_assert_cmphex((reg >> maxports), ==, 0);
+
+ /* 5 AHCI Version */
+ reg = ahci_rreg(ahci, AHCI_VS);
+ switch (reg) {
+ case AHCI_VERSION_0_95:
+ case AHCI_VERSION_1_0:
+ case AHCI_VERSION_1_1:
+ case AHCI_VERSION_1_2:
+ case AHCI_VERSION_1_3:
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ /* 6 Command Completion Coalescing Control: depends on CAP.CCCS. */
+ reg = ahci_rreg(ahci, AHCI_CCCCTL);
+ if (BITSET(ahci->cap, AHCI_CAP_CCCS)) {
+ ASSERT_BIT_CLEAR(reg, AHCI_CCCCTL_EN);
+ ASSERT_BIT_CLEAR(reg, AHCI_CCCCTL_RESERVED);
+ ASSERT_BIT_SET(reg, AHCI_CCCCTL_CC);
+ ASSERT_BIT_SET(reg, AHCI_CCCCTL_TV);
+ } else {
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* 7 CCC_PORTS */
+ reg = ahci_rreg(ahci, AHCI_CCCPORTS);
+ /* Must be zeroes initially regardless of CAP.CCCS */
+ g_assert_cmphex(reg, ==, 0);
+
+ /* 8 EM_LOC */
+ reg = ahci_rreg(ahci, AHCI_EMLOC);
+ if (BITCLR(ahci->cap, AHCI_CAP_EMS)) {
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* 9 EM_CTL */
+ reg = ahci_rreg(ahci, AHCI_EMCTL);
+ if (BITSET(ahci->cap, AHCI_CAP_EMS)) {
+ ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_STSMR);
+ ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_CTLTM);
+ ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_CTLRST);
+ ASSERT_BIT_CLEAR(reg, AHCI_EMCTL_RESERVED);
+ } else {
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* 10 CAP2 -- Capabilities Extended */
+ ahci->cap2 = ahci_rreg(ahci, AHCI_CAP2);
+ ASSERT_BIT_CLEAR(ahci->cap2, AHCI_CAP2_RESERVED);
+
+ /* 11 BOHC -- Bios/OS Handoff Control */
+ reg = ahci_rreg(ahci, AHCI_BOHC);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* 12 -- 23: Reserved */
+ g_test_message("Verifying HBA reserved area is empty.");
+ for (i = AHCI_RESERVED; i < AHCI_NVMHCI; ++i) {
+ reg = ahci_rreg(ahci, i);
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* 24 -- 39: NVMHCI */
+ if (BITCLR(ahci->cap2, AHCI_CAP2_NVMP)) {
+ g_test_message("Verifying HBA/NVMHCI area is empty.");
+ for (i = AHCI_NVMHCI; i < AHCI_VENDOR; ++i) {
+ reg = ahci_rreg(ahci, i);
+ g_assert_cmphex(reg, ==, 0);
+ }
+ }
+
+ /* 40 -- 63: Vendor */
+ g_test_message("Verifying HBA/Vendor area is empty.");
+ for (i = AHCI_VENDOR; i < AHCI_PORTS; ++i) {
+ reg = ahci_rreg(ahci, i);
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* 64 -- XX: Port Space */
+ for (i = 0; ports || (i < maxports); ports >>= 1, ++i) {
+ if (BITSET(ports, 0x1)) {
+ g_test_message("Testing port %u for spec", i);
+ ahci_test_port_spec(ahci, i);
+ } else {
+ uint16_t j;
+ uint16_t low = AHCI_PORTS + (32 * i);
+ uint16_t high = AHCI_PORTS + (32 * (i + 1));
+ g_test_message("Asserting unimplemented port %u "
+ "(reg [%u-%u]) is empty.",
+ i, low, high - 1);
+ for (j = low; j < high; ++j) {
+ reg = ahci_rreg(ahci, j);
+ g_assert_cmphex(reg, ==, 0);
+ }
+ }
+ }
+}
+
+/**
+ * Test the memory space for one port for specification adherence.
+ */
+static void ahci_test_port_spec(AHCIQState *ahci, uint8_t port)
+{
+ uint32_t reg;
+ unsigned i;
+
+ /* (0) CLB */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_CLB);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CLB_RESERVED);
+
+ /* (1) CLBU */
+ if (BITCLR(ahci->cap, AHCI_CAP_S64A)) {
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_CLBU);
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* (2) FB */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_FB);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FB_RESERVED);
+
+ /* (3) FBU */
+ if (BITCLR(ahci->cap, AHCI_CAP_S64A)) {
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_FBU);
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* (4) IS */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_IS);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (5) IE */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_IE);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (6) CMD */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_CMD);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FRE);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_RESERVED);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CCS);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FR);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_PMA); /* And RW only if CAP.SPM */
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_APSTE); /* RW only if CAP2.APST */
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ATAPI);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_DLAE);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ALPE); /* RW only if CAP.SALP */
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ASP); /* RW only if CAP.SALP */
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_ICC);
+ /* If CPDetect support does not exist, CPState must be off. */
+ if (BITCLR(reg, AHCI_PX_CMD_CPD)) {
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CPS);
+ }
+ /* If MPSPresence is not set, MPSState must be off. */
+ if (BITCLR(reg, AHCI_PX_CMD_MPSP)) {
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_MPSS);
+ }
+ /* If we do not support MPS, MPSS and MPSP must be off. */
+ if (BITCLR(ahci->cap, AHCI_CAP_SMPS)) {
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_MPSS);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_MPSP);
+ }
+ /* If, via CPD or MPSP we detect a drive, HPCP must be on. */
+ if (BITANY(reg, AHCI_PX_CMD_CPD | AHCI_PX_CMD_MPSP)) {
+ ASSERT_BIT_SET(reg, AHCI_PX_CMD_HPCP);
+ }
+ /* HPCP and ESP cannot both be active. */
+ g_assert(!BITSET(reg, AHCI_PX_CMD_HPCP | AHCI_PX_CMD_ESP));
+ /* If CAP.FBSS is not set, FBSCP must not be set. */
+ if (BITCLR(ahci->cap, AHCI_CAP_FBSS)) {
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FBSCP);
+ }
+
+ /* (7) RESERVED */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_RES1);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (8) TFD */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD);
+ /* At boot, prior to an FIS being received, the TFD register should be 0x7F,
+ * which breaks down as follows, as seen in AHCI 1.3 sec 3.3.8, p. 27. */
+ ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_ERR);
+ ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_CS1);
+ ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_DRQ);
+ ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_CS2);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_BSY);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_RESERVED);
+
+ /* (9) SIG */
+ /* Though AHCI specifies the boot value should be 0xFFFFFFFF,
+ * Even when GHC.ST is zero, the AHCI HBA may receive the initial
+ * D2H register FIS and update the signature asynchronously,
+ * so we cannot expect a value here. AHCI 1.3, sec 3.3.9, pp 27-28 */
+
+ /* (10) SSTS / SCR0: SStatus */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_SSTS);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_SSTS_RESERVED);
+ /* Even though the register should be 0 at boot, it is asynchronous and
+ * prone to change, so we cannot test any well known value. */
+
+ /* (11) SCTL / SCR2: SControl */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_SCTL);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (12) SERR / SCR1: SError */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_SERR);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (13) SACT / SCR3: SActive */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_SACT);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (14) CI */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_CI);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (15) SNTF */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_SNTF);
+ g_assert_cmphex(reg, ==, 0);
+
+ /* (16) FBS */
+ reg = ahci_px_rreg(ahci, port, AHCI_PX_FBS);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_EN);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_DEC);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_SDE);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_DEV);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_DWE);
+ ASSERT_BIT_CLEAR(reg, AHCI_PX_FBS_RESERVED);
+ if (BITSET(ahci->cap, AHCI_CAP_FBSS)) {
+ /* if Port-Multiplier FIS-based switching avail, ADO must >= 2 */
+ g_assert((reg & AHCI_PX_FBS_ADO) >> ctzl(AHCI_PX_FBS_ADO) >= 2);
+ }
+
+ /* [17 -- 27] RESERVED */
+ for (i = AHCI_PX_RES2; i < AHCI_PX_VS; ++i) {
+ reg = ahci_px_rreg(ahci, port, i);
+ g_assert_cmphex(reg, ==, 0);
+ }
+
+ /* [28 -- 31] Vendor-Specific */
+ for (i = AHCI_PX_VS; i < 32; ++i) {
+ reg = ahci_px_rreg(ahci, port, i);
+ if (reg) {
+ g_test_message("INFO: Vendor register %u non-empty", i);
+ }
+ }
+}
+
+/**
+ * Utilizing an initialized AHCI HBA, issue an IDENTIFY command to the first
+ * device we see, then read and check the response.
+ */
+static void ahci_test_identify(AHCIQState *ahci)
+{
+ uint16_t buff[256];
+ unsigned px;
+ int rc;
+ uint16_t sect_size;
+ const size_t buffsize = 512;
+
+ g_assert(ahci != NULL);
+
+ /**
+ * This serves as a bit of a tutorial on AHCI device programming:
+ *
+ * (1) Create a data buffer for the IDENTIFY response to be sent to
+ * (2) Create a Command Table buffer, where we will store the
+ * command and PRDT (Physical Region Descriptor Table)
+ * (3) Construct an FIS host-to-device command structure, and write it to
+ * the top of the Command Table buffer.
+ * (4) Create one or more Physical Region Descriptors (PRDs) that describe
+ * a location in memory where data may be stored/retrieved.
+ * (5) Write these PRDTs to the bottom (offset 0x80) of the Command Table.
+ * (6) Each AHCI port has up to 32 command slots. Each slot contains a
+ * header that points to a Command Table buffer. Pick an unused slot
+ * and update it to point to the Command Table we have built.
+ * (7) Now: Command #n points to our Command Table, and our Command Table
+ * contains the FIS (that describes our command) and the PRDTL, which
+ * describes our buffer.
+ * (8) We inform the HBA via PxCI (Command Issue) that the command in slot
+ * #n is ready for processing.
+ */
+
+ /* Pick the first implemented and running port */
+ px = ahci_port_select(ahci);
+ g_test_message("Selected port %u for test", px);
+
+ /* Clear out the FIS Receive area and any pending interrupts. */
+ ahci_port_clear(ahci, px);
+
+ /* "Read" 512 bytes using CMD_IDENTIFY into the host buffer. */
+ ahci_io(ahci, px, CMD_IDENTIFY, &buff, buffsize, 0);
+
+ /* Check serial number/version in the buffer */
+ /* NB: IDENTIFY strings are packed in 16bit little endian chunks.
+ * Since we copy byte-for-byte in ahci-test, on both LE and BE, we need to
+ * unchunk this data. By contrast, ide-test copies 2 bytes at a time, and
+ * as a consequence, only needs to unchunk the data on LE machines. */
+ string_bswap16(&buff[10], 20);
+ rc = memcmp(&buff[10], "testdisk ", 20);
+ g_assert_cmphex(rc, ==, 0);
+
+ string_bswap16(&buff[23], 8);
+ rc = memcmp(&buff[23], "version ", 8);
+ g_assert_cmphex(rc, ==, 0);
+
+ sect_size = le16_to_cpu(*((uint16_t *)(&buff[5])));
+ g_assert_cmphex(sect_size, ==, AHCI_SECTOR_SIZE);
+}
+
+static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize,
+ uint64_t sector, uint8_t read_cmd,
+ uint8_t write_cmd)
+{
+ uint64_t ptr;
+ uint8_t port;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+
+ g_assert(ahci != NULL);
+
+ /* Pick the first running port and clear it. */
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ /*** Create pattern and transfer to guest ***/
+ /* Data buffer in the guest */
+ ptr = ahci_alloc(ahci, bufsize);
+ g_assert(ptr);
+
+ /* Write some indicative pattern to our buffer. */
+ generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
+ qtest_bufwrite(ahci->parent->qts, ptr, tx, bufsize);
+
+ /* Write this buffer to disk, then read it back to the DMA buffer. */
+ ahci_guest_io(ahci, port, write_cmd, ptr, bufsize, sector);
+ qtest_memset(ahci->parent->qts, ptr, 0x00, bufsize);
+ ahci_guest_io(ahci, port, read_cmd, ptr, bufsize, sector);
+
+ /*** Read back the Data ***/
+ qtest_bufread(ahci->parent->qts, ptr, rx, bufsize);
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ ahci_free(ahci, ptr);
+ g_free(tx);
+ g_free(rx);
+}
+
+static uint8_t ahci_test_nondata(AHCIQState *ahci, uint8_t ide_cmd)
+{
+ uint8_t port;
+
+ /* Sanitize */
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ ahci_io(ahci, port, ide_cmd, NULL, 0, 0);
+
+ return port;
+}
+
+static void ahci_test_flush(AHCIQState *ahci)
+{
+ ahci_test_nondata(ahci, CMD_FLUSH_CACHE);
+}
+
+static void ahci_test_max(AHCIQState *ahci)
+{
+ RegD2HFIS *d2h = g_malloc0(0x20);
+ uint64_t nsect;
+ uint8_t port;
+ uint8_t cmd;
+ uint64_t config_sect = mb_to_sectors(test_image_size_mb) - 1;
+
+ if (config_sect > 0xFFFFFF) {
+ cmd = CMD_READ_MAX_EXT;
+ } else {
+ cmd = CMD_READ_MAX;
+ }
+
+ port = ahci_test_nondata(ahci, cmd);
+ qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x40, d2h, 0x20);
+ nsect = (uint64_t)d2h->lba_hi[2] << 40 |
+ (uint64_t)d2h->lba_hi[1] << 32 |
+ (uint64_t)d2h->lba_hi[0] << 24 |
+ (uint64_t)d2h->lba_lo[2] << 16 |
+ (uint64_t)d2h->lba_lo[1] << 8 |
+ (uint64_t)d2h->lba_lo[0];
+
+ g_assert_cmphex(nsect, ==, config_sect);
+ g_free(d2h);
+}
+
+
+/******************************************************************************/
+/* Test Interfaces */
+/******************************************************************************/
+
+/**
+ * Basic sanity test to boot a machine, find an AHCI device, and shutdown.
+ */
+static void test_sanity(void)
+{
+ AHCIQState *ahci;
+ ahci = ahci_boot(NULL);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Ensure that the PCI configuration space for the AHCI device is in-line with
+ * the AHCI 1.3 specification for initial values.
+ */
+static void test_pci_spec(void)
+{
+ AHCIQState *ahci;
+ ahci = ahci_boot(NULL);
+ ahci_test_pci_spec(ahci);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Engage the PCI AHCI device and sanity check the response.
+ * Perform additional PCI config space bringup for the HBA.
+ */
+static void test_pci_enable(void)
+{
+ AHCIQState *ahci;
+ ahci = ahci_boot(NULL);
+ ahci_pci_enable(ahci);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Investigate the memory mapped regions of the HBA,
+ * and test them for AHCI specification adherence.
+ */
+static void test_hba_spec(void)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot(NULL);
+ ahci_pci_enable(ahci);
+ ahci_test_hba_spec(ahci);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Engage the HBA functionality of the AHCI PCI device,
+ * and bring it into a functional idle state.
+ */
+static void test_hba_enable(void)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot(NULL);
+ ahci_pci_enable(ahci);
+ ahci_hba_enable(ahci);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Bring up the device and issue an IDENTIFY command.
+ * Inspect the state of the HBA device and the data returned.
+ */
+static void test_identify(void)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot_and_enable(NULL);
+ ahci_test_identify(ahci);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Fragmented DMA test: Perform a standard 4K DMA read/write
+ * test, but make sure the physical regions are fragmented to
+ * be very small, each just 32 bytes, to see how AHCI performs
+ * with chunks defined to be much less than a sector.
+ */
+static void test_dma_fragmented(void)
+{
+ AHCIQState *ahci;
+ AHCICommand *cmd;
+ uint8_t px;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ uint64_t ptr;
+
+ ahci = ahci_boot_and_enable(NULL);
+ px = ahci_port_select(ahci);
+ ahci_port_clear(ahci, px);
+
+ /* create pattern */
+ generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
+
+ /* Create a DMA buffer in guest memory, and write our pattern to it. */
+ ptr = guest_alloc(&ahci->parent->alloc, bufsize);
+ g_assert(ptr);
+ qtest_bufwrite(ahci->parent->qts, ptr, tx, bufsize);
+
+ cmd = ahci_command_create(CMD_WRITE_DMA);
+ ahci_command_adjust(cmd, 0, ptr, bufsize, 32);
+ ahci_command_commit(ahci, cmd, px);
+ ahci_command_issue(ahci, cmd);
+ ahci_command_verify(ahci, cmd);
+ ahci_command_free(cmd);
+
+ cmd = ahci_command_create(CMD_READ_DMA);
+ ahci_command_adjust(cmd, 0, ptr, bufsize, 32);
+ ahci_command_commit(ahci, cmd, px);
+ ahci_command_issue(ahci, cmd);
+ ahci_command_verify(ahci, cmd);
+ ahci_command_free(cmd);
+
+ /* Read back the guest's receive buffer into local memory */
+ qtest_bufread(ahci->parent->qts, ptr, rx, bufsize);
+ guest_free(&ahci->parent->alloc, ptr);
+
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ ahci_shutdown(ahci);
+
+ g_free(rx);
+ g_free(tx);
+}
+
+/*
+ * Write sector 1 with random data to make AHCI storage dirty
+ * Needed for flush tests so that flushes actually go though the block layer
+ */
+static void make_dirty(AHCIQState* ahci, uint8_t port)
+{
+ uint64_t ptr;
+ unsigned bufsize = 512;
+
+ ptr = ahci_alloc(ahci, bufsize);
+ g_assert(ptr);
+
+ ahci_guest_io(ahci, port, CMD_WRITE_DMA, ptr, bufsize, 1);
+ ahci_free(ahci, ptr);
+}
+
+static void test_flush(void)
+{
+ AHCIQState *ahci;
+ uint8_t port;
+
+ ahci = ahci_boot_and_enable(NULL);
+
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ make_dirty(ahci, port);
+
+ ahci_test_flush(ahci);
+ ahci_shutdown(ahci);
+}
+
+static void test_flush_retry(void)
+{
+ AHCIQState *ahci;
+ AHCICommand *cmd;
+ uint8_t port;
+
+ prepare_blkdebug_script(debug_path, "flush_to_disk");
+ ahci = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "format=%s,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path,
+ tmp_path, imgfmt);
+
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ /* Issue write so that flush actually goes to disk */
+ make_dirty(ahci, port);
+
+ /* Issue Flush Command and wait for error */
+ cmd = ahci_guest_io_halt(ahci, port, CMD_FLUSH_CACHE, 0, 0, 0);
+ ahci_guest_io_resume(ahci, cmd);
+
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Basic sanity test to boot a machine, find an AHCI device, and shutdown.
+ */
+static void test_migrate_sanity(void)
+{
+ AHCIQState *src, *dst;
+ char *uri = g_strdup_printf("unix:%s", mig_socket);
+
+ src = ahci_boot("-m 384 -M q35 "
+ "-drive if=ide,file=%s,format=%s ", tmp_path, imgfmt);
+ dst = ahci_boot("-m 384 -M q35 "
+ "-drive if=ide,file=%s,format=%s "
+ "-incoming %s", tmp_path, imgfmt, uri);
+
+ ahci_migrate(src, dst, uri);
+
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+ g_free(uri);
+}
+
+/**
+ * Simple migration test: Write a pattern, migrate, then read.
+ */
+static void ahci_migrate_simple(uint8_t cmd_read, uint8_t cmd_write)
+{
+ AHCIQState *src, *dst;
+ uint8_t px;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ char *uri = g_strdup_printf("unix:%s", mig_socket);
+
+ src = ahci_boot_and_enable("-m 384 -M q35 "
+ "-drive if=ide,format=%s,file=%s ",
+ imgfmt, tmp_path);
+ dst = ahci_boot("-m 384 -M q35 "
+ "-drive if=ide,format=%s,file=%s "
+ "-incoming %s", imgfmt, tmp_path, uri);
+
+ /* initialize */
+ px = ahci_port_select(src);
+ ahci_port_clear(src, px);
+
+ /* create pattern */
+ generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
+
+ /* Write, migrate, then read. */
+ ahci_io(src, px, cmd_write, tx, bufsize, 0);
+ ahci_migrate(src, dst, uri);
+ ahci_io(dst, px, cmd_read, rx, bufsize, 0);
+
+ /* Verify pattern */
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+ g_free(rx);
+ g_free(tx);
+ g_free(uri);
+}
+
+static void test_migrate_dma(void)
+{
+ ahci_migrate_simple(CMD_READ_DMA, CMD_WRITE_DMA);
+}
+
+static void test_migrate_ncq(void)
+{
+ ahci_migrate_simple(READ_FPDMA_QUEUED, WRITE_FPDMA_QUEUED);
+}
+
+/**
+ * Halted IO Error Test
+ *
+ * Simulate an error on first write, Try to write a pattern,
+ * Confirm the VM has stopped, resume the VM, verify command
+ * has completed, then read back the data and verify.
+ */
+static void ahci_halted_io_test(uint8_t cmd_read, uint8_t cmd_write)
+{
+ AHCIQState *ahci;
+ uint8_t port;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ uint64_t ptr;
+ AHCICommand *cmd;
+
+ prepare_blkdebug_script(debug_path, "write_aio");
+
+ ahci = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "format=%s,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path,
+ tmp_path, imgfmt);
+
+ /* Initialize and prepare */
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ /* create DMA source buffer and write pattern */
+ generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
+ ptr = ahci_alloc(ahci, bufsize);
+ g_assert(ptr);
+ qtest_memwrite(ahci->parent->qts, ptr, tx, bufsize);
+
+ /* Attempt to write (and fail) */
+ cmd = ahci_guest_io_halt(ahci, port, cmd_write,
+ ptr, bufsize, 0);
+
+ /* Attempt to resume the command */
+ ahci_guest_io_resume(ahci, cmd);
+ ahci_free(ahci, ptr);
+
+ /* Read back and verify */
+ ahci_io(ahci, port, cmd_read, rx, bufsize, 0);
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ /* Cleanup and go home */
+ ahci_shutdown(ahci);
+ g_free(rx);
+ g_free(tx);
+}
+
+static void test_halted_dma(void)
+{
+ ahci_halted_io_test(CMD_READ_DMA, CMD_WRITE_DMA);
+}
+
+static void test_halted_ncq(void)
+{
+ ahci_halted_io_test(READ_FPDMA_QUEUED, WRITE_FPDMA_QUEUED);
+}
+
+/**
+ * IO Error Migration Test
+ *
+ * Simulate an error on first write, Try to write a pattern,
+ * Confirm the VM has stopped, migrate, resume the VM,
+ * verify command has completed, then read back the data and verify.
+ */
+static void ahci_migrate_halted_io(uint8_t cmd_read, uint8_t cmd_write)
+{
+ AHCIQState *src, *dst;
+ uint8_t port;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ uint64_t ptr;
+ AHCICommand *cmd;
+ char *uri = g_strdup_printf("unix:%s", mig_socket);
+
+ prepare_blkdebug_script(debug_path, "write_aio");
+
+ src = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "format=%s,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path,
+ tmp_path, imgfmt);
+
+ dst = ahci_boot("-drive file=%s,if=none,id=drive0,"
+ "format=%s,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 "
+ "-incoming %s",
+ tmp_path, imgfmt, uri);
+
+ /* Initialize and prepare */
+ port = ahci_port_select(src);
+ ahci_port_clear(src, port);
+ generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
+
+ /* create DMA source buffer and write pattern */
+ ptr = ahci_alloc(src, bufsize);
+ g_assert(ptr);
+ qtest_memwrite(src->parent->qts, ptr, tx, bufsize);
+
+ /* Write, trigger the VM to stop, migrate, then resume. */
+ cmd = ahci_guest_io_halt(src, port, cmd_write,
+ ptr, bufsize, 0);
+ ahci_migrate(src, dst, uri);
+ ahci_guest_io_resume(dst, cmd);
+ ahci_free(dst, ptr);
+
+ /* Read back */
+ ahci_io(dst, port, cmd_read, rx, bufsize, 0);
+
+ /* Verify TX and RX are identical */
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ /* Cleanup and go home. */
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+ g_free(rx);
+ g_free(tx);
+ g_free(uri);
+}
+
+static void test_migrate_halted_dma(void)
+{
+ ahci_migrate_halted_io(CMD_READ_DMA, CMD_WRITE_DMA);
+}
+
+static void test_migrate_halted_ncq(void)
+{
+ ahci_migrate_halted_io(READ_FPDMA_QUEUED, WRITE_FPDMA_QUEUED);
+}
+
+/**
+ * Migration test: Try to flush, migrate, then resume.
+ */
+static void test_flush_migrate(void)
+{
+ AHCIQState *src, *dst;
+ AHCICommand *cmd;
+ uint8_t px;
+ char *uri = g_strdup_printf("unix:%s", mig_socket);
+
+ prepare_blkdebug_script(debug_path, "flush_to_disk");
+
+ src = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "cache=writeback,rerror=stop,werror=stop,"
+ "format=%s "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path, tmp_path, imgfmt);
+ dst = ahci_boot("-drive file=%s,if=none,id=drive0,"
+ "cache=writeback,rerror=stop,werror=stop,"
+ "format=%s "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 "
+ "-incoming %s", tmp_path, imgfmt, uri);
+
+ px = ahci_port_select(src);
+ ahci_port_clear(src, px);
+
+ /* Dirty device so that flush reaches disk */
+ make_dirty(src, px);
+
+ /* Issue Flush Command */
+ cmd = ahci_command_create(CMD_FLUSH_CACHE);
+ ahci_command_commit(src, cmd, px);
+ ahci_command_issue_async(src, cmd);
+ qtest_qmp_eventwait(src->parent->qts, "STOP");
+
+ /* Migrate over */
+ ahci_migrate(src, dst, uri);
+
+ /* Complete the command */
+ qtest_qmp_send(dst->parent->qts, "{'execute':'cont' }");
+ qtest_qmp_eventwait(dst->parent->qts, "RESUME");
+ ahci_command_wait(dst, cmd);
+ ahci_command_verify(dst, cmd);
+
+ ahci_command_free(cmd);
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+ g_free(uri);
+}
+
+static void test_max(void)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot_and_enable(NULL);
+ ahci_test_max(ahci);
+ ahci_shutdown(ahci);
+}
+
+static void test_reset(void)
+{
+ AHCIQState *ahci;
+ int i;
+
+ ahci = ahci_boot(NULL);
+ ahci_test_pci_spec(ahci);
+ ahci_pci_enable(ahci);
+
+ for (i = 0; i < 2; i++) {
+ ahci_test_hba_spec(ahci);
+ ahci_hba_enable(ahci);
+ ahci_test_identify(ahci);
+ ahci_test_io_rw_simple(ahci, 4096, 0,
+ CMD_READ_DMA_EXT,
+ CMD_WRITE_DMA_EXT);
+ ahci_set(ahci, AHCI_GHC, AHCI_GHC_HR);
+ ahci_clean_mem(ahci);
+ }
+
+ ahci_shutdown(ahci);
+}
+
+static void test_ncq_simple(void)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot_and_enable(NULL);
+ ahci_test_io_rw_simple(ahci, 4096, 0,
+ READ_FPDMA_QUEUED,
+ WRITE_FPDMA_QUEUED);
+ ahci_shutdown(ahci);
+}
+
+static int prepare_iso(size_t size, unsigned char **buf, char **name)
+{
+ char cdrom_path[] = "/tmp/qtest.iso.XXXXXX";
+ unsigned char *patt;
+ ssize_t ret;
+ int fd = mkstemp(cdrom_path);
+
+ g_assert(buf);
+ g_assert(name);
+ patt = g_malloc(size);
+
+ /* Generate a pattern and build a CDROM image to read from */
+ generate_pattern(patt, size, ATAPI_SECTOR_SIZE);
+ ret = write(fd, patt, size);
+ g_assert(ret == size);
+
+ *name = g_strdup(cdrom_path);
+ *buf = patt;
+ return fd;
+}
+
+static void remove_iso(int fd, char *name)
+{
+ unlink(name);
+ g_free(name);
+ close(fd);
+}
+
+static int ahci_cb_cmp_buff(AHCIQState *ahci, AHCICommand *cmd,
+ const AHCIOpts *opts)
+{
+ unsigned char *tx = opts->opaque;
+ unsigned char *rx;
+
+ if (!opts->size) {
+ return 0;
+ }
+
+ rx = g_malloc0(opts->size);
+ qtest_bufread(ahci->parent->qts, opts->buffer, rx, opts->size);
+ g_assert_cmphex(memcmp(tx, rx, opts->size), ==, 0);
+ g_free(rx);
+
+ return 0;
+}
+
+static void ahci_test_cdrom(int nsectors, bool dma, uint8_t cmd,
+ bool override_bcl, uint16_t bcl)
+{
+ AHCIQState *ahci;
+ unsigned char *tx;
+ char *iso;
+ int fd;
+ AHCIOpts opts = {
+ .size = (ATAPI_SECTOR_SIZE * nsectors),
+ .atapi = true,
+ .atapi_dma = dma,
+ .post_cb = ahci_cb_cmp_buff,
+ .set_bcl = override_bcl,
+ .bcl = bcl,
+ };
+ uint64_t iso_size = ATAPI_SECTOR_SIZE * (nsectors + 1);
+
+ /* Prepare ISO and fill 'tx' buffer */
+ fd = prepare_iso(iso_size, &tx, &iso);
+ opts.opaque = tx;
+
+ /* Standard startup wonkery, but use ide-cd and our special iso file */
+ ahci = ahci_boot_and_enable("-drive if=none,id=drive0,file=%s,format=raw "
+ "-M q35 "
+ "-device ide-cd,drive=drive0 ", iso);
+
+ /* Build & Send AHCI command */
+ ahci_exec(ahci, ahci_port_select(ahci), cmd, &opts);
+
+ /* Cleanup */
+ g_free(tx);
+ ahci_shutdown(ahci);
+ remove_iso(fd, iso);
+}
+
+static void ahci_test_cdrom_read10(int nsectors, bool dma)
+{
+ ahci_test_cdrom(nsectors, dma, CMD_ATAPI_READ_10, false, 0);
+}
+
+static void test_cdrom_dma(void)
+{
+ ahci_test_cdrom_read10(1, true);
+}
+
+static void test_cdrom_dma_multi(void)
+{
+ ahci_test_cdrom_read10(3, true);
+}
+
+static void test_cdrom_pio(void)
+{
+ ahci_test_cdrom_read10(1, false);
+}
+
+static void test_cdrom_pio_multi(void)
+{
+ ahci_test_cdrom_read10(3, false);
+}
+
+/* Regression test: Test that a READ_CD command with a BCL of 0 but a size of 0
+ * completes as a NOP instead of erroring out. */
+static void test_atapi_bcl(void)
+{
+ ahci_test_cdrom(0, false, CMD_ATAPI_READ_CD, true, 0);
+}
+
+
+static void atapi_wait_tray(AHCIQState *ahci, bool open)
+{
+ QDict *rsp = qtest_qmp_eventwait_ref(ahci->parent->qts,
+ "DEVICE_TRAY_MOVED");
+ QDict *data = qdict_get_qdict(rsp, "data");
+ if (open) {
+ g_assert(qdict_get_bool(data, "tray-open"));
+ } else {
+ g_assert(!qdict_get_bool(data, "tray-open"));
+ }
+ qobject_unref(rsp);
+}
+
+static void test_atapi_tray(void)
+{
+ AHCIQState *ahci;
+ unsigned char *tx;
+ char *iso;
+ int fd;
+ uint8_t port, sense, asc;
+ uint64_t iso_size = ATAPI_SECTOR_SIZE;
+ QDict *rsp;
+
+ fd = prepare_iso(iso_size, &tx, &iso);
+ ahci = ahci_boot_and_enable("-blockdev node-name=drive0,driver=file,filename=%s "
+ "-M q35 "
+ "-device ide-cd,id=cd0,drive=drive0 ", iso);
+ port = ahci_port_select(ahci);
+
+ ahci_atapi_eject(ahci, port);
+ atapi_wait_tray(ahci, true);
+
+ ahci_atapi_load(ahci, port);
+ atapi_wait_tray(ahci, false);
+
+ /* Remove media */
+ qtest_qmp_send(ahci->parent->qts, "{'execute': 'blockdev-open-tray', "
+ "'arguments': {'id': 'cd0'}}");
+ atapi_wait_tray(ahci, true);
+ rsp = qtest_qmp_receive(ahci->parent->qts);
+ qobject_unref(rsp);
+
+ qmp_discard_response(ahci->parent->qts,
+ "{'execute': 'blockdev-remove-medium', "
+ "'arguments': {'id': 'cd0'}}");
+
+ /* Test the tray without a medium */
+ ahci_atapi_load(ahci, port);
+ atapi_wait_tray(ahci, false);
+
+ ahci_atapi_eject(ahci, port);
+ atapi_wait_tray(ahci, true);
+
+ /* Re-insert media */
+ qmp_discard_response(ahci->parent->qts,
+ "{'execute': 'blockdev-add', "
+ "'arguments': {'node-name': 'node0', "
+ "'driver': 'raw', "
+ "'file': { 'driver': 'file', "
+ "'filename': %s }}}", iso);
+ qmp_discard_response(ahci->parent->qts,
+ "{'execute': 'blockdev-insert-medium',"
+ "'arguments': { 'id': 'cd0', "
+ "'node-name': 'node0' }}");
+
+ /* Again, the event shows up first */
+ qtest_qmp_send(ahci->parent->qts, "{'execute': 'blockdev-close-tray', "
+ "'arguments': {'id': 'cd0'}}");
+ atapi_wait_tray(ahci, false);
+ rsp = qtest_qmp_receive(ahci->parent->qts);
+ qobject_unref(rsp);
+
+ /* Now, to convince ATAPI we understand the media has changed... */
+ ahci_atapi_test_ready(ahci, port, false, SENSE_NOT_READY);
+ ahci_atapi_get_sense(ahci, port, &sense, &asc);
+ g_assert_cmpuint(sense, ==, SENSE_NOT_READY);
+ g_assert_cmpuint(asc, ==, ASC_MEDIUM_NOT_PRESENT);
+
+ ahci_atapi_test_ready(ahci, port, false, SENSE_UNIT_ATTENTION);
+ ahci_atapi_get_sense(ahci, port, &sense, &asc);
+ g_assert_cmpuint(sense, ==, SENSE_UNIT_ATTENTION);
+ g_assert_cmpuint(asc, ==, ASC_MEDIUM_MAY_HAVE_CHANGED);
+
+ ahci_atapi_test_ready(ahci, port, true, SENSE_NO_SENSE);
+ ahci_atapi_get_sense(ahci, port, &sense, &asc);
+ g_assert_cmpuint(sense, ==, SENSE_NO_SENSE);
+
+ /* Final tray test. */
+ ahci_atapi_eject(ahci, port);
+ atapi_wait_tray(ahci, true);
+
+ ahci_atapi_load(ahci, port);
+ atapi_wait_tray(ahci, false);
+
+ /* Cleanup */
+ g_free(tx);
+ ahci_shutdown(ahci);
+ remove_iso(fd, iso);
+}
+
+/******************************************************************************/
+/* AHCI I/O Test Matrix Definitions */
+
+enum BuffLen {
+ LEN_BEGIN = 0,
+ LEN_SIMPLE = LEN_BEGIN,
+ LEN_DOUBLE,
+ LEN_LONG,
+ LEN_SHORT,
+ NUM_LENGTHS
+};
+
+static const char *buff_len_str[NUM_LENGTHS] = { "simple", "double",
+ "long", "short" };
+
+enum AddrMode {
+ ADDR_MODE_BEGIN = 0,
+ ADDR_MODE_LBA28 = ADDR_MODE_BEGIN,
+ ADDR_MODE_LBA48,
+ NUM_ADDR_MODES
+};
+
+static const char *addr_mode_str[NUM_ADDR_MODES] = { "lba28", "lba48" };
+
+enum IOMode {
+ MODE_BEGIN = 0,
+ MODE_PIO = MODE_BEGIN,
+ MODE_DMA,
+ NUM_MODES
+};
+
+static const char *io_mode_str[NUM_MODES] = { "pio", "dma" };
+
+enum IOOps {
+ IO_BEGIN = 0,
+ IO_READ = IO_BEGIN,
+ IO_WRITE,
+ NUM_IO_OPS
+};
+
+enum OffsetType {
+ OFFSET_BEGIN = 0,
+ OFFSET_ZERO = OFFSET_BEGIN,
+ OFFSET_LOW,
+ OFFSET_HIGH,
+ NUM_OFFSETS
+};
+
+static const char *offset_str[NUM_OFFSETS] = { "zero", "low", "high" };
+
+typedef struct AHCIIOTestOptions {
+ enum BuffLen length;
+ enum AddrMode address_type;
+ enum IOMode io_type;
+ enum OffsetType offset;
+} AHCIIOTestOptions;
+
+static uint64_t offset_sector(enum OffsetType ofst,
+ enum AddrMode addr_type,
+ uint64_t buffsize)
+{
+ uint64_t ceil;
+ uint64_t nsectors;
+
+ switch (ofst) {
+ case OFFSET_ZERO:
+ return 0;
+ case OFFSET_LOW:
+ return 1;
+ case OFFSET_HIGH:
+ ceil = (addr_type == ADDR_MODE_LBA28) ? 0xfffffff : 0xffffffffffff;
+ ceil = MIN(ceil, mb_to_sectors(test_image_size_mb) - 1);
+ nsectors = buffsize / AHCI_SECTOR_SIZE;
+ return ceil - nsectors + 1;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/**
+ * Table of possible I/O ATA commands given a set of enumerations.
+ */
+static const uint8_t io_cmds[NUM_MODES][NUM_ADDR_MODES][NUM_IO_OPS] = {
+ [MODE_PIO] = {
+ [ADDR_MODE_LBA28] = {
+ [IO_READ] = CMD_READ_PIO,
+ [IO_WRITE] = CMD_WRITE_PIO },
+ [ADDR_MODE_LBA48] = {
+ [IO_READ] = CMD_READ_PIO_EXT,
+ [IO_WRITE] = CMD_WRITE_PIO_EXT }
+ },
+ [MODE_DMA] = {
+ [ADDR_MODE_LBA28] = {
+ [IO_READ] = CMD_READ_DMA,
+ [IO_WRITE] = CMD_WRITE_DMA },
+ [ADDR_MODE_LBA48] = {
+ [IO_READ] = CMD_READ_DMA_EXT,
+ [IO_WRITE] = CMD_WRITE_DMA_EXT }
+ }
+};
+
+/**
+ * Test a Read/Write pattern using various commands, addressing modes,
+ * transfer modes, and buffer sizes.
+ */
+static void test_io_rw_interface(enum AddrMode lba48, enum IOMode dma,
+ unsigned bufsize, uint64_t sector)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot_and_enable(NULL);
+ ahci_test_io_rw_simple(ahci, bufsize, sector,
+ io_cmds[dma][lba48][IO_READ],
+ io_cmds[dma][lba48][IO_WRITE]);
+ ahci_shutdown(ahci);
+}
+
+/**
+ * Demultiplex the test data and invoke the actual test routine.
+ */
+static void test_io_interface(gconstpointer opaque)
+{
+ AHCIIOTestOptions *opts = (AHCIIOTestOptions *)opaque;
+ unsigned bufsize;
+ uint64_t sector;
+
+ switch (opts->length) {
+ case LEN_SIMPLE:
+ bufsize = 4096;
+ break;
+ case LEN_DOUBLE:
+ bufsize = 8192;
+ break;
+ case LEN_LONG:
+ bufsize = 4096 * 64;
+ break;
+ case LEN_SHORT:
+ bufsize = 512;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ sector = offset_sector(opts->offset, opts->address_type, bufsize);
+ test_io_rw_interface(opts->address_type, opts->io_type, bufsize, sector);
+ g_free(opts);
+ return;
+}
+
+static void create_ahci_io_test(enum IOMode type, enum AddrMode addr,
+ enum BuffLen len, enum OffsetType offset)
+{
+ char *name;
+ AHCIIOTestOptions *opts;
+
+ opts = g_new(AHCIIOTestOptions, 1);
+ opts->length = len;
+ opts->address_type = addr;
+ opts->io_type = type;
+ opts->offset = offset;
+
+ name = g_strdup_printf("ahci/io/%s/%s/%s/%s",
+ io_mode_str[type],
+ addr_mode_str[addr],
+ buff_len_str[len],
+ offset_str[offset]);
+
+ if ((addr == ADDR_MODE_LBA48) && (offset == OFFSET_HIGH) &&
+ (mb_to_sectors(test_image_size_mb) <= 0xFFFFFFF)) {
+ g_test_message("%s: skipped; test image too small", name);
+ g_free(opts);
+ g_free(name);
+ return;
+ }
+
+ qtest_add_data_func(name, opts, test_io_interface);
+ g_free(name);
+}
+
+/******************************************************************************/
+
+int main(int argc, char **argv)
+{
+ const char *arch;
+ int ret;
+ int fd;
+ int c;
+ int i, j, k, m;
+
+ static struct option long_options[] = {
+ {"pedantic", no_argument, 0, 'p' },
+ {0, 0, 0, 0},
+ };
+
+ /* Should be first to utilize g_test functionality, So we can see errors. */
+ g_test_init(&argc, &argv, NULL);
+
+ while (1) {
+ c = getopt_long(argc, argv, "", long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case -1:
+ break;
+ case 'p':
+ ahci_pedantic = 1;
+ break;
+ default:
+ fprintf(stderr, "Unrecognized ahci_test option.\n");
+ g_assert_not_reached();
+ }
+ }
+
+ /* Check architecture */
+ arch = qtest_get_arch();
+ if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) {
+ g_test_message("Skipping test for non-x86");
+ return 0;
+ }
+
+ /* Create a temporary image */
+ fd = mkstemp(tmp_path);
+ g_assert(fd >= 0);
+ if (have_qemu_img()) {
+ imgfmt = "qcow2";
+ test_image_size_mb = TEST_IMAGE_SIZE_MB_LARGE;
+ mkqcow2(tmp_path, TEST_IMAGE_SIZE_MB_LARGE);
+ } else {
+ g_test_message("QTEST_QEMU_IMG not set or qemu-img missing; "
+ "skipping LBA48 high-sector tests");
+ imgfmt = "raw";
+ test_image_size_mb = TEST_IMAGE_SIZE_MB_SMALL;
+ ret = ftruncate(fd, test_image_size_mb * 1024 * 1024);
+ g_assert(ret == 0);
+ }
+ close(fd);
+
+ /* Create temporary blkdebug instructions */
+ fd = mkstemp(debug_path);
+ g_assert(fd >= 0);
+ close(fd);
+
+ /* Reserve a hollow file to use as a socket for migration tests */
+ fd = mkstemp(mig_socket);
+ g_assert(fd >= 0);
+ close(fd);
+
+ /* Run the tests */
+ qtest_add_func("/ahci/sanity", test_sanity);
+ qtest_add_func("/ahci/pci_spec", test_pci_spec);
+ qtest_add_func("/ahci/pci_enable", test_pci_enable);
+ qtest_add_func("/ahci/hba_spec", test_hba_spec);
+ qtest_add_func("/ahci/hba_enable", test_hba_enable);
+ qtest_add_func("/ahci/identify", test_identify);
+
+ for (i = MODE_BEGIN; i < NUM_MODES; i++) {
+ for (j = ADDR_MODE_BEGIN; j < NUM_ADDR_MODES; j++) {
+ for (k = LEN_BEGIN; k < NUM_LENGTHS; k++) {
+ for (m = OFFSET_BEGIN; m < NUM_OFFSETS; m++) {
+ create_ahci_io_test(i, j, k, m);
+ }
+ }
+ }
+ }
+
+ qtest_add_func("/ahci/io/dma/lba28/fragmented", test_dma_fragmented);
+
+ qtest_add_func("/ahci/flush/simple", test_flush);
+ qtest_add_func("/ahci/flush/retry", test_flush_retry);
+ qtest_add_func("/ahci/flush/migrate", test_flush_migrate);
+
+ qtest_add_func("/ahci/migrate/sanity", test_migrate_sanity);
+ qtest_add_func("/ahci/migrate/dma/simple", test_migrate_dma);
+ qtest_add_func("/ahci/io/dma/lba28/retry", test_halted_dma);
+ qtest_add_func("/ahci/migrate/dma/halted", test_migrate_halted_dma);
+
+ qtest_add_func("/ahci/max", test_max);
+ qtest_add_func("/ahci/reset", test_reset);
+
+ qtest_add_func("/ahci/io/ncq/simple", test_ncq_simple);
+ qtest_add_func("/ahci/migrate/ncq/simple", test_migrate_ncq);
+ qtest_add_func("/ahci/io/ncq/retry", test_halted_ncq);
+ qtest_add_func("/ahci/migrate/ncq/halted", test_migrate_halted_ncq);
+
+ qtest_add_func("/ahci/cdrom/dma/single", test_cdrom_dma);
+ qtest_add_func("/ahci/cdrom/dma/multi", test_cdrom_dma_multi);
+ qtest_add_func("/ahci/cdrom/pio/single", test_cdrom_pio);
+ qtest_add_func("/ahci/cdrom/pio/multi", test_cdrom_pio_multi);
+
+ qtest_add_func("/ahci/cdrom/pio/bcl", test_atapi_bcl);
+ qtest_add_func("/ahci/cdrom/eject", test_atapi_tray);
+
+ ret = g_test_run();
+
+ /* Cleanup */
+ unlink(tmp_path);
+ unlink(debug_path);
+ unlink(mig_socket);
+
+ return ret;
+}
diff --git a/tests/qtest/arm-cpu-features.c b/tests/qtest/arm-cpu-features.c
new file mode 100644
index 0000000000..bef3ed24b6
--- /dev/null
+++ b/tests/qtest/arm-cpu-features.c
@@ -0,0 +1,559 @@
+/*
+ * Arm CPU feature test cases
+ *
+ * Copyright (c) 2019 Red Hat Inc.
+ * Authors:
+ * Andrew Jones <drjones@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qjson.h"
+
+/*
+ * We expect the SVE max-vq to be 16. Also it must be <= 64
+ * for our test code, otherwise 'vls' can't just be a uint64_t.
+ */
+#define SVE_MAX_VQ 16
+
+#define MACHINE "-machine virt,gic-version=max -accel tcg "
+#define MACHINE_KVM "-machine virt,gic-version=max -accel kvm -accel tcg "
+#define QUERY_HEAD "{ 'execute': 'query-cpu-model-expansion', " \
+ " 'arguments': { 'type': 'full', "
+#define QUERY_TAIL "}}"
+
+static bool kvm_enabled(QTestState *qts)
+{
+ QDict *resp, *qdict;
+ bool enabled;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-kvm' }");
+ g_assert(qdict_haskey(resp, "return"));
+ qdict = qdict_get_qdict(resp, "return");
+ g_assert(qdict_haskey(qdict, "enabled"));
+ enabled = qdict_get_bool(qdict, "enabled");
+ qobject_unref(resp);
+
+ return enabled;
+}
+
+static QDict *do_query_no_props(QTestState *qts, const char *cpu_type)
+{
+ return qtest_qmp(qts, QUERY_HEAD "'model': { 'name': %s }"
+ QUERY_TAIL, cpu_type);
+}
+
+static QDict *do_query(QTestState *qts, const char *cpu_type,
+ const char *fmt, ...)
+{
+ QDict *resp;
+
+ if (fmt) {
+ QDict *args;
+ va_list ap;
+
+ va_start(ap, fmt);
+ args = qdict_from_vjsonf_nofail(fmt, ap);
+ va_end(ap);
+
+ resp = qtest_qmp(qts, QUERY_HEAD "'model': { 'name': %s, "
+ "'props': %p }"
+ QUERY_TAIL, cpu_type, args);
+ } else {
+ resp = do_query_no_props(qts, cpu_type);
+ }
+
+ return resp;
+}
+
+static const char *resp_get_error(QDict *resp)
+{
+ QDict *qdict;
+
+ g_assert(resp);
+
+ qdict = qdict_get_qdict(resp, "error");
+ if (qdict) {
+ return qdict_get_str(qdict, "desc");
+ }
+
+ return NULL;
+}
+
+#define assert_error(qts, cpu_type, expected_error, fmt, ...) \
+({ \
+ QDict *_resp; \
+ const char *_error; \
+ \
+ _resp = do_query(qts, cpu_type, fmt, ##__VA_ARGS__); \
+ g_assert(_resp); \
+ _error = resp_get_error(_resp); \
+ g_assert(_error); \
+ g_assert(g_str_equal(_error, expected_error)); \
+ qobject_unref(_resp); \
+})
+
+static bool resp_has_props(QDict *resp)
+{
+ QDict *qdict;
+
+ g_assert(resp);
+
+ if (!qdict_haskey(resp, "return")) {
+ return false;
+ }
+ qdict = qdict_get_qdict(resp, "return");
+
+ if (!qdict_haskey(qdict, "model")) {
+ return false;
+ }
+ qdict = qdict_get_qdict(qdict, "model");
+
+ return qdict_haskey(qdict, "props");
+}
+
+static QDict *resp_get_props(QDict *resp)
+{
+ QDict *qdict;
+
+ g_assert(resp);
+ g_assert(resp_has_props(resp));
+
+ qdict = qdict_get_qdict(resp, "return");
+ qdict = qdict_get_qdict(qdict, "model");
+ qdict = qdict_get_qdict(qdict, "props");
+
+ return qdict;
+}
+
+static bool resp_get_feature(QDict *resp, const char *feature)
+{
+ QDict *props;
+
+ g_assert(resp);
+ g_assert(resp_has_props(resp));
+ props = resp_get_props(resp);
+ g_assert(qdict_get(props, feature));
+ return qdict_get_bool(props, feature);
+}
+
+#define assert_has_feature(qts, cpu_type, feature) \
+({ \
+ QDict *_resp = do_query_no_props(qts, cpu_type); \
+ g_assert(_resp); \
+ g_assert(resp_has_props(_resp)); \
+ g_assert(qdict_get(resp_get_props(_resp), feature)); \
+ qobject_unref(_resp); \
+})
+
+#define assert_has_not_feature(qts, cpu_type, feature) \
+({ \
+ QDict *_resp = do_query_no_props(qts, cpu_type); \
+ g_assert(_resp); \
+ g_assert(!resp_has_props(_resp) || \
+ !qdict_get(resp_get_props(_resp), feature)); \
+ qobject_unref(_resp); \
+})
+
+static void assert_type_full(QTestState *qts)
+{
+ const char *error;
+ QDict *resp;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-cpu-model-expansion', "
+ "'arguments': { 'type': 'static', "
+ "'model': { 'name': 'foo' }}}");
+ g_assert(resp);
+ error = resp_get_error(resp);
+ g_assert(error);
+ g_assert(g_str_equal(error,
+ "The requested expansion type is not supported"));
+ qobject_unref(resp);
+}
+
+static void assert_bad_props(QTestState *qts, const char *cpu_type)
+{
+ const char *error;
+ QDict *resp;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-cpu-model-expansion', "
+ "'arguments': { 'type': 'full', "
+ "'model': { 'name': %s, "
+ "'props': false }}}",
+ cpu_type);
+ g_assert(resp);
+ error = resp_get_error(resp);
+ g_assert(error);
+ g_assert(g_str_equal(error,
+ "Invalid parameter type for 'props', expected: dict"));
+ qobject_unref(resp);
+}
+
+static uint64_t resp_get_sve_vls(QDict *resp)
+{
+ QDict *props;
+ const QDictEntry *e;
+ uint64_t vls = 0;
+ int n = 0;
+
+ g_assert(resp);
+ g_assert(resp_has_props(resp));
+
+ props = resp_get_props(resp);
+
+ for (e = qdict_first(props); e; e = qdict_next(props, e)) {
+ if (strlen(e->key) > 3 && !strncmp(e->key, "sve", 3) &&
+ g_ascii_isdigit(e->key[3])) {
+ char *endptr;
+ int bits;
+
+ bits = g_ascii_strtoll(&e->key[3], &endptr, 10);
+ if (!bits || *endptr != '\0') {
+ continue;
+ }
+
+ if (qdict_get_bool(props, e->key)) {
+ vls |= BIT_ULL((bits / 128) - 1);
+ }
+ ++n;
+ }
+ }
+
+ g_assert(n == SVE_MAX_VQ);
+
+ return vls;
+}
+
+#define assert_sve_vls(qts, cpu_type, expected_vls, fmt, ...) \
+({ \
+ QDict *_resp = do_query(qts, cpu_type, fmt, ##__VA_ARGS__); \
+ g_assert(_resp); \
+ g_assert(resp_has_props(_resp)); \
+ g_assert(resp_get_sve_vls(_resp) == expected_vls); \
+ qobject_unref(_resp); \
+})
+
+static void sve_tests_default(QTestState *qts, const char *cpu_type)
+{
+ /*
+ * With no sve-max-vq or sve<N> properties on the command line
+ * the default is to have all vector lengths enabled. This also
+ * tests that 'sve' is 'on' by default.
+ */
+ assert_sve_vls(qts, cpu_type, BIT_ULL(SVE_MAX_VQ) - 1, NULL);
+
+ /* With SVE off, all vector lengths should also be off. */
+ assert_sve_vls(qts, cpu_type, 0, "{ 'sve': false }");
+
+ /* With SVE on, we must have at least one vector length enabled. */
+ assert_error(qts, cpu_type, "cannot disable sve128", "{ 'sve128': false }");
+
+ /* Basic enable/disable tests. */
+ assert_sve_vls(qts, cpu_type, 0x7, "{ 'sve384': true }");
+ assert_sve_vls(qts, cpu_type, ((BIT_ULL(SVE_MAX_VQ) - 1) & ~BIT_ULL(2)),
+ "{ 'sve384': false }");
+
+ /*
+ * ---------------------------------------------------------------------
+ * power-of-two(vq) all-power- can can
+ * of-two(< vq) enable disable
+ * ---------------------------------------------------------------------
+ * vq < max_vq no MUST* yes yes
+ * vq < max_vq yes MUST* yes no
+ * ---------------------------------------------------------------------
+ * vq == max_vq n/a MUST* yes** yes**
+ * ---------------------------------------------------------------------
+ * vq > max_vq n/a no no yes
+ * vq > max_vq n/a yes yes yes
+ * ---------------------------------------------------------------------
+ *
+ * [*] "MUST" means this requirement must already be satisfied,
+ * otherwise 'max_vq' couldn't itself be enabled.
+ *
+ * [**] Not testable with the QMP interface, only with the command line.
+ */
+
+ /* max_vq := 8 */
+ assert_sve_vls(qts, cpu_type, 0x8b, "{ 'sve1024': true }");
+
+ /* max_vq := 8, vq < max_vq, !power-of-two(vq) */
+ assert_sve_vls(qts, cpu_type, 0x8f,
+ "{ 'sve1024': true, 'sve384': true }");
+ assert_sve_vls(qts, cpu_type, 0x8b,
+ "{ 'sve1024': true, 'sve384': false }");
+
+ /* max_vq := 8, vq < max_vq, power-of-two(vq) */
+ assert_sve_vls(qts, cpu_type, 0x8b,
+ "{ 'sve1024': true, 'sve256': true }");
+ assert_error(qts, cpu_type, "cannot disable sve256",
+ "{ 'sve1024': true, 'sve256': false }");
+
+ /* max_vq := 3, vq > max_vq, !all-power-of-two(< vq) */
+ assert_error(qts, cpu_type, "cannot disable sve512",
+ "{ 'sve384': true, 'sve512': false, 'sve640': true }");
+
+ /*
+ * We can disable power-of-two vector lengths when all larger lengths
+ * are also disabled. We only need to disable the power-of-two length,
+ * as all non-enabled larger lengths will then be auto-disabled.
+ */
+ assert_sve_vls(qts, cpu_type, 0x7, "{ 'sve512': false }");
+
+ /* max_vq := 3, vq > max_vq, all-power-of-two(< vq) */
+ assert_sve_vls(qts, cpu_type, 0x1f,
+ "{ 'sve384': true, 'sve512': true, 'sve640': true }");
+ assert_sve_vls(qts, cpu_type, 0xf,
+ "{ 'sve384': true, 'sve512': true, 'sve640': false }");
+}
+
+static void sve_tests_sve_max_vq_8(const void *data)
+{
+ QTestState *qts;
+
+ qts = qtest_init(MACHINE "-cpu max,sve-max-vq=8");
+
+ assert_sve_vls(qts, "max", BIT_ULL(8) - 1, NULL);
+
+ /*
+ * Disabling the max-vq set by sve-max-vq is not allowed, but
+ * of course enabling it is OK.
+ */
+ assert_error(qts, "max", "cannot disable sve1024", "{ 'sve1024': false }");
+ assert_sve_vls(qts, "max", 0xff, "{ 'sve1024': true }");
+
+ /*
+ * Enabling anything larger than max-vq set by sve-max-vq is not
+ * allowed, but of course disabling everything larger is OK.
+ */
+ assert_error(qts, "max", "cannot enable sve1152", "{ 'sve1152': true }");
+ assert_sve_vls(qts, "max", 0xff, "{ 'sve1152': false }");
+
+ /*
+ * We can enable/disable non power-of-two lengths smaller than the
+ * max-vq set by sve-max-vq, but, while we can enable power-of-two
+ * lengths, we can't disable them.
+ */
+ assert_sve_vls(qts, "max", 0xff, "{ 'sve384': true }");
+ assert_sve_vls(qts, "max", 0xfb, "{ 'sve384': false }");
+ assert_sve_vls(qts, "max", 0xff, "{ 'sve256': true }");
+ assert_error(qts, "max", "cannot disable sve256", "{ 'sve256': false }");
+
+ qtest_quit(qts);
+}
+
+static void sve_tests_sve_off(const void *data)
+{
+ QTestState *qts;
+
+ qts = qtest_init(MACHINE "-cpu max,sve=off");
+
+ /* SVE is off, so the map should be empty. */
+ assert_sve_vls(qts, "max", 0, NULL);
+
+ /* The map stays empty even if we turn lengths off. */
+ assert_sve_vls(qts, "max", 0, "{ 'sve128': false }");
+
+ /* It's an error to enable lengths when SVE is off. */
+ assert_error(qts, "max", "cannot enable sve128", "{ 'sve128': true }");
+
+ /* With SVE re-enabled we should get all vector lengths enabled. */
+ assert_sve_vls(qts, "max", BIT_ULL(SVE_MAX_VQ) - 1, "{ 'sve': true }");
+
+ /* Or enable SVE with just specific vector lengths. */
+ assert_sve_vls(qts, "max", 0x3,
+ "{ 'sve': true, 'sve128': true, 'sve256': true }");
+
+ qtest_quit(qts);
+}
+
+static void sve_tests_sve_off_kvm(const void *data)
+{
+ QTestState *qts;
+
+ qts = qtest_init(MACHINE_KVM "-cpu max,sve=off");
+
+ /*
+ * We don't know if this host supports SVE so we don't
+ * attempt to test enabling anything. We only test that
+ * everything is disabled (as it should be with sve=off)
+ * and that using sve<N>=off to explicitly disable vector
+ * lengths is OK too.
+ */
+ assert_sve_vls(qts, "max", 0, NULL);
+ assert_sve_vls(qts, "max", 0, "{ 'sve128': false }");
+
+ qtest_quit(qts);
+}
+
+static void test_query_cpu_model_expansion(const void *data)
+{
+ QTestState *qts;
+
+ qts = qtest_init(MACHINE "-cpu max");
+
+ /* Test common query-cpu-model-expansion input validation */
+ assert_type_full(qts);
+ assert_bad_props(qts, "max");
+ assert_error(qts, "foo", "The CPU type 'foo' is not a recognized "
+ "ARM CPU type", NULL);
+ assert_error(qts, "max", "Parameter 'not-a-prop' is unexpected",
+ "{ 'not-a-prop': false }");
+ assert_error(qts, "host", "The CPU type 'host' requires KVM", NULL);
+
+ /* Test expected feature presence/absence for some cpu types */
+ assert_has_feature(qts, "max", "pmu");
+ assert_has_feature(qts, "cortex-a15", "pmu");
+ assert_has_not_feature(qts, "cortex-a15", "aarch64");
+
+ if (g_str_equal(qtest_get_arch(), "aarch64")) {
+ assert_has_feature(qts, "max", "aarch64");
+ assert_has_feature(qts, "max", "sve");
+ assert_has_feature(qts, "max", "sve128");
+ assert_has_feature(qts, "cortex-a57", "pmu");
+ assert_has_feature(qts, "cortex-a57", "aarch64");
+
+ sve_tests_default(qts, "max");
+
+ /* Test that features that depend on KVM generate errors without. */
+ assert_error(qts, "max",
+ "'aarch64' feature cannot be disabled "
+ "unless KVM is enabled and 32-bit EL1 "
+ "is supported",
+ "{ 'aarch64': false }");
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_query_cpu_model_expansion_kvm(const void *data)
+{
+ QTestState *qts;
+
+ qts = qtest_init(MACHINE_KVM "-cpu max");
+
+ /*
+ * These tests target the 'host' CPU type, so KVM must be enabled.
+ */
+ if (!kvm_enabled(qts)) {
+ qtest_quit(qts);
+ return;
+ }
+
+ if (g_str_equal(qtest_get_arch(), "aarch64")) {
+ bool kvm_supports_sve;
+ char max_name[8], name[8];
+ uint32_t max_vq, vq;
+ uint64_t vls;
+ QDict *resp;
+ char *error;
+
+ assert_has_feature(qts, "host", "aarch64");
+ assert_has_feature(qts, "host", "pmu");
+
+ assert_error(qts, "cortex-a15",
+ "We cannot guarantee the CPU type 'cortex-a15' works "
+ "with KVM on this host", NULL);
+
+ assert_has_feature(qts, "host", "sve");
+ resp = do_query_no_props(qts, "host");
+ kvm_supports_sve = resp_get_feature(resp, "sve");
+ vls = resp_get_sve_vls(resp);
+ qobject_unref(resp);
+
+ if (kvm_supports_sve) {
+ g_assert(vls != 0);
+ max_vq = 64 - __builtin_clzll(vls);
+ sprintf(max_name, "sve%d", max_vq * 128);
+
+ /* Enabling a supported length is of course fine. */
+ assert_sve_vls(qts, "host", vls, "{ %s: true }", max_name);
+
+ /* Get the next supported length smaller than max-vq. */
+ vq = 64 - __builtin_clzll(vls & ~BIT_ULL(max_vq - 1));
+ if (vq) {
+ /*
+ * We have at least one length smaller than max-vq,
+ * so we can disable max-vq.
+ */
+ assert_sve_vls(qts, "host", (vls & ~BIT_ULL(max_vq - 1)),
+ "{ %s: false }", max_name);
+
+ /*
+ * Smaller, supported vector lengths cannot be disabled
+ * unless all larger, supported vector lengths are also
+ * disabled.
+ */
+ sprintf(name, "sve%d", vq * 128);
+ error = g_strdup_printf("cannot disable %s", name);
+ assert_error(qts, "host", error,
+ "{ %s: true, %s: false }",
+ max_name, name);
+ g_free(error);
+ }
+
+ /*
+ * The smallest, supported vector length is required, because
+ * we need at least one vector length enabled.
+ */
+ vq = __builtin_ffsll(vls);
+ sprintf(name, "sve%d", vq * 128);
+ error = g_strdup_printf("cannot disable %s", name);
+ assert_error(qts, "host", error, "{ %s: false }", name);
+ g_free(error);
+
+ /* Get an unsupported length. */
+ for (vq = 1; vq <= max_vq; ++vq) {
+ if (!(vls & BIT_ULL(vq - 1))) {
+ break;
+ }
+ }
+ if (vq <= SVE_MAX_VQ) {
+ sprintf(name, "sve%d", vq * 128);
+ error = g_strdup_printf("cannot enable %s", name);
+ assert_error(qts, "host", error, "{ %s: true }", name);
+ g_free(error);
+ }
+ } else {
+ g_assert(vls == 0);
+ }
+ } else {
+ assert_has_not_feature(qts, "host", "aarch64");
+ assert_has_not_feature(qts, "host", "pmu");
+ assert_has_not_feature(qts, "host", "sve");
+ }
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/arm/query-cpu-model-expansion",
+ NULL, test_query_cpu_model_expansion);
+
+ /*
+ * For now we only run KVM specific tests with AArch64 QEMU in
+ * order avoid attempting to run an AArch32 QEMU with KVM on
+ * AArch64 hosts. That won't work and isn't easy to detect.
+ */
+ if (g_str_equal(qtest_get_arch(), "aarch64")) {
+ qtest_add_data_func("/arm/kvm/query-cpu-model-expansion",
+ NULL, test_query_cpu_model_expansion_kvm);
+ }
+
+ if (g_str_equal(qtest_get_arch(), "aarch64")) {
+ qtest_add_data_func("/arm/max/query-cpu-model-expansion/sve-max-vq-8",
+ NULL, sve_tests_sve_max_vq_8);
+ qtest_add_data_func("/arm/max/query-cpu-model-expansion/sve-off",
+ NULL, sve_tests_sve_off);
+ qtest_add_data_func("/arm/kvm/query-cpu-model-expansion/sve-off",
+ NULL, sve_tests_sve_off_kvm);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/bios-tables-test-allowed-diff.h b/tests/qtest/bios-tables-test-allowed-diff.h
new file mode 100644
index 0000000000..dfb8523c8b
--- /dev/null
+++ b/tests/qtest/bios-tables-test-allowed-diff.h
@@ -0,0 +1 @@
+/* List of comma-separated changed AML files to ignore */
diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c
new file mode 100644
index 0000000000..f1ac2d7e96
--- /dev/null
+++ b/tests/qtest/bios-tables-test.c
@@ -0,0 +1,1046 @@
+/*
+ * Boot order test cases.
+ *
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * How to add or update the tests:
+ * Contributor:
+ * 1. add empty files for new tables, if any, under tests/data/acpi
+ * 2. list any changed files in tests/bios-tables-test-allowed-diff.h
+ * 3. commit the above *before* making changes that affect the tables
+ * Maintainer:
+ * After 1-3 above tests will pass but ignore differences with the expected files.
+ * You will also notice that tests/bios-tables-test-allowed-diff.h lists
+ * a bunch of files. This is your hint that you need to do the below:
+ * 4. Run
+ * make check V=1
+ * this will produce a bunch of warnings about differences
+ * beween actual and expected ACPI tables. If you have IASL installed,
+ * they will also be disassembled so you can look at the disassembled
+ * output. If not - disassemble them yourself in any way you like.
+ * Look at the differences - make sure they make sense and match what the
+ * changes you are merging are supposed to do.
+ *
+ * 5. From build directory, run:
+ * $(SRC_PATH)/tests/data/acpi/rebuild-expected-aml.sh
+ * 6. Now commit any changes.
+ * 7. Before doing a pull request, make sure tests/bios-tables-test-allowed-diff.h
+ * is empty - this will ensure following changes to ACPI tables will
+ * be noticed.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "qemu-common.h"
+#include "hw/firmware/smbios.h"
+#include "qemu/bitmap.h"
+#include "acpi-utils.h"
+#include "boot-sector.h"
+
+#define MACHINE_PC "pc"
+#define MACHINE_Q35 "q35"
+
+#define ACPI_REBUILD_EXPECTED_AML "TEST_ACPI_REBUILD_AML"
+
+typedef struct {
+ bool tcg_only;
+ const char *machine;
+ const char *variant;
+ const char *uefi_fl1;
+ const char *uefi_fl2;
+ const char *cd;
+ const uint64_t ram_start;
+ const uint64_t scan_len;
+ uint64_t rsdp_addr;
+ uint8_t rsdp_table[36 /* ACPI 2.0+ RSDP size */];
+ GArray *tables;
+ uint32_t smbios_ep_addr;
+ struct smbios_21_entry_point smbios_ep_table;
+ uint8_t *required_struct_types;
+ int required_struct_types_len;
+ QTestState *qts;
+} test_data;
+
+static char disk[] = "tests/acpi-test-disk-XXXXXX";
+static const char *data_dir = "tests/data/acpi";
+#ifdef CONFIG_IASL
+static const char *iasl = stringify(CONFIG_IASL);
+#else
+static const char *iasl;
+#endif
+
+static bool compare_signature(const AcpiSdtTable *sdt, const char *signature)
+{
+ return !memcmp(sdt->aml, signature, 4);
+}
+
+static void cleanup_table_descriptor(AcpiSdtTable *table)
+{
+ g_free(table->aml);
+ if (table->aml_file &&
+ !table->tmp_files_retain &&
+ g_strstr_len(table->aml_file, -1, "aml-")) {
+ unlink(table->aml_file);
+ }
+ g_free(table->aml_file);
+ g_free(table->asl);
+ if (table->asl_file &&
+ !table->tmp_files_retain) {
+ unlink(table->asl_file);
+ }
+ g_free(table->asl_file);
+}
+
+static void free_test_data(test_data *data)
+{
+ int i;
+
+ for (i = 0; i < data->tables->len; ++i) {
+ cleanup_table_descriptor(&g_array_index(data->tables, AcpiSdtTable, i));
+ }
+
+ g_array_free(data->tables, true);
+}
+
+static void test_acpi_rsdp_table(test_data *data)
+{
+ uint8_t *rsdp_table = data->rsdp_table;
+
+ acpi_fetch_rsdp_table(data->qts, data->rsdp_addr, rsdp_table);
+
+ switch (rsdp_table[15 /* Revision offset */]) {
+ case 0: /* ACPI 1.0 RSDP */
+ /* With rev 1, checksum is only for the first 20 bytes */
+ g_assert(!acpi_calc_checksum(rsdp_table, 20));
+ break;
+ case 2: /* ACPI 2.0+ RSDP */
+ /* With revision 2, we have 2 checksums */
+ g_assert(!acpi_calc_checksum(rsdp_table, 20));
+ g_assert(!acpi_calc_checksum(rsdp_table, 36));
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void test_acpi_rxsdt_table(test_data *data)
+{
+ const char *sig = "RSDT";
+ AcpiSdtTable rsdt = {};
+ int entry_size = 4;
+ int addr_off = 16 /* RsdtAddress */;
+ uint8_t *ent;
+
+ if (data->rsdp_table[15 /* Revision offset */] != 0) {
+ addr_off = 24 /* XsdtAddress */;
+ entry_size = 8;
+ sig = "XSDT";
+ }
+ /* read [RX]SDT table */
+ acpi_fetch_table(data->qts, &rsdt.aml, &rsdt.aml_len,
+ &data->rsdp_table[addr_off], entry_size, sig, true);
+
+ /* Load all tables and add to test list directly RSDT referenced tables */
+ ACPI_FOREACH_RSDT_ENTRY(rsdt.aml, rsdt.aml_len, ent, entry_size) {
+ AcpiSdtTable ssdt_table = {};
+
+ acpi_fetch_table(data->qts, &ssdt_table.aml, &ssdt_table.aml_len, ent,
+ entry_size, NULL, true);
+ /* Add table to ASL test tables list */
+ g_array_append_val(data->tables, ssdt_table);
+ }
+ cleanup_table_descriptor(&rsdt);
+}
+
+static void test_acpi_fadt_table(test_data *data)
+{
+ /* FADT table is 1st */
+ AcpiSdtTable table = g_array_index(data->tables, typeof(table), 0);
+ uint8_t *fadt_aml = table.aml;
+ uint32_t fadt_len = table.aml_len;
+ uint32_t val;
+ int dsdt_offset = 40 /* DSDT */;
+ int dsdt_entry_size = 4;
+
+ g_assert(compare_signature(&table, "FACP"));
+
+ /* Since DSDT/FACS isn't in RSDT, add them to ASL test list manually */
+ memcpy(&val, fadt_aml + 112 /* Flags */, 4);
+ val = le32_to_cpu(val);
+ if (!(val & 1UL << 20 /* HW_REDUCED_ACPI */)) {
+ acpi_fetch_table(data->qts, &table.aml, &table.aml_len,
+ fadt_aml + 36 /* FIRMWARE_CTRL */, 4, "FACS", false);
+ g_array_append_val(data->tables, table);
+ }
+
+ memcpy(&val, fadt_aml + dsdt_offset, 4);
+ val = le32_to_cpu(val);
+ if (!val) {
+ dsdt_offset = 140 /* X_DSDT */;
+ dsdt_entry_size = 8;
+ }
+ acpi_fetch_table(data->qts, &table.aml, &table.aml_len,
+ fadt_aml + dsdt_offset, dsdt_entry_size, "DSDT", true);
+ g_array_append_val(data->tables, table);
+
+ memset(fadt_aml + 36, 0, 4); /* sanitize FIRMWARE_CTRL ptr */
+ memset(fadt_aml + 40, 0, 4); /* sanitize DSDT ptr */
+ if (fadt_aml[8 /* FADT Major Version */] >= 3) {
+ memset(fadt_aml + 132, 0, 8); /* sanitize X_FIRMWARE_CTRL ptr */
+ memset(fadt_aml + 140, 0, 8); /* sanitize X_DSDT ptr */
+ }
+
+ /* update checksum */
+ fadt_aml[9 /* Checksum */] = 0;
+ fadt_aml[9 /* Checksum */] -= acpi_calc_checksum(fadt_aml, fadt_len);
+}
+
+static void dump_aml_files(test_data *data, bool rebuild)
+{
+ AcpiSdtTable *sdt;
+ GError *error = NULL;
+ gchar *aml_file = NULL;
+ gint fd;
+ ssize_t ret;
+ int i;
+
+ for (i = 0; i < data->tables->len; ++i) {
+ const char *ext = data->variant ? data->variant : "";
+ sdt = &g_array_index(data->tables, AcpiSdtTable, i);
+ g_assert(sdt->aml);
+
+ if (rebuild) {
+ aml_file = g_strdup_printf("%s/%s/%.4s%s", data_dir, data->machine,
+ sdt->aml, ext);
+ fd = g_open(aml_file, O_WRONLY|O_TRUNC|O_CREAT,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
+ if (fd < 0) {
+ perror(aml_file);
+ }
+ g_assert(fd >= 0);
+ } else {
+ fd = g_file_open_tmp("aml-XXXXXX", &sdt->aml_file, &error);
+ g_assert_no_error(error);
+ }
+
+ ret = qemu_write_full(fd, sdt->aml, sdt->aml_len);
+ g_assert(ret == sdt->aml_len);
+
+ close(fd);
+
+ g_free(aml_file);
+ }
+}
+
+static bool load_asl(GArray *sdts, AcpiSdtTable *sdt)
+{
+ AcpiSdtTable *temp;
+ GError *error = NULL;
+ GString *command_line = g_string_new(iasl);
+ gint fd;
+ gchar *out, *out_err;
+ gboolean ret;
+ int i;
+
+ fd = g_file_open_tmp("asl-XXXXXX.dsl", &sdt->asl_file, &error);
+ g_assert_no_error(error);
+ close(fd);
+
+ /* build command line */
+ g_string_append_printf(command_line, " -p %s ", sdt->asl_file);
+ if (compare_signature(sdt, "DSDT") ||
+ compare_signature(sdt, "SSDT")) {
+ for (i = 0; i < sdts->len; ++i) {
+ temp = &g_array_index(sdts, AcpiSdtTable, i);
+ if (compare_signature(temp, "DSDT") ||
+ compare_signature(temp, "SSDT")) {
+ g_string_append_printf(command_line, "-e %s ", temp->aml_file);
+ }
+ }
+ }
+ g_string_append_printf(command_line, "-d %s", sdt->aml_file);
+
+ /* pass 'out' and 'out_err' in order to be redirected */
+ ret = g_spawn_command_line_sync(command_line->str, &out, &out_err, NULL, &error);
+ g_assert_no_error(error);
+ if (ret) {
+ ret = g_file_get_contents(sdt->asl_file, &sdt->asl,
+ &sdt->asl_len, &error);
+ g_assert(ret);
+ g_assert_no_error(error);
+ ret = (sdt->asl_len > 0);
+ }
+
+ g_free(out);
+ g_free(out_err);
+ g_string_free(command_line, true);
+
+ return !ret;
+}
+
+#define COMMENT_END "*/"
+#define DEF_BLOCK "DefinitionBlock ("
+#define BLOCK_NAME_END ","
+
+static GString *normalize_asl(gchar *asl_code)
+{
+ GString *asl = g_string_new(asl_code);
+ gchar *comment, *block_name;
+
+ /* strip comments (different generation days) */
+ comment = g_strstr_len(asl->str, asl->len, COMMENT_END);
+ if (comment) {
+ comment += strlen(COMMENT_END);
+ while (*comment == '\n') {
+ comment++;
+ }
+ asl = g_string_erase(asl, 0, comment - asl->str);
+ }
+
+ /* strip def block name (it has file path in it) */
+ if (g_str_has_prefix(asl->str, DEF_BLOCK)) {
+ block_name = g_strstr_len(asl->str, asl->len, BLOCK_NAME_END);
+ g_assert(block_name);
+ asl = g_string_erase(asl, 0,
+ block_name + sizeof(BLOCK_NAME_END) - asl->str);
+ }
+
+ return asl;
+}
+
+static GArray *load_expected_aml(test_data *data)
+{
+ int i;
+ AcpiSdtTable *sdt;
+ GError *error = NULL;
+ gboolean ret;
+ gsize aml_len;
+
+ GArray *exp_tables = g_array_new(false, true, sizeof(AcpiSdtTable));
+ if (getenv("V")) {
+ fputc('\n', stderr);
+ }
+ for (i = 0; i < data->tables->len; ++i) {
+ AcpiSdtTable exp_sdt;
+ gchar *aml_file = NULL;
+ const char *ext = data->variant ? data->variant : "";
+
+ sdt = &g_array_index(data->tables, AcpiSdtTable, i);
+
+ memset(&exp_sdt, 0, sizeof(exp_sdt));
+
+try_again:
+ aml_file = g_strdup_printf("%s/%s/%.4s%s", data_dir, data->machine,
+ sdt->aml, ext);
+ if (getenv("V")) {
+ fprintf(stderr, "Looking for expected file '%s'\n", aml_file);
+ }
+ if (g_file_test(aml_file, G_FILE_TEST_EXISTS)) {
+ exp_sdt.aml_file = aml_file;
+ } else if (*ext != '\0') {
+ /* try fallback to generic (extension less) expected file */
+ ext = "";
+ g_free(aml_file);
+ goto try_again;
+ }
+ g_assert(exp_sdt.aml_file);
+ if (getenv("V")) {
+ fprintf(stderr, "Using expected file '%s'\n", aml_file);
+ }
+ ret = g_file_get_contents(aml_file, (gchar **)&exp_sdt.aml,
+ &aml_len, &error);
+ exp_sdt.aml_len = aml_len;
+ g_assert(ret);
+ g_assert_no_error(error);
+ g_assert(exp_sdt.aml);
+ if (!exp_sdt.aml_len) {
+ fprintf(stderr, "Warning! zero length expected file '%s'\n",
+ aml_file);
+ }
+
+ g_array_append_val(exp_tables, exp_sdt);
+ }
+
+ return exp_tables;
+}
+
+static bool test_acpi_find_diff_allowed(AcpiSdtTable *sdt)
+{
+ const gchar *allowed_diff_file[] = {
+#include "bios-tables-test-allowed-diff.h"
+ NULL
+ };
+ const gchar **f;
+
+ for (f = allowed_diff_file; *f; ++f) {
+ if (!g_strcmp0(sdt->aml_file, *f)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* test the list of tables in @data->tables against reference tables */
+static void test_acpi_asl(test_data *data)
+{
+ int i;
+ AcpiSdtTable *sdt, *exp_sdt;
+ test_data exp_data;
+ gboolean exp_err, err, all_tables_match = true;
+
+ memset(&exp_data, 0, sizeof(exp_data));
+ exp_data.tables = load_expected_aml(data);
+ dump_aml_files(data, false);
+ for (i = 0; i < data->tables->len; ++i) {
+ GString *asl, *exp_asl;
+
+ sdt = &g_array_index(data->tables, AcpiSdtTable, i);
+ exp_sdt = &g_array_index(exp_data.tables, AcpiSdtTable, i);
+
+ if (sdt->aml_len == exp_sdt->aml_len &&
+ !memcmp(sdt->aml, exp_sdt->aml, sdt->aml_len)) {
+ /* Identical table binaries: no need to disassemble. */
+ continue;
+ }
+
+ fprintf(stderr,
+ "acpi-test: Warning! %.4s binary file mismatch. "
+ "Actual [aml:%s], Expected [aml:%s].\n",
+ exp_sdt->aml, sdt->aml_file, exp_sdt->aml_file);
+
+ all_tables_match = all_tables_match &&
+ test_acpi_find_diff_allowed(exp_sdt);
+
+ /*
+ * don't try to decompile if IASL isn't present, in this case user
+ * will just 'get binary file mismatch' warnings and test failure
+ */
+ if (!iasl) {
+ continue;
+ }
+
+ err = load_asl(data->tables, sdt);
+ asl = normalize_asl(sdt->asl);
+
+ exp_err = load_asl(exp_data.tables, exp_sdt);
+ exp_asl = normalize_asl(exp_sdt->asl);
+
+ /* TODO: check for warnings */
+ g_assert(!err || exp_err);
+
+ if (g_strcmp0(asl->str, exp_asl->str)) {
+ sdt->tmp_files_retain = true;
+ if (exp_err) {
+ fprintf(stderr,
+ "Warning! iasl couldn't parse the expected aml\n");
+ } else {
+ exp_sdt->tmp_files_retain = true;
+ fprintf(stderr,
+ "acpi-test: Warning! %.4s mismatch. "
+ "Actual [asl:%s, aml:%s], Expected [asl:%s, aml:%s].\n",
+ exp_sdt->aml, sdt->asl_file, sdt->aml_file,
+ exp_sdt->asl_file, exp_sdt->aml_file);
+ if (getenv("V")) {
+ const char *diff_cmd = getenv("DIFF");
+ if (diff_cmd) {
+ int ret G_GNUC_UNUSED;
+ char *diff = g_strdup_printf("%s %s %s", diff_cmd,
+ exp_sdt->asl_file, sdt->asl_file);
+ ret = system(diff) ;
+ g_free(diff);
+ } else {
+ fprintf(stderr, "acpi-test: Warning. not showing "
+ "difference since no diff utility is specified. "
+ "Set 'DIFF' environment variable to a preferred "
+ "diff utility and run 'make V=1 check' again to "
+ "see ASL difference.");
+ }
+ }
+ }
+ }
+ g_string_free(asl, true);
+ g_string_free(exp_asl, true);
+ }
+ if (!iasl && !all_tables_match) {
+ fprintf(stderr, "to see ASL diff between mismatched files install IASL,"
+ " rebuild QEMU from scratch and re-run tests with V=1"
+ " environment variable set");
+ }
+ g_assert(all_tables_match);
+
+ free_test_data(&exp_data);
+}
+
+static bool smbios_ep_table_ok(test_data *data)
+{
+ struct smbios_21_entry_point *ep_table = &data->smbios_ep_table;
+ uint32_t addr = data->smbios_ep_addr;
+
+ qtest_memread(data->qts, addr, ep_table, sizeof(*ep_table));
+ if (memcmp(ep_table->anchor_string, "_SM_", 4)) {
+ return false;
+ }
+ if (memcmp(ep_table->intermediate_anchor_string, "_DMI_", 5)) {
+ return false;
+ }
+ if (ep_table->structure_table_length == 0) {
+ return false;
+ }
+ if (ep_table->number_of_structures == 0) {
+ return false;
+ }
+ if (acpi_calc_checksum((uint8_t *)ep_table, sizeof *ep_table) ||
+ acpi_calc_checksum((uint8_t *)ep_table + 0x10,
+ sizeof *ep_table - 0x10)) {
+ return false;
+ }
+ return true;
+}
+
+static void test_smbios_entry_point(test_data *data)
+{
+ uint32_t off;
+
+ /* find smbios entry point structure */
+ for (off = 0xf0000; off < 0x100000; off += 0x10) {
+ uint8_t sig[] = "_SM_";
+ int i;
+
+ for (i = 0; i < sizeof sig - 1; ++i) {
+ sig[i] = qtest_readb(data->qts, off + i);
+ }
+
+ if (!memcmp(sig, "_SM_", sizeof sig)) {
+ /* signature match, but is this a valid entry point? */
+ data->smbios_ep_addr = off;
+ if (smbios_ep_table_ok(data)) {
+ break;
+ }
+ }
+ }
+
+ g_assert_cmphex(off, <, 0x100000);
+}
+
+static inline bool smbios_single_instance(uint8_t type)
+{
+ switch (type) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 16:
+ case 32:
+ case 127:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void test_smbios_structs(test_data *data)
+{
+ DECLARE_BITMAP(struct_bitmap, SMBIOS_MAX_TYPE+1) = { 0 };
+ struct smbios_21_entry_point *ep_table = &data->smbios_ep_table;
+ uint32_t addr = le32_to_cpu(ep_table->structure_table_address);
+ int i, len, max_len = 0;
+ uint8_t type, prv, crt;
+
+ /* walk the smbios tables */
+ for (i = 0; i < le16_to_cpu(ep_table->number_of_structures); i++) {
+
+ /* grab type and formatted area length from struct header */
+ type = qtest_readb(data->qts, addr);
+ g_assert_cmpuint(type, <=, SMBIOS_MAX_TYPE);
+ len = qtest_readb(data->qts, addr + 1);
+
+ /* single-instance structs must not have been encountered before */
+ if (smbios_single_instance(type)) {
+ g_assert(!test_bit(type, struct_bitmap));
+ }
+ set_bit(type, struct_bitmap);
+
+ /* seek to end of unformatted string area of this struct ("\0\0") */
+ prv = crt = 1;
+ while (prv || crt) {
+ prv = crt;
+ crt = qtest_readb(data->qts, addr + len);
+ len++;
+ }
+
+ /* keep track of max. struct size */
+ if (max_len < len) {
+ max_len = len;
+ g_assert_cmpuint(max_len, <=, ep_table->max_structure_size);
+ }
+
+ /* start of next structure */
+ addr += len;
+ }
+
+ /* total table length and max struct size must match entry point values */
+ g_assert_cmpuint(le16_to_cpu(ep_table->structure_table_length), ==,
+ addr - le32_to_cpu(ep_table->structure_table_address));
+ g_assert_cmpuint(le16_to_cpu(ep_table->max_structure_size), ==, max_len);
+
+ /* required struct types must all be present */
+ for (i = 0; i < data->required_struct_types_len; i++) {
+ g_assert(test_bit(data->required_struct_types[i], struct_bitmap));
+ }
+}
+
+static void test_acpi_one(const char *params, test_data *data)
+{
+ char *args;
+ bool use_uefi = data->uefi_fl1 && data->uefi_fl2;
+
+ if (use_uefi) {
+ /*
+ * TODO: convert '-drive if=pflash' to new syntax (see e33763be7cd3)
+ * when arm/virt boad starts to support it.
+ */
+ args = g_strdup_printf("-machine %s %s -accel tcg -nodefaults -nographic "
+ "-drive if=pflash,format=raw,file=%s,readonly "
+ "-drive if=pflash,format=raw,file=%s,snapshot=on -cdrom %s %s",
+ data->machine, data->tcg_only ? "" : "-accel kvm",
+ data->uefi_fl1, data->uefi_fl2, data->cd, params ? params : "");
+
+ } else {
+ /* Disable kernel irqchip to be able to override apic irq0. */
+ args = g_strdup_printf("-machine %s,kernel-irqchip=off %s -accel tcg "
+ "-net none -display none %s "
+ "-drive id=hd0,if=none,file=%s,format=raw "
+ "-device ide-hd,drive=hd0 ",
+ data->machine, data->tcg_only ? "" : "-accel kvm",
+ params ? params : "", disk);
+ }
+
+ data->qts = qtest_init(args);
+
+ if (use_uefi) {
+ g_assert(data->scan_len);
+ data->rsdp_addr = acpi_find_rsdp_address_uefi(data->qts,
+ data->ram_start, data->scan_len);
+ } else {
+ boot_sector_test(data->qts);
+ data->rsdp_addr = acpi_find_rsdp_address(data->qts);
+ g_assert_cmphex(data->rsdp_addr, <, 0x100000);
+ }
+
+ data->tables = g_array_new(false, true, sizeof(AcpiSdtTable));
+ test_acpi_rsdp_table(data);
+ test_acpi_rxsdt_table(data);
+ test_acpi_fadt_table(data);
+
+ if (getenv(ACPI_REBUILD_EXPECTED_AML)) {
+ dump_aml_files(data, true);
+ } else {
+ test_acpi_asl(data);
+ }
+
+ /*
+ * TODO: make SMBIOS tests work with UEFI firmware,
+ * Bug on uefi-test-tools to provide entry point:
+ * https://bugs.launchpad.net/qemu/+bug/1821884
+ */
+ if (!use_uefi) {
+ test_smbios_entry_point(data);
+ test_smbios_structs(data);
+ }
+
+ qtest_quit(data->qts);
+ g_free(args);
+}
+
+static uint8_t base_required_struct_types[] = {
+ 0, 1, 3, 4, 16, 17, 19, 32, 127
+};
+
+static void test_acpi_piix4_tcg(void)
+{
+ test_data data;
+
+ /* Supplying -machine accel argument overrides the default (qtest).
+ * This is to make guest actually run.
+ */
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_PC;
+ data.required_struct_types = base_required_struct_types;
+ data.required_struct_types_len = ARRAY_SIZE(base_required_struct_types);
+ test_acpi_one(NULL, &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_piix4_tcg_bridge(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_PC;
+ data.variant = ".bridge";
+ data.required_struct_types = base_required_struct_types;
+ data.required_struct_types_len = ARRAY_SIZE(base_required_struct_types);
+ test_acpi_one("-device pci-bridge,chassis_nr=1", &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_Q35;
+ data.required_struct_types = base_required_struct_types;
+ data.required_struct_types_len = ARRAY_SIZE(base_required_struct_types);
+ test_acpi_one(NULL, &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_bridge(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_Q35;
+ data.variant = ".bridge";
+ data.required_struct_types = base_required_struct_types;
+ data.required_struct_types_len = ARRAY_SIZE(base_required_struct_types);
+ test_acpi_one("-device pci-bridge,chassis_nr=1",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_mmio64(void)
+{
+ test_data data = {
+ .machine = MACHINE_Q35,
+ .variant = ".mmio64",
+ .required_struct_types = base_required_struct_types,
+ .required_struct_types_len = ARRAY_SIZE(base_required_struct_types)
+ };
+
+ test_acpi_one("-m 128M,slots=1,maxmem=2G "
+ "-object memory-backend-ram,id=ram0,size=128M "
+ "-numa node,memdev=ram0 "
+ "-device pci-testdev,membar=2G",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_piix4_tcg_cphp(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_PC;
+ data.variant = ".cphp";
+ test_acpi_one("-smp 2,cores=3,sockets=2,maxcpus=6"
+ " -object memory-backend-ram,id=ram0,size=64M"
+ " -object memory-backend-ram,id=ram1,size=64M"
+ " -numa node,memdev=ram0 -numa node,memdev=ram1"
+ " -numa dist,src=0,dst=1,val=21",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_cphp(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_Q35;
+ data.variant = ".cphp";
+ test_acpi_one(" -smp 2,cores=3,sockets=2,maxcpus=6"
+ " -object memory-backend-ram,id=ram0,size=64M"
+ " -object memory-backend-ram,id=ram1,size=64M"
+ " -numa node,memdev=ram0 -numa node,memdev=ram1"
+ " -numa dist,src=0,dst=1,val=21",
+ &data);
+ free_test_data(&data);
+}
+
+static uint8_t ipmi_required_struct_types[] = {
+ 0, 1, 3, 4, 16, 17, 19, 32, 38, 127
+};
+
+static void test_acpi_q35_tcg_ipmi(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_Q35;
+ data.variant = ".ipmibt";
+ data.required_struct_types = ipmi_required_struct_types;
+ data.required_struct_types_len = ARRAY_SIZE(ipmi_required_struct_types);
+ test_acpi_one("-device ipmi-bmc-sim,id=bmc0"
+ " -device isa-ipmi-bt,bmc=bmc0",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_piix4_tcg_ipmi(void)
+{
+ test_data data;
+
+ /* Supplying -machine accel argument overrides the default (qtest).
+ * This is to make guest actually run.
+ */
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_PC;
+ data.variant = ".ipmikcs";
+ data.required_struct_types = ipmi_required_struct_types;
+ data.required_struct_types_len = ARRAY_SIZE(ipmi_required_struct_types);
+ test_acpi_one("-device ipmi-bmc-sim,id=bmc0"
+ " -device isa-ipmi-kcs,irq=0,bmc=bmc0",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_memhp(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_Q35;
+ data.variant = ".memhp";
+ test_acpi_one(" -m 128,slots=3,maxmem=1G"
+ " -object memory-backend-ram,id=ram0,size=64M"
+ " -object memory-backend-ram,id=ram1,size=64M"
+ " -numa node,memdev=ram0 -numa node,memdev=ram1"
+ " -numa dist,src=0,dst=1,val=21",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_piix4_tcg_memhp(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_PC;
+ data.variant = ".memhp";
+ test_acpi_one(" -m 128,slots=3,maxmem=1G"
+ " -object memory-backend-ram,id=ram0,size=64M"
+ " -object memory-backend-ram,id=ram1,size=64M"
+ " -numa node,memdev=ram0 -numa node,memdev=ram1"
+ " -numa dist,src=0,dst=1,val=21",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_numamem(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_Q35;
+ data.variant = ".numamem";
+ test_acpi_one(" -object memory-backend-ram,id=ram0,size=128M"
+ " -numa node -numa node,memdev=ram0", &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_piix4_tcg_numamem(void)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = MACHINE_PC;
+ data.variant = ".numamem";
+ test_acpi_one(" -object memory-backend-ram,id=ram0,size=128M"
+ " -numa node -numa node,memdev=ram0", &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_tcg_dimm_pxm(const char *machine)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = machine;
+ data.variant = ".dimmpxm";
+ test_acpi_one(" -machine nvdimm=on,nvdimm-persistence=cpu"
+ " -smp 4,sockets=4"
+ " -m 128M,slots=3,maxmem=1G"
+ " -object memory-backend-ram,id=ram0,size=32M"
+ " -object memory-backend-ram,id=ram1,size=32M"
+ " -object memory-backend-ram,id=ram2,size=32M"
+ " -object memory-backend-ram,id=ram3,size=32M"
+ " -numa node,memdev=ram0,nodeid=0"
+ " -numa node,memdev=ram1,nodeid=1"
+ " -numa node,memdev=ram2,nodeid=2"
+ " -numa node,memdev=ram3,nodeid=3"
+ " -numa cpu,node-id=0,socket-id=0"
+ " -numa cpu,node-id=1,socket-id=1"
+ " -numa cpu,node-id=2,socket-id=2"
+ " -numa cpu,node-id=3,socket-id=3"
+ " -object memory-backend-ram,id=ram4,size=128M"
+ " -object memory-backend-ram,id=nvm0,size=128M"
+ " -device pc-dimm,id=dimm0,memdev=ram4,node=1"
+ " -device nvdimm,id=dimm1,memdev=nvm0,node=2",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_dimm_pxm(void)
+{
+ test_acpi_tcg_dimm_pxm(MACHINE_Q35);
+}
+
+static void test_acpi_piix4_tcg_dimm_pxm(void)
+{
+ test_acpi_tcg_dimm_pxm(MACHINE_PC);
+}
+
+static void test_acpi_virt_tcg_memhp(void)
+{
+ test_data data = {
+ .machine = "virt",
+ .tcg_only = true,
+ .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd",
+ .uefi_fl2 = "pc-bios/edk2-arm-vars.fd",
+ .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2",
+ .ram_start = 0x40000000ULL,
+ .scan_len = 256ULL * 1024 * 1024,
+ };
+
+ data.variant = ".memhp";
+ test_acpi_one(" -cpu cortex-a57"
+ " -m 256M,slots=3,maxmem=1G"
+ " -object memory-backend-ram,id=ram0,size=128M"
+ " -object memory-backend-ram,id=ram1,size=128M"
+ " -numa node,memdev=ram0 -numa node,memdev=ram1"
+ " -numa dist,src=0,dst=1,val=21",
+ &data);
+
+ free_test_data(&data);
+
+}
+
+static void test_acpi_virt_tcg_numamem(void)
+{
+ test_data data = {
+ .machine = "virt",
+ .tcg_only = true,
+ .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd",
+ .uefi_fl2 = "pc-bios/edk2-arm-vars.fd",
+ .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2",
+ .ram_start = 0x40000000ULL,
+ .scan_len = 128ULL * 1024 * 1024,
+ };
+
+ data.variant = ".numamem";
+ test_acpi_one(" -cpu cortex-a57"
+ " -object memory-backend-ram,id=ram0,size=128M"
+ " -numa node,memdev=ram0",
+ &data);
+
+ free_test_data(&data);
+
+}
+
+static void test_acpi_tcg_acpi_hmat(const char *machine)
+{
+ test_data data;
+
+ memset(&data, 0, sizeof(data));
+ data.machine = machine;
+ data.variant = ".acpihmat";
+ test_acpi_one(" -machine hmat=on"
+ " -smp 2,sockets=2"
+ " -m 128M,slots=2,maxmem=1G"
+ " -object memory-backend-ram,size=64M,id=m0"
+ " -object memory-backend-ram,size=64M,id=m1"
+ " -numa node,nodeid=0,memdev=m0"
+ " -numa node,nodeid=1,memdev=m1,initiator=0"
+ " -numa cpu,node-id=0,socket-id=0"
+ " -numa cpu,node-id=0,socket-id=1"
+ " -numa hmat-lb,initiator=0,target=0,hierarchy=memory,"
+ "data-type=access-latency,latency=1"
+ " -numa hmat-lb,initiator=0,target=0,hierarchy=memory,"
+ "data-type=access-bandwidth,bandwidth=65534M"
+ " -numa hmat-lb,initiator=0,target=1,hierarchy=memory,"
+ "data-type=access-latency,latency=65534"
+ " -numa hmat-lb,initiator=0,target=1,hierarchy=memory,"
+ "data-type=access-bandwidth,bandwidth=32767M"
+ " -numa hmat-cache,node-id=0,size=10K,level=1,"
+ "associativity=direct,policy=write-back,line=8"
+ " -numa hmat-cache,node-id=1,size=10K,level=1,"
+ "associativity=direct,policy=write-back,line=8",
+ &data);
+ free_test_data(&data);
+}
+
+static void test_acpi_q35_tcg_acpi_hmat(void)
+{
+ test_acpi_tcg_acpi_hmat(MACHINE_Q35);
+}
+
+static void test_acpi_piix4_tcg_acpi_hmat(void)
+{
+ test_acpi_tcg_acpi_hmat(MACHINE_PC);
+}
+
+static void test_acpi_virt_tcg(void)
+{
+ test_data data = {
+ .machine = "virt",
+ .tcg_only = true,
+ .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd",
+ .uefi_fl2 = "pc-bios/edk2-arm-vars.fd",
+ .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2",
+ .ram_start = 0x40000000ULL,
+ .scan_len = 128ULL * 1024 * 1024,
+ };
+
+ test_acpi_one("-cpu cortex-a57", &data);
+ free_test_data(&data);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *arch = qtest_get_arch();
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ ret = boot_sector_init(disk);
+ if (ret) {
+ return ret;
+ }
+
+ qtest_add_func("acpi/piix4", test_acpi_piix4_tcg);
+ qtest_add_func("acpi/piix4/bridge", test_acpi_piix4_tcg_bridge);
+ qtest_add_func("acpi/q35", test_acpi_q35_tcg);
+ qtest_add_func("acpi/q35/bridge", test_acpi_q35_tcg_bridge);
+ qtest_add_func("acpi/q35/mmio64", test_acpi_q35_tcg_mmio64);
+ qtest_add_func("acpi/piix4/ipmi", test_acpi_piix4_tcg_ipmi);
+ qtest_add_func("acpi/q35/ipmi", test_acpi_q35_tcg_ipmi);
+ qtest_add_func("acpi/piix4/cpuhp", test_acpi_piix4_tcg_cphp);
+ qtest_add_func("acpi/q35/cpuhp", test_acpi_q35_tcg_cphp);
+ qtest_add_func("acpi/piix4/memhp", test_acpi_piix4_tcg_memhp);
+ qtest_add_func("acpi/q35/memhp", test_acpi_q35_tcg_memhp);
+ qtest_add_func("acpi/piix4/numamem", test_acpi_piix4_tcg_numamem);
+ qtest_add_func("acpi/q35/numamem", test_acpi_q35_tcg_numamem);
+ qtest_add_func("acpi/piix4/dimmpxm", test_acpi_piix4_tcg_dimm_pxm);
+ qtest_add_func("acpi/q35/dimmpxm", test_acpi_q35_tcg_dimm_pxm);
+ qtest_add_func("acpi/piix4/acpihmat", test_acpi_piix4_tcg_acpi_hmat);
+ qtest_add_func("acpi/q35/acpihmat", test_acpi_q35_tcg_acpi_hmat);
+ } else if (strcmp(arch, "aarch64") == 0) {
+ qtest_add_func("acpi/virt", test_acpi_virt_tcg);
+ qtest_add_func("acpi/virt/numamem", test_acpi_virt_tcg_numamem);
+ qtest_add_func("acpi/virt/memhp", test_acpi_virt_tcg_memhp);
+ }
+ ret = g_test_run();
+ boot_sector_cleanup(disk);
+ return ret;
+}
diff --git a/tests/qtest/boot-order-test.c b/tests/qtest/boot-order-test.c
new file mode 100644
index 0000000000..a725bce729
--- /dev/null
+++ b/tests/qtest/boot-order-test.c
@@ -0,0 +1,205 @@
+/*
+ * Boot order test cases.
+ *
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * Authors:
+ * Markus Armbruster <armbru@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqos/fw_cfg.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "standard-headers/linux/qemu_fw_cfg.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(qs, ...) qobject_unref(qtest_qmp(qs, __VA_ARGS__))
+
+typedef struct {
+ const char *args;
+ uint64_t expected_boot;
+ uint64_t expected_reboot;
+} boot_order_test;
+
+static void test_a_boot_order(const char *machine,
+ const char *test_args,
+ uint64_t (*read_boot_order)(QTestState *),
+ uint64_t expected_boot,
+ uint64_t expected_reboot)
+{
+ uint64_t actual;
+ QTestState *qts;
+
+ qts = qtest_initf("-nodefaults%s%s %s", machine ? " -M " : "",
+ machine ?: "", test_args);
+ actual = read_boot_order(qts);
+ g_assert_cmphex(actual, ==, expected_boot);
+ qmp_discard_response(qts, "{ 'execute': 'system_reset' }");
+ /*
+ * system_reset only requests reset. We get a RESET event after
+ * the actual reset completes. Need to wait for that.
+ */
+ qtest_qmp_eventwait(qts, "RESET");
+ actual = read_boot_order(qts);
+ g_assert_cmphex(actual, ==, expected_reboot);
+ qtest_quit(qts);
+}
+
+static void test_boot_orders(const char *machine,
+ uint64_t (*read_boot_order)(QTestState *),
+ const boot_order_test *tests)
+{
+ int i;
+
+ for (i = 0; tests[i].args; i++) {
+ test_a_boot_order(machine, tests[i].args,
+ read_boot_order,
+ tests[i].expected_boot,
+ tests[i].expected_reboot);
+ }
+}
+
+static uint8_t read_mc146818(QTestState *qts, uint16_t port, uint8_t reg)
+{
+ qtest_outb(qts, port, reg);
+ return qtest_inb(qts, port + 1);
+}
+
+static uint64_t read_boot_order_pc(QTestState *qts)
+{
+ uint8_t b1 = read_mc146818(qts, 0x70, 0x38);
+ uint8_t b2 = read_mc146818(qts, 0x70, 0x3d);
+
+ return b1 | (b2 << 8);
+}
+
+static const boot_order_test test_cases_pc[] = {
+ { "",
+ 0x1230, 0x1230 },
+ { "-no-fd-bootchk",
+ 0x1231, 0x1231 },
+ { "-boot c",
+ 0x0200, 0x0200 },
+ { "-boot nda",
+ 0x3410, 0x3410 },
+ { "-boot order=",
+ 0, 0 },
+ { "-boot order= -boot order=c",
+ 0x0200, 0x0200 },
+ { "-boot once=a",
+ 0x0100, 0x1230 },
+ { "-boot once=a -no-fd-bootchk",
+ 0x0101, 0x1231 },
+ { "-boot once=a,order=c",
+ 0x0100, 0x0200 },
+ { "-boot once=d -boot order=nda",
+ 0x0300, 0x3410 },
+ { "-boot once=a -boot once=b -boot once=c",
+ 0x0200, 0x1230 },
+ {}
+};
+
+static void test_pc_boot_order(void)
+{
+ test_boot_orders(NULL, read_boot_order_pc, test_cases_pc);
+}
+
+static uint8_t read_m48t59(QTestState *qts, uint64_t addr, uint16_t reg)
+{
+ qtest_writeb(qts, addr, reg & 0xff);
+ qtest_writeb(qts, addr + 1, reg >> 8);
+ return qtest_readb(qts, addr + 3);
+}
+
+static uint64_t read_boot_order_prep(QTestState *qts)
+{
+ return read_m48t59(qts, 0x80000000 + 0x74, 0x34);
+}
+
+static const boot_order_test test_cases_prep[] = {
+ { "", 'c', 'c' },
+ { "-boot c", 'c', 'c' },
+ { "-boot d", 'd', 'd' },
+ {}
+};
+
+static void test_prep_boot_order(void)
+{
+ test_boot_orders("prep", read_boot_order_prep, test_cases_prep);
+}
+
+static uint64_t read_boot_order_pmac(QTestState *qts)
+{
+ QFWCFG *fw_cfg = mm_fw_cfg_init(qts, 0xf0000510);
+
+ return qfw_cfg_get_u16(fw_cfg, FW_CFG_BOOT_DEVICE);
+}
+
+static const boot_order_test test_cases_fw_cfg[] = {
+ { "", 'c', 'c' },
+ { "-boot c", 'c', 'c' },
+ { "-boot d", 'd', 'd' },
+ { "-boot once=d,order=c", 'd', 'c' },
+ {}
+};
+
+static void test_pmac_oldworld_boot_order(void)
+{
+ test_boot_orders("g3beige", read_boot_order_pmac, test_cases_fw_cfg);
+}
+
+static void test_pmac_newworld_boot_order(void)
+{
+ test_boot_orders("mac99", read_boot_order_pmac, test_cases_fw_cfg);
+}
+
+static uint64_t read_boot_order_sun4m(QTestState *qts)
+{
+ QFWCFG *fw_cfg = mm_fw_cfg_init(qts, 0xd00000510ULL);
+
+ return qfw_cfg_get_u16(fw_cfg, FW_CFG_BOOT_DEVICE);
+}
+
+static void test_sun4m_boot_order(void)
+{
+ test_boot_orders("SS-5", read_boot_order_sun4m, test_cases_fw_cfg);
+}
+
+static uint64_t read_boot_order_sun4u(QTestState *qts)
+{
+ QFWCFG *fw_cfg = io_fw_cfg_init(qts, 0x510);
+
+ return qfw_cfg_get_u16(fw_cfg, FW_CFG_BOOT_DEVICE);
+}
+
+static void test_sun4u_boot_order(void)
+{
+ test_boot_orders("sun4u", read_boot_order_sun4u, test_cases_fw_cfg);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *arch = qtest_get_arch();
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ qtest_add_func("boot-order/pc", test_pc_boot_order);
+ } else if (strcmp(arch, "ppc") == 0 || strcmp(arch, "ppc64") == 0) {
+ qtest_add_func("boot-order/prep", test_prep_boot_order);
+ qtest_add_func("boot-order/pmac_oldworld",
+ test_pmac_oldworld_boot_order);
+ qtest_add_func("boot-order/pmac_newworld",
+ test_pmac_newworld_boot_order);
+ } else if (strcmp(arch, "sparc") == 0) {
+ qtest_add_func("boot-order/sun4m", test_sun4m_boot_order);
+ } else if (strcmp(arch, "sparc64") == 0) {
+ qtest_add_func("boot-order/sun4u", test_sun4u_boot_order);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/boot-sector.c b/tests/qtest/boot-sector.c
new file mode 100644
index 0000000000..9e66c6d013
--- /dev/null
+++ b/tests/qtest/boot-sector.c
@@ -0,0 +1,168 @@
+/*
+ * QEMU boot sector testing helpers.
+ *
+ * Copyright (c) 2016 Red Hat Inc.
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>
+ * Victor Kaplansky <victork@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "boot-sector.h"
+#include "qemu-common.h"
+#include "libqtest.h"
+
+#define LOW(x) ((x) & 0xff)
+#define HIGH(x) ((x) >> 8)
+
+#define SIGNATURE 0xdead
+#define SIGNATURE_OFFSET 0x10
+#define BOOT_SECTOR_ADDRESS 0x7c00
+#define SIGNATURE_ADDR (BOOT_SECTOR_ADDRESS + SIGNATURE_OFFSET)
+
+/* x86 boot sector code: write SIGNATURE into memory,
+ * then halt.
+ */
+static uint8_t x86_boot_sector[512] = {
+ /* The first sector will be placed at RAM address 00007C00, and
+ * the BIOS transfers control to 00007C00
+ */
+
+ /* Data Segment register should be initialized, since pxe
+ * boot loader can leave it dirty.
+ */
+
+ /* 7c00: move $0000,%ax */
+ [0x00] = 0xb8,
+ [0x01] = 0x00,
+ [0x02] = 0x00,
+ /* 7c03: move %ax,%ds */
+ [0x03] = 0x8e,
+ [0x04] = 0xd8,
+
+ /* 7c05: mov $0xdead,%ax */
+ [0x05] = 0xb8,
+ [0x06] = LOW(SIGNATURE),
+ [0x07] = HIGH(SIGNATURE),
+ /* 7c08: mov %ax,0x7c10 */
+ [0x08] = 0xa3,
+ [0x09] = LOW(SIGNATURE_ADDR),
+ [0x0a] = HIGH(SIGNATURE_ADDR),
+
+ /* 7c0b cli */
+ [0x0b] = 0xfa,
+ /* 7c0c: hlt */
+ [0x0c] = 0xf4,
+ /* 7c0e: jmp 0x7c07=0x7c0f-3 */
+ [0x0d] = 0xeb,
+ [0x0e] = LOW(-3),
+ /* We mov 0xdead here: set value to make debugging easier */
+ [SIGNATURE_OFFSET] = LOW(0xface),
+ [SIGNATURE_OFFSET + 1] = HIGH(0xface),
+ /* End of boot sector marker */
+ [0x1FE] = 0x55,
+ [0x1FF] = 0xAA,
+};
+
+/* For s390x, use a mini "kernel" with the appropriate signature */
+static const uint8_t s390x_psw_and_magic[] = {
+ 0x00, 0x08, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, /* Program status word */
+ 0x02, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x50, /* Magic: */
+ 0x02, 0x00, 0x00, 0x68, 0x60, 0x00, 0x00, 0x50, /* see linux_s390_magic */
+ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* in the s390-ccw bios */
+};
+static const uint8_t s390x_code[] = {
+ 0xa7, 0xf4, 0x00, 0x08, /* j 0x10010 */
+ 0x00, 0x00, 0x00, 0x00,
+ 'S', '3', '9', '0',
+ 'E', 'P', 0x00, 0x01,
+ 0xa7, 0x39, HIGH(SIGNATURE_ADDR), LOW(SIGNATURE_ADDR), /* lghi r3,0x7c10 */
+ 0xa7, 0x48, LOW(SIGNATURE), HIGH(SIGNATURE), /* lhi r4,0xadde */
+ 0x40, 0x40, 0x30, 0x00, /* sth r4,0(r3) */
+ 0xa7, 0xf4, 0xff, 0xfa /* j 0x10010 */
+};
+
+/* Create boot disk file. */
+int boot_sector_init(char *fname)
+{
+ int fd, ret;
+ size_t len;
+ char *boot_code;
+ const char *arch = qtest_get_arch();
+
+ fd = mkstemp(fname);
+ if (fd < 0) {
+ fprintf(stderr, "Couldn't open \"%s\": %s", fname, strerror(errno));
+ return 1;
+ }
+
+ if (g_str_equal(arch, "i386") || g_str_equal(arch, "x86_64")) {
+ /* Q35 requires a minimum 0x7e000 bytes disk (bug or feature?) */
+ len = MAX(0x7e000, sizeof(x86_boot_sector));
+ boot_code = g_malloc0(len);
+ memcpy(boot_code, x86_boot_sector, sizeof(x86_boot_sector));
+ } else if (g_str_equal(arch, "ppc64")) {
+ /* For Open Firmware based system, use a Forth script */
+ boot_code = g_strdup_printf("\\ Bootscript\n%x %x c! %x %x c!\n",
+ LOW(SIGNATURE), SIGNATURE_ADDR,
+ HIGH(SIGNATURE), SIGNATURE_ADDR + 1);
+ len = strlen(boot_code);
+ } else if (g_str_equal(arch, "s390x")) {
+ len = 0x10000 + sizeof(s390x_code);
+ boot_code = g_malloc0(len);
+ memcpy(boot_code, s390x_psw_and_magic, sizeof(s390x_psw_and_magic));
+ memcpy(&boot_code[0x10000], s390x_code, sizeof(s390x_code));
+ } else {
+ g_assert_not_reached();
+ }
+
+ ret = write(fd, boot_code, len);
+ close(fd);
+
+ g_free(boot_code);
+
+ if (ret != len) {
+ fprintf(stderr, "Could not write \"%s\"", fname);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Loop until signature in memory is OK. */
+void boot_sector_test(QTestState *qts)
+{
+ uint8_t signature_low;
+ uint8_t signature_high;
+ uint16_t signature;
+ int i;
+
+ /* Wait at most 600 seconds (test is slow with TCI and --enable-debug) */
+#define TEST_DELAY (1 * G_USEC_PER_SEC / 10)
+#define TEST_CYCLES MAX((600 * G_USEC_PER_SEC / TEST_DELAY), 1)
+
+ /* Poll until code has run and modified memory. Once it has we know BIOS
+ * initialization is done. TODO: check that IP reached the halt
+ * instruction.
+ */
+ for (i = 0; i < TEST_CYCLES; ++i) {
+ signature_low = qtest_readb(qts, SIGNATURE_ADDR);
+ signature_high = qtest_readb(qts, SIGNATURE_ADDR + 1);
+ signature = (signature_high << 8) | signature_low;
+ if (signature == SIGNATURE) {
+ break;
+ }
+ g_usleep(TEST_DELAY);
+ }
+
+ g_assert_cmphex(signature, ==, SIGNATURE);
+}
+
+/* unlink boot disk file. */
+void boot_sector_cleanup(const char *fname)
+{
+ unlink(fname);
+}
diff --git a/tests/qtest/boot-sector.h b/tests/qtest/boot-sector.h
new file mode 100644
index 0000000000..6ee6bb4d97
--- /dev/null
+++ b/tests/qtest/boot-sector.h
@@ -0,0 +1,28 @@
+/*
+ * QEMU boot sector testing helpers.
+ *
+ * Copyright (c) 2016 Red Hat Inc.
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>
+ * Victor Kaplansky <victork@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef TEST_BOOT_SECTOR_H
+#define TEST_BOOT_SECTOR_H
+
+#include "libqtest.h"
+
+/* Create boot disk file. fname must be a suitable string for mkstemp() */
+int boot_sector_init(char *fname);
+
+/* Loop until signature in memory is OK. */
+void boot_sector_test(QTestState *qts);
+
+/* unlink boot disk file. */
+void boot_sector_cleanup(const char *fname);
+
+#endif /* TEST_BOOT_SECTOR_H */
diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
new file mode 100644
index 0000000000..05c7f44457
--- /dev/null
+++ b/tests/qtest/boot-serial-test.c
@@ -0,0 +1,254 @@
+/*
+ * Test serial output of some machines.
+ *
+ * Copyright 2016 Thomas Huth, Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2
+ * or later. See the COPYING file in the top-level directory.
+ *
+ * This test is used to check that the serial output of the firmware
+ * (that we provide for some machines) or some small mini-kernels that
+ * we provide here contains an expected string. Thus we check that the
+ * firmware/kernel still boots at least to a certain point and so we
+ * know that the machine is not completely broken.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+static const uint8_t kernel_mcf5208[] = {
+ 0x41, 0xf9, 0xfc, 0x06, 0x00, 0x00, /* lea 0xfc060000,%a0 */
+ 0x10, 0x3c, 0x00, 0x54, /* move.b #'T',%d0 */
+ 0x11, 0x7c, 0x00, 0x04, 0x00, 0x08, /* move.b #4,8(%a0) Enable TX */
+ 0x11, 0x40, 0x00, 0x0c, /* move.b %d0,12(%a0) Print 'T' */
+ 0x60, 0xfa /* bra.s loop */
+};
+
+static const uint8_t bios_nextcube[] = {
+ 0x06, 0x00, 0x00, 0x00, /* Initial SP */
+ 0x01, 0x00, 0x00, 0x08, /* Initial PC */
+ 0x41, 0xf9, 0x02, 0x11, 0x80, 0x00, /* lea 0x02118000,%a0 */
+ 0x10, 0x3c, 0x00, 0x54, /* move.b #'T',%d0 */
+ 0x11, 0x7c, 0x00, 0x05, 0x00, 0x01, /* move.b #5,1(%a0) Sel TXCTRL */
+ 0x11, 0x7c, 0x00, 0x68, 0x00, 0x01, /* move.b #0x68,1(%a0) Enable TX */
+ 0x11, 0x40, 0x00, 0x03, /* move.b %d0,3(%a0) Print 'T' */
+ 0x60, 0xfa /* bra.s loop */
+};
+
+static const uint8_t kernel_pls3adsp1800[] = {
+ 0xb0, 0x00, 0x84, 0x00, /* imm 0x8400 */
+ 0x30, 0x60, 0x00, 0x04, /* addik r3,r0,4 */
+ 0x30, 0x80, 0x00, 0x54, /* addik r4,r0,'T' */
+ 0xf0, 0x83, 0x00, 0x00, /* sbi r4,r3,0 */
+ 0xb8, 0x00, 0xff, 0xfc /* bri -4 loop */
+};
+
+static const uint8_t kernel_plml605[] = {
+ 0xe0, 0x83, 0x00, 0xb0, /* imm 0x83e0 */
+ 0x00, 0x10, 0x60, 0x30, /* addik r3,r0,0x1000 */
+ 0x54, 0x00, 0x80, 0x30, /* addik r4,r0,'T' */
+ 0x00, 0x00, 0x83, 0xf0, /* sbi r4,r3,0 */
+ 0xfc, 0xff, 0x00, 0xb8 /* bri -4 loop */
+};
+
+static const uint8_t bios_moxiesim[] = {
+ 0x20, 0x10, 0x00, 0x00, 0x03, 0xf8, /* ldi.s r1,0x3f8 */
+ 0x1b, 0x20, 0x00, 0x00, 0x00, 0x54, /* ldi.b r2,'T' */
+ 0x1e, 0x12, /* st.b r1,r2 */
+ 0x1a, 0x00, 0x00, 0x00, 0x10, 0x00 /* jmpa 0x1000 */
+};
+
+static const uint8_t bios_raspi2[] = {
+ 0x08, 0x30, 0x9f, 0xe5, /* ldr r3,[pc,#8] Get base */
+ 0x54, 0x20, 0xa0, 0xe3, /* mov r2,#'T' */
+ 0x00, 0x20, 0xc3, 0xe5, /* strb r2,[r3] */
+ 0xfb, 0xff, 0xff, 0xea, /* b loop */
+ 0x00, 0x10, 0x20, 0x3f, /* 0x3f201000 = UART0 base addr */
+};
+
+static const uint8_t kernel_aarch64[] = {
+ 0x81, 0x0a, 0x80, 0x52, /* mov w1, #0x54 */
+ 0x02, 0x20, 0xa1, 0xd2, /* mov x2, #0x9000000 */
+ 0x41, 0x00, 0x00, 0x39, /* strb w1, [x2] */
+ 0xfd, 0xff, 0xff, 0x17, /* b -12 (loop) */
+};
+
+static const uint8_t kernel_nrf51[] = {
+ 0x00, 0x00, 0x00, 0x00, /* Stack top address */
+ 0x09, 0x00, 0x00, 0x00, /* Reset handler address */
+ 0x04, 0x4a, /* ldr r2, [pc, #16] Get ENABLE */
+ 0x04, 0x21, /* movs r1, #4 */
+ 0x11, 0x60, /* str r1, [r2] */
+ 0x04, 0x4a, /* ldr r2, [pc, #16] Get STARTTX */
+ 0x01, 0x21, /* movs r1, #1 */
+ 0x11, 0x60, /* str r1, [r2] */
+ 0x03, 0x4a, /* ldr r2, [pc, #12] Get TXD */
+ 0x54, 0x21, /* movs r1, 'T' */
+ 0x11, 0x60, /* str r1, [r2] */
+ 0xfe, 0xe7, /* b . */
+ 0x00, 0x25, 0x00, 0x40, /* 0x40002500 = UART ENABLE */
+ 0x08, 0x20, 0x00, 0x40, /* 0x40002008 = UART STARTTX */
+ 0x1c, 0x25, 0x00, 0x40 /* 0x4000251c = UART TXD */
+};
+
+typedef struct testdef {
+ const char *arch; /* Target architecture */
+ const char *machine; /* Name of the machine */
+ const char *extra; /* Additional parameters */
+ const char *expect; /* Expected string in the serial output */
+ size_t codesize; /* Size of the kernel or bios data */
+ const uint8_t *kernel; /* Set in case we use our own mini kernel */
+ const uint8_t *bios; /* Set in case we use our own mini bios */
+} testdef_t;
+
+static testdef_t tests[] = {
+ { "alpha", "clipper", "", "PCI:" },
+ { "ppc", "ppce500", "", "U-Boot" },
+ { "ppc", "40p", "-vga none -boot d", "Trying cd:," },
+ { "ppc", "g3beige", "", "PowerPC,750" },
+ { "ppc", "mac99", "", "PowerPC,G4" },
+ { "ppc", "sam460ex", "-m 256", "DRAM: 256 MiB" },
+ { "ppc64", "ppce500", "", "U-Boot" },
+ { "ppc64", "40p", "-m 192", "Memory: 192M" },
+ { "ppc64", "mac99", "", "PowerPC,970FX" },
+ { "ppc64", "pseries",
+ "-machine cap-cfpc=broken,cap-sbbc=broken,cap-ibs=broken",
+ "Open Firmware" },
+ { "ppc64", "powernv8", "", "OPAL" },
+ { "ppc64", "powernv9", "", "OPAL" },
+ { "ppc64", "sam460ex", "-device e1000", "8086 100e" },
+ { "i386", "isapc", "-cpu qemu32 -device sga", "SGABIOS" },
+ { "i386", "pc", "-device sga", "SGABIOS" },
+ { "i386", "q35", "-device sga", "SGABIOS" },
+ { "x86_64", "isapc", "-cpu qemu32 -device sga", "SGABIOS" },
+ { "x86_64", "q35", "-device sga", "SGABIOS" },
+ { "sparc", "LX", "", "TMS390S10" },
+ { "sparc", "SS-4", "", "MB86904" },
+ { "sparc", "SS-600MP", "", "TMS390Z55" },
+ { "sparc64", "sun4u", "", "UltraSPARC" },
+ { "s390x", "s390-ccw-virtio", "", "device" },
+ { "m68k", "mcf5208evb", "", "TT", sizeof(kernel_mcf5208), kernel_mcf5208 },
+ { "m68k", "next-cube", "", "TT", sizeof(bios_nextcube), 0, bios_nextcube },
+ { "microblaze", "petalogix-s3adsp1800", "", "TT",
+ sizeof(kernel_pls3adsp1800), kernel_pls3adsp1800 },
+ { "microblazeel", "petalogix-ml605", "", "TT",
+ sizeof(kernel_plml605), kernel_plml605 },
+ { "moxie", "moxiesim", "", "TT", sizeof(bios_moxiesim), 0, bios_moxiesim },
+ { "arm", "raspi2", "", "TT", sizeof(bios_raspi2), 0, bios_raspi2 },
+ { "hppa", "hppa", "", "SeaBIOS wants SYSTEM HALT" },
+ { "aarch64", "virt", "-cpu cortex-a57", "TT", sizeof(kernel_aarch64),
+ kernel_aarch64 },
+ { "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 },
+
+ { NULL }
+};
+
+static bool check_guest_output(QTestState *qts, const testdef_t *test, int fd)
+{
+ int nbr = 0, pos = 0, ccnt;
+ time_t now, start = time(NULL);
+ char ch;
+
+ /* Poll serial output... */
+ while (1) {
+ ccnt = 0;
+ while (ccnt++ < 512 && (nbr = read(fd, &ch, 1)) == 1) {
+ if (ch == test->expect[pos]) {
+ pos += 1;
+ if (test->expect[pos] == '\0') {
+ /* We've reached the end of the expected string! */
+ return true;
+ }
+ } else {
+ pos = 0;
+ }
+ }
+ g_assert(nbr >= 0);
+ /* Wait only if the child is still alive. */
+ if (!qtest_probe_child(qts)) {
+ break;
+ }
+ /* Wait at most 360 seconds. */
+ now = time(NULL);
+ if (now - start >= 360) {
+ break;
+ }
+ g_usleep(10000);
+ }
+
+ return false;
+}
+
+static void test_machine(const void *data)
+{
+ const testdef_t *test = data;
+ char serialtmp[] = "/tmp/qtest-boot-serial-sXXXXXX";
+ char codetmp[] = "/tmp/qtest-boot-serial-cXXXXXX";
+ const char *codeparam = "";
+ const uint8_t *code = NULL;
+ QTestState *qts;
+ int ser_fd;
+
+ ser_fd = mkstemp(serialtmp);
+ g_assert(ser_fd != -1);
+
+ if (test->kernel) {
+ code = test->kernel;
+ codeparam = "-kernel";
+ } else if (test->bios) {
+ code = test->bios;
+ codeparam = "-bios";
+ }
+
+ if (code) {
+ ssize_t wlen;
+ int code_fd;
+
+ code_fd = mkstemp(codetmp);
+ g_assert(code_fd != -1);
+ wlen = write(code_fd, code, test->codesize);
+ g_assert(wlen == test->codesize);
+ close(code_fd);
+ }
+
+ /*
+ * Make sure that this test uses tcg if available: It is used as a
+ * fast-enough smoketest for that.
+ */
+ qts = qtest_initf("%s %s -M %s -no-shutdown "
+ "-chardev file,id=serial0,path=%s "
+ "-serial chardev:serial0 -accel tcg -accel kvm %s",
+ codeparam, code ? codetmp : "", test->machine,
+ serialtmp, test->extra);
+ if (code) {
+ unlink(codetmp);
+ }
+
+ if (!check_guest_output(qts, test, ser_fd)) {
+ g_error("Failed to find expected string. Please check '%s'",
+ serialtmp);
+ }
+ unlink(serialtmp);
+
+ qtest_quit(qts);
+
+ close(ser_fd);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *arch = qtest_get_arch();
+ int i;
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (i = 0; tests[i].arch != NULL; i++) {
+ if (strcmp(arch, tests[i].arch) == 0) {
+ char *name = g_strdup_printf("boot-serial/%s", tests[i].machine);
+ qtest_add_data_func(name, &tests[i], test_machine);
+ g_free(name);
+ }
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/cdrom-test.c b/tests/qtest/cdrom-test.c
new file mode 100644
index 0000000000..67635e387a
--- /dev/null
+++ b/tests/qtest/cdrom-test.c
@@ -0,0 +1,228 @@
+/*
+ * Various tests for emulated CD-ROM drives.
+ *
+ * Copyright (c) 2018 Red Hat Inc.
+ *
+ * Author:
+ * Thomas Huth <thuth@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2
+ * or later. See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "boot-sector.h"
+#include "qapi/qmp/qdict.h"
+
+static char isoimage[] = "cdrom-boot-iso-XXXXXX";
+
+static int exec_genisoimg(const char **args)
+{
+ gchar *out_err = NULL;
+ gint exit_status = -1;
+ bool success;
+
+ success = g_spawn_sync(NULL, (gchar **)args, NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
+ NULL, NULL, NULL, &out_err, &exit_status, NULL);
+ if (!success) {
+ return -ENOENT;
+ }
+ if (out_err) {
+ fputs(out_err, stderr);
+ g_free(out_err);
+ }
+
+ return exit_status;
+}
+
+static int prepare_image(const char *arch, char *isoimage)
+{
+ char srcdir[] = "cdrom-test-dir-XXXXXX";
+ char *codefile = NULL;
+ int ifh, ret = -1;
+ const char *args[] = {
+ "genisoimage", "-quiet", "-l", "-no-emul-boot",
+ "-b", NULL, "-o", isoimage, srcdir, NULL
+ };
+
+ ifh = mkstemp(isoimage);
+ if (ifh < 0) {
+ perror("Error creating temporary iso image file");
+ return -1;
+ }
+ if (!mkdtemp(srcdir)) {
+ perror("Error creating temporary directory");
+ goto cleanup;
+ }
+
+ if (g_str_equal(arch, "i386") || g_str_equal(arch, "x86_64") ||
+ g_str_equal(arch, "s390x")) {
+ codefile = g_strdup_printf("%s/bootcode-XXXXXX", srcdir);
+ ret = boot_sector_init(codefile);
+ if (ret) {
+ goto cleanup;
+ }
+ } else {
+ /* Just create a dummy file */
+ char txt[] = "empty disc";
+ codefile = g_strdup_printf("%s/readme.txt", srcdir);
+ if (!g_file_set_contents(codefile, txt, sizeof(txt) - 1, NULL)) {
+ fprintf(stderr, "Failed to create '%s'\n", codefile);
+ goto cleanup;
+ }
+ }
+
+ args[5] = strchr(codefile, '/') + 1;
+ ret = exec_genisoimg(args);
+ if (ret) {
+ fprintf(stderr, "genisoimage failed: %i\n", ret);
+ }
+
+ unlink(codefile);
+
+cleanup:
+ g_free(codefile);
+ rmdir(srcdir);
+ close(ifh);
+
+ return ret;
+}
+
+/**
+ * Check that at least the -cdrom parameter is basically working, i.e. we can
+ * see the filename of the ISO image in the output of "info block" afterwards
+ */
+static void test_cdrom_param(gconstpointer data)
+{
+ QTestState *qts;
+ char *resp;
+
+ qts = qtest_initf("-M %s -cdrom %s", (const char *)data, isoimage);
+ resp = qtest_hmp(qts, "info block");
+ g_assert(strstr(resp, isoimage) != 0);
+ g_free(resp);
+ qtest_quit(qts);
+}
+
+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);
+ machines++;
+ }
+}
+
+static void test_cdboot(gconstpointer data)
+{
+ QTestState *qts;
+
+ qts = qtest_initf("-accel kvm -accel tcg -no-shutdown %s%s", (const char *)data,
+ isoimage);
+ boot_sector_test(qts);
+ qtest_quit(qts);
+}
+
+static void add_x86_tests(void)
+{
+ qtest_add_data_func("cdrom/boot/default", "-cdrom ", test_cdboot);
+ qtest_add_data_func("cdrom/boot/virtio-scsi",
+ "-device virtio-scsi -device scsi-cd,drive=cdr "
+ "-blockdev file,node-name=cdr,filename=", test_cdboot);
+ /*
+ * Unstable CI test under load
+ * See https://lists.gnu.org/archive/html/qemu-devel/2019-02/msg05509.html
+ */
+ if (g_test_slow()) {
+ qtest_add_data_func("cdrom/boot/isapc", "-M isapc "
+ "-drive if=ide,media=cdrom,file=", test_cdboot);
+ }
+ qtest_add_data_func("cdrom/boot/am53c974",
+ "-device am53c974 -device scsi-cd,drive=cd1 "
+ "-drive if=none,id=cd1,format=raw,file=", test_cdboot);
+ qtest_add_data_func("cdrom/boot/dc390",
+ "-device dc390 -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=", test_cdboot);
+ qtest_add_data_func("cdrom/boot/lsi53c895a",
+ "-device lsi53c895a -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=", test_cdboot);
+ qtest_add_data_func("cdrom/boot/megasas", "-M q35 "
+ "-device megasas -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=", test_cdboot);
+ qtest_add_data_func("cdrom/boot/megasas-gen2", "-M q35 "
+ "-device megasas-gen2 -device scsi-cd,drive=cd1 "
+ "-blockdev file,node-name=cd1,filename=", test_cdboot);
+}
+
+static void add_s390x_tests(void)
+{
+ qtest_add_data_func("cdrom/boot/default", "-cdrom ", test_cdboot);
+ qtest_add_data_func("cdrom/boot/virtio-scsi",
+ "-device virtio-scsi -device scsi-cd,drive=cdr "
+ "-blockdev file,node-name=cdr,filename=", test_cdboot);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ const char *arch = qtest_get_arch();
+ const char *genisocheck[] = { "genisoimage", "-version", NULL };
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (exec_genisoimg(genisocheck)) {
+ /* genisoimage not available - so can't run tests */
+ return g_test_run();
+ }
+
+ ret = prepare_image(arch, isoimage);
+ if (ret) {
+ return ret;
+ }
+
+ if (g_str_equal(arch, "i386") || g_str_equal(arch, "x86_64")) {
+ add_x86_tests();
+ } else if (g_str_equal(arch, "s390x")) {
+ add_s390x_tests();
+ } else if (g_str_equal(arch, "ppc64")) {
+ const char *ppcmachines[] = {
+ "pseries", "mac99", "g3beige", "40p", "prep", NULL
+ };
+ add_cdrom_param_tests(ppcmachines);
+ } else if (g_str_equal(arch, "sparc")) {
+ const char *sparcmachines[] = {
+ "LX", "SPARCClassic", "SPARCbook", "SS-10", "SS-20", "SS-4",
+ "SS-5", "SS-600MP", "Voyager", "leon3_generic", NULL
+ };
+ add_cdrom_param_tests(sparcmachines);
+ } else if (g_str_equal(arch, "sparc64")) {
+ const char *sparc64machines[] = {
+ "niagara", "sun4u", "sun4v", NULL
+ };
+ add_cdrom_param_tests(sparc64machines);
+ } else if (!strncmp(arch, "mips64", 6)) {
+ const char *mips64machines[] = {
+ "magnum", "malta", "mips", "pica61", NULL
+ };
+ add_cdrom_param_tests(mips64machines);
+ } else if (g_str_equal(arch, "arm") || g_str_equal(arch, "aarch64")) {
+ const char *armmachines[] = {
+ "realview-eb", "realview-eb-mpcore", "realview-pb-a8",
+ "realview-pbx-a9", "versatileab", "versatilepb", "vexpress-a15",
+ "vexpress-a9", "virt", NULL
+ };
+ add_cdrom_param_tests(armmachines);
+ } else {
+ const char *nonemachine[] = { "none", NULL };
+ add_cdrom_param_tests(nonemachine);
+ }
+
+ ret = g_test_run();
+
+ unlink(isoimage);
+
+ return ret;
+}
diff --git a/tests/qtest/cpu-plug-test.c b/tests/qtest/cpu-plug-test.c
new file mode 100644
index 0000000000..e8ffbbce4b
--- /dev/null
+++ b/tests/qtest/cpu-plug-test.c
@@ -0,0 +1,257 @@
+/*
+ * QTest testcase for CPU plugging
+ *
+ * Copyright (c) 2015 SUSE Linux GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu-common.h"
+#include "libqtest-single.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+
+struct PlugTestData {
+ char *machine;
+ const char *cpu_model;
+ char *device_model;
+ unsigned sockets;
+ unsigned cores;
+ unsigned threads;
+ unsigned maxcpus;
+};
+typedef struct PlugTestData PlugTestData;
+
+static void test_plug_with_cpu_add(gconstpointer data)
+{
+ const PlugTestData *s = data;
+ char *args;
+ QDict *response;
+ unsigned int i;
+
+ args = g_strdup_printf("-machine %s -cpu %s "
+ "-smp 1,sockets=%u,cores=%u,threads=%u,maxcpus=%u",
+ s->machine, s->cpu_model,
+ s->sockets, s->cores, s->threads, s->maxcpus);
+ qtest_start(args);
+
+ for (i = 1; i < s->maxcpus; i++) {
+ response = qmp("{ 'execute': 'cpu-add',"
+ " 'arguments': { 'id': %d } }", i);
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+ }
+
+ qtest_end();
+ g_free(args);
+}
+
+static void test_plug_without_cpu_add(gconstpointer data)
+{
+ const PlugTestData *s = data;
+ char *args;
+ QDict *response;
+
+ args = g_strdup_printf("-machine %s -cpu %s "
+ "-smp 1,sockets=%u,cores=%u,threads=%u,maxcpus=%u",
+ s->machine, s->cpu_model,
+ s->sockets, s->cores, s->threads, s->maxcpus);
+ qtest_start(args);
+
+ response = qmp("{ 'execute': 'cpu-add',"
+ " 'arguments': { 'id': %d } }",
+ s->sockets * s->cores * s->threads);
+ g_assert(response);
+ g_assert(qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ qtest_end();
+ g_free(args);
+}
+
+static void test_plug_with_device_add(gconstpointer data)
+{
+ const PlugTestData *td = data;
+ char *args;
+ QTestState *qts;
+ QDict *resp;
+ QList *cpus;
+ QObject *e;
+ int hotplugged = 0;
+
+ args = g_strdup_printf("-machine %s -cpu %s "
+ "-smp 1,sockets=%u,cores=%u,threads=%u,maxcpus=%u",
+ td->machine, td->cpu_model,
+ td->sockets, td->cores, td->threads, td->maxcpus);
+ qts = qtest_init(args);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-hotpluggable-cpus'}");
+ g_assert(qdict_haskey(resp, "return"));
+ cpus = qdict_get_qlist(resp, "return");
+ g_assert(cpus);
+
+ while ((e = qlist_pop(cpus))) {
+ const QDict *cpu, *props;
+
+ cpu = qobject_to(QDict, e);
+ if (qdict_haskey(cpu, "qom-path")) {
+ qobject_unref(e);
+ continue;
+ }
+
+ g_assert(qdict_haskey(cpu, "props"));
+ props = qdict_get_qdict(cpu, "props");
+
+ qtest_qmp_device_add_qdict(qts, td->device_model, props);
+ hotplugged++;
+ qobject_unref(e);
+ }
+
+ /* make sure that there were hotplugged CPUs */
+ g_assert(hotplugged);
+ qobject_unref(resp);
+ qtest_quit(qts);
+ g_free(args);
+}
+
+static void test_data_free(gpointer data)
+{
+ PlugTestData *pc = data;
+
+ g_free(pc->machine);
+ g_free(pc->device_model);
+ g_free(pc);
+}
+
+static void add_pc_test_case(const char *mname)
+{
+ char *path;
+ PlugTestData *data;
+
+ if (!g_str_has_prefix(mname, "pc-")) {
+ return;
+ }
+ data = g_new(PlugTestData, 1);
+ data->machine = g_strdup(mname);
+ data->cpu_model = "Haswell"; /* 1.3+ theoretically */
+ data->device_model = g_strdup_printf("%s-%s-cpu", data->cpu_model,
+ qtest_get_arch());
+ data->sockets = 1;
+ data->cores = 3;
+ data->threads = 2;
+ data->maxcpus = data->sockets * data->cores * data->threads;
+ if (g_str_has_suffix(mname, "-1.4") ||
+ (strcmp(mname, "pc-1.3") == 0) ||
+ (strcmp(mname, "pc-1.2") == 0) ||
+ (strcmp(mname, "pc-1.1") == 0) ||
+ (strcmp(mname, "pc-1.0") == 0)) {
+ path = g_strdup_printf("cpu-plug/%s/init/%ux%ux%u&maxcpus=%u",
+ mname, data->sockets, data->cores,
+ data->threads, data->maxcpus);
+ qtest_add_data_func_full(path, data, test_plug_without_cpu_add,
+ test_data_free);
+ g_free(path);
+ } else {
+ PlugTestData *data2 = g_memdup(data, sizeof(PlugTestData));
+
+ data2->machine = g_strdup(data->machine);
+ data2->device_model = g_strdup(data->device_model);
+
+ path = g_strdup_printf("cpu-plug/%s/cpu-add/%ux%ux%u&maxcpus=%u",
+ mname, data->sockets, data->cores,
+ data->threads, data->maxcpus);
+ qtest_add_data_func_full(path, data, test_plug_with_cpu_add,
+ test_data_free);
+ g_free(path);
+ path = g_strdup_printf("cpu-plug/%s/device-add/%ux%ux%u&maxcpus=%u",
+ mname, data2->sockets, data2->cores,
+ data2->threads, data2->maxcpus);
+ qtest_add_data_func_full(path, data2, test_plug_with_device_add,
+ test_data_free);
+ g_free(path);
+ }
+}
+
+static void add_pseries_test_case(const char *mname)
+{
+ char *path;
+ PlugTestData *data;
+
+ if (!g_str_has_prefix(mname, "pseries-") ||
+ (g_str_has_prefix(mname, "pseries-2.") && atoi(&mname[10]) < 7)) {
+ return;
+ }
+ data = g_new(PlugTestData, 1);
+ data->machine = g_strdup(mname);
+ data->cpu_model = "power8_v2.0";
+ data->device_model = g_strdup("power8_v2.0-spapr-cpu-core");
+ data->sockets = 2;
+ data->cores = 3;
+ data->threads = 1;
+ data->maxcpus = data->sockets * data->cores * data->threads;
+
+ path = g_strdup_printf("cpu-plug/%s/device-add/%ux%ux%u&maxcpus=%u",
+ mname, data->sockets, data->cores,
+ data->threads, data->maxcpus);
+ qtest_add_data_func_full(path, data, test_plug_with_device_add,
+ test_data_free);
+ g_free(path);
+}
+
+static void add_s390x_test_case(const char *mname)
+{
+ char *path;
+ PlugTestData *data, *data2;
+
+ if (!g_str_has_prefix(mname, "s390-ccw-virtio-")) {
+ return;
+ }
+
+ data = g_new(PlugTestData, 1);
+ data->machine = g_strdup(mname);
+ data->cpu_model = "qemu";
+ data->device_model = g_strdup("qemu-s390x-cpu");
+ data->sockets = 1;
+ data->cores = 3;
+ data->threads = 1;
+ data->maxcpus = data->sockets * data->cores * data->threads;
+
+ data2 = g_memdup(data, sizeof(PlugTestData));
+ data2->machine = g_strdup(data->machine);
+ data2->device_model = g_strdup(data->device_model);
+
+ path = g_strdup_printf("cpu-plug/%s/cpu-add/%ux%ux%u&maxcpus=%u",
+ mname, data->sockets, data->cores,
+ data->threads, data->maxcpus);
+ qtest_add_data_func_full(path, data, test_plug_with_cpu_add,
+ test_data_free);
+ g_free(path);
+
+ path = g_strdup_printf("cpu-plug/%s/device-add/%ux%ux%u&maxcpus=%u",
+ mname, data2->sockets, data2->cores,
+ data2->threads, data2->maxcpus);
+ qtest_add_data_func_full(path, data2, test_plug_with_device_add,
+ test_data_free);
+ g_free(path);
+}
+
+int main(int argc, char **argv)
+{
+ const char *arch = qtest_get_arch();
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ qtest_cb_for_every_machine(add_pc_test_case, g_test_quick());
+ } else if (g_str_equal(arch, "ppc64")) {
+ qtest_cb_for_every_machine(add_pseries_test_case, g_test_quick());
+ } else if (g_str_equal(arch, "s390x")) {
+ qtest_cb_for_every_machine(add_s390x_test_case, g_test_quick());
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/dbus-vmstate-test.c b/tests/qtest/dbus-vmstate-test.c
new file mode 100644
index 0000000000..2e5e47dec2
--- /dev/null
+++ b/tests/qtest/dbus-vmstate-test.c
@@ -0,0 +1,382 @@
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include "libqtest.h"
+#include "qemu-common.h"
+#include "dbus-vmstate1.h"
+#include "migration-helpers.h"
+
+static char *workdir;
+
+typedef struct TestServerId {
+ const char *name;
+ const char *data;
+ size_t size;
+} TestServerId;
+
+static const TestServerId idA = {
+ "idA", "I'am\0idA!", sizeof("I'am\0idA!")
+};
+
+static const TestServerId idB = {
+ "idB", "I'am\0idB!", sizeof("I'am\0idB!")
+};
+
+typedef struct TestServer {
+ const TestServerId *id;
+ bool save_called;
+ bool load_called;
+} TestServer;
+
+typedef struct Test {
+ const char *id_list;
+ bool migrate_fail;
+ bool without_dst_b;
+ TestServer srcA;
+ TestServer dstA;
+ TestServer srcB;
+ TestServer dstB;
+ GMainLoop *loop;
+ QTestState *src_qemu;
+} Test;
+
+static gboolean
+vmstate_load(VMState1 *object, GDBusMethodInvocation *invocation,
+ const gchar *arg_data, gpointer user_data)
+{
+ TestServer *h = user_data;
+ g_autoptr(GVariant) var = NULL;
+ GVariant *args;
+ const uint8_t *data;
+ size_t size;
+
+ args = g_dbus_method_invocation_get_parameters(invocation);
+ var = g_variant_get_child_value(args, 0);
+ data = g_variant_get_fixed_array(var, &size, sizeof(char));
+ g_assert_cmpuint(size, ==, h->id->size);
+ g_assert(!memcmp(data, h->id->data, h->id->size));
+ h->load_called = true;
+
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
+ return TRUE;
+}
+
+static gboolean
+vmstate_save(VMState1 *object, GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ TestServer *h = user_data;
+ GVariant *var;
+
+ var = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
+ h->id->data, h->id->size, sizeof(char));
+ g_dbus_method_invocation_return_value(invocation,
+ g_variant_new("(@ay)", var));
+ h->save_called = true;
+
+ return TRUE;
+}
+
+typedef struct WaitNamed {
+ GMainLoop *loop;
+ bool named;
+} WaitNamed;
+
+static void
+named_cb(GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ WaitNamed *t = user_data;
+
+ t->named = true;
+ g_main_loop_quit(t->loop);
+}
+
+static GDBusConnection *
+get_connection(Test *test, guint *ownid)
+{
+ g_autofree gchar *addr = NULL;
+ WaitNamed *wait;
+ GError *err = NULL;
+ GDBusConnection *c;
+
+ wait = g_new0(WaitNamed, 1);
+ wait->loop = test->loop;
+ addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, NULL, &err);
+ g_assert_no_error(err);
+
+ c = g_dbus_connection_new_for_address_sync(
+ addr,
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+ NULL, NULL, &err);
+ g_assert_no_error(err);
+ *ownid = g_bus_own_name_on_connection(c, "org.qemu.VMState1",
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ named_cb, named_cb, wait, g_free);
+ if (!wait->named) {
+ g_main_loop_run(wait->loop);
+ }
+
+ return c;
+}
+
+static GDBusObjectManagerServer *
+get_server(GDBusConnection *conn, TestServer *s, const TestServerId *id)
+{
+ g_autoptr(GDBusObjectSkeleton) sk = NULL;
+ g_autoptr(VMState1Skeleton) v = NULL;
+ GDBusObjectManagerServer *os;
+
+ s->id = id;
+ os = g_dbus_object_manager_server_new("/org/qemu");
+ sk = g_dbus_object_skeleton_new("/org/qemu/VMState1");
+
+ v = VMSTATE1_SKELETON(vmstate1_skeleton_new());
+ g_object_set(v, "id", id->name, NULL);
+
+ g_signal_connect(v, "handle-load", G_CALLBACK(vmstate_load), s);
+ g_signal_connect(v, "handle-save", G_CALLBACK(vmstate_save), s);
+
+ g_dbus_object_skeleton_add_interface(sk, G_DBUS_INTERFACE_SKELETON(v));
+ g_dbus_object_manager_server_export(os, sk);
+ g_dbus_object_manager_server_set_connection(os, conn);
+
+ return os;
+}
+
+static void
+set_id_list(Test *test, QTestState *s)
+{
+ if (!test->id_list) {
+ return;
+ }
+
+ g_assert(!qmp_rsp_is_err(qtest_qmp(s,
+ "{ 'execute': 'qom-set', 'arguments': "
+ "{ 'path': '/objects/dv', 'property': 'id-list', 'value': %s } }",
+ test->id_list)));
+}
+
+static gpointer
+dbus_vmstate_thread(gpointer data)
+{
+ GMainLoop *loop = data;
+
+ g_main_loop_run(loop);
+
+ return NULL;
+}
+
+static void
+test_dbus_vmstate(Test *test)
+{
+ g_autofree char *src_qemu_args = NULL;
+ g_autofree char *dst_qemu_args = NULL;
+ g_autoptr(GTestDBus) srcbus = NULL;
+ g_autoptr(GTestDBus) dstbus = NULL;
+ g_autoptr(GDBusConnection) srcconnA = NULL;
+ g_autoptr(GDBusConnection) srcconnB = NULL;
+ g_autoptr(GDBusConnection) dstconnA = NULL;
+ g_autoptr(GDBusConnection) dstconnB = NULL;
+ g_autoptr(GDBusObjectManagerServer) srcserverA = NULL;
+ g_autoptr(GDBusObjectManagerServer) srcserverB = NULL;
+ g_autoptr(GDBusObjectManagerServer) dstserverA = NULL;
+ g_autoptr(GDBusObjectManagerServer) dstserverB = NULL;
+ g_auto(GStrv) srcaddr = NULL;
+ g_auto(GStrv) dstaddr = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
+ g_autofree char *uri = NULL;
+ QTestState *src_qemu = NULL, *dst_qemu = NULL;
+ guint ownsrcA, ownsrcB, owndstA, owndstB;
+
+ uri = g_strdup_printf("unix:%s/migsocket", workdir);
+
+ loop = g_main_loop_new(NULL, FALSE);
+ test->loop = loop;
+
+ srcbus = g_test_dbus_new(G_TEST_DBUS_NONE);
+ g_test_dbus_up(srcbus);
+ srcconnA = get_connection(test, &ownsrcA);
+ srcserverA = get_server(srcconnA, &test->srcA, &idA);
+ srcconnB = get_connection(test, &ownsrcB);
+ srcserverB = get_server(srcconnB, &test->srcB, &idB);
+
+ /* remove ,guid=foo part */
+ srcaddr = g_strsplit(g_test_dbus_get_bus_address(srcbus), ",", 2);
+ src_qemu_args =
+ g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s", srcaddr[0]);
+
+ dstbus = g_test_dbus_new(G_TEST_DBUS_NONE);
+ g_test_dbus_up(dstbus);
+ dstconnA = get_connection(test, &owndstA);
+ dstserverA = get_server(dstconnA, &test->dstA, &idA);
+ if (!test->without_dst_b) {
+ dstconnB = get_connection(test, &owndstB);
+ dstserverB = get_server(dstconnB, &test->dstB, &idB);
+ }
+
+ dstaddr = g_strsplit(g_test_dbus_get_bus_address(dstbus), ",", 2);
+ dst_qemu_args =
+ g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s -incoming %s",
+ dstaddr[0], uri);
+
+ src_qemu = qtest_init(src_qemu_args);
+ dst_qemu = qtest_init(dst_qemu_args);
+ set_id_list(test, src_qemu);
+ set_id_list(test, dst_qemu);
+
+ thread = g_thread_new("dbus-vmstate-thread", dbus_vmstate_thread, loop);
+
+ migrate_qmp(src_qemu, uri, "{}");
+ test->src_qemu = src_qemu;
+ if (test->migrate_fail) {
+ wait_for_migration_fail(src_qemu, true);
+ qtest_set_expected_status(dst_qemu, 1);
+ } else {
+ wait_for_migration_complete(src_qemu);
+ }
+
+ qtest_quit(dst_qemu);
+ qtest_quit(src_qemu);
+ g_bus_unown_name(ownsrcA);
+ g_bus_unown_name(ownsrcB);
+ g_bus_unown_name(owndstA);
+ if (!test->without_dst_b) {
+ g_bus_unown_name(owndstB);
+ }
+
+ g_main_loop_quit(test->loop);
+}
+
+static void
+check_not_migrated(TestServer *s, TestServer *d)
+{
+ assert(!s->save_called);
+ assert(!s->load_called);
+ assert(!d->save_called);
+ assert(!d->load_called);
+}
+
+static void
+check_migrated(TestServer *s, TestServer *d)
+{
+ assert(s->save_called);
+ assert(!s->load_called);
+ assert(!d->save_called);
+ assert(d->load_called);
+}
+
+static void
+test_dbus_vmstate_without_list(void)
+{
+ Test test = { 0, };
+
+ test_dbus_vmstate(&test);
+
+ check_migrated(&test.srcA, &test.dstA);
+ check_migrated(&test.srcB, &test.dstB);
+}
+
+static void
+test_dbus_vmstate_with_list(void)
+{
+ Test test = { .id_list = "idA,idB" };
+
+ test_dbus_vmstate(&test);
+
+ check_migrated(&test.srcA, &test.dstA);
+ check_migrated(&test.srcB, &test.dstB);
+}
+
+static void
+test_dbus_vmstate_only_a(void)
+{
+ Test test = { .id_list = "idA" };
+
+ test_dbus_vmstate(&test);
+
+ check_migrated(&test.srcA, &test.dstA);
+ check_not_migrated(&test.srcB, &test.dstB);
+}
+
+static void
+test_dbus_vmstate_missing_src(void)
+{
+ Test test = { .id_list = "idA,idC", .migrate_fail = true };
+
+ /* run in subprocess to silence QEMU error reporting */
+ if (g_test_subprocess()) {
+ test_dbus_vmstate(&test);
+ check_not_migrated(&test.srcA, &test.dstA);
+ check_not_migrated(&test.srcB, &test.dstB);
+ return;
+ }
+
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_passed();
+}
+
+static void
+test_dbus_vmstate_missing_dst(void)
+{
+ Test test = { .id_list = "idA,idB",
+ .without_dst_b = true,
+ .migrate_fail = true };
+
+ /* run in subprocess to silence QEMU error reporting */
+ if (g_test_subprocess()) {
+ test_dbus_vmstate(&test);
+ assert(test.srcA.save_called);
+ assert(test.srcB.save_called);
+ assert(!test.dstB.save_called);
+ return;
+ }
+
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_passed();
+}
+
+int
+main(int argc, char **argv)
+{
+ GError *err = NULL;
+ g_autofree char *dbus_daemon = NULL;
+ int ret;
+
+ dbus_daemon = g_build_filename(G_STRINGIFY(SRCDIR),
+ "tests",
+ "dbus-vmstate-daemon.sh",
+ NULL);
+ g_setenv("G_TEST_DBUS_DAEMON", dbus_daemon, true);
+
+ g_test_init(&argc, &argv, NULL);
+
+ workdir = g_dir_make_tmp("dbus-vmstate-test-XXXXXX", &err);
+ if (!workdir) {
+ g_error("Unable to create temporary dir: %s\n", err->message);
+ exit(1);
+ }
+
+ g_setenv("DBUS_VMSTATE_TEST_TMPDIR", workdir, true);
+
+ qtest_add_func("/dbus-vmstate/without-list",
+ test_dbus_vmstate_without_list);
+ qtest_add_func("/dbus-vmstate/with-list",
+ test_dbus_vmstate_with_list);
+ qtest_add_func("/dbus-vmstate/only-a",
+ test_dbus_vmstate_only_a);
+ qtest_add_func("/dbus-vmstate/missing-src",
+ test_dbus_vmstate_missing_src);
+ qtest_add_func("/dbus-vmstate/missing-dst",
+ test_dbus_vmstate_missing_dst);
+
+ ret = g_test_run();
+
+ rmdir(workdir);
+ g_free(workdir);
+
+ return ret;
+}
diff --git a/tests/qtest/dbus-vmstate1.xml b/tests/qtest/dbus-vmstate1.xml
new file mode 100644
index 0000000000..cc8563be4c
--- /dev/null
+++ b/tests/qtest/dbus-vmstate1.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name="org.qemu.VMState1">
+ <property name="Id" type="s" access="read"/>
+ <method name="Load">
+ <arg type="ay" name="data" direction="in"/>
+ </method>
+ <method name="Save">
+ <arg type="ay" name="data" direction="out"/>
+ </method>
+ </interface>
+</node>
diff --git a/tests/qtest/device-introspect-test.c b/tests/qtest/device-introspect-test.c
new file mode 100644
index 0000000000..04f22903b0
--- /dev/null
+++ b/tests/qtest/device-introspect-test.c
@@ -0,0 +1,323 @@
+/*
+ * Device introspection test cases
+ *
+ * Copyright (c) 2015 Red Hat Inc.
+ *
+ * Authors:
+ * Markus Armbruster <armbru@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * Covers QMP device-list-properties and HMP device_add help. We
+ * currently don't check that their output makes sense, only that QEMU
+ * survives. Useful since we've had an astounding number of crash
+ * bugs around here.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/qmp/qstring.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+#include "libqtest.h"
+
+const char common_args[] = "-nodefaults -machine none";
+
+static QList *qom_list_types(QTestState * qts, const char *implements,
+ bool abstract)
+{
+ QDict *resp;
+ QList *ret;
+ QDict *args = qdict_new();
+
+ qdict_put_bool(args, "abstract", abstract);
+ if (implements) {
+ qdict_put_str(args, "implements", implements);
+ }
+ resp = qtest_qmp(qts, "{'execute': 'qom-list-types', 'arguments': %p }",
+ args);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_qlist(resp, "return");
+ qobject_ref(ret);
+ qobject_unref(resp);
+ return ret;
+}
+
+/* Build a name -> ObjectTypeInfo index from a ObjectTypeInfo list */
+static QDict *qom_type_index(QList *types)
+{
+ QDict *index = qdict_new();
+ QListEntry *e;
+
+ QLIST_FOREACH_ENTRY(types, e) {
+ QDict *d = qobject_to(QDict, qlist_entry_obj(e));
+ const char *name = qdict_get_str(d, "name");
+ qobject_ref(d);
+ qdict_put(index, name, d);
+ }
+ return index;
+}
+
+/* Check if @parent is present in the parent chain of @type */
+static bool qom_has_parent(QDict *index, const char *type, const char *parent)
+{
+ while (type) {
+ QDict *d = qdict_get_qdict(index, type);
+ const char *p = d && qdict_haskey(d, "parent") ?
+ qdict_get_str(d, "parent") :
+ NULL;
+
+ if (!strcmp(type, parent)) {
+ return true;
+ }
+
+ type = p;
+ }
+
+ return false;
+}
+
+/* Find an entry on a list returned by qom-list-types */
+static QDict *type_list_find(QList *types, const char *name)
+{
+ QListEntry *e;
+
+ QLIST_FOREACH_ENTRY(types, e) {
+ QDict *d = qobject_to(QDict, qlist_entry_obj(e));
+ const char *ename = qdict_get_str(d, "name");
+ if (!strcmp(ename, name)) {
+ return d;
+ }
+ }
+
+ return NULL;
+}
+
+static QList *device_type_list(QTestState *qts, bool abstract)
+{
+ return qom_list_types(qts, "device", abstract);
+}
+
+static void test_one_device(QTestState *qts, const char *type)
+{
+ QDict *resp;
+ char *help;
+ char *qom_tree_start, *qom_tree_end;
+ char *qtree_start, *qtree_end;
+
+ g_test_message("Testing device '%s'", type);
+
+ qom_tree_start = qtest_hmp(qts, "info qom-tree");
+ qtree_start = qtest_hmp(qts, "info qtree");
+
+ resp = qtest_qmp(qts, "{'execute': 'device-list-properties',"
+ " 'arguments': {'typename': %s}}",
+ type);
+ qobject_unref(resp);
+
+ help = qtest_hmp(qts, "device_add \"%s,help\"", type);
+ g_free(help);
+
+ /*
+ * Some devices leave dangling pointers in QOM behind.
+ * "info qom-tree" or "info qtree" have a good chance at crashing then.
+ * Also make sure that the tree did not change.
+ */
+ qom_tree_end = qtest_hmp(qts, "info qom-tree");
+ g_assert_cmpstr(qom_tree_start, ==, qom_tree_end);
+ g_free(qom_tree_start);
+ g_free(qom_tree_end);
+
+ qtree_end = qtest_hmp(qts, "info qtree");
+ g_assert_cmpstr(qtree_start, ==, qtree_end);
+ g_free(qtree_start);
+ g_free(qtree_end);
+}
+
+static void test_device_intro_list(void)
+{
+ QList *types;
+ char *help;
+ QTestState *qts;
+
+ qts = qtest_init(common_args);
+
+ types = device_type_list(qts, true);
+ qobject_unref(types);
+
+ help = qtest_hmp(qts, "device_add help");
+ g_free(help);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Ensure all entries returned by qom-list-types implements=<parent>
+ * have <parent> as a parent.
+ */
+static void test_qom_list_parents(QTestState *qts, const char *parent)
+{
+ QList *types;
+ QListEntry *e;
+ QDict *index;
+
+ types = qom_list_types(qts, parent, true);
+ index = qom_type_index(types);
+
+ QLIST_FOREACH_ENTRY(types, e) {
+ QDict *d = qobject_to(QDict, qlist_entry_obj(e));
+ const char *name = qdict_get_str(d, "name");
+
+ g_assert(qom_has_parent(index, name, parent));
+ }
+
+ qobject_unref(types);
+ qobject_unref(index);
+}
+
+static void test_qom_list_fields(void)
+{
+ QList *all_types;
+ QList *non_abstract;
+ QListEntry *e;
+ QTestState *qts;
+
+ qts = qtest_init(common_args);
+
+ all_types = qom_list_types(qts, NULL, true);
+ non_abstract = qom_list_types(qts, NULL, false);
+
+ QLIST_FOREACH_ENTRY(all_types, e) {
+ QDict *d = qobject_to(QDict, qlist_entry_obj(e));
+ const char *name = qdict_get_str(d, "name");
+ bool abstract = qdict_haskey(d, "abstract") ?
+ qdict_get_bool(d, "abstract") :
+ false;
+ bool expected_abstract = !type_list_find(non_abstract, name);
+
+ g_assert(abstract == expected_abstract);
+ }
+
+ test_qom_list_parents(qts, "object");
+ test_qom_list_parents(qts, "device");
+ test_qom_list_parents(qts, "sys-bus-device");
+
+ qobject_unref(all_types);
+ qobject_unref(non_abstract);
+ qtest_quit(qts);
+}
+
+static void test_device_intro_none(void)
+{
+ QTestState *qts = qtest_init(common_args);
+
+ test_one_device(qts, "nonexistent");
+ qtest_quit(qts);
+}
+
+static void test_device_intro_abstract(void)
+{
+ QTestState *qts = qtest_init(common_args);
+
+ test_one_device(qts, "device");
+ qtest_quit(qts);
+}
+
+static void test_device_intro_concrete(const void *args)
+{
+ QList *types;
+ QListEntry *entry;
+ const char *type;
+ QTestState *qts;
+
+ qts = qtest_init(args);
+ types = device_type_list(qts, false);
+
+ QLIST_FOREACH_ENTRY(types, entry) {
+ type = qdict_get_try_str(qobject_to(QDict, qlist_entry_obj(entry)),
+ "name");
+ g_assert(type);
+ test_one_device(qts, type);
+ }
+
+ qobject_unref(types);
+ qtest_quit(qts);
+ g_free((void *)args);
+}
+
+static void test_abstract_interfaces(void)
+{
+ QList *all_types;
+ QListEntry *e;
+ QDict *index;
+ QTestState *qts;
+
+ qts = qtest_init(common_args);
+
+ all_types = qom_list_types(qts, "interface", true);
+ index = qom_type_index(all_types);
+
+ QLIST_FOREACH_ENTRY(all_types, e) {
+ QDict *d = qobject_to(QDict, qlist_entry_obj(e));
+ const char *name = qdict_get_str(d, "name");
+
+ /*
+ * qom-list-types implements=interface returns all types
+ * that implement _any_ interface (not just interface
+ * types), so skip the ones that don't have "interface"
+ * on the parent type chain.
+ */
+ if (!qom_has_parent(index, name, "interface")) {
+ /* Not an interface type */
+ continue;
+ }
+
+ g_assert(qdict_haskey(d, "abstract") && qdict_get_bool(d, "abstract"));
+ }
+
+ qobject_unref(all_types);
+ qobject_unref(index);
+ qtest_quit(qts);
+}
+
+static void add_machine_test_case(const char *mname)
+{
+ char *path, *args;
+
+ /* Ignore blacklisted machines */
+ if (g_str_equal("xenfv", mname) || g_str_equal("xenpv", mname)) {
+ return;
+ }
+
+ path = g_strdup_printf("device/introspect/concrete/defaults/%s", mname);
+ args = g_strdup_printf("-M %s", mname);
+ qtest_add_data_func(path, args, test_device_intro_concrete);
+ g_free(path);
+
+ path = g_strdup_printf("device/introspect/concrete/nodefaults/%s", mname);
+ args = g_strdup_printf("-nodefaults -M %s", mname);
+ qtest_add_data_func(path, args, test_device_intro_concrete);
+ g_free(path);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("device/introspect/list", test_device_intro_list);
+ qtest_add_func("device/introspect/list-fields", test_qom_list_fields);
+ qtest_add_func("device/introspect/none", test_device_intro_none);
+ qtest_add_func("device/introspect/abstract", test_device_intro_abstract);
+ qtest_add_func("device/introspect/abstract-interfaces", test_abstract_interfaces);
+ if (g_test_quick()) {
+ qtest_add_data_func("device/introspect/concrete/defaults/none",
+ g_strdup(common_args), test_device_intro_concrete);
+ } else {
+ qtest_cb_for_every_machine(add_machine_test_case, true);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/device-plug-test.c b/tests/qtest/device-plug-test.c
new file mode 100644
index 0000000000..318e422d51
--- /dev/null
+++ b/tests/qtest/device-plug-test.c
@@ -0,0 +1,178 @@
+/*
+ * QEMU device plug/unplug handling
+ *
+ * Copyright (C) 2019 Red Hat Inc.
+ *
+ * Authors:
+ * David Hildenbrand <david@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qstring.h"
+
+static void device_del_start(QTestState *qtest, const char *id)
+{
+ qtest_qmp_send(qtest,
+ "{'execute': 'device_del', 'arguments': { 'id': %s } }", id);
+}
+
+static void device_del_finish(QTestState *qtest)
+{
+ QDict *resp = qtest_qmp_receive(qtest);
+
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static void device_del_request(QTestState *qtest, const char *id)
+{
+ device_del_start(qtest, id);
+ device_del_finish(qtest);
+}
+
+static void system_reset(QTestState *qtest)
+{
+ QDict *resp;
+
+ resp = qtest_qmp(qtest, "{'execute': 'system_reset'}");
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static void wait_device_deleted_event(QTestState *qtest, const char *id)
+{
+ QDict *resp, *data;
+ QString *qstr;
+
+ /*
+ * Other devices might get removed along with the removed device. Skip
+ * these. The device of interest will be the last one.
+ */
+ for (;;) {
+ resp = qtest_qmp_eventwait_ref(qtest, "DEVICE_DELETED");
+ data = qdict_get_qdict(resp, "data");
+ if (!data || !qdict_get(data, "device")) {
+ qobject_unref(resp);
+ continue;
+ }
+ qstr = qobject_to(QString, qdict_get(data, "device"));
+ g_assert(qstr);
+ if (!strcmp(qstring_get_str(qstr), id)) {
+ qobject_unref(resp);
+ break;
+ }
+ qobject_unref(resp);
+ }
+}
+
+static void test_pci_unplug_request(void)
+{
+ QTestState *qtest = qtest_initf("-device virtio-mouse-pci,id=dev0");
+
+ /*
+ * Request device removal. As the guest is not running, the request won't
+ * be processed. However during system reset, the removal will be
+ * handled, removing the device.
+ */
+ device_del_request(qtest, "dev0");
+ system_reset(qtest);
+ wait_device_deleted_event(qtest, "dev0");
+
+ qtest_quit(qtest);
+}
+
+static void test_ccw_unplug(void)
+{
+ QTestState *qtest = qtest_initf("-device virtio-balloon-ccw,id=dev0");
+
+ /*
+ * The DEVICE_DELETED events will be sent before the command
+ * completes.
+ */
+ device_del_start(qtest, "dev0");
+ wait_device_deleted_event(qtest, "dev0");
+ device_del_finish(qtest);
+
+ qtest_quit(qtest);
+}
+
+static void test_spapr_cpu_unplug_request(void)
+{
+ QTestState *qtest;
+
+ qtest = qtest_initf("-cpu power9_v2.0 -smp 1,maxcpus=2 "
+ "-device power9_v2.0-spapr-cpu-core,core-id=1,id=dev0");
+
+ /* similar to test_pci_unplug_request */
+ device_del_request(qtest, "dev0");
+ system_reset(qtest);
+ wait_device_deleted_event(qtest, "dev0");
+
+ qtest_quit(qtest);
+}
+
+static void test_spapr_memory_unplug_request(void)
+{
+ QTestState *qtest;
+
+ qtest = qtest_initf("-m 256M,slots=1,maxmem=768M "
+ "-object memory-backend-ram,id=mem0,size=512M "
+ "-device pc-dimm,id=dev0,memdev=mem0");
+
+ /* similar to test_pci_unplug_request */
+ device_del_request(qtest, "dev0");
+ system_reset(qtest);
+ wait_device_deleted_event(qtest, "dev0");
+
+ qtest_quit(qtest);
+}
+
+static void test_spapr_phb_unplug_request(void)
+{
+ QTestState *qtest;
+
+ qtest = qtest_initf("-device spapr-pci-host-bridge,index=1,id=dev0");
+
+ /* similar to test_pci_unplug_request */
+ device_del_request(qtest, "dev0");
+ system_reset(qtest);
+ wait_device_deleted_event(qtest, "dev0");
+
+ qtest_quit(qtest);
+}
+
+int main(int argc, char **argv)
+{
+ const char *arch = qtest_get_arch();
+
+ g_test_init(&argc, &argv, NULL);
+
+ /*
+ * We need a system that will process unplug requests during system resets
+ * and does not do PCI surprise removal. This holds for x86 ACPI,
+ * s390x and spapr.
+ */
+ qtest_add_func("/device-plug/pci-unplug-request",
+ test_pci_unplug_request);
+
+ if (!strcmp(arch, "s390x")) {
+ qtest_add_func("/device-plug/ccw-unplug",
+ test_ccw_unplug);
+ }
+
+ if (!strcmp(arch, "ppc64")) {
+ qtest_add_func("/device-plug/spapr-cpu-unplug-request",
+ test_spapr_cpu_unplug_request);
+ qtest_add_func("/device-plug/spapr-memory-unplug-request",
+ test_spapr_memory_unplug_request);
+ qtest_add_func("/device-plug/spapr-phb-unplug-request",
+ test_spapr_phb_unplug_request);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/display-vga-test.c b/tests/qtest/display-vga-test.c
new file mode 100644
index 0000000000..ace3bb28e0
--- /dev/null
+++ b/tests/qtest/display-vga-test.c
@@ -0,0 +1,69 @@
+/*
+ * QTest testcase for vga cards
+ *
+ * Copyright (c) 2014 Red Hat, Inc
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+static void pci_cirrus(void)
+{
+ qtest_start("-vga none -device cirrus-vga");
+ qtest_end();
+}
+
+static void pci_stdvga(void)
+{
+ qtest_start("-vga none -device VGA");
+ qtest_end();
+}
+
+static void pci_secondary(void)
+{
+ qtest_start("-vga none -device secondary-vga");
+ qtest_end();
+}
+
+static void pci_multihead(void)
+{
+ qtest_start("-vga none -device VGA -device secondary-vga");
+ qtest_end();
+}
+
+static void pci_virtio_gpu(void)
+{
+ qtest_start("-vga none -device virtio-gpu-pci");
+ qtest_end();
+}
+
+static void pci_virtio_vga(void)
+{
+ qtest_start("-vga none -device virtio-vga");
+ qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+ const char *arch = qtest_get_arch();
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (strcmp(arch, "alpha") == 0 || strcmp(arch, "i386") == 0 ||
+ strcmp(arch, "mips") == 0 || strcmp(arch, "x86_64") == 0) {
+ qtest_add_func("/display/pci/cirrus", pci_cirrus);
+ }
+ qtest_add_func("/display/pci/stdvga", pci_stdvga);
+ qtest_add_func("/display/pci/secondary", pci_secondary);
+ qtest_add_func("/display/pci/multihead", pci_multihead);
+ qtest_add_func("/display/pci/virtio-gpu", pci_virtio_gpu);
+ if (g_str_equal(arch, "i386") || g_str_equal(arch, "x86_64") ||
+ g_str_equal(arch, "hppa") || g_str_equal(arch, "ppc64")) {
+ qtest_add_func("/display/pci/virtio-vga", pci_virtio_vga);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/drive_del-test.c b/tests/qtest/drive_del-test.c
new file mode 100644
index 0000000000..5f8839b232
--- /dev/null
+++ b/tests/qtest/drive_del-test.c
@@ -0,0 +1,154 @@
+/*
+ * blockdev.c test cases
+ *
+ * Copyright (C) 2013-2014 Red Hat Inc.
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/virtio.h"
+#include "qapi/qmp/qdict.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(q, ...) qobject_unref(qtest_qmp(q, __VA_ARGS__))
+
+static void drive_add(QTestState *qts)
+{
+ char *resp = qtest_hmp(qts, "drive_add 0 if=none,id=drive0");
+
+ g_assert_cmpstr(resp, ==, "OK\r\n");
+ g_free(resp);
+}
+
+static void drive_del(QTestState *qts)
+{
+ char *resp = qtest_hmp(qts, "drive_del drive0");
+
+ g_assert_cmpstr(resp, ==, "");
+ g_free(resp);
+}
+
+static void device_del(QTestState *qts)
+{
+ QDict *response;
+
+ /* Complication: ignore DEVICE_DELETED event */
+ qmp_discard_response(qts, "{'execute': 'device_del',"
+ " 'arguments': { 'id': 'dev0' } }");
+ response = qtest_qmp_receive(qts);
+ g_assert(response);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+static void test_drive_without_dev(void)
+{
+ QTestState *qts;
+
+ /* Start with an empty drive */
+ qts = qtest_init("-drive if=none,id=drive0");
+
+ /* Delete the drive */
+ drive_del(qts);
+
+ /* Ensure re-adding the drive works - there should be no duplicate ID error
+ * because the old drive must be gone.
+ */
+ drive_add(qts);
+
+ qtest_quit(qts);
+}
+
+/*
+ * qvirtio_get_dev_type:
+ * Returns: the preferred virtio bus/device type for the current architecture.
+ * TODO: delete this
+ */
+static const char *qvirtio_get_dev_type(void)
+{
+ const char *arch = qtest_get_arch();
+
+ if (g_str_equal(arch, "arm") || g_str_equal(arch, "aarch64")) {
+ return "device"; /* for virtio-mmio */
+ } else if (g_str_equal(arch, "s390x")) {
+ return "ccw";
+ } else {
+ return "pci";
+ }
+}
+
+static void test_after_failed_device_add(void)
+{
+ char driver[32];
+ QDict *response;
+ QTestState *qts;
+
+ snprintf(driver, sizeof(driver), "virtio-blk-%s",
+ qvirtio_get_dev_type());
+
+ qts = qtest_init("-drive if=none,id=drive0");
+
+ /* Make device_add fail. If this leaks the virtio-blk device then a
+ * reference to drive0 will also be held (via qdev properties).
+ */
+ response = qtest_qmp(qts, "{'execute': 'device_add',"
+ " 'arguments': {"
+ " 'driver': %s,"
+ " 'drive': 'drive0'"
+ "}}", driver);
+ g_assert(response);
+ qmp_assert_error_class(response, "GenericError");
+
+ /* Delete the drive */
+ drive_del(qts);
+
+ /* Try to re-add the drive. This fails with duplicate IDs if a leaked
+ * virtio-blk device exists that holds a reference to the old drive0.
+ */
+ drive_add(qts);
+
+ qtest_quit(qts);
+}
+
+static void test_drive_del_device_del(void)
+{
+ QTestState *qts;
+
+ /* Start with a drive used by a device that unplugs instantaneously */
+ qts = qtest_initf("-drive if=none,id=drive0,file=null-co://,"
+ "file.read-zeroes=on,format=raw"
+ " -device virtio-scsi-%s"
+ " -device scsi-hd,drive=drive0,id=dev0",
+ qvirtio_get_dev_type());
+
+ /*
+ * Delete the drive, and then the device
+ * Doing it in this order takes notoriously tricky special paths
+ */
+ drive_del(qts);
+ device_del(qts);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/drive_del/without-dev", test_drive_without_dev);
+
+ if (qvirtio_get_dev_type() != NULL) {
+ qtest_add_func("/drive_del/after_failed_device_add",
+ test_after_failed_device_add);
+ qtest_add_func("/blockdev/drive_del_device_del",
+ test_drive_del_device_del);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/ds1338-test.c b/tests/qtest/ds1338-test.c
new file mode 100644
index 0000000000..f6ade9a050
--- /dev/null
+++ b/tests/qtest/ds1338-test.c
@@ -0,0 +1,58 @@
+/*
+ * QTest testcase for the DS1338 RTC
+ *
+ * Copyright (c) 2013 Jean-Christophe Dubois
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/i2c.h"
+
+#define DS1338_ADDR 0x68
+
+static inline uint8_t bcd2bin(uint8_t x)
+{
+ return ((x) & 0x0f) + ((x) >> 4) * 10;
+}
+
+static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ uint8_t resp[7];
+ time_t now = time(NULL);
+ struct tm *tm_ptr = gmtime(&now);
+
+ i2c_read_block(i2cdev, 0, resp, sizeof(resp));
+
+ /* check retrieved time againt local time */
+ g_assert_cmpuint(bcd2bin(resp[4]), == , tm_ptr->tm_mday);
+ g_assert_cmpuint(bcd2bin(resp[5]), == , 1 + tm_ptr->tm_mon);
+ g_assert_cmpuint(2000 + bcd2bin(resp[6]), == , 1900 + tm_ptr->tm_year);
+}
+
+static void ds1338_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "address=0x68"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { DS1338_ADDR });
+
+ qos_node_create_driver("ds1338", i2c_device_create);
+ qos_node_consumes("ds1338", "i2c-bus", &opts);
+ qos_add_test("tx-rx", "ds1338", send_and_receive, NULL);
+}
+libqos_init(ds1338_register_nodes);
diff --git a/tests/qtest/e1000-test.c b/tests/qtest/e1000-test.c
new file mode 100644
index 0000000000..c387984ef6
--- /dev/null
+++ b/tests/qtest/e1000-test.c
@@ -0,0 +1,68 @@
+/*
+ * QTest testcase for e1000 NIC
+ *
+ * Copyright (c) 2013-2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QE1000 QE1000;
+
+struct QE1000 {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static const char *models[] = {
+ "e1000",
+ "e1000-82540em",
+ "e1000-82544gc",
+ "e1000-82545em",
+};
+
+static void *e1000_get_driver(void *obj, const char *interface)
+{
+ QE1000 *e1000 = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &e1000->dev;
+ }
+
+ fprintf(stderr, "%s not present in e1000e\n", interface);
+ g_assert_not_reached();
+}
+
+static void *e1000_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QE1000 *e1000 = g_new0(QE1000, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&e1000->dev, bus, addr);
+ e1000->obj.get_driver = e1000_get_driver;
+
+ return &e1000->obj;
+}
+
+static void e1000_register_nodes(void)
+{
+ int i;
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ for (i = 0; i < ARRAY_SIZE(models); i++) {
+ qos_node_create_driver(models[i], e1000_create);
+ qos_node_consumes(models[i], "pci-bus", &opts);
+ qos_node_produces(models[i], "pci-device");
+ }
+}
+
+libqos_init(e1000_register_nodes);
diff --git a/tests/qtest/e1000e-test.c b/tests/qtest/e1000e-test.c
new file mode 100644
index 0000000000..1a232a663a
--- /dev/null
+++ b/tests/qtest/e1000e-test.c
@@ -0,0 +1,279 @@
+ /*
+ * QTest testcase for e1000e NIC
+ *
+ * Copyright (c) 2015 Ravello Systems LTD (http://ravellosystems.com)
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Authors:
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Leonid Bloch <leonid@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "libqtest-single.h"
+#include "qemu-common.h"
+#include "libqos/pci-pc.h"
+#include "qemu/sockets.h"
+#include "qemu/iov.h"
+#include "qemu/module.h"
+#include "qemu/bitops.h"
+#include "libqos/malloc.h"
+#include "libqos/e1000e.h"
+
+static void e1000e_send_verify(QE1000E *d, int *test_sockets, QGuestAllocator *alloc)
+{
+ struct {
+ uint64_t buffer_addr;
+ union {
+ uint32_t data;
+ struct {
+ uint16_t length;
+ uint8_t cso;
+ uint8_t cmd;
+ } flags;
+ } lower;
+ union {
+ uint32_t data;
+ struct {
+ uint8_t status;
+ uint8_t css;
+ uint16_t special;
+ } fields;
+ } upper;
+ } descr;
+
+ static const uint32_t dtyp_data = BIT(20);
+ static const uint32_t dtyp_ext = BIT(29);
+ static const uint32_t dcmd_rs = BIT(27);
+ static const uint32_t dcmd_eop = BIT(24);
+ static const uint32_t dsta_dd = BIT(0);
+ static const int data_len = 64;
+ char buffer[64];
+ int ret;
+ uint32_t recv_len;
+
+ /* Prepare test data buffer */
+ uint64_t data = guest_alloc(alloc, data_len);
+ memwrite(data, "TEST", 5);
+
+ /* Prepare TX descriptor */
+ memset(&descr, 0, sizeof(descr));
+ descr.buffer_addr = cpu_to_le64(data);
+ descr.lower.data = cpu_to_le32(dcmd_rs |
+ dcmd_eop |
+ dtyp_ext |
+ dtyp_data |
+ data_len);
+
+ /* Put descriptor to the ring */
+ e1000e_tx_ring_push(d, &descr);
+
+ /* Wait for TX WB interrupt */
+ e1000e_wait_isr(d, E1000E_TX0_MSG_ID);
+
+ /* Check DD bit */
+ g_assert_cmphex(le32_to_cpu(descr.upper.data) & dsta_dd, ==, dsta_dd);
+
+ /* Check data sent to the backend */
+ ret = qemu_recv(test_sockets[0], &recv_len, sizeof(recv_len), 0);
+ g_assert_cmpint(ret, == , sizeof(recv_len));
+ qemu_recv(test_sockets[0], buffer, 64, 0);
+ g_assert_cmpstr(buffer, == , "TEST");
+
+ /* Free test data buffer */
+ guest_free(alloc, data);
+}
+
+static void e1000e_receive_verify(QE1000E *d, int *test_sockets, QGuestAllocator *alloc)
+{
+ union {
+ struct {
+ uint64_t buffer_addr;
+ uint64_t reserved;
+ } read;
+ struct {
+ struct {
+ uint32_t mrq;
+ union {
+ uint32_t rss;
+ struct {
+ uint16_t ip_id;
+ uint16_t csum;
+ } csum_ip;
+ } hi_dword;
+ } lower;
+ struct {
+ uint32_t status_error;
+ uint16_t length;
+ uint16_t vlan;
+ } upper;
+ } wb;
+ } descr;
+
+ static const uint32_t esta_dd = BIT(0);
+
+ char test[] = "TEST";
+ int len = htonl(sizeof(test));
+ struct iovec iov[] = {
+ {
+ .iov_base = &len,
+ .iov_len = sizeof(len),
+ },{
+ .iov_base = test,
+ .iov_len = sizeof(test),
+ },
+ };
+
+ static const int data_len = 64;
+ char buffer[64];
+ int ret;
+
+ /* Send a dummy packet to device's socket*/
+ ret = iov_send(test_sockets[0], iov, 2, 0, sizeof(len) + sizeof(test));
+ g_assert_cmpint(ret, == , sizeof(test) + sizeof(len));
+
+ /* Prepare test data buffer */
+ uint64_t data = guest_alloc(alloc, data_len);
+
+ /* Prepare RX descriptor */
+ memset(&descr, 0, sizeof(descr));
+ descr.read.buffer_addr = cpu_to_le64(data);
+
+ /* Put descriptor to the ring */
+ e1000e_rx_ring_push(d, &descr);
+
+ /* Wait for TX WB interrupt */
+ e1000e_wait_isr(d, E1000E_RX0_MSG_ID);
+
+ /* Check DD bit */
+ g_assert_cmphex(le32_to_cpu(descr.wb.upper.status_error) &
+ esta_dd, ==, esta_dd);
+
+ /* Check data sent to the backend */
+ memread(data, buffer, sizeof(buffer));
+ g_assert_cmpstr(buffer, == , "TEST");
+
+ /* Free test data buffer */
+ guest_free(alloc, data);
+}
+
+static void test_e1000e_init(void *obj, void *data, QGuestAllocator * alloc)
+{
+ /* init does nothing */
+}
+
+static void test_e1000e_tx(void *obj, void *data, QGuestAllocator * alloc)
+{
+ QE1000E_PCI *e1000e = obj;
+ QE1000E *d = &e1000e->e1000e;
+ QOSGraphObject *e_object = obj;
+ QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");
+
+ /* FIXME: add spapr support */
+ if (qpci_check_buggy_msi(dev)) {
+ return;
+ }
+
+ e1000e_send_verify(d, data, alloc);
+}
+
+static void test_e1000e_rx(void *obj, void *data, QGuestAllocator * alloc)
+{
+ QE1000E_PCI *e1000e = obj;
+ QE1000E *d = &e1000e->e1000e;
+ QOSGraphObject *e_object = obj;
+ QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");
+
+ /* FIXME: add spapr support */
+ if (qpci_check_buggy_msi(dev)) {
+ return;
+ }
+
+ e1000e_receive_verify(d, data, alloc);
+}
+
+static void test_e1000e_multiple_transfers(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ static const long iterations = 4 * 1024;
+ long i;
+
+ QE1000E_PCI *e1000e = obj;
+ QE1000E *d = &e1000e->e1000e;
+ QOSGraphObject *e_object = obj;
+ QPCIDevice *dev = e_object->get_driver(e_object, "pci-device");
+
+ /* FIXME: add spapr support */
+ if (qpci_check_buggy_msi(dev)) {
+ return;
+ }
+
+ for (i = 0; i < iterations; i++) {
+ e1000e_send_verify(d, data, alloc);
+ e1000e_receive_verify(d, data, alloc);
+ }
+
+}
+
+static void test_e1000e_hotplug(void *obj, void *data, QGuestAllocator * alloc)
+{
+ QTestState *qts = global_qtest; /* TODO: get rid of global_qtest here */
+
+ qtest_qmp_device_add(qts, "e1000e", "e1000e_net", "{'addr': '0x06'}");
+ qpci_unplug_acpi_device_test(qts, "e1000e_net", 0x06);
+}
+
+static void data_test_clear(void *sockets)
+{
+ int *test_sockets = sockets;
+
+ close(test_sockets[0]);
+ qos_invalidate_command_line();
+ close(test_sockets[1]);
+ g_free(test_sockets);
+}
+
+static void *data_test_init(GString *cmd_line, void *arg)
+{
+ int *test_sockets = g_new(int, 2);
+ int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, test_sockets);
+ g_assert_cmpint(ret, != , -1);
+
+ g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ",
+ test_sockets[1]);
+
+ g_test_queue_destroy(data_test_clear, test_sockets);
+ return test_sockets;
+}
+
+static void register_e1000e_test(void)
+{
+ QOSGraphTestOptions opts = {
+ .before = data_test_init,
+ };
+
+ qos_add_test("init", "e1000e", test_e1000e_init, &opts);
+ qos_add_test("tx", "e1000e", test_e1000e_tx, &opts);
+ qos_add_test("rx", "e1000e", test_e1000e_rx, &opts);
+ qos_add_test("multiple_transfers", "e1000e",
+ test_e1000e_multiple_transfers, &opts);
+ qos_add_test("hotplug", "e1000e", test_e1000e_hotplug, &opts);
+}
+
+libqos_init(register_e1000e_test);
diff --git a/tests/qtest/eepro100-test.c b/tests/qtest/eepro100-test.c
new file mode 100644
index 0000000000..8dbffff0b8
--- /dev/null
+++ b/tests/qtest/eepro100-test.c
@@ -0,0 +1,77 @@
+/*
+ * QTest testcase for eepro100 NIC
+ *
+ * Copyright (c) 2013-2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QEEPRO100 QEEPRO100;
+
+struct QEEPRO100 {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static const char *models[] = {
+ "i82550",
+ "i82551",
+ "i82557a",
+ "i82557b",
+ "i82557c",
+ "i82558a",
+ "i82558b",
+ "i82559a",
+ "i82559b",
+ "i82559c",
+ "i82559er",
+ "i82562",
+ "i82801",
+};
+
+static void *eepro100_get_driver(void *obj, const char *interface)
+{
+ QEEPRO100 *eepro100 = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &eepro100->dev;
+ }
+
+ fprintf(stderr, "%s not present in eepro100\n", interface);
+ g_assert_not_reached();
+}
+
+static void *eepro100_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QEEPRO100 *eepro100 = g_new0(QEEPRO100, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&eepro100->dev, bus, addr);
+ eepro100->obj.get_driver = eepro100_get_driver;
+
+ return &eepro100->obj;
+}
+
+static void eepro100_register_nodes(void)
+{
+ int i;
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+ for (i = 0; i < ARRAY_SIZE(models); i++) {
+ qos_node_create_driver(models[i], eepro100_create);
+ qos_node_consumes(models[i], "pci-bus", &opts);
+ qos_node_produces(models[i], "pci-device");
+ }
+}
+
+libqos_init(eepro100_register_nodes);
diff --git a/tests/qtest/endianness-test.c b/tests/qtest/endianness-test.c
new file mode 100644
index 0000000000..58527952a5
--- /dev/null
+++ b/tests/qtest/endianness-test.c
@@ -0,0 +1,306 @@
+/*
+ * QTest testcase for ISA endianness
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Authors:
+ * Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "qemu/bswap.h"
+
+typedef struct TestCase TestCase;
+struct TestCase {
+ const char *arch;
+ const char *machine;
+ uint64_t isa_base;
+ bool bswap;
+ const char *superio;
+};
+
+static const TestCase test_cases[] = {
+ { "i386", "pc", -1 },
+ { "mips", "mips", 0x14000000, .bswap = true },
+ { "mips", "malta", 0x10000000, .bswap = true },
+ { "mips64", "magnum", 0x90000000, .bswap = true },
+ { "mips64", "pica61", 0x90000000, .bswap = true },
+ { "mips64", "mips", 0x14000000, .bswap = true },
+ { "mips64", "malta", 0x10000000, .bswap = true },
+ { "mips64el", "fulong2e", 0x1fd00000 },
+ { "ppc", "g3beige", 0xfe000000, .bswap = true, .superio = "i82378" },
+ { "ppc", "prep", 0x80000000, .bswap = true },
+ { "ppc", "bamboo", 0xe8000000, .bswap = true, .superio = "i82378" },
+ { "ppc64", "mac99", 0xf2000000, .bswap = true, .superio = "i82378" },
+ { "ppc64", "pseries", (1ULL << 45), .bswap = true, .superio = "i82378" },
+ { "ppc64", "pseries-2.7", 0x10080000000ULL,
+ .bswap = true, .superio = "i82378" },
+ { "sh4", "r2d", 0xfe240000, .superio = "i82378" },
+ { "sh4eb", "r2d", 0xfe240000, .bswap = true, .superio = "i82378" },
+ { "sparc64", "sun4u", 0x1fe02000000LL, .bswap = true },
+ { "x86_64", "pc", -1 },
+ {}
+};
+
+static uint8_t isa_inb(QTestState *qts, const TestCase *test, uint16_t addr)
+{
+ uint8_t value;
+ if (test->isa_base == -1) {
+ value = qtest_inb(qts, addr);
+ } else {
+ value = qtest_readb(qts, test->isa_base + addr);
+ }
+ return value;
+}
+
+static uint16_t isa_inw(QTestState *qts, const TestCase *test, uint16_t addr)
+{
+ uint16_t value;
+ if (test->isa_base == -1) {
+ value = qtest_inw(qts, addr);
+ } else {
+ value = qtest_readw(qts, test->isa_base + addr);
+ }
+ return test->bswap ? bswap16(value) : value;
+}
+
+static uint32_t isa_inl(QTestState *qts, const TestCase *test, uint16_t addr)
+{
+ uint32_t value;
+ if (test->isa_base == -1) {
+ value = qtest_inl(qts, addr);
+ } else {
+ value = qtest_readl(qts, test->isa_base + addr);
+ }
+ return test->bswap ? bswap32(value) : value;
+}
+
+static void isa_outb(QTestState *qts, const TestCase *test, uint16_t addr,
+ uint8_t value)
+{
+ if (test->isa_base == -1) {
+ qtest_outb(qts, addr, value);
+ } else {
+ qtest_writeb(qts, test->isa_base + addr, value);
+ }
+}
+
+static void isa_outw(QTestState *qts, const TestCase *test, uint16_t addr,
+ uint16_t value)
+{
+ value = test->bswap ? bswap16(value) : value;
+ if (test->isa_base == -1) {
+ qtest_outw(qts, addr, value);
+ } else {
+ qtest_writew(qts, test->isa_base + addr, value);
+ }
+}
+
+static void isa_outl(QTestState *qts, const TestCase *test, uint16_t addr,
+ uint32_t value)
+{
+ value = test->bswap ? bswap32(value) : value;
+ if (test->isa_base == -1) {
+ qtest_outl(qts, addr, value);
+ } else {
+ qtest_writel(qts, test->isa_base + addr, value);
+ }
+}
+
+
+static void test_endianness(gconstpointer data)
+{
+ const TestCase *test = data;
+ QTestState *qts;
+
+ qts = qtest_initf("-M %s%s%s -device pc-testdev", test->machine,
+ test->superio ? " -device " : "",
+ test->superio ?: "");
+ isa_outl(qts, test, 0xe0, 0x87654321);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654321);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4321);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x87);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x65);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x43);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x21);
+
+ isa_outw(qts, test, 0xe2, 0x8866);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x88664321);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8866);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4321);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x88);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x66);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x43);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x21);
+
+ isa_outw(qts, test, 0xe0, 0x4422);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x88664422);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8866);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4422);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x88);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x66);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x44);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x22);
+
+ isa_outb(qts, test, 0xe3, 0x87);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87664422);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8766);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x87);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x66);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x44);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x22);
+
+ isa_outb(qts, test, 0xe2, 0x65);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654422);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4422);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x87);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x65);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x44);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x22);
+
+ isa_outb(qts, test, 0xe1, 0x43);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654322);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4322);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x87);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x65);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x43);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x22);
+
+ isa_outb(qts, test, 0xe0, 0x21);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654321);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4321);
+ g_assert_cmphex(isa_inb(qts, test, 0xe3), ==, 0x87);
+ g_assert_cmphex(isa_inb(qts, test, 0xe2), ==, 0x65);
+ g_assert_cmphex(isa_inb(qts, test, 0xe1), ==, 0x43);
+ g_assert_cmphex(isa_inb(qts, test, 0xe0), ==, 0x21);
+ qtest_quit(qts);
+}
+
+static void test_endianness_split(gconstpointer data)
+{
+ const TestCase *test = data;
+ QTestState *qts;
+
+ qts = qtest_initf("-M %s%s%s -device pc-testdev", test->machine,
+ test->superio ? " -device " : "",
+ test->superio ?: "");
+ isa_outl(qts, test, 0xe8, 0x87654321);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654321);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4321);
+
+ isa_outw(qts, test, 0xea, 0x8866);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x88664321);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8866);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4321);
+
+ isa_outw(qts, test, 0xe8, 0x4422);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x88664422);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8866);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4422);
+
+ isa_outb(qts, test, 0xeb, 0x87);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87664422);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8766);
+
+ isa_outb(qts, test, 0xea, 0x65);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654422);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4422);
+
+ isa_outb(qts, test, 0xe9, 0x43);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654322);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4322);
+
+ isa_outb(qts, test, 0xe8, 0x21);
+ g_assert_cmphex(isa_inl(qts, test, 0xe0), ==, 0x87654321);
+ g_assert_cmphex(isa_inw(qts, test, 0xe2), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe0), ==, 0x4321);
+ qtest_quit(qts);
+}
+
+static void test_endianness_combine(gconstpointer data)
+{
+ const TestCase *test = data;
+ QTestState *qts;
+
+ qts = qtest_initf("-M %s%s%s -device pc-testdev", test->machine,
+ test->superio ? " -device " : "",
+ test->superio ?: "");
+ isa_outl(qts, test, 0xe0, 0x87654321);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x87654321);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe8), ==, 0x4321);
+
+ isa_outw(qts, test, 0xe2, 0x8866);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x88664321);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8866);
+ g_assert_cmphex(isa_inw(qts, test, 0xe8), ==, 0x4321);
+
+ isa_outw(qts, test, 0xe0, 0x4422);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x88664422);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8866);
+ g_assert_cmphex(isa_inw(qts, test, 0xe8), ==, 0x4422);
+
+ isa_outb(qts, test, 0xe3, 0x87);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x87664422);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8766);
+
+ isa_outb(qts, test, 0xe2, 0x65);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x87654422);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe8), ==, 0x4422);
+
+ isa_outb(qts, test, 0xe1, 0x43);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x87654322);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe8), ==, 0x4322);
+
+ isa_outb(qts, test, 0xe0, 0x21);
+ g_assert_cmphex(isa_inl(qts, test, 0xe8), ==, 0x87654321);
+ g_assert_cmphex(isa_inw(qts, test, 0xea), ==, 0x8765);
+ g_assert_cmphex(isa_inw(qts, test, 0xe8), ==, 0x4321);
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ const char *arch = qtest_get_arch();
+ int i;
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (i = 0; test_cases[i].arch; i++) {
+ gchar *path;
+ if (strcmp(test_cases[i].arch, arch) != 0) {
+ continue;
+ }
+ path = g_strdup_printf("endianness/%s",
+ test_cases[i].machine);
+ qtest_add_data_func(path, &test_cases[i], test_endianness);
+ g_free(path);
+
+ path = g_strdup_printf("endianness/split/%s",
+ test_cases[i].machine);
+ qtest_add_data_func(path, &test_cases[i], test_endianness_split);
+ g_free(path);
+
+ path = g_strdup_printf("endianness/combine/%s",
+ test_cases[i].machine);
+ qtest_add_data_func(path, &test_cases[i], test_endianness_combine);
+ g_free(path);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/es1370-test.c b/tests/qtest/es1370-test.c
new file mode 100644
index 0000000000..adccdac1be
--- /dev/null
+++ b/tests/qtest/es1370-test.c
@@ -0,0 +1,58 @@
+/*
+ * QTest testcase for ES1370
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QES1370 QES1370;
+
+struct QES1370 {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *es1370_get_driver(void *obj, const char *interface)
+{
+ QES1370 *es1370 = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &es1370->dev;
+ }
+
+ fprintf(stderr, "%s not present in e1000e\n", interface);
+ g_assert_not_reached();
+}
+
+static void *es1370_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QES1370 *es1370 = g_new0(QES1370, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&es1370->dev, bus, addr);
+ es1370->obj.get_driver = es1370_get_driver;
+
+ return &es1370->obj;
+}
+
+static void es1370_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("ES1370", es1370_create);
+ qos_node_consumes("ES1370", "pci-bus", &opts);
+ qos_node_produces("ES1370", "pci-device");
+}
+
+libqos_init(es1370_register_nodes);
diff --git a/tests/qtest/fdc-test.c b/tests/qtest/fdc-test.c
new file mode 100644
index 0000000000..26b69f7c5c
--- /dev/null
+++ b/tests/qtest/fdc-test.c
@@ -0,0 +1,587 @@
+/*
+ * Floppy test cases.
+ *
+ * Copyright (c) 2012 Kevin Wolf <kwolf@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+
+#include "libqtest-single.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu-common.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__))
+
+#define TEST_IMAGE_SIZE 1440 * 1024
+
+#define FLOPPY_BASE 0x3f0
+#define FLOPPY_IRQ 6
+
+enum {
+ reg_sra = 0x0,
+ reg_srb = 0x1,
+ reg_dor = 0x2,
+ reg_msr = 0x4,
+ reg_dsr = 0x4,
+ reg_fifo = 0x5,
+ reg_dir = 0x7,
+};
+
+enum {
+ CMD_SENSE_INT = 0x08,
+ CMD_READ_ID = 0x0a,
+ CMD_SEEK = 0x0f,
+ CMD_VERIFY = 0x16,
+ CMD_READ = 0xe6,
+ CMD_RELATIVE_SEEK_OUT = 0x8f,
+ CMD_RELATIVE_SEEK_IN = 0xcf,
+};
+
+enum {
+ BUSY = 0x10,
+ NONDMA = 0x20,
+ RQM = 0x80,
+ DIO = 0x40,
+
+ DSKCHG = 0x80,
+};
+
+static char test_image[] = "/tmp/qtest.XXXXXX";
+
+#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
+#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
+
+static uint8_t base = 0x70;
+
+enum {
+ CMOS_FLOPPY = 0x10,
+};
+
+static void floppy_send(uint8_t byte)
+{
+ uint8_t msr;
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_set(msr, RQM);
+ assert_bit_clear(msr, DIO);
+
+ outb(FLOPPY_BASE + reg_fifo, byte);
+}
+
+static uint8_t floppy_recv(void)
+{
+ uint8_t msr;
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_set(msr, RQM | DIO);
+
+ return inb(FLOPPY_BASE + reg_fifo);
+}
+
+/* pcn: Present Cylinder Number */
+static void ack_irq(uint8_t *pcn)
+{
+ uint8_t ret;
+
+ g_assert(get_irq(FLOPPY_IRQ));
+ floppy_send(CMD_SENSE_INT);
+ floppy_recv();
+
+ ret = floppy_recv();
+ if (pcn != NULL) {
+ *pcn = ret;
+ }
+
+ g_assert(!get_irq(FLOPPY_IRQ));
+}
+
+static uint8_t send_read_command(uint8_t cmd)
+{
+ uint8_t drive = 0;
+ uint8_t head = 0;
+ uint8_t cyl = 0;
+ uint8_t sect_addr = 1;
+ uint8_t sect_size = 2;
+ uint8_t eot = 1;
+ uint8_t gap = 0x1b;
+ uint8_t gpl = 0xff;
+
+ uint8_t msr = 0;
+ uint8_t st0;
+
+ uint8_t ret = 0;
+
+ floppy_send(cmd);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+ floppy_send(head);
+ floppy_send(sect_addr);
+ floppy_send(sect_size);
+ floppy_send(eot);
+ floppy_send(gap);
+ floppy_send(gpl);
+
+ uint8_t i = 0;
+ uint8_t n = 2;
+ for (; i < n; i++) {
+ msr = inb(FLOPPY_BASE + reg_msr);
+ if (msr == 0xd0) {
+ break;
+ }
+ sleep(1);
+ }
+
+ if (i >= n) {
+ return 1;
+ }
+
+ st0 = floppy_recv();
+ if (st0 != 0x40) {
+ ret = 1;
+ }
+
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+
+ return ret;
+}
+
+static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
+{
+ uint8_t drive = 0;
+ uint8_t head = 0;
+ uint8_t cyl = 0;
+ uint8_t sect_addr = 1;
+ uint8_t sect_size = 2;
+ uint8_t eot = nb_sect;
+ uint8_t gap = 0x1b;
+ uint8_t gpl = 0xff;
+
+ uint8_t msr = 0;
+ uint8_t st0;
+
+ uint8_t ret = 0;
+
+ floppy_send(CMD_READ);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+ floppy_send(head);
+ floppy_send(sect_addr);
+ floppy_send(sect_size);
+ floppy_send(eot);
+ floppy_send(gap);
+ floppy_send(gpl);
+
+ uint16_t i = 0;
+ uint8_t n = 2;
+ for (; i < n; i++) {
+ msr = inb(FLOPPY_BASE + reg_msr);
+ if (msr == (BUSY | NONDMA | DIO | RQM)) {
+ break;
+ }
+ sleep(1);
+ }
+
+ if (i >= n) {
+ return 1;
+ }
+
+ /* Non-DMA mode */
+ for (i = 0; i < 512 * 2 * nb_sect; i++) {
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_set(msr, BUSY | RQM | DIO);
+ inb(FLOPPY_BASE + reg_fifo);
+ }
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_set(msr, BUSY | RQM | DIO);
+ g_assert(get_irq(FLOPPY_IRQ));
+
+ st0 = floppy_recv();
+ if (st0 != expected_st0) {
+ ret = 1;
+ }
+
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ g_assert(get_irq(FLOPPY_IRQ));
+ floppy_recv();
+
+ /* Check that we're back in command phase */
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_clear(msr, BUSY | DIO);
+ assert_bit_set(msr, RQM);
+ g_assert(!get_irq(FLOPPY_IRQ));
+
+ return ret;
+}
+
+static void send_seek(int cyl)
+{
+ int drive = 0;
+ int head = 0;
+
+ floppy_send(CMD_SEEK);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+ ack_irq(NULL);
+}
+
+static uint8_t cmos_read(uint8_t reg)
+{
+ outb(base + 0, reg);
+ return inb(base + 1);
+}
+
+static void test_cmos(void)
+{
+ uint8_t cmos;
+
+ cmos = cmos_read(CMOS_FLOPPY);
+ g_assert(cmos == 0x40 || cmos == 0x50);
+}
+
+static void test_no_media_on_start(void)
+{
+ uint8_t dir;
+
+ /* Media changed bit must be set all time after start if there is
+ * no media in drive. */
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ send_seek(1);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+}
+
+static void test_read_without_media(void)
+{
+ uint8_t ret;
+
+ ret = send_read_command(CMD_READ);
+ g_assert(ret == 0);
+}
+
+static void test_media_insert(void)
+{
+ uint8_t dir;
+
+ /* Insert media in drive. DSKCHK should not be reset until a step pulse
+ * is sent. */
+ qmp_discard_response("{'execute':'blockdev-change-medium', 'arguments':{"
+ " 'id':'floppy0', 'filename': %s, 'format': 'raw' }}",
+ test_image);
+
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+
+ send_seek(0);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+
+ /* Step to next track should clear DSKCHG bit. */
+ send_seek(1);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_clear(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_clear(dir, DSKCHG);
+}
+
+static void test_media_change(void)
+{
+ uint8_t dir;
+
+ test_media_insert();
+
+ /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
+ * reset the bit. */
+ qmp_discard_response("{'execute':'eject', 'arguments':{"
+ " 'id':'floppy0' }}");
+
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+
+ send_seek(0);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+
+ send_seek(1);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+ dir = inb(FLOPPY_BASE + reg_dir);
+ assert_bit_set(dir, DSKCHG);
+}
+
+static void test_sense_interrupt(void)
+{
+ int drive = 0;
+ int head = 0;
+ int cyl = 0;
+ int ret = 0;
+
+ floppy_send(CMD_SENSE_INT);
+ ret = floppy_recv();
+ g_assert(ret == 0x80);
+
+ floppy_send(CMD_SEEK);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+
+ floppy_send(CMD_SENSE_INT);
+ ret = floppy_recv();
+ g_assert(ret == 0x20);
+ floppy_recv();
+}
+
+static void test_relative_seek(void)
+{
+ uint8_t drive = 0;
+ uint8_t head = 0;
+ uint8_t cyl = 1;
+ uint8_t pcn;
+
+ /* Send seek to track 0 */
+ send_seek(0);
+
+ /* Send relative seek to increase track by 1 */
+ floppy_send(CMD_RELATIVE_SEEK_IN);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+
+ ack_irq(&pcn);
+ g_assert(pcn == 1);
+
+ /* Send relative seek to decrease track by 1 */
+ floppy_send(CMD_RELATIVE_SEEK_OUT);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+
+ ack_irq(&pcn);
+ g_assert(pcn == 0);
+}
+
+static void test_read_id(void)
+{
+ uint8_t drive = 0;
+ uint8_t head = 0;
+ uint8_t cyl;
+ uint8_t st0;
+ uint8_t msr;
+
+ /* Seek to track 0 and check with READ ID */
+ send_seek(0);
+
+ floppy_send(CMD_READ_ID);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(head << 2 | drive);
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ if (!get_irq(FLOPPY_IRQ)) {
+ assert_bit_set(msr, BUSY);
+ assert_bit_clear(msr, RQM);
+ }
+
+ while (!get_irq(FLOPPY_IRQ)) {
+ /* qemu involves a timer with READ ID... */
+ clock_step(1000000000LL / 50);
+ }
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_set(msr, BUSY | RQM | DIO);
+
+ st0 = floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ cyl = floppy_recv();
+ head = floppy_recv();
+ floppy_recv();
+ g_assert(get_irq(FLOPPY_IRQ));
+ floppy_recv();
+ g_assert(!get_irq(FLOPPY_IRQ));
+
+ g_assert_cmpint(cyl, ==, 0);
+ g_assert_cmpint(head, ==, 0);
+ g_assert_cmpint(st0, ==, head << 2);
+
+ /* Seek to track 8 on head 1 and check with READ ID */
+ head = 1;
+ cyl = 8;
+
+ floppy_send(CMD_SEEK);
+ floppy_send(head << 2 | drive);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(cyl);
+ g_assert(get_irq(FLOPPY_IRQ));
+ ack_irq(NULL);
+
+ floppy_send(CMD_READ_ID);
+ g_assert(!get_irq(FLOPPY_IRQ));
+ floppy_send(head << 2 | drive);
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ if (!get_irq(FLOPPY_IRQ)) {
+ assert_bit_set(msr, BUSY);
+ assert_bit_clear(msr, RQM);
+ }
+
+ while (!get_irq(FLOPPY_IRQ)) {
+ /* qemu involves a timer with READ ID... */
+ clock_step(1000000000LL / 50);
+ }
+
+ msr = inb(FLOPPY_BASE + reg_msr);
+ assert_bit_set(msr, BUSY | RQM | DIO);
+
+ st0 = floppy_recv();
+ floppy_recv();
+ floppy_recv();
+ cyl = floppy_recv();
+ head = floppy_recv();
+ floppy_recv();
+ g_assert(get_irq(FLOPPY_IRQ));
+ floppy_recv();
+ g_assert(!get_irq(FLOPPY_IRQ));
+
+ g_assert_cmpint(cyl, ==, 8);
+ g_assert_cmpint(head, ==, 1);
+ g_assert_cmpint(st0, ==, head << 2);
+}
+
+static void test_read_no_dma_1(void)
+{
+ uint8_t ret;
+
+ outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
+ send_seek(0);
+ ret = send_read_no_dma_command(1, 0x04);
+ g_assert(ret == 0);
+}
+
+static void test_read_no_dma_18(void)
+{
+ uint8_t ret;
+
+ outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
+ send_seek(0);
+ ret = send_read_no_dma_command(18, 0x04);
+ g_assert(ret == 0);
+}
+
+static void test_read_no_dma_19(void)
+{
+ uint8_t ret;
+
+ outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
+ send_seek(0);
+ ret = send_read_no_dma_command(19, 0x20);
+ g_assert(ret == 0);
+}
+
+static void test_verify(void)
+{
+ uint8_t ret;
+
+ ret = send_read_command(CMD_VERIFY);
+ g_assert(ret == 0);
+}
+
+/* success if no crash or abort */
+static void fuzz_registers(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < 1000; i++) {
+ uint8_t reg, val;
+
+ reg = (uint8_t)g_test_rand_int_range(0, 8);
+ val = (uint8_t)g_test_rand_int_range(0, 256);
+
+ outb(FLOPPY_BASE + reg, val);
+ inb(FLOPPY_BASE + reg);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int fd;
+ int ret;
+
+ /* Create a temporary raw image */
+ fd = mkstemp(test_image);
+ g_assert(fd >= 0);
+ ret = ftruncate(fd, TEST_IMAGE_SIZE);
+ g_assert(ret == 0);
+ close(fd);
+
+ /* Run the tests */
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_start("-device floppy,id=floppy0");
+ qtest_irq_intercept_in(global_qtest, "ioapic");
+ qtest_add_func("/fdc/cmos", test_cmos);
+ qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start);
+ qtest_add_func("/fdc/read_without_media", test_read_without_media);
+ qtest_add_func("/fdc/media_change", test_media_change);
+ qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
+ qtest_add_func("/fdc/relative_seek", test_relative_seek);
+ qtest_add_func("/fdc/read_id", test_read_id);
+ qtest_add_func("/fdc/verify", test_verify);
+ qtest_add_func("/fdc/media_insert", test_media_insert);
+ qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
+ qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
+ qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
+ qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
+
+ ret = g_test_run();
+
+ /* Cleanup */
+ qtest_end();
+ unlink(test_image);
+
+ return ret;
+}
diff --git a/tests/qtest/fw_cfg-test.c b/tests/qtest/fw_cfg-test.c
new file mode 100644
index 0000000000..5dc807ba23
--- /dev/null
+++ b/tests/qtest/fw_cfg-test.c
@@ -0,0 +1,260 @@
+/*
+ * qtest fw_cfg test case
+ *
+ * Copyright IBM, Corp. 2012-2013
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "standard-headers/linux/qemu_fw_cfg.h"
+#include "libqos/fw_cfg.h"
+#include "qemu/bswap.h"
+
+static uint64_t ram_size = 128 << 20;
+static uint16_t nb_cpus = 1;
+static uint16_t max_cpus = 1;
+static uint64_t nb_nodes = 0;
+static uint16_t boot_menu = 0;
+
+static void test_fw_cfg_signature(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+ char buf[5];
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ qfw_cfg_get(fw_cfg, FW_CFG_SIGNATURE, buf, 4);
+ buf[4] = 0;
+
+ g_assert_cmpstr(buf, ==, "QEMU");
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_id(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+ uint32_t id;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ id = qfw_cfg_get_u32(fw_cfg, FW_CFG_ID);
+ g_assert((id == 1) ||
+ (id == 3));
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_uuid(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+
+ uint8_t buf[16];
+ static const uint8_t uuid[16] = {
+ 0x46, 0x00, 0xcb, 0x32, 0x38, 0xec, 0x4b, 0x2f,
+ 0x8a, 0xcb, 0x81, 0xc6, 0xea, 0x54, 0xf2, 0xd8,
+ };
+
+ s = qtest_init("-uuid 4600cb32-38ec-4b2f-8acb-81c6ea54f2d8");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ qfw_cfg_get(fw_cfg, FW_CFG_UUID, buf, 16);
+ g_assert(memcmp(buf, uuid, sizeof(buf)) == 0);
+
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+
+}
+
+static void test_fw_cfg_ram_size(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ g_assert_cmpint(qfw_cfg_get_u64(fw_cfg, FW_CFG_RAM_SIZE), ==, ram_size);
+
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_nographic(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ g_assert_cmpint(qfw_cfg_get_u16(fw_cfg, FW_CFG_NOGRAPHIC), ==, 0);
+
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_nb_cpus(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ g_assert_cmpint(qfw_cfg_get_u16(fw_cfg, FW_CFG_NB_CPUS), ==, nb_cpus);
+
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_max_cpus(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ g_assert_cmpint(qfw_cfg_get_u16(fw_cfg, FW_CFG_MAX_CPUS), ==, max_cpus);
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_numa(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+ uint64_t *cpu_mask;
+ uint64_t *node_mask;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ g_assert_cmpint(qfw_cfg_get_u64(fw_cfg, FW_CFG_NUMA), ==, nb_nodes);
+
+ cpu_mask = g_new0(uint64_t, max_cpus);
+ node_mask = g_new0(uint64_t, nb_nodes);
+
+ qfw_cfg_read_data(fw_cfg, cpu_mask, sizeof(uint64_t) * max_cpus);
+ qfw_cfg_read_data(fw_cfg, node_mask, sizeof(uint64_t) * nb_nodes);
+
+ if (nb_nodes) {
+ g_assert(cpu_mask[0] & 0x01);
+ g_assert_cmpint(node_mask[0], ==, ram_size);
+ }
+
+ g_free(node_mask);
+ g_free(cpu_mask);
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_boot_menu(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+
+ s = qtest_init("");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ g_assert_cmpint(qfw_cfg_get_u16(fw_cfg, FW_CFG_BOOT_MENU), ==, boot_menu);
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_reboot_timeout(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+ uint32_t reboot_timeout = 0;
+ size_t filesize;
+
+ s = qtest_init("-boot reboot-timeout=15");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ filesize = qfw_cfg_get_file(fw_cfg, "etc/boot-fail-wait",
+ &reboot_timeout, sizeof(reboot_timeout));
+ g_assert_cmpint(filesize, ==, sizeof(reboot_timeout));
+ reboot_timeout = le32_to_cpu(reboot_timeout);
+ g_assert_cmpint(reboot_timeout, ==, 15);
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_no_reboot_timeout(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+ uint32_t reboot_timeout = 0;
+ size_t filesize;
+
+ /* Special value -1 means "don't reboot" */
+ s = qtest_init("-boot reboot-timeout=-1");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ filesize = qfw_cfg_get_file(fw_cfg, "etc/boot-fail-wait",
+ &reboot_timeout, sizeof(reboot_timeout));
+ g_assert_cmpint(filesize, ==, sizeof(reboot_timeout));
+ reboot_timeout = le32_to_cpu(reboot_timeout);
+ g_assert_cmpint(reboot_timeout, ==, UINT32_MAX);
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+static void test_fw_cfg_splash_time(void)
+{
+ QFWCFG *fw_cfg;
+ QTestState *s;
+ uint16_t splash_time = 0;
+ size_t filesize;
+
+ s = qtest_init("-boot splash-time=12");
+ fw_cfg = pc_fw_cfg_init(s);
+
+ filesize = qfw_cfg_get_file(fw_cfg, "etc/boot-menu-wait",
+ &splash_time, sizeof(splash_time));
+ g_assert_cmpint(filesize, ==, sizeof(splash_time));
+ splash_time = le16_to_cpu(splash_time);
+ g_assert_cmpint(splash_time, ==, 12);
+ pc_fw_cfg_uninit(fw_cfg);
+ qtest_quit(s);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("fw_cfg/signature", test_fw_cfg_signature);
+ qtest_add_func("fw_cfg/id", test_fw_cfg_id);
+ qtest_add_func("fw_cfg/uuid", test_fw_cfg_uuid);
+ qtest_add_func("fw_cfg/ram_size", test_fw_cfg_ram_size);
+ qtest_add_func("fw_cfg/nographic", test_fw_cfg_nographic);
+ qtest_add_func("fw_cfg/nb_cpus", test_fw_cfg_nb_cpus);
+#if 0
+ qtest_add_func("fw_cfg/machine_id", test_fw_cfg_machine_id);
+ qtest_add_func("fw_cfg/kernel", test_fw_cfg_kernel);
+ qtest_add_func("fw_cfg/initrd", test_fw_cfg_initrd);
+ qtest_add_func("fw_cfg/boot_device", test_fw_cfg_boot_device);
+#endif
+ qtest_add_func("fw_cfg/max_cpus", test_fw_cfg_max_cpus);
+ qtest_add_func("fw_cfg/numa", test_fw_cfg_numa);
+ qtest_add_func("fw_cfg/boot_menu", test_fw_cfg_boot_menu);
+ qtest_add_func("fw_cfg/reboot_timeout", test_fw_cfg_reboot_timeout);
+ qtest_add_func("fw_cfg/no_reboot_timeout", test_fw_cfg_no_reboot_timeout);
+ qtest_add_func("fw_cfg/splash_time", test_fw_cfg_splash_time);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/hd-geo-test.c b/tests/qtest/hd-geo-test.c
new file mode 100644
index 0000000000..a249800544
--- /dev/null
+++ b/tests/qtest/hd-geo-test.c
@@ -0,0 +1,988 @@
+/*
+ * Hard disk geometry test cases.
+ *
+ * Copyright (c) 2012 Red Hat Inc.
+ *
+ * Authors:
+ * Markus Armbruster <armbru@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * Covers only IDE and tests only CMOS contents. Better than nothing.
+ * Improvements welcome.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/bswap.h"
+#include "qapi/qmp/qlist.h"
+#include "libqtest.h"
+#include "libqos/fw_cfg.h"
+#include "libqos/libqos.h"
+#include "standard-headers/linux/qemu_fw_cfg.h"
+
+#define ARGV_SIZE 256
+
+static char *create_test_img(int secs)
+{
+ char *template = strdup("/tmp/qtest.XXXXXX");
+ int fd, ret;
+
+ fd = mkstemp(template);
+ g_assert(fd >= 0);
+ ret = ftruncate(fd, (off_t)secs * 512);
+ close(fd);
+
+ if (ret) {
+ free(template);
+ template = NULL;
+ }
+
+ return template;
+}
+
+typedef struct {
+ int cyls, heads, secs, trans;
+} CHST;
+
+typedef enum {
+ mbr_blank, mbr_lba, mbr_chs,
+ mbr_last
+} MBRcontents;
+
+typedef enum {
+ /* order is relevant */
+ backend_small, backend_large, backend_empty,
+ backend_last
+} Backend;
+
+static const int img_secs[backend_last] = {
+ [backend_small] = 61440,
+ [backend_large] = 8388608,
+ [backend_empty] = -1,
+};
+
+static const CHST hd_chst[backend_last][mbr_last] = {
+ [backend_small] = {
+ [mbr_blank] = { 60, 16, 63, 0 },
+ [mbr_lba] = { 60, 16, 63, 2 },
+ [mbr_chs] = { 60, 16, 63, 0 }
+ },
+ [backend_large] = {
+ [mbr_blank] = { 8322, 16, 63, 1 },
+ [mbr_lba] = { 8322, 16, 63, 1 },
+ [mbr_chs] = { 8322, 16, 63, 0 }
+ },
+};
+
+static char *img_file_name[backend_last];
+
+static const CHST *cur_ide[4];
+
+static bool is_hd(const CHST *expected_chst)
+{
+ return expected_chst && expected_chst->cyls;
+}
+
+static void test_cmos_byte(QTestState *qts, int reg, int expected)
+{
+ enum { cmos_base = 0x70 };
+ int actual;
+
+ qtest_outb(qts, cmos_base + 0, reg);
+ actual = qtest_inb(qts, cmos_base + 1);
+ g_assert(actual == expected);
+}
+
+static void test_cmos_bytes(QTestState *qts, int reg0, int n,
+ uint8_t expected[])
+{
+ int i;
+
+ for (i = 0; i < 9; i++) {
+ test_cmos_byte(qts, reg0 + i, expected[i]);
+ }
+}
+
+static void test_cmos_disk_data(QTestState *qts)
+{
+ test_cmos_byte(qts, 0x12,
+ (is_hd(cur_ide[0]) ? 0xf0 : 0) |
+ (is_hd(cur_ide[1]) ? 0x0f : 0));
+}
+
+static void test_cmos_drive_cyl(QTestState *qts, int reg0,
+ const CHST *expected_chst)
+{
+ if (is_hd(expected_chst)) {
+ int c = expected_chst->cyls;
+ int h = expected_chst->heads;
+ int s = expected_chst->secs;
+ uint8_t expected_bytes[9] = {
+ c & 0xff, c >> 8, h, 0xff, 0xff, 0xc0 | ((h > 8) << 3),
+ c & 0xff, c >> 8, s
+ };
+ test_cmos_bytes(qts, reg0, 9, expected_bytes);
+ } else {
+ int i;
+
+ for (i = 0; i < 9; i++) {
+ test_cmos_byte(qts, reg0 + i, 0);
+ }
+ }
+}
+
+static void test_cmos_drive1(QTestState *qts)
+{
+ test_cmos_byte(qts, 0x19, is_hd(cur_ide[0]) ? 47 : 0);
+ test_cmos_drive_cyl(qts, 0x1b, cur_ide[0]);
+}
+
+static void test_cmos_drive2(QTestState *qts)
+{
+ test_cmos_byte(qts, 0x1a, is_hd(cur_ide[1]) ? 47 : 0);
+ test_cmos_drive_cyl(qts, 0x24, cur_ide[1]);
+}
+
+static void test_cmos_disktransflag(QTestState *qts)
+{
+ int val, i;
+
+ val = 0;
+ for (i = 0; i < ARRAY_SIZE(cur_ide); i++) {
+ if (is_hd(cur_ide[i])) {
+ val |= cur_ide[i]->trans << (2 * i);
+ }
+ }
+ test_cmos_byte(qts, 0x39, val);
+}
+
+static void test_cmos(QTestState *qts)
+{
+ test_cmos_disk_data(qts);
+ test_cmos_drive1(qts);
+ test_cmos_drive2(qts);
+ test_cmos_disktransflag(qts);
+}
+
+static int append_arg(int argc, char *argv[], int argv_sz, char *arg)
+{
+ g_assert(argc + 1 < argv_sz);
+ argv[argc++] = arg;
+ argv[argc] = NULL;
+ return argc;
+}
+
+static int setup_common(char *argv[], int argv_sz)
+{
+ memset(cur_ide, 0, sizeof(cur_ide));
+ return append_arg(0, argv, argv_sz,
+ g_strdup("-nodefaults"));
+}
+
+static void setup_mbr(int img_idx, MBRcontents mbr)
+{
+ static const uint8_t part_lba[16] = {
+ /* chs 0,1,1 (lba 63) to chs 0,127,63 (8001 sectors) */
+ 0x80, 1, 1, 0, 6, 127, 63, 0, 63, 0, 0, 0, 0x41, 0x1F, 0, 0,
+ };
+ static const uint8_t part_chs[16] = {
+ /* chs 0,1,1 (lba 63) to chs 7,15,63 (8001 sectors) */
+ 0x80, 1, 1, 0, 6, 15, 63, 7, 63, 0, 0, 0, 0x41, 0x1F, 0, 0,
+ };
+ uint8_t buf[512];
+ int fd, ret;
+
+ memset(buf, 0, sizeof(buf));
+
+ if (mbr != mbr_blank) {
+ buf[0x1fe] = 0x55;
+ buf[0x1ff] = 0xAA;
+ memcpy(buf + 0x1BE, mbr == mbr_lba ? part_lba : part_chs, 16);
+ }
+
+ fd = open(img_file_name[img_idx], O_WRONLY);
+ g_assert(fd >= 0);
+ ret = write(fd, buf, sizeof(buf));
+ g_assert(ret == sizeof(buf));
+ close(fd);
+}
+
+static int setup_ide(int argc, char *argv[], int argv_sz,
+ int ide_idx, const char *dev, int img_idx,
+ MBRcontents mbr)
+{
+ char *s1, *s2, *s3;
+
+ s1 = g_strdup_printf("-drive id=drive%d,if=%s",
+ ide_idx, dev ? "none" : "ide");
+ s2 = dev ? g_strdup("") : g_strdup_printf(",index=%d", ide_idx);
+
+ if (img_secs[img_idx] >= 0) {
+ setup_mbr(img_idx, mbr);
+ s3 = g_strdup_printf(",format=raw,file=%s", img_file_name[img_idx]);
+ } else {
+ s3 = g_strdup(",media=cdrom");
+ }
+ argc = append_arg(argc, argv, argv_sz,
+ g_strdup_printf("%s%s%s", s1, s2, s3));
+ g_free(s1);
+ g_free(s2);
+ g_free(s3);
+
+ if (dev) {
+ argc = append_arg(argc, argv, argv_sz,
+ g_strdup_printf("-device %s,drive=drive%d,"
+ "bus=ide.%d,unit=%d",
+ dev, ide_idx,
+ ide_idx / 2, ide_idx % 2));
+ }
+ return argc;
+}
+
+/*
+ * Test case: no IDE devices
+ */
+static void test_ide_none(void)
+{
+ char **argv = g_new0(char *, ARGV_SIZE);
+ char *args;
+ QTestState *qts;
+
+ setup_common(argv, ARGV_SIZE);
+ args = g_strjoinv(" ", argv);
+ qts = qtest_init(args);
+ g_strfreev(argv);
+ g_free(args);
+ test_cmos(qts);
+ qtest_quit(qts);
+}
+
+static void test_ide_mbr(bool use_device, MBRcontents mbr)
+{
+ char **argv = g_new0(char *, ARGV_SIZE);
+ char *args;
+ int argc;
+ Backend i;
+ const char *dev;
+ QTestState *qts;
+
+ argc = setup_common(argv, ARGV_SIZE);
+ for (i = 0; i < backend_last; i++) {
+ cur_ide[i] = &hd_chst[i][mbr];
+ dev = use_device ? (is_hd(cur_ide[i]) ? "ide-hd" : "ide-cd") : NULL;
+ argc = setup_ide(argc, argv, ARGV_SIZE, i, dev, i, mbr);
+ }
+ args = g_strjoinv(" ", argv);
+ qts = qtest_init(args);
+ g_strfreev(argv);
+ g_free(args);
+ test_cmos(qts);
+ qtest_quit(qts);
+}
+
+/*
+ * Test case: IDE devices (if=ide) with blank MBRs
+ */
+static void test_ide_drive_mbr_blank(void)
+{
+ test_ide_mbr(false, mbr_blank);
+}
+
+/*
+ * Test case: IDE devices (if=ide) with MBRs indicating LBA is in use
+ */
+static void test_ide_drive_mbr_lba(void)
+{
+ test_ide_mbr(false, mbr_lba);
+}
+
+/*
+ * Test case: IDE devices (if=ide) with MBRs indicating CHS is in use
+ */
+static void test_ide_drive_mbr_chs(void)
+{
+ test_ide_mbr(false, mbr_chs);
+}
+
+/*
+ * Test case: IDE devices (if=none) with blank MBRs
+ */
+static void test_ide_device_mbr_blank(void)
+{
+ test_ide_mbr(true, mbr_blank);
+}
+
+/*
+ * Test case: IDE devices (if=none) with MBRs indicating LBA is in use
+ */
+static void test_ide_device_mbr_lba(void)
+{
+ test_ide_mbr(true, mbr_lba);
+}
+
+/*
+ * Test case: IDE devices (if=none) with MBRs indicating CHS is in use
+ */
+static void test_ide_device_mbr_chs(void)
+{
+ test_ide_mbr(true, mbr_chs);
+}
+
+static void test_ide_drive_user(const char *dev, bool trans)
+{
+ char **argv = g_new0(char *, ARGV_SIZE);
+ char *args, *opts;
+ int argc;
+ int secs = img_secs[backend_small];
+ const CHST expected_chst = { secs / (4 * 32) , 4, 32, trans };
+ QTestState *qts;
+
+ argc = setup_common(argv, ARGV_SIZE);
+ opts = g_strdup_printf("%s,%scyls=%d,heads=%d,secs=%d",
+ dev, trans ? "bios-chs-trans=lba," : "",
+ expected_chst.cyls, expected_chst.heads,
+ expected_chst.secs);
+ cur_ide[0] = &expected_chst;
+ argc = setup_ide(argc, argv, ARGV_SIZE, 0, opts, backend_small, mbr_chs);
+ g_free(opts);
+ args = g_strjoinv(" ", argv);
+ qts = qtest_init(args);
+ g_strfreev(argv);
+ g_free(args);
+ test_cmos(qts);
+ qtest_quit(qts);
+}
+
+/*
+ * Test case: IDE device (if=none) with explicit CHS
+ */
+static void test_ide_device_user_chs(void)
+{
+ test_ide_drive_user("ide-hd", false);
+}
+
+/*
+ * Test case: IDE device (if=none) with explicit CHS and translation
+ */
+static void test_ide_device_user_chst(void)
+{
+ test_ide_drive_user("ide-hd", true);
+}
+
+/*
+ * Test case: IDE devices (if=ide), but use index=0 for CD-ROM
+ */
+static void test_ide_drive_cd_0(void)
+{
+ char **argv = g_new0(char *, ARGV_SIZE);
+ char *args;
+ int argc, ide_idx;
+ Backend i;
+ QTestState *qts;
+
+ argc = setup_common(argv, ARGV_SIZE);
+ for (i = 0; i <= backend_empty; i++) {
+ ide_idx = backend_empty - i;
+ cur_ide[ide_idx] = &hd_chst[i][mbr_blank];
+ argc = setup_ide(argc, argv, ARGV_SIZE, ide_idx, NULL, i, mbr_blank);
+ }
+ args = g_strjoinv(" ", argv);
+ qts = qtest_init(args);
+ g_strfreev(argv);
+ g_free(args);
+ test_cmos(qts);
+ qtest_quit(qts);
+}
+
+typedef struct {
+ bool active;
+ uint32_t head;
+ uint32_t sector;
+ uint32_t cyl;
+ uint32_t end_head;
+ uint32_t end_sector;
+ uint32_t end_cyl;
+ uint32_t start_sect;
+ uint32_t nr_sects;
+} MBRpartitions[4];
+
+static MBRpartitions empty_mbr = { {false, 0, 0, 0, 0, 0, 0, 0, 0},
+ {false, 0, 0, 0, 0, 0, 0, 0, 0},
+ {false, 0, 0, 0, 0, 0, 0, 0, 0},
+ {false, 0, 0, 0, 0, 0, 0, 0, 0} };
+
+static char *create_qcow2_with_mbr(MBRpartitions mbr, uint64_t sectors)
+{
+ const char *template = "/tmp/qtest.XXXXXX";
+ char *raw_path = strdup(template);
+ char *qcow2_path = strdup(template);
+ char cmd[100 + 2 * PATH_MAX];
+ uint8_t buf[512];
+ int i, ret, fd, offset;
+ uint64_t qcow2_size = sectors * 512;
+ uint8_t status, parttype, head, sector, cyl;
+ char *qemu_img_path;
+ char *qemu_img_abs_path;
+
+ offset = 0xbe;
+
+ for (i = 0; i < 4; i++) {
+ status = mbr[i].active ? 0x80 : 0x00;
+ g_assert(mbr[i].head < 256);
+ g_assert(mbr[i].sector < 64);
+ g_assert(mbr[i].cyl < 1024);
+ head = mbr[i].head;
+ sector = mbr[i].sector + ((mbr[i].cyl & 0x300) >> 2);
+ cyl = mbr[i].cyl & 0xff;
+
+ buf[offset + 0x0] = status;
+ buf[offset + 0x1] = head;
+ buf[offset + 0x2] = sector;
+ buf[offset + 0x3] = cyl;
+
+ parttype = 0;
+ g_assert(mbr[i].end_head < 256);
+ g_assert(mbr[i].end_sector < 64);
+ g_assert(mbr[i].end_cyl < 1024);
+ head = mbr[i].end_head;
+ sector = mbr[i].end_sector + ((mbr[i].end_cyl & 0x300) >> 2);
+ cyl = mbr[i].end_cyl & 0xff;
+
+ buf[offset + 0x4] = parttype;
+ buf[offset + 0x5] = head;
+ buf[offset + 0x6] = sector;
+ buf[offset + 0x7] = cyl;
+
+ (*(uint32_t *)&buf[offset + 0x8]) = cpu_to_le32(mbr[i].start_sect);
+ (*(uint32_t *)&buf[offset + 0xc]) = cpu_to_le32(mbr[i].nr_sects);
+
+ offset += 0x10;
+ }
+
+ fd = mkstemp(raw_path);
+ g_assert(fd);
+ close(fd);
+
+ fd = open(raw_path, O_WRONLY);
+ g_assert(fd >= 0);
+ ret = write(fd, buf, sizeof(buf));
+ g_assert(ret == sizeof(buf));
+ close(fd);
+
+ fd = mkstemp(qcow2_path);
+ g_assert(fd);
+ close(fd);
+
+ qemu_img_path = getenv("QTEST_QEMU_IMG");
+ g_assert(qemu_img_path);
+ qemu_img_abs_path = realpath(qemu_img_path, NULL);
+ g_assert(qemu_img_abs_path);
+
+ ret = snprintf(cmd, sizeof(cmd),
+ "%s convert -f raw -O qcow2 %s %s > /dev/null",
+ qemu_img_abs_path,
+ raw_path, qcow2_path);
+ g_assert((0 < ret) && (ret <= sizeof(cmd)));
+ ret = system(cmd);
+ g_assert(ret == 0);
+
+ ret = snprintf(cmd, sizeof(cmd),
+ "%s resize %s %" PRIu64 " > /dev/null",
+ qemu_img_abs_path,
+ qcow2_path, qcow2_size);
+ g_assert((0 < ret) && (ret <= sizeof(cmd)));
+ ret = system(cmd);
+ g_assert(ret == 0);
+
+ free(qemu_img_abs_path);
+
+ unlink(raw_path);
+ free(raw_path);
+
+ return qcow2_path;
+}
+
+#define BIOS_GEOMETRY_MAX_SIZE 10000
+
+typedef struct {
+ uint32_t c;
+ uint32_t h;
+ uint32_t s;
+} CHS;
+
+typedef struct {
+ const char *dev_path;
+ CHS chs;
+} CHSResult;
+
+static void read_bootdevices(QFWCFG *fw_cfg, CHSResult expected[])
+{
+ char *buf = g_malloc0(BIOS_GEOMETRY_MAX_SIZE);
+ char *cur;
+ GList *results = NULL, *cur_result;
+ CHSResult *r;
+ int i;
+ int res;
+ bool found;
+
+ qfw_cfg_get_file(fw_cfg, "bios-geometry", buf, BIOS_GEOMETRY_MAX_SIZE);
+
+ for (cur = buf; *cur; cur++) {
+ if (*cur == '\n') {
+ *cur = '\0';
+ }
+ }
+ cur = buf;
+
+ while (strlen(cur)) {
+
+ r = g_malloc0(sizeof(*r));
+ r->dev_path = g_malloc0(strlen(cur) + 1);
+ res = sscanf(cur, "%s %" PRIu32 " %" PRIu32 " %" PRIu32,
+ (char *)r->dev_path,
+ &(r->chs.c), &(r->chs.h), &(r->chs.s));
+
+ g_assert(res == 4);
+
+ results = g_list_prepend(results, r);
+
+ cur += strlen(cur) + 1;
+ }
+
+ i = 0;
+
+ while (expected[i].dev_path) {
+ found = false;
+ cur_result = results;
+ while (cur_result) {
+ r = cur_result->data;
+ if (!strcmp(r->dev_path, expected[i].dev_path) &&
+ !memcmp(&(r->chs), &(expected[i].chs), sizeof(r->chs))) {
+ found = true;
+ break;
+ }
+ cur_result = g_list_next(cur_result);
+ }
+ g_assert(found);
+ g_free((char *)((CHSResult *)cur_result->data)->dev_path);
+ g_free(cur_result->data);
+ results = g_list_delete_link(results, cur_result);
+ i++;
+ }
+
+ g_assert(results == NULL);
+
+ g_free(buf);
+}
+
+#define MAX_DRIVES 30
+
+typedef struct {
+ char **argv;
+ int argc;
+ char **drives;
+ int n_drives;
+ int n_scsi_disks;
+ int n_scsi_controllers;
+ int n_virtio_disks;
+} TestArgs;
+
+static TestArgs *create_args(void)
+{
+ TestArgs *args = g_malloc0(sizeof(*args));
+ args->argv = g_new0(char *, ARGV_SIZE);
+ args->argc = append_arg(args->argc, args->argv,
+ ARGV_SIZE, g_strdup("-nodefaults"));
+ args->drives = g_new0(char *, MAX_DRIVES);
+ return args;
+}
+
+static void add_drive_with_mbr(TestArgs *args,
+ MBRpartitions mbr, uint64_t sectors)
+{
+ char *img_file_name;
+ char part[300];
+ int ret;
+
+ g_assert(args->n_drives < MAX_DRIVES);
+
+ img_file_name = create_qcow2_with_mbr(mbr, sectors);
+
+ args->drives[args->n_drives] = img_file_name;
+ ret = snprintf(part, sizeof(part),
+ "-drive file=%s,if=none,format=qcow2,id=disk%d",
+ img_file_name, args->n_drives);
+ g_assert((0 < ret) && (ret <= sizeof(part)));
+ args->argc = append_arg(args->argc, args->argv, ARGV_SIZE, g_strdup(part));
+ args->n_drives++;
+}
+
+static void add_ide_disk(TestArgs *args,
+ int drive_idx, int bus, int unit, int c, int h, int s)
+{
+ char part[300];
+ int ret;
+
+ ret = snprintf(part, sizeof(part),
+ "-device ide-hd,drive=disk%d,bus=ide.%d,unit=%d,"
+ "lcyls=%d,lheads=%d,lsecs=%d",
+ drive_idx, bus, unit, c, h, s);
+ g_assert((0 < ret) && (ret <= sizeof(part)));
+ args->argc = append_arg(args->argc, args->argv, ARGV_SIZE, g_strdup(part));
+}
+
+static void add_scsi_controller(TestArgs *args,
+ const char *type,
+ const char *bus,
+ int addr)
+{
+ char part[300];
+ int ret;
+
+ ret = snprintf(part, sizeof(part),
+ "-device %s,id=scsi%d,bus=%s,addr=%d",
+ type, args->n_scsi_controllers, bus, addr);
+ g_assert((0 < ret) && (ret <= sizeof(part)));
+ args->argc = append_arg(args->argc, args->argv, ARGV_SIZE, g_strdup(part));
+ args->n_scsi_controllers++;
+}
+
+static void add_scsi_disk(TestArgs *args,
+ int drive_idx, int bus,
+ int channel, int scsi_id, int lun,
+ int c, int h, int s)
+{
+ char part[300];
+ int ret;
+
+ ret = snprintf(part, sizeof(part),
+ "-device scsi-hd,id=scsi-disk%d,drive=disk%d,"
+ "bus=scsi%d.0,"
+ "channel=%d,scsi-id=%d,lun=%d,"
+ "lcyls=%d,lheads=%d,lsecs=%d",
+ args->n_scsi_disks, drive_idx, bus, channel, scsi_id, lun,
+ c, h, s);
+ g_assert((0 < ret) && (ret <= sizeof(part)));
+ args->argc = append_arg(args->argc, args->argv, ARGV_SIZE, g_strdup(part));
+ args->n_scsi_disks++;
+}
+
+static void add_virtio_disk(TestArgs *args,
+ int drive_idx, const char *bus, int addr,
+ int c, int h, int s)
+{
+ char part[300];
+ int ret;
+
+ ret = snprintf(part, sizeof(part),
+ "-device virtio-blk-pci,id=virtio-disk%d,"
+ "drive=disk%d,bus=%s,addr=%d,"
+ "lcyls=%d,lheads=%d,lsecs=%d",
+ args->n_virtio_disks, drive_idx, bus, addr, c, h, s);
+ g_assert((0 < ret) && (ret <= sizeof(part)));
+ args->argc = append_arg(args->argc, args->argv, ARGV_SIZE, g_strdup(part));
+ args->n_virtio_disks++;
+}
+
+static void test_override(TestArgs *args, CHSResult expected[])
+{
+ QTestState *qts;
+ char *joined_args;
+ QFWCFG *fw_cfg;
+ int i;
+
+ joined_args = g_strjoinv(" ", args->argv);
+
+ qts = qtest_init(joined_args);
+ fw_cfg = pc_fw_cfg_init(qts);
+
+ read_bootdevices(fw_cfg, expected);
+
+ g_free(joined_args);
+ qtest_quit(qts);
+
+ g_free(fw_cfg);
+
+ for (i = 0; i < args->n_drives; i++) {
+ unlink(args->drives[i]);
+ free(args->drives[i]);
+ }
+ g_free(args->drives);
+ g_strfreev(args->argv);
+ g_free(args);
+}
+
+static void test_override_ide(void)
+{
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {"/pci@i0cf8/ide@1,1/drive@0/disk@0", {10000, 120, 30} },
+ {"/pci@i0cf8/ide@1,1/drive@0/disk@1", {9000, 120, 30} },
+ {"/pci@i0cf8/ide@1,1/drive@1/disk@0", {0, 1, 1} },
+ {"/pci@i0cf8/ide@1,1/drive@1/disk@1", {1, 0, 0} },
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_ide_disk(args, 0, 0, 0, 10000, 120, 30);
+ add_ide_disk(args, 1, 0, 1, 9000, 120, 30);
+ add_ide_disk(args, 2, 1, 0, 0, 1, 1);
+ add_ide_disk(args, 3, 1, 1, 1, 0, 0);
+ test_override(args, expected);
+}
+
+static void test_override_scsi(void)
+{
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {"/pci@i0cf8/scsi@3/channel@0/disk@0,0", {10000, 120, 30} },
+ {"/pci@i0cf8/scsi@3/channel@0/disk@1,0", {9000, 120, 30} },
+ {"/pci@i0cf8/scsi@3/channel@0/disk@2,0", {1, 0, 0} },
+ {"/pci@i0cf8/scsi@3/channel@0/disk@3,0", {0, 1, 0} },
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_scsi_controller(args, "lsi53c895a", "pci.0", 3);
+ add_scsi_disk(args, 0, 0, 0, 0, 0, 10000, 120, 30);
+ add_scsi_disk(args, 1, 0, 0, 1, 0, 9000, 120, 30);
+ add_scsi_disk(args, 2, 0, 0, 2, 0, 1, 0, 0);
+ add_scsi_disk(args, 3, 0, 0, 3, 0, 0, 1, 0);
+ test_override(args, expected);
+}
+
+static void test_override_scsi_2_controllers(void)
+{
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {"/pci@i0cf8/scsi@3/channel@0/disk@0,0", {10000, 120, 30} },
+ {"/pci@i0cf8/scsi@3/channel@0/disk@1,0", {9000, 120, 30} },
+ {"/pci@i0cf8/scsi@4/channel@0/disk@0,1", {1, 0, 0} },
+ {"/pci@i0cf8/scsi@4/channel@0/disk@1,2", {0, 1, 0} },
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_scsi_controller(args, "lsi53c895a", "pci.0", 3);
+ add_scsi_controller(args, "virtio-scsi-pci", "pci.0", 4);
+ add_scsi_disk(args, 0, 0, 0, 0, 0, 10000, 120, 30);
+ add_scsi_disk(args, 1, 0, 0, 1, 0, 9000, 120, 30);
+ add_scsi_disk(args, 2, 1, 0, 0, 1, 1, 0, 0);
+ add_scsi_disk(args, 3, 1, 0, 1, 2, 0, 1, 0);
+ test_override(args, expected);
+}
+
+static void test_override_virtio_blk(void)
+{
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {"/pci@i0cf8/scsi@3/disk@0,0", {10000, 120, 30} },
+ {"/pci@i0cf8/scsi@4/disk@0,0", {9000, 120, 30} },
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_virtio_disk(args, 0, "pci.0", 3, 10000, 120, 30);
+ add_virtio_disk(args, 1, "pci.0", 4, 9000, 120, 30);
+ test_override(args, expected);
+}
+
+static void test_override_zero_chs(void)
+{
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_ide_disk(args, 0, 1, 1, 0, 0, 0);
+ test_override(args, expected);
+}
+
+static void test_override_scsi_hot_unplug(void)
+{
+ QTestState *qts;
+ char *joined_args;
+ QFWCFG *fw_cfg;
+ QDict *response;
+ int i;
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {"/pci@i0cf8/scsi@2/channel@0/disk@0,0", {10000, 120, 30} },
+ {"/pci@i0cf8/scsi@2/channel@0/disk@1,0", {20, 20, 20} },
+ {NULL, {0, 0, 0} }
+ };
+ CHSResult expected2[] = {
+ {"/pci@i0cf8/scsi@2/channel@0/disk@1,0", {20, 20, 20} },
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_scsi_controller(args, "virtio-scsi-pci", "pci.0", 2);
+ add_scsi_disk(args, 0, 0, 0, 0, 0, 10000, 120, 30);
+ add_scsi_disk(args, 1, 0, 0, 1, 0, 20, 20, 20);
+
+ joined_args = g_strjoinv(" ", args->argv);
+
+ qts = qtest_init(joined_args);
+ fw_cfg = pc_fw_cfg_init(qts);
+
+ read_bootdevices(fw_cfg, expected);
+
+ /* unplug device an restart */
+ response = qtest_qmp(qts,
+ "{ 'execute': 'device_del',"
+ " 'arguments': {'id': 'scsi-disk0' }}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+ response = qtest_qmp(qts,
+ "{ 'execute': 'system_reset', 'arguments': { }}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ qtest_qmp_eventwait(qts, "RESET");
+
+ read_bootdevices(fw_cfg, expected2);
+
+ g_free(joined_args);
+ qtest_quit(qts);
+
+ g_free(fw_cfg);
+
+ for (i = 0; i < args->n_drives; i++) {
+ unlink(args->drives[i]);
+ free(args->drives[i]);
+ }
+ g_free(args->drives);
+ g_strfreev(args->argv);
+ g_free(args);
+}
+
+static void test_override_virtio_hot_unplug(void)
+{
+ QTestState *qts;
+ char *joined_args;
+ QFWCFG *fw_cfg;
+ QDict *response;
+ int i;
+ TestArgs *args = create_args();
+ CHSResult expected[] = {
+ {"/pci@i0cf8/scsi@2/disk@0,0", {10000, 120, 30} },
+ {"/pci@i0cf8/scsi@3/disk@0,0", {20, 20, 20} },
+ {NULL, {0, 0, 0} }
+ };
+ CHSResult expected2[] = {
+ {"/pci@i0cf8/scsi@3/disk@0,0", {20, 20, 20} },
+ {NULL, {0, 0, 0} }
+ };
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_drive_with_mbr(args, empty_mbr, 1);
+ add_virtio_disk(args, 0, "pci.0", 2, 10000, 120, 30);
+ add_virtio_disk(args, 1, "pci.0", 3, 20, 20, 20);
+
+ joined_args = g_strjoinv(" ", args->argv);
+
+ qts = qtest_init(joined_args);
+ fw_cfg = pc_fw_cfg_init(qts);
+
+ read_bootdevices(fw_cfg, expected);
+
+ /* unplug device an restart */
+ response = qtest_qmp(qts,
+ "{ 'execute': 'device_del',"
+ " 'arguments': {'id': 'virtio-disk0' }}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+ response = qtest_qmp(qts,
+ "{ 'execute': 'system_reset', 'arguments': { }}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ qtest_qmp_eventwait(qts, "RESET");
+
+ read_bootdevices(fw_cfg, expected2);
+
+ g_free(joined_args);
+ qtest_quit(qts);
+
+ g_free(fw_cfg);
+
+ for (i = 0; i < args->n_drives; i++) {
+ unlink(args->drives[i]);
+ free(args->drives[i]);
+ }
+ g_free(args->drives);
+ g_strfreev(args->argv);
+ g_free(args);
+}
+
+int main(int argc, char **argv)
+{
+ Backend i;
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (i = 0; i < backend_last; i++) {
+ if (img_secs[i] >= 0) {
+ img_file_name[i] = create_test_img(img_secs[i]);
+ if (!img_file_name[i]) {
+ g_test_message("Could not create test images.");
+ goto test_add_done;
+ }
+ } else {
+ img_file_name[i] = NULL;
+ }
+ }
+
+ qtest_add_func("hd-geo/ide/none", test_ide_none);
+ qtest_add_func("hd-geo/ide/drive/mbr/blank", test_ide_drive_mbr_blank);
+ qtest_add_func("hd-geo/ide/drive/mbr/lba", test_ide_drive_mbr_lba);
+ qtest_add_func("hd-geo/ide/drive/mbr/chs", test_ide_drive_mbr_chs);
+ qtest_add_func("hd-geo/ide/drive/cd_0", test_ide_drive_cd_0);
+ qtest_add_func("hd-geo/ide/device/mbr/blank", test_ide_device_mbr_blank);
+ qtest_add_func("hd-geo/ide/device/mbr/lba", test_ide_device_mbr_lba);
+ qtest_add_func("hd-geo/ide/device/mbr/chs", test_ide_device_mbr_chs);
+ qtest_add_func("hd-geo/ide/device/user/chs", test_ide_device_user_chs);
+ qtest_add_func("hd-geo/ide/device/user/chst", test_ide_device_user_chst);
+ if (have_qemu_img()) {
+ qtest_add_func("hd-geo/override/ide", test_override_ide);
+ qtest_add_func("hd-geo/override/scsi", test_override_scsi);
+ qtest_add_func("hd-geo/override/scsi_2_controllers",
+ test_override_scsi_2_controllers);
+ qtest_add_func("hd-geo/override/virtio_blk", test_override_virtio_blk);
+ qtest_add_func("hd-geo/override/zero_chs", test_override_zero_chs);
+ qtest_add_func("hd-geo/override/scsi_hot_unplug",
+ test_override_scsi_hot_unplug);
+ qtest_add_func("hd-geo/override/virtio_hot_unplug",
+ test_override_virtio_hot_unplug);
+ } else {
+ g_test_message("QTEST_QEMU_IMG not set or qemu-img missing; "
+ "skipping hd-geo/override/* tests");
+ }
+
+test_add_done:
+ ret = g_test_run();
+
+ for (i = 0; i < backend_last; i++) {
+ if (img_file_name[i]) {
+ unlink(img_file_name[i]);
+ free(img_file_name[i]);
+ }
+ }
+
+ return ret;
+}
diff --git a/tests/qtest/hexloader-test.c b/tests/qtest/hexloader-test.c
new file mode 100644
index 0000000000..8b7aa2d72d
--- /dev/null
+++ b/tests/qtest/hexloader-test.c
@@ -0,0 +1,45 @@
+/*
+ * QTest testcase for the Intel Hexadecimal Object File Loader
+ *
+ * Authors:
+ * Su Hang <suhang16@mails.ucas.ac.cn> 2018
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+/* Load 'test.hex' and verify that the in-memory contents are as expected.
+ * 'test.hex' is a memory test pattern stored in Hexadecimal Object
+ * format. It loads at 0x10000 in RAM and contains values from 0 through
+ * 255.
+ */
+static void hex_loader_test(void)
+{
+ unsigned int i;
+ const unsigned int base_addr = 0x00010000;
+
+ QTestState *s = qtest_initf(
+ "-M vexpress-a9 -device loader,file=tests/data/hex-loader/test.hex");
+
+ for (i = 0; i < 256; ++i) {
+ uint8_t val = qtest_readb(s, base_addr + i);
+ g_assert_cmpuint(i, ==, val);
+ }
+ qtest_quit(s);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/tmp/hex_loader", hex_loader_test);
+ ret = g_test_run();
+
+ return ret;
+}
diff --git a/tests/qtest/i440fx-test.c b/tests/qtest/i440fx-test.c
new file mode 100644
index 0000000000..1f57d9684b
--- /dev/null
+++ b/tests/qtest/i440fx-test.c
@@ -0,0 +1,413 @@
+/*
+ * qtest I440FX test case
+ *
+ * Copyright IBM, Corp. 2012-2013
+ * Copyright Red Hat, Inc. 2013
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Laszlo Ersek <lersek@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest-single.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "hw/pci/pci_regs.h"
+
+#define BROKEN 1
+
+typedef struct TestData
+{
+ int num_cpus;
+} TestData;
+
+typedef struct FirmwareTestFixture {
+ /* decides whether we're testing -bios or -pflash */
+ bool is_bios;
+} FirmwareTestFixture;
+
+static QPCIBus *test_start_get_bus(const TestData *s)
+{
+ char *cmdline;
+
+ cmdline = g_strdup_printf("-smp %d", s->num_cpus);
+ qtest_start(cmdline);
+ g_free(cmdline);
+ return qpci_new_pc(global_qtest, NULL);
+}
+
+static void test_i440fx_defaults(gconstpointer opaque)
+{
+ const TestData *s = opaque;
+ QPCIBus *bus;
+ QPCIDevice *dev;
+ uint32_t value;
+
+ bus = test_start_get_bus(s);
+ dev = qpci_device_find(bus, QPCI_DEVFN(0, 0));
+ g_assert(dev != NULL);
+
+ /* 3.2.2 */
+ g_assert_cmpint(qpci_config_readw(dev, PCI_VENDOR_ID), ==, 0x8086);
+ /* 3.2.3 */
+ g_assert_cmpint(qpci_config_readw(dev, PCI_DEVICE_ID), ==, 0x1237);
+#ifndef BROKEN
+ /* 3.2.4 */
+ g_assert_cmpint(qpci_config_readw(dev, PCI_COMMAND), ==, 0x0006);
+ /* 3.2.5 */
+ g_assert_cmpint(qpci_config_readw(dev, PCI_STATUS), ==, 0x0280);
+#endif
+ /* 3.2.7 */
+ g_assert_cmpint(qpci_config_readb(dev, PCI_CLASS_PROG), ==, 0x00);
+ g_assert_cmpint(qpci_config_readw(dev, PCI_CLASS_DEVICE), ==, 0x0600);
+ /* 3.2.8 */
+ g_assert_cmpint(qpci_config_readb(dev, PCI_LATENCY_TIMER), ==, 0x00);
+ /* 3.2.9 */
+ g_assert_cmpint(qpci_config_readb(dev, PCI_HEADER_TYPE), ==, 0x00);
+ /* 3.2.10 */
+ g_assert_cmpint(qpci_config_readb(dev, PCI_BIST), ==, 0x00);
+
+ /* 3.2.11 */
+ value = qpci_config_readw(dev, 0x50); /* PMCCFG */
+ if (s->num_cpus == 1) { /* WPE */
+ g_assert(!(value & (1 << 15)));
+ } else {
+ g_assert((value & (1 << 15)));
+ }
+
+ g_assert(!(value & (1 << 6))); /* EPTE */
+
+ /* 3.2.12 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x52), ==, 0x00); /* DETURBO */
+ /* 3.2.13 */
+#ifndef BROKEN
+ g_assert_cmpint(qpci_config_readb(dev, 0x53), ==, 0x80); /* DBC */
+#endif
+ /* 3.2.14 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x54), ==, 0x00); /* AXC */
+ /* 3.2.15 */
+ g_assert_cmpint(qpci_config_readw(dev, 0x55), ==, 0x0000); /* DRT */
+#ifndef BROKEN
+ /* 3.2.16 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x57), ==, 0x01); /* DRAMC */
+ /* 3.2.17 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x58), ==, 0x10); /* DRAMT */
+#endif
+ /* 3.2.18 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x59), ==, 0x00); /* PAM0 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x5A), ==, 0x00); /* PAM1 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x5B), ==, 0x00); /* PAM2 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x5C), ==, 0x00); /* PAM3 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x5D), ==, 0x00); /* PAM4 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x5E), ==, 0x00); /* PAM5 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x5F), ==, 0x00); /* PAM6 */
+#ifndef BROKEN
+ /* 3.2.19 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x60), ==, 0x01); /* DRB0 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x61), ==, 0x01); /* DRB1 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x62), ==, 0x01); /* DRB2 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x63), ==, 0x01); /* DRB3 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x64), ==, 0x01); /* DRB4 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x65), ==, 0x01); /* DRB5 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x66), ==, 0x01); /* DRB6 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x67), ==, 0x01); /* DRB7 */
+#endif
+ /* 3.2.20 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x68), ==, 0x00); /* FDHC */
+ /* 3.2.21 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x70), ==, 0x00); /* MTT */
+#ifndef BROKEN
+ /* 3.2.22 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x71), ==, 0x10); /* CLT */
+#endif
+ /* 3.2.23 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x72), ==, 0x02); /* SMRAM */
+ /* 3.2.24 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x90), ==, 0x00); /* ERRCMD */
+ /* 3.2.25 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x91), ==, 0x00); /* ERRSTS */
+ /* 3.2.26 */
+ g_assert_cmpint(qpci_config_readb(dev, 0x93), ==, 0x00); /* TRC */
+
+ g_free(dev);
+ qpci_free_pc(bus);
+ qtest_end();
+}
+
+#define PAM_RE 1
+#define PAM_WE 2
+
+static void pam_set(QPCIDevice *dev, int index, int flags)
+{
+ int regno = 0x59 + (index / 2);
+ uint8_t reg;
+
+ reg = qpci_config_readb(dev, regno);
+ if (index & 1) {
+ reg = (reg & 0x0F) | (flags << 4);
+ } else {
+ reg = (reg & 0xF0) | flags;
+ }
+ qpci_config_writeb(dev, regno, reg);
+}
+
+static gboolean verify_area(uint32_t start, uint32_t end, uint8_t value)
+{
+ uint32_t size = end - start + 1;
+ gboolean ret = TRUE;
+ uint8_t *data;
+ int i;
+
+ data = g_malloc0(size);
+ memread(start, data, size);
+
+ g_test_message("verify_area: data[0] = 0x%x", data[0]);
+
+ for (i = 0; i < size; i++) {
+ if (data[i] != value) {
+ ret = FALSE;
+ break;
+ }
+ }
+
+ g_free(data);
+
+ return ret;
+}
+
+static void write_area(uint32_t start, uint32_t end, uint8_t value)
+{
+ uint32_t size = end - start + 1;
+ uint8_t *data;
+
+ data = g_malloc(size);
+ memset(data, value, size);
+ memwrite(start, data, size);
+
+ g_free(data);
+}
+
+static void test_i440fx_pam(gconstpointer opaque)
+{
+ const TestData *s = opaque;
+ QPCIBus *bus;
+ QPCIDevice *dev;
+ int i;
+ static struct {
+ uint32_t start;
+ uint32_t end;
+ } pam_area[] = {
+ { 0, 0 }, /* Reserved */
+ { 0xF0000, 0xFFFFF }, /* BIOS Area */
+ { 0xC0000, 0xC3FFF }, /* Option ROM */
+ { 0xC4000, 0xC7FFF }, /* Option ROM */
+ { 0xC8000, 0xCBFFF }, /* Option ROM */
+ { 0xCC000, 0xCFFFF }, /* Option ROM */
+ { 0xD0000, 0xD3FFF }, /* Option ROM */
+ { 0xD4000, 0xD7FFF }, /* Option ROM */
+ { 0xD8000, 0xDBFFF }, /* Option ROM */
+ { 0xDC000, 0xDFFFF }, /* Option ROM */
+ { 0xE0000, 0xE3FFF }, /* BIOS Extension */
+ { 0xE4000, 0xE7FFF }, /* BIOS Extension */
+ { 0xE8000, 0xEBFFF }, /* BIOS Extension */
+ { 0xEC000, 0xEFFFF }, /* BIOS Extension */
+ };
+
+ bus = test_start_get_bus(s);
+ dev = qpci_device_find(bus, QPCI_DEVFN(0, 0));
+ g_assert(dev != NULL);
+
+ for (i = 0; i < ARRAY_SIZE(pam_area); i++) {
+ if (pam_area[i].start == pam_area[i].end) {
+ continue;
+ }
+
+ g_test_message("Checking area 0x%05x..0x%05x",
+ pam_area[i].start, pam_area[i].end);
+ /* Switch to RE for the area */
+ pam_set(dev, i, PAM_RE);
+ /* Verify the RAM is all zeros */
+ g_assert(verify_area(pam_area[i].start, pam_area[i].end, 0));
+
+ /* Switch to WE for the area */
+ pam_set(dev, i, PAM_RE | PAM_WE);
+ /* Write out a non-zero mask to the full area */
+ write_area(pam_area[i].start, pam_area[i].end, 0x42);
+
+#ifndef BROKEN
+ /* QEMU only supports a limited form of PAM */
+
+ /* Switch to !RE for the area */
+ pam_set(dev, i, PAM_WE);
+ /* Verify the area is not our mask */
+ g_assert(!verify_area(pam_area[i].start, pam_area[i].end, 0x42));
+#endif
+
+ /* Verify the area is our new mask */
+ g_assert(verify_area(pam_area[i].start, pam_area[i].end, 0x42));
+
+ /* Write out a new mask */
+ write_area(pam_area[i].start, pam_area[i].end, 0x82);
+
+#ifndef BROKEN
+ /* QEMU only supports a limited form of PAM */
+
+ /* Verify the area is not our mask */
+ g_assert(!verify_area(pam_area[i].start, pam_area[i].end, 0x82));
+
+ /* Switch to RE for the area */
+ pam_set(dev, i, PAM_RE | PAM_WE);
+#endif
+ /* Verify the area is our new mask */
+ g_assert(verify_area(pam_area[i].start, pam_area[i].end, 0x82));
+
+ /* Reset area */
+ pam_set(dev, i, 0);
+
+ /* Verify the area is not our new mask */
+ g_assert(!verify_area(pam_area[i].start, pam_area[i].end, 0x82));
+ }
+
+ g_free(dev);
+ qpci_free_pc(bus);
+ qtest_end();
+}
+
+#define BLOB_SIZE ((size_t)65536)
+#define ISA_BIOS_MAXSZ ((size_t)(128 * 1024))
+
+/* Create a blob file, and return its absolute pathname as a dynamically
+ * allocated string.
+ * The file is closed before the function returns.
+ * In case of error, NULL is returned. The function prints the error message.
+ */
+static char *create_blob_file(void)
+{
+ int ret, fd;
+ char *pathname;
+ GError *error = NULL;
+
+ ret = -1;
+ fd = g_file_open_tmp("blob_XXXXXX", &pathname, &error);
+ if (fd == -1) {
+ fprintf(stderr, "unable to create blob file: %s\n", error->message);
+ g_error_free(error);
+ } else {
+ if (ftruncate(fd, BLOB_SIZE) == -1) {
+ fprintf(stderr, "ftruncate(\"%s\", %zu): %s\n", pathname,
+ BLOB_SIZE, strerror(errno));
+ } else {
+ void *buf;
+
+ buf = mmap(NULL, BLOB_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
+ if (buf == MAP_FAILED) {
+ fprintf(stderr, "mmap(\"%s\", %zu): %s\n", pathname, BLOB_SIZE,
+ strerror(errno));
+ } else {
+ size_t i;
+
+ for (i = 0; i < BLOB_SIZE; ++i) {
+ ((uint8_t *)buf)[i] = i;
+ }
+ munmap(buf, BLOB_SIZE);
+ ret = 0;
+ }
+ }
+ close(fd);
+ if (ret == -1) {
+ unlink(pathname);
+ g_free(pathname);
+ }
+ }
+
+ return ret == -1 ? NULL : pathname;
+}
+
+static void test_i440fx_firmware(FirmwareTestFixture *fixture,
+ gconstpointer user_data)
+{
+ char *fw_pathname, *cmdline;
+ uint8_t *buf;
+ size_t i, isa_bios_size;
+
+ fw_pathname = create_blob_file();
+ g_assert(fw_pathname != NULL);
+
+ /* Better hope the user didn't put metacharacters in TMPDIR and co. */
+ cmdline = g_strdup_printf("-S %s%s", fixture->is_bios
+ ? "-bios "
+ : "-drive if=pflash,format=raw,file=",
+ fw_pathname);
+ g_test_message("qemu cmdline: %s", cmdline);
+ qtest_start(cmdline);
+ g_free(cmdline);
+
+ /* QEMU has loaded the firmware (because qtest_start() only returns after
+ * the QMP handshake completes). We must unlink the firmware blob right
+ * here, because any assertion firing below would leak it in the
+ * filesystem. This is also the reason why we recreate the blob every time
+ * this function is invoked.
+ */
+ unlink(fw_pathname);
+ g_free(fw_pathname);
+
+ /* check below 4G */
+ buf = g_malloc0(BLOB_SIZE);
+ memread(0x100000000ULL - BLOB_SIZE, buf, BLOB_SIZE);
+ for (i = 0; i < BLOB_SIZE; ++i) {
+ g_assert_cmphex(buf[i], ==, (uint8_t)i);
+ }
+
+ /* check in ISA space too */
+ memset(buf, 0, BLOB_SIZE);
+ isa_bios_size = ISA_BIOS_MAXSZ < BLOB_SIZE ? ISA_BIOS_MAXSZ : BLOB_SIZE;
+ memread(0x100000 - isa_bios_size, buf, isa_bios_size);
+ for (i = 0; i < isa_bios_size; ++i) {
+ g_assert_cmphex(buf[i], ==,
+ (uint8_t)((BLOB_SIZE - isa_bios_size) + i));
+ }
+
+ g_free(buf);
+ qtest_end();
+}
+
+static void add_firmware_test(const char *testpath,
+ void (*setup_fixture)(FirmwareTestFixture *f,
+ gconstpointer test_data))
+{
+ qtest_add(testpath, FirmwareTestFixture, NULL, setup_fixture,
+ test_i440fx_firmware, NULL);
+}
+
+static void request_bios(FirmwareTestFixture *fixture,
+ gconstpointer user_data)
+{
+ fixture->is_bios = true;
+}
+
+static void request_pflash(FirmwareTestFixture *fixture,
+ gconstpointer user_data)
+{
+ fixture->is_bios = false;
+}
+
+int main(int argc, char **argv)
+{
+ TestData data;
+
+ g_test_init(&argc, &argv, NULL);
+
+ data.num_cpus = 1;
+
+ qtest_add_data_func("i440fx/defaults", &data, test_i440fx_defaults);
+ qtest_add_data_func("i440fx/pam", &data, test_i440fx_pam);
+ add_firmware_test("i440fx/firmware/bios", request_bios);
+ add_firmware_test("i440fx/firmware/pflash", request_pflash);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/i82801b11-test.c b/tests/qtest/i82801b11-test.c
new file mode 100644
index 0000000000..4345da338b
--- /dev/null
+++ b/tests/qtest/i82801b11-test.c
@@ -0,0 +1,31 @@
+/*
+ * QTest testcase for i82801b11
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void nop(void)
+{
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/i82801b11/nop", nop);
+
+ qtest_start("-machine q35 -device i82801b11-bridge,bus=pcie.0,addr=1e.0");
+ ret = g_test_run();
+
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
new file mode 100644
index 0000000000..0277e7d5a9
--- /dev/null
+++ b/tests/qtest/ide-test.c
@@ -0,0 +1,1092 @@
+/*
+ * IDE test cases
+ *
+ * Copyright (c) 2013 Kevin Wolf <kwolf@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+
+#include "libqtest.h"
+#include "libqos/libqos.h"
+#include "libqos/pci-pc.h"
+#include "libqos/malloc-pc.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu-common.h"
+#include "qemu/bswap.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/pci/pci_regs.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(q, ...) qobject_unref(qtest_qmp(q, __VA_ARGS__))
+
+#define TEST_IMAGE_SIZE 64 * 1024 * 1024
+
+#define IDE_PCI_DEV 1
+#define IDE_PCI_FUNC 1
+
+#define IDE_BASE 0x1f0
+#define IDE_PRIMARY_IRQ 14
+
+#define ATAPI_BLOCK_SIZE 2048
+
+/* How many bytes to receive via ATAPI PIO at one time.
+ * Must be less than 0xFFFF. */
+#define BYTE_COUNT_LIMIT 5120
+
+enum {
+ reg_data = 0x0,
+ reg_feature = 0x1,
+ reg_error = 0x1,
+ reg_nsectors = 0x2,
+ reg_lba_low = 0x3,
+ reg_lba_middle = 0x4,
+ reg_lba_high = 0x5,
+ reg_device = 0x6,
+ reg_status = 0x7,
+ reg_command = 0x7,
+};
+
+enum {
+ BSY = 0x80,
+ DRDY = 0x40,
+ DF = 0x20,
+ DRQ = 0x08,
+ ERR = 0x01,
+};
+
+/* Error field */
+enum {
+ ABRT = 0x04,
+};
+
+enum {
+ DEV = 0x10,
+ LBA = 0x40,
+};
+
+enum {
+ bmreg_cmd = 0x0,
+ bmreg_status = 0x2,
+ bmreg_prdt = 0x4,
+};
+
+enum {
+ CMD_DSM = 0x06,
+ CMD_READ_DMA = 0xc8,
+ CMD_WRITE_DMA = 0xca,
+ CMD_FLUSH_CACHE = 0xe7,
+ CMD_IDENTIFY = 0xec,
+ CMD_PACKET = 0xa0,
+
+ CMDF_ABORT = 0x100,
+ CMDF_NO_BM = 0x200,
+};
+
+enum {
+ BM_CMD_START = 0x1,
+ BM_CMD_WRITE = 0x8, /* write = from device to memory */
+};
+
+enum {
+ BM_STS_ACTIVE = 0x1,
+ BM_STS_ERROR = 0x2,
+ BM_STS_INTR = 0x4,
+};
+
+enum {
+ PRDT_EOT = 0x80000000,
+};
+
+#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
+#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
+
+static QPCIBus *pcibus = NULL;
+static QGuestAllocator guest_malloc;
+
+static char tmp_path[] = "/tmp/qtest.XXXXXX";
+static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX";
+
+static QTestState *ide_test_start(const char *cmdline_fmt, ...)
+{
+ QTestState *qts;
+ va_list ap;
+
+ va_start(ap, cmdline_fmt);
+ qts = qtest_vinitf(cmdline_fmt, ap);
+ va_end(ap);
+
+ pc_alloc_init(&guest_malloc, qts, 0);
+
+ return qts;
+}
+
+static void ide_test_quit(QTestState *qts)
+{
+ if (pcibus) {
+ qpci_free_pc(pcibus);
+ pcibus = NULL;
+ }
+ alloc_destroy(&guest_malloc);
+ qtest_quit(qts);
+}
+
+static QPCIDevice *get_pci_device(QTestState *qts, QPCIBar *bmdma_bar,
+ QPCIBar *ide_bar)
+{
+ QPCIDevice *dev;
+ uint16_t vendor_id, device_id;
+
+ if (!pcibus) {
+ pcibus = qpci_new_pc(qts, NULL);
+ }
+
+ /* Find PCI device and verify it's the right one */
+ dev = qpci_device_find(pcibus, QPCI_DEVFN(IDE_PCI_DEV, IDE_PCI_FUNC));
+ g_assert(dev != NULL);
+
+ vendor_id = qpci_config_readw(dev, PCI_VENDOR_ID);
+ device_id = qpci_config_readw(dev, PCI_DEVICE_ID);
+ g_assert(vendor_id == PCI_VENDOR_ID_INTEL);
+ g_assert(device_id == PCI_DEVICE_ID_INTEL_82371SB_1);
+
+ /* Map bmdma BAR */
+ *bmdma_bar = qpci_iomap(dev, 4, NULL);
+
+ *ide_bar = qpci_legacy_iomap(dev, IDE_BASE);
+
+ qpci_device_enable(dev);
+
+ return dev;
+}
+
+static void free_pci_device(QPCIDevice *dev)
+{
+ /* libqos doesn't have a function for this, so free it manually */
+ g_free(dev);
+}
+
+typedef struct PrdtEntry {
+ uint32_t addr;
+ uint32_t size;
+} QEMU_PACKED PrdtEntry;
+
+#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
+#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
+
+static uint64_t trim_range_le(uint64_t sector, uint16_t count)
+{
+ /* 2-byte range, 6-byte LBA */
+ return cpu_to_le64(((uint64_t)count << 48) + sector);
+}
+
+static int send_dma_request(QTestState *qts, int cmd, uint64_t sector,
+ int nb_sectors, PrdtEntry *prdt, int prdt_entries,
+ void(*post_exec)(QPCIDevice *dev, QPCIBar ide_bar,
+ uint64_t sector, int nb_sectors))
+{
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uintptr_t guest_prdt;
+ size_t len;
+ bool from_dev;
+ uint8_t status;
+ int flags;
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ flags = cmd & ~0xff;
+ cmd &= 0xff;
+
+ switch (cmd) {
+ case CMD_READ_DMA:
+ case CMD_PACKET:
+ /* Assuming we only test data reads w/ ATAPI, otherwise we need to know
+ * the SCSI command being sent in the packet, too. */
+ from_dev = true;
+ break;
+ case CMD_DSM:
+ case CMD_WRITE_DMA:
+ from_dev = false;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (flags & CMDF_NO_BM) {
+ qpci_config_writew(dev, PCI_COMMAND,
+ PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
+ }
+
+ /* Select device 0 */
+ qpci_io_writeb(dev, ide_bar, reg_device, 0 | LBA);
+
+ /* Stop any running transfer, clear any pending interrupt */
+ qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
+ qpci_io_writeb(dev, bmdma_bar, bmreg_status, BM_STS_INTR);
+
+ /* Setup PRDT */
+ len = sizeof(*prdt) * prdt_entries;
+ guest_prdt = guest_alloc(&guest_malloc, len);
+ qtest_memwrite(qts, guest_prdt, prdt, len);
+ qpci_io_writel(dev, bmdma_bar, bmreg_prdt, guest_prdt);
+
+ /* ATA DMA command */
+ if (cmd == CMD_PACKET) {
+ /* Enables ATAPI DMA; otherwise PIO is attempted */
+ qpci_io_writeb(dev, ide_bar, reg_feature, 0x01);
+ } else {
+ if (cmd == CMD_DSM) {
+ /* trim bit */
+ qpci_io_writeb(dev, ide_bar, reg_feature, 0x01);
+ }
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, nb_sectors);
+ qpci_io_writeb(dev, ide_bar, reg_lba_low, sector & 0xff);
+ qpci_io_writeb(dev, ide_bar, reg_lba_middle, (sector >> 8) & 0xff);
+ qpci_io_writeb(dev, ide_bar, reg_lba_high, (sector >> 16) & 0xff);
+ }
+
+ qpci_io_writeb(dev, ide_bar, reg_command, cmd);
+
+ if (post_exec) {
+ post_exec(dev, ide_bar, sector, nb_sectors);
+ }
+
+ /* Start DMA transfer */
+ qpci_io_writeb(dev, bmdma_bar, bmreg_cmd,
+ BM_CMD_START | (from_dev ? BM_CMD_WRITE : 0));
+
+ if (flags & CMDF_ABORT) {
+ qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
+ }
+
+ /* Wait for the DMA transfer to complete */
+ do {
+ status = qpci_io_readb(dev, bmdma_bar, bmreg_status);
+ } while ((status & (BM_STS_ACTIVE | BM_STS_INTR)) == BM_STS_ACTIVE);
+
+ g_assert_cmpint(qtest_get_irq(qts, IDE_PRIMARY_IRQ), ==,
+ !!(status & BM_STS_INTR));
+
+ /* Check IDE status code */
+ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), DRDY);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), BSY | DRQ);
+
+ /* Reading the status register clears the IRQ */
+ g_assert(!qtest_get_irq(qts, IDE_PRIMARY_IRQ));
+
+ /* Stop DMA transfer if still active */
+ if (status & BM_STS_ACTIVE) {
+ qpci_io_writeb(dev, bmdma_bar, bmreg_cmd, 0);
+ }
+
+ free_pci_device(dev);
+
+ return status;
+}
+
+static QTestState *test_bmdma_setup(void)
+{
+ QTestState *qts;
+
+ qts = ide_test_start(
+ "-drive file=%s,if=ide,cache=writeback,format=raw "
+ "-global ide-hd.serial=%s -global ide-hd.ver=%s",
+ tmp_path, "testdisk", "version");
+ qtest_irq_intercept_in(qts, "ioapic");
+
+ return qts;
+}
+
+static void test_bmdma_teardown(QTestState *qts)
+{
+ ide_test_quit(qts);
+}
+
+static void test_bmdma_simple_rw(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+ uint8_t *buf;
+ uint8_t *cmpbuf;
+ size_t len = 512;
+ uintptr_t guest_buf;
+ PrdtEntry prdt[1];
+
+ qts = test_bmdma_setup();
+
+ guest_buf = guest_alloc(&guest_malloc, len);
+ prdt[0].addr = cpu_to_le32(guest_buf);
+ prdt[0].size = cpu_to_le32(len | PRDT_EOT);
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ buf = g_malloc(len);
+ cmpbuf = g_malloc(len);
+
+ /* Write 0x55 pattern to sector 0 */
+ memset(buf, 0x55, len);
+ qtest_memwrite(qts, guest_buf, buf, len);
+
+ status = send_dma_request(qts, CMD_WRITE_DMA, 0, 1, prdt,
+ ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ /* Write 0xaa pattern to sector 1 */
+ memset(buf, 0xaa, len);
+ qtest_memwrite(qts, guest_buf, buf, len);
+
+ status = send_dma_request(qts, CMD_WRITE_DMA, 1, 1, prdt,
+ ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ /* Read and verify 0x55 pattern in sector 0 */
+ memset(cmpbuf, 0x55, len);
+
+ status = send_dma_request(qts, CMD_READ_DMA, 0, 1, prdt, ARRAY_SIZE(prdt),
+ NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ qtest_memread(qts, guest_buf, buf, len);
+ g_assert(memcmp(buf, cmpbuf, len) == 0);
+
+ /* Read and verify 0xaa pattern in sector 1 */
+ memset(cmpbuf, 0xaa, len);
+
+ status = send_dma_request(qts, CMD_READ_DMA, 1, 1, prdt, ARRAY_SIZE(prdt),
+ NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ qtest_memread(qts, guest_buf, buf, len);
+ g_assert(memcmp(buf, cmpbuf, len) == 0);
+
+ free_pci_device(dev);
+ g_free(buf);
+ g_free(cmpbuf);
+
+ test_bmdma_teardown(qts);
+}
+
+static void test_bmdma_trim(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+ const uint64_t trim_range[] = { trim_range_le(0, 2),
+ trim_range_le(6, 8),
+ trim_range_le(10, 1),
+ };
+ const uint64_t bad_range = trim_range_le(TEST_IMAGE_SIZE / 512 - 1, 2);
+ size_t len = 512;
+ uint8_t *buf;
+ uintptr_t guest_buf;
+ PrdtEntry prdt[1];
+
+ qts = test_bmdma_setup();
+
+ guest_buf = guest_alloc(&guest_malloc, len);
+ prdt[0].addr = cpu_to_le32(guest_buf),
+ prdt[0].size = cpu_to_le32(len | PRDT_EOT),
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ buf = g_malloc(len);
+
+ /* Normal request */
+ *((uint64_t *)buf) = trim_range[0];
+ *((uint64_t *)buf + 1) = trim_range[1];
+
+ qtest_memwrite(qts, guest_buf, buf, 2 * sizeof(uint64_t));
+
+ status = send_dma_request(qts, CMD_DSM, 0, 1, prdt,
+ ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ /* Request contains invalid range */
+ *((uint64_t *)buf) = trim_range[2];
+ *((uint64_t *)buf + 1) = bad_range;
+
+ qtest_memwrite(qts, guest_buf, buf, 2 * sizeof(uint64_t));
+
+ status = send_dma_request(qts, CMD_DSM, 0, 1, prdt,
+ ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), ERR);
+ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_error), ABRT);
+
+ free_pci_device(dev);
+ g_free(buf);
+ test_bmdma_teardown(qts);
+}
+
+static void test_bmdma_short_prdt(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+
+ PrdtEntry prdt[] = {
+ {
+ .addr = 0,
+ .size = cpu_to_le32(0x10 | PRDT_EOT),
+ },
+ };
+
+ qts = test_bmdma_setup();
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* Normal request */
+ status = send_dma_request(qts, CMD_READ_DMA, 0, 1,
+ prdt, ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, 0);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ /* Abort the request before it completes */
+ status = send_dma_request(qts, CMD_READ_DMA | CMDF_ABORT, 0, 1,
+ prdt, ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, 0);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+ free_pci_device(dev);
+ test_bmdma_teardown(qts);
+}
+
+static void test_bmdma_one_sector_short_prdt(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+
+ /* Read 2 sectors but only give 1 sector in PRDT */
+ PrdtEntry prdt[] = {
+ {
+ .addr = 0,
+ .size = cpu_to_le32(0x200 | PRDT_EOT),
+ },
+ };
+
+ qts = test_bmdma_setup();
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* Normal request */
+ status = send_dma_request(qts, CMD_READ_DMA, 0, 2,
+ prdt, ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, 0);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ /* Abort the request before it completes */
+ status = send_dma_request(qts, CMD_READ_DMA | CMDF_ABORT, 0, 2,
+ prdt, ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, 0);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+ free_pci_device(dev);
+ test_bmdma_teardown(qts);
+}
+
+static void test_bmdma_long_prdt(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+
+ PrdtEntry prdt[] = {
+ {
+ .addr = 0,
+ .size = cpu_to_le32(0x1000 | PRDT_EOT),
+ },
+ };
+
+ qts = test_bmdma_setup();
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* Normal request */
+ status = send_dma_request(qts, CMD_READ_DMA, 0, 1,
+ prdt, ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_ACTIVE | BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ /* Abort the request before it completes */
+ status = send_dma_request(qts, CMD_READ_DMA | CMDF_ABORT, 0, 1,
+ prdt, ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+ free_pci_device(dev);
+ test_bmdma_teardown(qts);
+}
+
+static void test_bmdma_no_busmaster(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+
+ qts = test_bmdma_setup();
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* No PRDT_EOT, each entry addr 0/size 64k, and in theory qemu shouldn't be
+ * able to access it anyway because the Bus Master bit in the PCI command
+ * register isn't set. This is complete nonsense, but it used to be pretty
+ * good at confusing and occasionally crashing qemu. */
+ PrdtEntry prdt[4096] = { };
+
+ status = send_dma_request(qts, CMD_READ_DMA | CMDF_NO_BM, 0, 512,
+ prdt, ARRAY_SIZE(prdt), NULL);
+
+ /* Not entirely clear what the expected result is, but this is what we get
+ * in practice. At least we want to be aware of any changes. */
+ g_assert_cmphex(status, ==, BM_STS_ACTIVE | BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+ free_pci_device(dev);
+ test_bmdma_teardown(qts);
+}
+
+static void string_cpu_to_be16(uint16_t *s, size_t bytes)
+{
+ g_assert((bytes & 1) == 0);
+ bytes /= 2;
+
+ while (bytes--) {
+ *s = cpu_to_be16(*s);
+ s++;
+ }
+}
+
+static void test_identify(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t data;
+ uint16_t buf[256];
+ int i;
+ int ret;
+
+ qts = ide_test_start(
+ "-drive file=%s,if=ide,cache=writeback,format=raw "
+ "-global ide-hd.serial=%s -global ide-hd.ver=%s",
+ tmp_path, "testdisk", "version");
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* IDENTIFY command on device 0*/
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_IDENTIFY);
+
+ /* Read in the IDENTIFY buffer and check registers */
+ data = qpci_io_readb(dev, ide_bar, reg_device);
+ g_assert_cmpint(data & DEV, ==, 0);
+
+ for (i = 0; i < 256; i++) {
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ assert_bit_set(data, DRDY | DRQ);
+ assert_bit_clear(data, BSY | DF | ERR);
+
+ buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
+ }
+
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ assert_bit_set(data, DRDY);
+ assert_bit_clear(data, BSY | DF | ERR | DRQ);
+
+ /* Check serial number/version in the buffer */
+ string_cpu_to_be16(&buf[10], 20);
+ ret = memcmp(&buf[10], "testdisk ", 20);
+ g_assert(ret == 0);
+
+ string_cpu_to_be16(&buf[23], 8);
+ ret = memcmp(&buf[23], "version ", 8);
+ g_assert(ret == 0);
+
+ /* Write cache enabled bit */
+ assert_bit_set(buf[85], 0x20);
+
+ ide_test_quit(qts);
+ free_pci_device(dev);
+}
+
+/*
+ * Write sector 1 with random data to make IDE storage dirty
+ * Needed for flush tests so that flushes actually go though the block layer
+ */
+static void make_dirty(QTestState *qts, uint8_t device)
+{
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t status;
+ size_t len = 512;
+ uintptr_t guest_buf;
+ void* buf;
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ guest_buf = guest_alloc(&guest_malloc, len);
+ buf = g_malloc(len);
+ memset(buf, rand() % 255 + 1, len);
+ g_assert(guest_buf);
+ g_assert(buf);
+
+ qtest_memwrite(qts, guest_buf, buf, len);
+
+ PrdtEntry prdt[] = {
+ {
+ .addr = cpu_to_le32(guest_buf),
+ .size = cpu_to_le32(len | PRDT_EOT),
+ },
+ };
+
+ status = send_dma_request(qts, CMD_WRITE_DMA, 1, 1, prdt,
+ ARRAY_SIZE(prdt), NULL);
+ g_assert_cmphex(status, ==, BM_STS_INTR);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), DF | ERR);
+
+ g_free(buf);
+ free_pci_device(dev);
+}
+
+static void test_flush(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t data;
+
+ qts = ide_test_start(
+ "-drive file=blkdebug::%s,if=ide,cache=writeback,format=raw",
+ tmp_path);
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ qtest_irq_intercept_in(qts, "ioapic");
+
+ /* Dirty media so that CMD_FLUSH_CACHE will actually go to disk */
+ make_dirty(qts, 0);
+
+ /* Delay the completion of the flush request until we explicitly do it */
+ g_free(qtest_hmp(qts, "qemu-io ide0-hd0 \"break flush_to_os A\""));
+
+ /* FLUSH CACHE command on device 0*/
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE);
+
+ /* Check status while request is in flight*/
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ assert_bit_set(data, BSY | DRDY);
+ assert_bit_clear(data, DF | ERR | DRQ);
+
+ /* Complete the command */
+ g_free(qtest_hmp(qts, "qemu-io ide0-hd0 \"resume A\""));
+
+ /* Check registers */
+ data = qpci_io_readb(dev, ide_bar, reg_device);
+ g_assert_cmpint(data & DEV, ==, 0);
+
+ do {
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ } while (data & BSY);
+
+ assert_bit_set(data, DRDY);
+ assert_bit_clear(data, BSY | DF | ERR | DRQ);
+
+ ide_test_quit(qts);
+ free_pci_device(dev);
+}
+
+static void test_retry_flush(const char *machine)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t data;
+
+ prepare_blkdebug_script(debug_path, "flush_to_disk");
+
+ qts = ide_test_start(
+ "-drive file=blkdebug:%s:%s,if=ide,cache=writeback,format=raw,"
+ "rerror=stop,werror=stop",
+ debug_path, tmp_path);
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ qtest_irq_intercept_in(qts, "ioapic");
+
+ /* Dirty media so that CMD_FLUSH_CACHE will actually go to disk */
+ make_dirty(qts, 0);
+
+ /* FLUSH CACHE command on device 0*/
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE);
+
+ /* Check status while request is in flight*/
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ assert_bit_set(data, BSY | DRDY);
+ assert_bit_clear(data, DF | ERR | DRQ);
+
+ qtest_qmp_eventwait(qts, "STOP");
+
+ /* Complete the command */
+ qmp_discard_response(qts, "{'execute':'cont' }");
+
+ /* Check registers */
+ data = qpci_io_readb(dev, ide_bar, reg_device);
+ g_assert_cmpint(data & DEV, ==, 0);
+
+ do {
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ } while (data & BSY);
+
+ assert_bit_set(data, DRDY);
+ assert_bit_clear(data, BSY | DF | ERR | DRQ);
+
+ ide_test_quit(qts);
+ free_pci_device(dev);
+}
+
+static void test_flush_nodev(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+
+ qts = ide_test_start("");
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* FLUSH CACHE command on device 0*/
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE);
+
+ /* Just testing that qemu doesn't crash... */
+
+ free_pci_device(dev);
+ ide_test_quit(qts);
+}
+
+static void test_flush_empty_drive(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+
+ qts = ide_test_start("-device ide-cd,bus=ide.0");
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* FLUSH CACHE command on device 0 */
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE);
+
+ /* Just testing that qemu doesn't crash... */
+
+ free_pci_device(dev);
+ ide_test_quit(qts);
+}
+
+static void test_pci_retry_flush(void)
+{
+ test_retry_flush("pc");
+}
+
+static void test_isa_retry_flush(void)
+{
+ test_retry_flush("isapc");
+}
+
+typedef struct Read10CDB {
+ uint8_t opcode;
+ uint8_t flags;
+ uint32_t lba;
+ uint8_t reserved;
+ uint16_t nblocks;
+ uint8_t control;
+ uint16_t padding;
+} __attribute__((__packed__)) Read10CDB;
+
+static void send_scsi_cdb_read10(QPCIDevice *dev, QPCIBar ide_bar,
+ uint64_t lba, int nblocks)
+{
+ Read10CDB pkt = { .padding = 0 };
+ int i;
+
+ g_assert_cmpint(lba, <=, UINT32_MAX);
+ g_assert_cmpint(nblocks, <=, UINT16_MAX);
+ g_assert_cmpint(nblocks, >=, 0);
+
+ /* Construct SCSI CDB packet */
+ pkt.opcode = 0x28;
+ pkt.lba = cpu_to_be32(lba);
+ pkt.nblocks = cpu_to_be16(nblocks);
+
+ /* Send Packet */
+ for (i = 0; i < sizeof(Read10CDB)/2; i++) {
+ qpci_io_writew(dev, ide_bar, reg_data,
+ le16_to_cpu(((uint16_t *)&pkt)[i]));
+ }
+}
+
+static void nsleep(QTestState *qts, int64_t nsecs)
+{
+ const struct timespec val = { .tv_nsec = nsecs };
+ nanosleep(&val, NULL);
+ qtest_clock_set(qts, nsecs);
+}
+
+static uint8_t ide_wait_clear(QTestState *qts, uint8_t flag)
+{
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint8_t data;
+ time_t st;
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* Wait with a 5 second timeout */
+ time(&st);
+ while (true) {
+ data = qpci_io_readb(dev, ide_bar, reg_status);
+ if (!(data & flag)) {
+ free_pci_device(dev);
+ return data;
+ }
+ if (difftime(time(NULL), st) > 5.0) {
+ break;
+ }
+ nsleep(qts, 400);
+ }
+ g_assert_not_reached();
+}
+
+static void ide_wait_intr(QTestState *qts, int irq)
+{
+ time_t st;
+ bool intr;
+
+ time(&st);
+ while (true) {
+ intr = qtest_get_irq(qts, irq);
+ if (intr) {
+ return;
+ }
+ if (difftime(time(NULL), st) > 5.0) {
+ break;
+ }
+ nsleep(qts, 400);
+ }
+
+ g_assert_not_reached();
+}
+
+static void cdrom_pio_impl(int nblocks)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ FILE *fh;
+ int patt_blocks = MAX(16, nblocks);
+ size_t patt_len = ATAPI_BLOCK_SIZE * patt_blocks;
+ char *pattern = g_malloc(patt_len);
+ size_t rxsize = ATAPI_BLOCK_SIZE * nblocks;
+ uint16_t *rx = g_malloc0(rxsize);
+ int i, j;
+ uint8_t data;
+ uint16_t limit;
+ size_t ret;
+
+ /* Prepopulate the CDROM with an interesting pattern */
+ generate_pattern(pattern, patt_len, ATAPI_BLOCK_SIZE);
+ fh = fopen(tmp_path, "w+");
+ ret = fwrite(pattern, ATAPI_BLOCK_SIZE, patt_blocks, fh);
+ g_assert_cmpint(ret, ==, patt_blocks);
+ fclose(fh);
+
+ qts = ide_test_start(
+ "-drive if=none,file=%s,media=cdrom,format=raw,id=sr0,index=0 "
+ "-device ide-cd,drive=sr0,bus=ide.0", tmp_path);
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+ qtest_irq_intercept_in(qts, "ioapic");
+
+ /* PACKET command on device 0 */
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_lba_middle, BYTE_COUNT_LIMIT & 0xFF);
+ qpci_io_writeb(dev, ide_bar, reg_lba_high, (BYTE_COUNT_LIMIT >> 8 & 0xFF));
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_PACKET);
+ /* HP0: Check_Status_A State */
+ nsleep(qts, 400);
+ data = ide_wait_clear(qts, BSY);
+ /* HP1: Send_Packet State */
+ assert_bit_set(data, DRQ | DRDY);
+ assert_bit_clear(data, ERR | DF | BSY);
+
+ /* SCSI CDB (READ10) -- read n*2048 bytes from block 0 */
+ send_scsi_cdb_read10(dev, ide_bar, 0, nblocks);
+
+ /* Read data back: occurs in bursts of 'BYTE_COUNT_LIMIT' bytes.
+ * If BYTE_COUNT_LIMIT is odd, we transfer BYTE_COUNT_LIMIT - 1 bytes.
+ * We allow an odd limit only when the remaining transfer size is
+ * less than BYTE_COUNT_LIMIT. However, SCSI's read10 command can only
+ * request n blocks, so our request size is always even.
+ * For this reason, we assume there is never a hanging byte to fetch. */
+ g_assert(!(rxsize & 1));
+ limit = BYTE_COUNT_LIMIT & ~1;
+ for (i = 0; i < DIV_ROUND_UP(rxsize, limit); i++) {
+ size_t offset = i * (limit / 2);
+ size_t rem = (rxsize / 2) - offset;
+
+ /* HP3: INTRQ_Wait */
+ ide_wait_intr(qts, IDE_PRIMARY_IRQ);
+
+ /* HP2: Check_Status_B (and clear IRQ) */
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_set(data, DRQ | DRDY);
+ assert_bit_clear(data, ERR | DF | BSY);
+
+ /* HP4: Transfer_Data */
+ for (j = 0; j < MIN((limit / 2), rem); j++) {
+ rx[offset + j] = cpu_to_le16(qpci_io_readw(dev, ide_bar,
+ reg_data));
+ }
+ }
+
+ /* Check for final completion IRQ */
+ ide_wait_intr(qts, IDE_PRIMARY_IRQ);
+
+ /* Sanity check final state */
+ data = ide_wait_clear(qts, DRQ);
+ assert_bit_set(data, DRDY);
+ assert_bit_clear(data, DRQ | ERR | DF | BSY);
+
+ g_assert_cmpint(memcmp(pattern, rx, rxsize), ==, 0);
+ g_free(pattern);
+ g_free(rx);
+ test_bmdma_teardown(qts);
+ free_pci_device(dev);
+}
+
+static void test_cdrom_pio(void)
+{
+ cdrom_pio_impl(1);
+}
+
+static void test_cdrom_pio_large(void)
+{
+ /* Test a few loops of the PIO DRQ mechanism. */
+ cdrom_pio_impl(BYTE_COUNT_LIMIT * 4 / ATAPI_BLOCK_SIZE);
+}
+
+
+static void test_cdrom_dma(void)
+{
+ QTestState *qts;
+ static const size_t len = ATAPI_BLOCK_SIZE;
+ size_t ret;
+ char *pattern = g_malloc(ATAPI_BLOCK_SIZE * 16);
+ char *rx = g_malloc0(len);
+ uintptr_t guest_buf;
+ PrdtEntry prdt[1];
+ FILE *fh;
+
+ qts = ide_test_start(
+ "-drive if=none,file=%s,media=cdrom,format=raw,id=sr0,index=0 "
+ "-device ide-cd,drive=sr0,bus=ide.0", tmp_path);
+ qtest_irq_intercept_in(qts, "ioapic");
+
+ guest_buf = guest_alloc(&guest_malloc, len);
+ prdt[0].addr = cpu_to_le32(guest_buf);
+ prdt[0].size = cpu_to_le32(len | PRDT_EOT);
+
+ generate_pattern(pattern, ATAPI_BLOCK_SIZE * 16, ATAPI_BLOCK_SIZE);
+ fh = fopen(tmp_path, "w+");
+ ret = fwrite(pattern, ATAPI_BLOCK_SIZE, 16, fh);
+ g_assert_cmpint(ret, ==, 16);
+ fclose(fh);
+
+ send_dma_request(qts, CMD_PACKET, 0, 1, prdt, 1, send_scsi_cdb_read10);
+
+ /* Read back data from guest memory into local qtest memory */
+ qtest_memread(qts, guest_buf, rx, len);
+ g_assert_cmpint(memcmp(pattern, rx, len), ==, 0);
+
+ g_free(pattern);
+ g_free(rx);
+ test_bmdma_teardown(qts);
+}
+
+int main(int argc, char **argv)
+{
+ int fd;
+ int ret;
+
+ /* Create temporary blkdebug instructions */
+ fd = mkstemp(debug_path);
+ g_assert(fd >= 0);
+ close(fd);
+
+ /* Create a temporary raw image */
+ fd = mkstemp(tmp_path);
+ g_assert(fd >= 0);
+ ret = ftruncate(fd, TEST_IMAGE_SIZE);
+ g_assert(ret == 0);
+ close(fd);
+
+ /* Run the tests */
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/ide/identify", test_identify);
+
+ qtest_add_func("/ide/bmdma/simple_rw", test_bmdma_simple_rw);
+ qtest_add_func("/ide/bmdma/trim", test_bmdma_trim);
+ qtest_add_func("/ide/bmdma/short_prdt", test_bmdma_short_prdt);
+ qtest_add_func("/ide/bmdma/one_sector_short_prdt",
+ test_bmdma_one_sector_short_prdt);
+ qtest_add_func("/ide/bmdma/long_prdt", test_bmdma_long_prdt);
+ qtest_add_func("/ide/bmdma/no_busmaster", test_bmdma_no_busmaster);
+
+ qtest_add_func("/ide/flush", test_flush);
+ qtest_add_func("/ide/flush/nodev", test_flush_nodev);
+ qtest_add_func("/ide/flush/empty_drive", test_flush_empty_drive);
+ qtest_add_func("/ide/flush/retry_pci", test_pci_retry_flush);
+ qtest_add_func("/ide/flush/retry_isa", test_isa_retry_flush);
+
+ qtest_add_func("/ide/cdrom/pio", test_cdrom_pio);
+ qtest_add_func("/ide/cdrom/pio_large", test_cdrom_pio_large);
+ qtest_add_func("/ide/cdrom/dma", test_cdrom_dma);
+
+ ret = g_test_run();
+
+ /* Cleanup */
+ unlink(tmp_path);
+ unlink(debug_path);
+
+ return ret;
+}
diff --git a/tests/qtest/intel-hda-test.c b/tests/qtest/intel-hda-test.c
new file mode 100644
index 0000000000..fc25ccc33c
--- /dev/null
+++ b/tests/qtest/intel-hda-test.c
@@ -0,0 +1,39 @@
+/*
+ * QTest testcase for Intel HDA
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+#define HDA_ID "hda0"
+#define CODEC_DEVICES " -device hda-output,bus=" HDA_ID ".0" \
+ " -device hda-micro,bus=" HDA_ID ".0" \
+ " -device hda-duplex,bus=" HDA_ID ".0"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void ich6_test(void)
+{
+ qtest_start("-device intel-hda,id=" HDA_ID CODEC_DEVICES);
+ qtest_end();
+}
+
+static void ich9_test(void)
+{
+ qtest_start("-machine q35 -device ich9-intel-hda,bus=pcie.0,addr=1b.0,id="
+ HDA_ID CODEC_DEVICES);
+ qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/intel-hda/ich6", ich6_test);
+ qtest_add_func("/intel-hda/ich9", ich9_test);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/ioh3420-test.c b/tests/qtest/ioh3420-test.c
new file mode 100644
index 0000000000..f6ca43cca7
--- /dev/null
+++ b/tests/qtest/ioh3420-test.c
@@ -0,0 +1,32 @@
+/*
+ * QTest testcase for Intel X58 north bridge IOH
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void nop(void)
+{
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/ioh3420/nop", nop);
+
+ qtest_start("-machine q35 -device ioh3420,bus=pcie.0,addr=1c.0,port=1,"
+ "chassis=1,multifunction=on");
+ ret = g_test_run();
+
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/ipmi-bt-test.c b/tests/qtest/ipmi-bt-test.c
new file mode 100644
index 0000000000..a42207d416
--- /dev/null
+++ b/tests/qtest/ipmi-bt-test.c
@@ -0,0 +1,425 @@
+/*
+ * IPMI BT test cases, using the external interface for checking
+ *
+ * Copyright (c) 2012 Corey Minyard <cminyard@mvista.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+
+#include "libqtest-single.h"
+#include "qemu-common.h"
+
+#define IPMI_IRQ 5
+
+#define IPMI_BT_BASE 0xe4
+
+#define IPMI_BT_CTLREG_CLR_WR_PTR 0
+#define IPMI_BT_CTLREG_CLR_RD_PTR 1
+#define IPMI_BT_CTLREG_H2B_ATN 2
+#define IPMI_BT_CTLREG_B2H_ATN 3
+#define IPMI_BT_CTLREG_SMS_ATN 4
+#define IPMI_BT_CTLREG_H_BUSY 6
+#define IPMI_BT_CTLREG_B_BUSY 7
+
+#define IPMI_BT_CTLREG_GET(b) ((bt_get_ctrlreg() >> (b)) & 1)
+#define IPMI_BT_CTLREG_GET_H2B_ATN() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_H2B_ATN)
+#define IPMI_BT_CTLREG_GET_B2H_ATN() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_B2H_ATN)
+#define IPMI_BT_CTLREG_GET_SMS_ATN() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_SMS_ATN)
+#define IPMI_BT_CTLREG_GET_H_BUSY() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_H_BUSY)
+#define IPMI_BT_CTLREG_GET_B_BUSY() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_B_BUSY)
+
+#define IPMI_BT_CTLREG_SET(b) bt_write_ctrlreg(1 << (b))
+#define IPMI_BT_CTLREG_SET_CLR_WR_PTR() IPMI_BT_CTLREG_SET( \
+ IPMI_BT_CTLREG_CLR_WR_PTR)
+#define IPMI_BT_CTLREG_SET_CLR_RD_PTR() IPMI_BT_CTLREG_SET( \
+ IPMI_BT_CTLREG_CLR_RD_PTR)
+#define IPMI_BT_CTLREG_SET_H2B_ATN() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_H2B_ATN)
+#define IPMI_BT_CTLREG_SET_B2H_ATN() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_B2H_ATN)
+#define IPMI_BT_CTLREG_SET_SMS_ATN() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_SMS_ATN)
+#define IPMI_BT_CTLREG_SET_H_BUSY() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_H_BUSY)
+
+static int bt_ints_enabled;
+
+static uint8_t bt_get_ctrlreg(void)
+{
+ return inb(IPMI_BT_BASE);
+}
+
+static void bt_write_ctrlreg(uint8_t val)
+{
+ outb(IPMI_BT_BASE, val);
+}
+
+static uint8_t bt_get_buf(void)
+{
+ return inb(IPMI_BT_BASE + 1);
+}
+
+static void bt_write_buf(uint8_t val)
+{
+ outb(IPMI_BT_BASE + 1, val);
+}
+
+static uint8_t bt_get_irqreg(void)
+{
+ return inb(IPMI_BT_BASE + 2);
+}
+
+static void bt_write_irqreg(uint8_t val)
+{
+ outb(IPMI_BT_BASE + 2, val);
+}
+
+static void bt_wait_b_busy(void)
+{
+ unsigned int count = 1000;
+ while (IPMI_BT_CTLREG_GET_B_BUSY() != 0) {
+ g_assert(--count != 0);
+ usleep(100);
+ }
+}
+
+static void bt_wait_b2h_atn(void)
+{
+ unsigned int count = 1000;
+ while (IPMI_BT_CTLREG_GET_B2H_ATN() == 0) {
+ g_assert(--count != 0);
+ usleep(100);
+ }
+}
+
+
+static int emu_lfd;
+static int emu_fd;
+static in_port_t emu_port;
+static uint8_t inbuf[100];
+static unsigned int inbuf_len;
+static unsigned int inbuf_pos;
+static int last_was_aa;
+
+static void read_emu_data(void)
+{
+ fd_set readfds;
+ int rv;
+ struct timeval tv;
+
+ FD_ZERO(&readfds);
+ FD_SET(emu_fd, &readfds);
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+ rv = select(emu_fd + 1, &readfds, NULL, NULL, &tv);
+ if (rv == -1) {
+ perror("select");
+ }
+ g_assert(rv == 1);
+ rv = read(emu_fd, inbuf, sizeof(inbuf));
+ if (rv == -1) {
+ perror("read");
+ }
+ g_assert(rv > 0);
+ inbuf_len = rv;
+ inbuf_pos = 0;
+}
+
+static void write_emu_msg(uint8_t *msg, unsigned int len)
+{
+ int rv;
+
+#ifdef DEBUG_TEST
+ {
+ unsigned int i;
+ printf("sending:");
+ for (i = 0; i < len; i++) {
+ printf(" %2.2x", msg[i]);
+ }
+ printf("\n");
+ }
+#endif
+ rv = write(emu_fd, msg, len);
+ g_assert(rv == len);
+}
+
+static void get_emu_msg(uint8_t *msg, unsigned int *len)
+{
+ unsigned int outpos = 0;
+
+ for (;;) {
+ while (inbuf_pos < inbuf_len) {
+ uint8_t ch = inbuf[inbuf_pos++];
+
+ g_assert(outpos < *len);
+ if (last_was_aa) {
+ assert(ch & 0x10);
+ msg[outpos++] = ch & ~0x10;
+ last_was_aa = 0;
+ } else if (ch == 0xaa) {
+ last_was_aa = 1;
+ } else {
+ msg[outpos++] = ch;
+ if ((ch == 0xa0) || (ch == 0xa1)) {
+ /* Message complete */
+ *len = outpos;
+ goto done;
+ }
+ }
+ }
+ read_emu_data();
+ }
+ done:
+#ifdef DEBUG_TEST
+ {
+ unsigned int i;
+ printf("Msg:");
+ for (i = 0; i < outpos; i++) {
+ printf(" %2.2x", msg[i]);
+ }
+ printf("\n");
+ }
+#endif
+ return;
+}
+
+static uint8_t
+ipmb_checksum(const unsigned char *data, int size, unsigned char start)
+{
+ unsigned char csum = start;
+
+ for (; size > 0; size--, data++) {
+ csum += *data;
+ }
+ return csum;
+}
+
+static uint8_t get_dev_id_cmd[] = { 0x18, 0x01 };
+static uint8_t get_dev_id_rsp[] = { 0x1c, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+static uint8_t set_bmc_globals_cmd[] = { 0x18, 0x2e, 0x0f };
+static uint8_t set_bmc_globals_rsp[] = { 0x1c, 0x2e, 0x00 };
+static uint8_t enable_irq_cmd[] = { 0x05, 0xa1 };
+
+static void emu_msg_handler(void)
+{
+ uint8_t msg[100];
+ unsigned int msg_len = sizeof(msg);
+
+ get_emu_msg(msg, &msg_len);
+ g_assert(msg_len >= 5);
+ g_assert(msg[msg_len - 1] == 0xa0);
+ msg_len--;
+ g_assert(ipmb_checksum(msg, msg_len, 0) == 0);
+ msg_len--;
+ if ((msg[1] == get_dev_id_cmd[0]) && (msg[2] == get_dev_id_cmd[1])) {
+ memcpy(msg + 1, get_dev_id_rsp, sizeof(get_dev_id_rsp));
+ msg_len = sizeof(get_dev_id_rsp) + 1;
+ msg[msg_len] = -ipmb_checksum(msg, msg_len, 0);
+ msg_len++;
+ msg[msg_len++] = 0xa0;
+ write_emu_msg(msg, msg_len);
+ } else if ((msg[1] == set_bmc_globals_cmd[0]) &&
+ (msg[2] == set_bmc_globals_cmd[1])) {
+ write_emu_msg(enable_irq_cmd, sizeof(enable_irq_cmd));
+ memcpy(msg + 1, set_bmc_globals_rsp, sizeof(set_bmc_globals_rsp));
+ msg_len = sizeof(set_bmc_globals_rsp) + 1;
+ msg[msg_len] = -ipmb_checksum(msg, msg_len, 0);
+ msg_len++;
+ msg[msg_len++] = 0xa0;
+ write_emu_msg(msg, msg_len);
+ } else {
+ g_assert(0);
+ }
+}
+
+static void bt_cmd(uint8_t *cmd, unsigned int cmd_len,
+ uint8_t *rsp, unsigned int *rsp_len)
+{
+ unsigned int i, len, j = 0;
+ uint8_t seq = 5;
+
+ /* Should be idle */
+ g_assert(bt_get_ctrlreg() == 0);
+
+ bt_wait_b_busy();
+ IPMI_BT_CTLREG_SET_CLR_WR_PTR();
+ bt_write_buf(cmd_len + 1);
+ bt_write_buf(cmd[0]);
+ bt_write_buf(seq);
+ for (i = 1; i < cmd_len; i++) {
+ bt_write_buf(cmd[i]);
+ }
+ IPMI_BT_CTLREG_SET_H2B_ATN();
+
+ emu_msg_handler(); /* We should get a message on the socket here. */
+
+ bt_wait_b2h_atn();
+ if (bt_ints_enabled) {
+ g_assert((bt_get_irqreg() & 0x02) == 0x02);
+ g_assert(get_irq(IPMI_IRQ));
+ bt_write_irqreg(0x03);
+ } else {
+ g_assert(!get_irq(IPMI_IRQ));
+ }
+ IPMI_BT_CTLREG_SET_H_BUSY();
+ IPMI_BT_CTLREG_SET_B2H_ATN();
+ IPMI_BT_CTLREG_SET_CLR_RD_PTR();
+ len = bt_get_buf();
+ g_assert(len >= 4);
+ rsp[0] = bt_get_buf();
+ assert(bt_get_buf() == seq);
+ len--;
+ for (j = 1; j < len; j++) {
+ rsp[j] = bt_get_buf();
+ }
+ IPMI_BT_CTLREG_SET_H_BUSY();
+ *rsp_len = j;
+}
+
+
+/*
+ * We should get a connect request and a short message with capabilities.
+ */
+static void test_connect(void)
+{
+ fd_set readfds;
+ int rv;
+ int val;
+ struct timeval tv;
+ uint8_t msg[100];
+ unsigned int msglen;
+ static uint8_t exp1[] = { 0xff, 0x01, 0xa1 }; /* A protocol version */
+ static uint8_t exp2[] = { 0x08, 0x3f, 0xa1 }; /* A capabilities cmd */
+
+ FD_ZERO(&readfds);
+ FD_SET(emu_lfd, &readfds);
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+ rv = select(emu_lfd + 1, &readfds, NULL, NULL, &tv);
+ g_assert(rv == 1);
+ emu_fd = accept(emu_lfd, NULL, 0);
+ if (emu_fd < 0) {
+ perror("accept");
+ }
+ g_assert(emu_fd >= 0);
+
+ val = 1;
+ rv = setsockopt(emu_fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+ g_assert(rv != -1);
+
+ /* Report our version */
+ write_emu_msg(exp1, sizeof(exp1));
+
+ /* Validate that we get the info we expect. */
+ msglen = sizeof(msg);
+ get_emu_msg(msg, &msglen);
+ g_assert(msglen == sizeof(exp1));
+ g_assert(memcmp(msg, exp1, msglen) == 0);
+ msglen = sizeof(msg);
+ get_emu_msg(msg, &msglen);
+ g_assert(msglen == sizeof(exp2));
+ g_assert(memcmp(msg, exp2, msglen) == 0);
+}
+
+/*
+ * Send a get_device_id to do a basic test.
+ */
+static void test_bt_base(void)
+{
+ uint8_t rsp[20];
+ unsigned int rsplen = sizeof(rsp);
+
+ bt_cmd(get_dev_id_cmd, sizeof(get_dev_id_cmd), rsp, &rsplen);
+ g_assert(rsplen == sizeof(get_dev_id_rsp));
+ g_assert(memcmp(get_dev_id_rsp, rsp, rsplen) == 0);
+}
+
+/*
+ * Enable IRQs for the interface.
+ */
+static void test_enable_irq(void)
+{
+ uint8_t rsp[20];
+ unsigned int rsplen = sizeof(rsp);
+
+ bt_cmd(set_bmc_globals_cmd, sizeof(set_bmc_globals_cmd), rsp, &rsplen);
+ g_assert(rsplen == sizeof(set_bmc_globals_rsp));
+ g_assert(memcmp(set_bmc_globals_rsp, rsp, rsplen) == 0);
+ bt_write_irqreg(0x01);
+ bt_ints_enabled = 1;
+}
+
+/*
+ * Create a local TCP socket with any port, then save off the port we got.
+ */
+static void open_socket(void)
+{
+ struct sockaddr_in myaddr;
+ socklen_t addrlen;
+
+ myaddr.sin_family = AF_INET;
+ myaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ myaddr.sin_port = 0;
+ emu_lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (emu_lfd == -1) {
+ perror("socket");
+ exit(1);
+ }
+ if (bind(emu_lfd, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) {
+ perror("bind");
+ exit(1);
+ }
+ addrlen = sizeof(myaddr);
+ if (getsockname(emu_lfd, (struct sockaddr *) &myaddr , &addrlen) == -1) {
+ perror("getsockname");
+ exit(1);
+ }
+ emu_port = ntohs(myaddr.sin_port);
+ assert(listen(emu_lfd, 1) != -1);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ open_socket();
+
+ /* Run the tests */
+ g_test_init(&argc, &argv, NULL);
+
+ global_qtest = qtest_initf(
+ " -chardev socket,id=ipmi0,host=localhost,port=%d,reconnect=10"
+ " -device ipmi-bmc-extern,chardev=ipmi0,id=bmc0"
+ " -device isa-ipmi-bt,bmc=bmc0", emu_port);
+ qtest_irq_intercept_in(global_qtest, "ioapic");
+ qtest_add_func("/ipmi/extern/connect", test_connect);
+ qtest_add_func("/ipmi/extern/bt_base", test_bt_base);
+ qtest_add_func("/ipmi/extern/bt_enable_irq", test_enable_irq);
+ qtest_add_func("/ipmi/extern/bt_base_irq", test_bt_base);
+ ret = g_test_run();
+ qtest_quit(global_qtest);
+
+ return ret;
+}
diff --git a/tests/qtest/ipmi-kcs-test.c b/tests/qtest/ipmi-kcs-test.c
new file mode 100644
index 0000000000..693a6aacb5
--- /dev/null
+++ b/tests/qtest/ipmi-kcs-test.c
@@ -0,0 +1,285 @@
+/*
+ * IPMI KCS test cases, using the local interface.
+ *
+ * Copyright (c) 2012 Corey Minyard <cminyard@mvista.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest-single.h"
+
+#define IPMI_IRQ 5
+
+#define IPMI_KCS_BASE 0xca2
+
+#define IPMI_KCS_STATUS_ABORT 0x60
+#define IPMI_KCS_CMD_WRITE_START 0x61
+#define IPMI_KCS_CMD_WRITE_END 0x62
+#define IPMI_KCS_CMD_READ 0x68
+
+#define IPMI_KCS_ABORTED_BY_CMD 0x01
+
+#define IPMI_KCS_CMDREG_GET_STATE() ((kcs_get_cmdreg() >> 6) & 3)
+#define IPMI_KCS_STATE_IDLE 0
+#define IPMI_KCS_STATE_READ 1
+#define IPMI_KCS_STATE_WRITE 2
+#define IPMI_KCS_STATE_ERROR 3
+#define IPMI_KCS_CMDREG_GET_CD() ((kcs_get_cmdreg() >> 3) & 1)
+#define IPMI_KCS_CMDREG_GET_ATN() ((kcs_get_cmdreg() >> 2) & 1)
+#define IPMI_KCS_CMDREG_GET_IBF() ((kcs_get_cmdreg() >> 1) & 1)
+#define IPMI_KCS_CMDREG_GET_OBF() ((kcs_get_cmdreg() >> 0) & 1)
+
+static int kcs_ints_enabled;
+
+static uint8_t kcs_get_cmdreg(void)
+{
+ return inb(IPMI_KCS_BASE + 1);
+}
+
+static void kcs_write_cmdreg(uint8_t val)
+{
+ outb(IPMI_KCS_BASE + 1, val);
+}
+
+static uint8_t kcs_get_datareg(void)
+{
+ return inb(IPMI_KCS_BASE);
+}
+
+static void kcs_write_datareg(uint8_t val)
+{
+ outb(IPMI_KCS_BASE, val);
+}
+
+static void kcs_wait_ibf(void)
+{
+ unsigned int count = 1000;
+ while (IPMI_KCS_CMDREG_GET_IBF() != 0) {
+ g_assert(--count != 0);
+ }
+}
+
+static void kcs_wait_obf(void)
+{
+ unsigned int count = 1000;
+ while (IPMI_KCS_CMDREG_GET_OBF() == 0) {
+ g_assert(--count != 0);
+ }
+}
+
+static void kcs_clear_obf(void)
+{
+ if (kcs_ints_enabled) {
+ g_assert(get_irq(IPMI_IRQ));
+ } else {
+ g_assert(!get_irq(IPMI_IRQ));
+ }
+ g_assert(IPMI_KCS_CMDREG_GET_OBF() == 1);
+ kcs_get_datareg();
+ g_assert(IPMI_KCS_CMDREG_GET_OBF() == 0);
+ g_assert(!get_irq(IPMI_IRQ));
+}
+
+static void kcs_check_state(uint8_t state)
+{
+ g_assert(IPMI_KCS_CMDREG_GET_STATE() == state);
+}
+
+static void kcs_cmd(uint8_t *cmd, unsigned int cmd_len,
+ uint8_t *rsp, unsigned int *rsp_len)
+{
+ unsigned int i, j = 0;
+
+ /* Should be idle */
+ g_assert(kcs_get_cmdreg() == 0);
+
+ kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_START);
+ kcs_wait_ibf();
+ kcs_check_state(IPMI_KCS_STATE_WRITE);
+ kcs_clear_obf();
+ for (i = 0; i < cmd_len; i++) {
+ kcs_write_datareg(cmd[i]);
+ kcs_wait_ibf();
+ kcs_check_state(IPMI_KCS_STATE_WRITE);
+ kcs_clear_obf();
+ }
+ kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_END);
+ kcs_wait_ibf();
+ kcs_check_state(IPMI_KCS_STATE_WRITE);
+ kcs_clear_obf();
+ kcs_write_datareg(0);
+ next_read_byte:
+ kcs_wait_ibf();
+ switch (IPMI_KCS_CMDREG_GET_STATE()) {
+ case IPMI_KCS_STATE_READ:
+ kcs_wait_obf();
+ g_assert(j < *rsp_len);
+ rsp[j++] = kcs_get_datareg();
+ kcs_write_datareg(IPMI_KCS_CMD_READ);
+ goto next_read_byte;
+ break;
+
+ case IPMI_KCS_STATE_IDLE:
+ kcs_wait_obf();
+ kcs_get_datareg();
+ break;
+
+ default:
+ g_assert(0);
+ }
+ *rsp_len = j;
+}
+
+static void kcs_abort(uint8_t *cmd, unsigned int cmd_len,
+ uint8_t *rsp, unsigned int *rsp_len)
+{
+ unsigned int i, j = 0;
+ unsigned int retries = 4;
+
+ /* Should be idle */
+ g_assert(kcs_get_cmdreg() == 0);
+
+ kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_START);
+ kcs_wait_ibf();
+ kcs_check_state(IPMI_KCS_STATE_WRITE);
+ kcs_clear_obf();
+ for (i = 0; i < cmd_len; i++) {
+ kcs_write_datareg(cmd[i]);
+ kcs_wait_ibf();
+ kcs_check_state(IPMI_KCS_STATE_WRITE);
+ kcs_clear_obf();
+ }
+ kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_END);
+ kcs_wait_ibf();
+ kcs_check_state(IPMI_KCS_STATE_WRITE);
+ kcs_clear_obf();
+ kcs_write_datareg(0);
+ kcs_wait_ibf();
+ switch (IPMI_KCS_CMDREG_GET_STATE()) {
+ case IPMI_KCS_STATE_READ:
+ kcs_wait_obf();
+ g_assert(j < *rsp_len);
+ rsp[j++] = kcs_get_datareg();
+ kcs_write_datareg(IPMI_KCS_CMD_READ);
+ break;
+
+ default:
+ g_assert(0);
+ }
+
+ /* Start the abort here */
+ retry_abort:
+ g_assert(retries > 0);
+
+ kcs_wait_ibf();
+ kcs_write_cmdreg(IPMI_KCS_STATUS_ABORT);
+ kcs_wait_ibf();
+ kcs_clear_obf();
+ kcs_write_datareg(0);
+ kcs_wait_ibf();
+ if (IPMI_KCS_CMDREG_GET_STATE() != IPMI_KCS_STATE_READ) {
+ retries--;
+ goto retry_abort;
+ }
+ kcs_wait_obf();
+ rsp[0] = kcs_get_datareg();
+ kcs_write_datareg(IPMI_KCS_CMD_READ);
+ kcs_wait_ibf();
+ if (IPMI_KCS_CMDREG_GET_STATE() != IPMI_KCS_STATE_IDLE) {
+ retries--;
+ goto retry_abort;
+ }
+ kcs_wait_obf();
+ kcs_clear_obf();
+
+ *rsp_len = j;
+}
+
+
+static uint8_t get_dev_id_cmd[] = { 0x18, 0x01 };
+static uint8_t get_dev_id_rsp[] = { 0x1c, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/*
+ * Send a get_device_id to do a basic test.
+ */
+static void test_kcs_base(void)
+{
+ uint8_t rsp[20];
+ unsigned int rsplen = sizeof(rsp);
+
+ kcs_cmd(get_dev_id_cmd, sizeof(get_dev_id_cmd), rsp, &rsplen);
+ g_assert(rsplen == sizeof(get_dev_id_rsp));
+ g_assert(memcmp(get_dev_id_rsp, rsp, rsplen) == 0);
+}
+
+/*
+ * Abort a kcs operation while reading
+ */
+static void test_kcs_abort(void)
+{
+ uint8_t rsp[20];
+ unsigned int rsplen = sizeof(rsp);
+
+ kcs_abort(get_dev_id_cmd, sizeof(get_dev_id_cmd), rsp, &rsplen);
+ g_assert(rsp[0] == IPMI_KCS_ABORTED_BY_CMD);
+}
+
+static uint8_t set_bmc_globals_cmd[] = { 0x18, 0x2e, 0x0f };
+static uint8_t set_bmc_globals_rsp[] = { 0x1c, 0x2e, 0x00 };
+
+/*
+ * Enable interrupts
+ */
+static void test_enable_irq(void)
+{
+ uint8_t rsp[20];
+ unsigned int rsplen = sizeof(rsp);
+
+ kcs_cmd(set_bmc_globals_cmd, sizeof(set_bmc_globals_cmd), rsp, &rsplen);
+ g_assert(rsplen == sizeof(set_bmc_globals_rsp));
+ g_assert(memcmp(set_bmc_globals_rsp, rsp, rsplen) == 0);
+ kcs_ints_enabled = 1;
+}
+
+int main(int argc, char **argv)
+{
+ char *cmdline;
+ int ret;
+
+ /* Run the tests */
+ g_test_init(&argc, &argv, NULL);
+
+ cmdline = g_strdup_printf("-device ipmi-bmc-sim,id=bmc0"
+ " -device isa-ipmi-kcs,bmc=bmc0");
+ qtest_start(cmdline);
+ g_free(cmdline);
+ qtest_irq_intercept_in(global_qtest, "ioapic");
+ qtest_add_func("/ipmi/local/kcs_base", test_kcs_base);
+ qtest_add_func("/ipmi/local/kcs_abort", test_kcs_abort);
+ qtest_add_func("/ipmi/local/kcs_enable_irq", test_enable_irq);
+ qtest_add_func("/ipmi/local/kcs_base_irq", test_kcs_base);
+ qtest_add_func("/ipmi/local/kcs_abort_irq", test_kcs_abort);
+ ret = g_test_run();
+ qtest_quit(global_qtest);
+
+ return ret;
+}
diff --git a/tests/qtest/ipoctal232-test.c b/tests/qtest/ipoctal232-test.c
new file mode 100644
index 0000000000..53a8c9b13c
--- /dev/null
+++ b/tests/qtest/ipoctal232-test.c
@@ -0,0 +1,49 @@
+/*
+ * QTest testcase for IndustryPack Octal-RS232
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+
+typedef struct QIpoctal232 QIpoctal232;
+
+struct QIpoctal232 {
+ QOSGraphObject obj;
+};
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void nop(void *obj, void *data, QGuestAllocator *alloc)
+{
+}
+
+static void *ipoctal232_create(void *pci_bus, QGuestAllocator *alloc,
+ void *addr)
+{
+ QIpoctal232 *ipoctal232 = g_new0(QIpoctal232, 1);
+
+ return &ipoctal232->obj;
+}
+
+static void ipoctal232_register_nodes(void)
+{
+ qos_node_create_driver("ipoctal232", ipoctal232_create);
+ qos_node_consumes("ipoctal232", "ipack", &(QOSGraphEdgeOptions) {
+ .extra_device_opts = "bus=ipack0.0",
+ });
+}
+
+libqos_init(ipoctal232_register_nodes);
+
+static void register_ipoctal232_test(void)
+{
+ qos_add_test("nop", "ipoctal232", nop, NULL);
+}
+
+libqos_init(register_ipoctal232_test);
diff --git a/tests/qtest/ivshmem-test.c b/tests/qtest/ivshmem-test.c
new file mode 100644
index 0000000000..ecda256472
--- /dev/null
+++ b/tests/qtest/ivshmem-test.c
@@ -0,0 +1,500 @@
+/*
+ * QTest testcase for ivshmem
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "contrib/ivshmem-server/ivshmem-server.h"
+#include "libqos/libqos-pc.h"
+#include "libqos/libqos-spapr.h"
+#include "libqtest.h"
+#include "qemu-common.h"
+
+#define TMPSHMSIZE (1 << 20)
+static char *tmpshm;
+static void *tmpshmem;
+static char *tmpdir;
+static char *tmpserver;
+
+static void save_fn(QPCIDevice *dev, int devfn, void *data)
+{
+ QPCIDevice **pdev = (QPCIDevice **) data;
+
+ *pdev = dev;
+}
+
+static QPCIDevice *get_device(QPCIBus *pcibus)
+{
+ QPCIDevice *dev;
+
+ dev = NULL;
+ qpci_device_foreach(pcibus, 0x1af4, 0x1110, save_fn, &dev);
+ g_assert(dev != NULL);
+
+ return dev;
+}
+
+typedef struct _IVState {
+ QOSState *qs;
+ QPCIBar reg_bar, mem_bar;
+ QPCIDevice *dev;
+} IVState;
+
+enum Reg {
+ INTRMASK = 0,
+ INTRSTATUS = 4,
+ IVPOSITION = 8,
+ DOORBELL = 12,
+};
+
+static const char* reg2str(enum Reg reg) {
+ switch (reg) {
+ case INTRMASK:
+ return "IntrMask";
+ case INTRSTATUS:
+ return "IntrStatus";
+ case IVPOSITION:
+ return "IVPosition";
+ case DOORBELL:
+ return "DoorBell";
+ default:
+ return NULL;
+ }
+}
+
+static inline unsigned in_reg(IVState *s, enum Reg reg)
+{
+ const char *name = reg2str(reg);
+ unsigned res;
+
+ res = qpci_io_readl(s->dev, s->reg_bar, reg);
+ g_test_message("*%s -> %x", name, res);
+
+ return res;
+}
+
+static inline void out_reg(IVState *s, enum Reg reg, unsigned v)
+{
+ const char *name = reg2str(reg);
+
+ g_test_message("%x -> *%s", v, name);
+ qpci_io_writel(s->dev, s->reg_bar, reg, v);
+}
+
+static inline void read_mem(IVState *s, uint64_t off, void *buf, size_t len)
+{
+ qpci_memread(s->dev, s->mem_bar, off, buf, len);
+}
+
+static inline void write_mem(IVState *s, uint64_t off,
+ const void *buf, size_t len)
+{
+ qpci_memwrite(s->dev, s->mem_bar, off, buf, len);
+}
+
+static void cleanup_vm(IVState *s)
+{
+ g_free(s->dev);
+ qtest_shutdown(s->qs);
+}
+
+static void setup_vm_cmd(IVState *s, const char *cmd, bool msix)
+{
+ uint64_t barsize;
+ const char *arch = qtest_get_arch();
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ s->qs = qtest_pc_boot(cmd);
+ } else if (strcmp(arch, "ppc64") == 0) {
+ s->qs = qtest_spapr_boot(cmd);
+ } else {
+ g_printerr("ivshmem-test tests are only available on x86 or ppc64\n");
+ exit(EXIT_FAILURE);
+ }
+ s->dev = get_device(s->qs->pcibus);
+
+ s->reg_bar = qpci_iomap(s->dev, 0, &barsize);
+ g_assert_cmpuint(barsize, ==, 256);
+
+ if (msix) {
+ qpci_msix_enable(s->dev);
+ }
+
+ s->mem_bar = qpci_iomap(s->dev, 2, &barsize);
+ g_assert_cmpuint(barsize, ==, TMPSHMSIZE);
+
+ qpci_device_enable(s->dev);
+}
+
+static void setup_vm(IVState *s)
+{
+ char *cmd = g_strdup_printf("-object memory-backend-file"
+ ",id=mb1,size=1M,share,mem-path=/dev/shm%s"
+ " -device ivshmem-plain,memdev=mb1", tmpshm);
+
+ setup_vm_cmd(s, cmd, false);
+
+ g_free(cmd);
+}
+
+static void test_ivshmem_single(void)
+{
+ IVState state, *s;
+ uint32_t data[1024];
+ int i;
+
+ setup_vm(&state);
+ s = &state;
+
+ /* initial state of readable registers */
+ g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0);
+ g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0);
+ g_assert_cmpuint(in_reg(s, IVPOSITION), ==, 0);
+
+ /* trigger interrupt via registers */
+ out_reg(s, INTRMASK, 0xffffffff);
+ g_assert_cmpuint(in_reg(s, INTRMASK), ==, 0xffffffff);
+ out_reg(s, INTRSTATUS, 1);
+ /* check interrupt status */
+ g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 1);
+ /* reading clears */
+ g_assert_cmpuint(in_reg(s, INTRSTATUS), ==, 0);
+ /* TODO intercept actual interrupt (needs qtest work) */
+
+ /* invalid register access */
+ out_reg(s, IVPOSITION, 1);
+ in_reg(s, DOORBELL);
+
+ /* ring the (non-functional) doorbell */
+ out_reg(s, DOORBELL, 8 << 16);
+
+ /* write shared memory */
+ for (i = 0; i < G_N_ELEMENTS(data); i++) {
+ data[i] = i;
+ }
+ write_mem(s, 0, data, sizeof(data));
+
+ /* verify write */
+ for (i = 0; i < G_N_ELEMENTS(data); i++) {
+ g_assert_cmpuint(((uint32_t *)tmpshmem)[i], ==, i);
+ }
+
+ /* read it back and verify read */
+ memset(data, 0, sizeof(data));
+ read_mem(s, 0, data, sizeof(data));
+ for (i = 0; i < G_N_ELEMENTS(data); i++) {
+ g_assert_cmpuint(data[i], ==, i);
+ }
+
+ cleanup_vm(s);
+}
+
+static void test_ivshmem_pair(void)
+{
+ IVState state1, state2, *s1, *s2;
+ char *data;
+ int i;
+
+ setup_vm(&state1);
+ s1 = &state1;
+ setup_vm(&state2);
+ s2 = &state2;
+
+ data = g_malloc0(TMPSHMSIZE);
+
+ /* host write, guest 1 & 2 read */
+ memset(tmpshmem, 0x42, TMPSHMSIZE);
+ read_mem(s1, 0, data, TMPSHMSIZE);
+ for (i = 0; i < TMPSHMSIZE; i++) {
+ g_assert_cmpuint(data[i], ==, 0x42);
+ }
+ read_mem(s2, 0, data, TMPSHMSIZE);
+ for (i = 0; i < TMPSHMSIZE; i++) {
+ g_assert_cmpuint(data[i], ==, 0x42);
+ }
+
+ /* guest 1 write, guest 2 read */
+ memset(data, 0x43, TMPSHMSIZE);
+ write_mem(s1, 0, data, TMPSHMSIZE);
+ memset(data, 0, TMPSHMSIZE);
+ read_mem(s2, 0, data, TMPSHMSIZE);
+ for (i = 0; i < TMPSHMSIZE; i++) {
+ g_assert_cmpuint(data[i], ==, 0x43);
+ }
+
+ /* guest 2 write, guest 1 read */
+ memset(data, 0x44, TMPSHMSIZE);
+ write_mem(s2, 0, data, TMPSHMSIZE);
+ memset(data, 0, TMPSHMSIZE);
+ read_mem(s1, 0, data, TMPSHMSIZE);
+ for (i = 0; i < TMPSHMSIZE; i++) {
+ g_assert_cmpuint(data[i], ==, 0x44);
+ }
+
+ cleanup_vm(s1);
+ cleanup_vm(s2);
+ g_free(data);
+}
+
+typedef struct ServerThread {
+ GThread *thread;
+ IvshmemServer *server;
+ int pipe[2]; /* to handle quit */
+} ServerThread;
+
+static void *server_thread(void *data)
+{
+ ServerThread *t = data;
+ IvshmemServer *server = t->server;
+
+ while (true) {
+ fd_set fds;
+ int maxfd, ret;
+
+ FD_ZERO(&fds);
+ FD_SET(t->pipe[0], &fds);
+ maxfd = t->pipe[0] + 1;
+
+ ivshmem_server_get_fds(server, &fds, &maxfd);
+
+ ret = select(maxfd, &fds, NULL, NULL, NULL);
+
+ if (ret < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ g_critical("select error: %s\n", strerror(errno));
+ break;
+ }
+ if (ret == 0) {
+ continue;
+ }
+
+ if (FD_ISSET(t->pipe[0], &fds)) {
+ break;
+ }
+
+ if (ivshmem_server_handle_fds(server, &fds, maxfd) < 0) {
+ g_critical("ivshmem_server_handle_fds() failed\n");
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+static void setup_vm_with_server(IVState *s, int nvectors)
+{
+ char *cmd;
+
+ cmd = g_strdup_printf("-chardev socket,id=chr0,path=%s "
+ "-device ivshmem-doorbell,chardev=chr0,vectors=%d",
+ tmpserver, nvectors);
+
+ setup_vm_cmd(s, cmd, true);
+
+ g_free(cmd);
+}
+
+static void test_ivshmem_server(void)
+{
+ IVState state1, state2, *s1, *s2;
+ ServerThread thread;
+ IvshmemServer server;
+ int ret, vm1, vm2;
+ int nvectors = 2;
+ guint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+
+ ret = ivshmem_server_init(&server, tmpserver, tmpshm, true,
+ TMPSHMSIZE, nvectors,
+ g_test_verbose());
+ g_assert_cmpint(ret, ==, 0);
+
+ ret = ivshmem_server_start(&server);
+ g_assert_cmpint(ret, ==, 0);
+
+ thread.server = &server;
+ ret = pipe(thread.pipe);
+ g_assert_cmpint(ret, ==, 0);
+ thread.thread = g_thread_new("ivshmem-server", server_thread, &thread);
+ g_assert(thread.thread != NULL);
+
+ setup_vm_with_server(&state1, nvectors);
+ s1 = &state1;
+ setup_vm_with_server(&state2, nvectors);
+ s2 = &state2;
+
+ /* check got different VM ids */
+ vm1 = in_reg(s1, IVPOSITION);
+ vm2 = in_reg(s2, IVPOSITION);
+ g_assert_cmpint(vm1, >=, 0);
+ g_assert_cmpint(vm2, >=, 0);
+ g_assert_cmpint(vm1, !=, vm2);
+
+ /* check number of MSI-X vectors */
+ ret = qpci_msix_table_size(s1->dev);
+ g_assert_cmpuint(ret, ==, nvectors);
+
+ /* TODO test behavior before MSI-X is enabled */
+
+ /* ping vm2 -> vm1 on vector 0 */
+ ret = qpci_msix_pending(s1->dev, 0);
+ g_assert_cmpuint(ret, ==, 0);
+ out_reg(s2, DOORBELL, vm1 << 16);
+ do {
+ g_usleep(10000);
+ ret = qpci_msix_pending(s1->dev, 0);
+ } while (ret == 0 && g_get_monotonic_time() < end_time);
+ g_assert_cmpuint(ret, !=, 0);
+
+ /* ping vm1 -> vm2 on vector 1 */
+ ret = qpci_msix_pending(s2->dev, 1);
+ g_assert_cmpuint(ret, ==, 0);
+ out_reg(s1, DOORBELL, vm2 << 16 | 1);
+ do {
+ g_usleep(10000);
+ ret = qpci_msix_pending(s2->dev, 1);
+ } while (ret == 0 && g_get_monotonic_time() < end_time);
+ g_assert_cmpuint(ret, !=, 0);
+
+ cleanup_vm(s2);
+ cleanup_vm(s1);
+
+ if (qemu_write_full(thread.pipe[1], "q", 1) != 1) {
+ g_error("qemu_write_full: %s", g_strerror(errno));
+ }
+
+ g_thread_join(thread.thread);
+
+ ivshmem_server_close(&server);
+ close(thread.pipe[1]);
+ close(thread.pipe[0]);
+}
+
+#define PCI_SLOT_HP 0x06
+
+static void test_ivshmem_hotplug(void)
+{
+ QTestState *qts;
+ const char *arch = qtest_get_arch();
+
+ qts = qtest_init("-object memory-backend-ram,size=1M,id=mb1");
+
+ qtest_qmp_device_add(qts, "ivshmem-plain", "iv1",
+ "{'addr': %s, 'memdev': 'mb1'}",
+ stringify(PCI_SLOT_HP));
+ if (strcmp(arch, "ppc64") != 0) {
+ qpci_unplug_acpi_device_test(qts, "iv1", PCI_SLOT_HP);
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_ivshmem_memdev(void)
+{
+ IVState state;
+
+ /* just for the sake of checking memory-backend property */
+ setup_vm_cmd(&state, "-object memory-backend-ram,size=1M,id=mb1"
+ " -device ivshmem-plain,memdev=mb1", false);
+
+ cleanup_vm(&state);
+}
+
+static void cleanup(void)
+{
+ if (tmpshmem) {
+ munmap(tmpshmem, TMPSHMSIZE);
+ tmpshmem = NULL;
+ }
+
+ if (tmpshm) {
+ shm_unlink(tmpshm);
+ g_free(tmpshm);
+ tmpshm = NULL;
+ }
+
+ if (tmpserver) {
+ g_unlink(tmpserver);
+ g_free(tmpserver);
+ tmpserver = NULL;
+ }
+
+ if (tmpdir) {
+ g_rmdir(tmpdir);
+ tmpdir = NULL;
+ }
+}
+
+static void abrt_handler(void *data)
+{
+ cleanup();
+}
+
+static gchar *mktempshm(int size, int *fd)
+{
+ while (true) {
+ gchar *name;
+
+ name = g_strdup_printf("/qtest-%u-%u", getpid(), g_test_rand_int());
+ *fd = shm_open(name, O_CREAT|O_RDWR|O_EXCL,
+ S_IRWXU|S_IRWXG|S_IRWXO);
+ if (*fd > 0) {
+ g_assert(ftruncate(*fd, size) == 0);
+ return name;
+ }
+
+ g_free(name);
+
+ if (errno != EEXIST) {
+ perror("shm_open");
+ return NULL;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int ret, fd;
+ const char *arch = qtest_get_arch();
+ gchar dir[] = "/tmp/ivshmem-test.XXXXXX";
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_abrt_handler(abrt_handler, NULL);
+ /* shm */
+ tmpshm = mktempshm(TMPSHMSIZE, &fd);
+ if (!tmpshm) {
+ goto out;
+ }
+ tmpshmem = mmap(0, TMPSHMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ g_assert(tmpshmem != MAP_FAILED);
+ /* server */
+ if (mkdtemp(dir) == NULL) {
+ g_error("mkdtemp: %s", g_strerror(errno));
+ }
+ tmpdir = dir;
+ tmpserver = g_strconcat(tmpdir, "/server", NULL);
+
+ qtest_add_func("/ivshmem/single", test_ivshmem_single);
+ qtest_add_func("/ivshmem/hotplug", test_ivshmem_hotplug);
+ qtest_add_func("/ivshmem/memdev", test_ivshmem_memdev);
+ if (g_test_slow()) {
+ qtest_add_func("/ivshmem/pair", test_ivshmem_pair);
+ if (strcmp(arch, "ppc64") != 0) {
+ qtest_add_func("/ivshmem/server", test_ivshmem_server);
+ }
+ }
+
+out:
+ ret = g_test_run();
+ cleanup();
+ return ret;
+}
diff --git a/tests/qtest/libqtest-single.h b/tests/qtest/libqtest-single.h
new file mode 100644
index 0000000000..6f1bb1331c
--- /dev/null
+++ b/tests/qtest/libqtest-single.h
@@ -0,0 +1,315 @@
+/*
+ * QTest - wrappers for test with single QEMU instances
+ *
+ * Copyright IBM, Corp. 2012
+ * Copyright Red Hat, Inc. 2012
+ * Copyright SUSE LINUX Products GmbH 2013
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef LIBQTEST_SINGLE_H
+#define LIBQTEST_SINGLE_H
+
+#include "libqtest.h"
+
+QTestState *global_qtest __attribute__((common, weak));
+
+/**
+ * qtest_start:
+ * @args: other arguments to pass to QEMU
+ *
+ * Start QEMU and assign the resulting #QTestState to a global variable.
+ * The global variable is used by "shortcut" functions documented below.
+ *
+ * Returns: #QTestState instance.
+ */
+static inline QTestState *qtest_start(const char *args)
+{
+ global_qtest = qtest_init(args);
+ return global_qtest;
+}
+
+/**
+ * qtest_end:
+ *
+ * Shut down the QEMU process started by qtest_start().
+ */
+static inline void qtest_end(void)
+{
+ if (!global_qtest) {
+ return;
+ }
+ qtest_quit(global_qtest);
+ global_qtest = NULL;
+}
+
+/**
+ * qmp:
+ * @fmt...: QMP message to send to qemu, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ *
+ * Sends a QMP message to QEMU and returns the response.
+ */
+GCC_FMT_ATTR(1, 2)
+static inline QDict *qmp(const char *fmt, ...)
+{
+ va_list ap;
+ QDict *response;
+
+ va_start(ap, fmt);
+ response = qtest_vqmp(global_qtest, fmt, ap);
+ va_end(ap);
+ return response;
+}
+
+/**
+ * qmp_eventwait:
+ * @s: #event event to wait for.
+ *
+ * Continuously polls for QMP responses until it receives the desired event.
+ */
+static inline void qmp_eventwait(const char *event)
+{
+ return qtest_qmp_eventwait(global_qtest, event);
+}
+
+/**
+ * get_irq:
+ * @num: Interrupt to observe.
+ *
+ * Returns: The level of the @num interrupt.
+ */
+static inline bool get_irq(int num)
+{
+ return qtest_get_irq(global_qtest, num);
+}
+
+/**
+ * outb:
+ * @addr: I/O port to write to.
+ * @value: Value being written.
+ *
+ * Write an 8-bit value to an I/O port.
+ */
+static inline void outb(uint16_t addr, uint8_t value)
+{
+ qtest_outb(global_qtest, addr, value);
+}
+
+/**
+ * outw:
+ * @addr: I/O port to write to.
+ * @value: Value being written.
+ *
+ * Write a 16-bit value to an I/O port.
+ */
+static inline void outw(uint16_t addr, uint16_t value)
+{
+ qtest_outw(global_qtest, addr, value);
+}
+
+/**
+ * outl:
+ * @addr: I/O port to write to.
+ * @value: Value being written.
+ *
+ * Write a 32-bit value to an I/O port.
+ */
+static inline void outl(uint16_t addr, uint32_t value)
+{
+ qtest_outl(global_qtest, addr, value);
+}
+
+/**
+ * inb:
+ * @addr: I/O port to read from.
+ *
+ * Reads an 8-bit value from an I/O port.
+ *
+ * Returns: Value read.
+ */
+static inline uint8_t inb(uint16_t addr)
+{
+ return qtest_inb(global_qtest, addr);
+}
+
+/**
+ * inw:
+ * @addr: I/O port to read from.
+ *
+ * Reads a 16-bit value from an I/O port.
+ *
+ * Returns: Value read.
+ */
+static inline uint16_t inw(uint16_t addr)
+{
+ return qtest_inw(global_qtest, addr);
+}
+
+/**
+ * inl:
+ * @addr: I/O port to read from.
+ *
+ * Reads a 32-bit value from an I/O port.
+ *
+ * Returns: Value read.
+ */
+static inline uint32_t inl(uint16_t addr)
+{
+ return qtest_inl(global_qtest, addr);
+}
+
+/**
+ * writeb:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes an 8-bit value to guest memory.
+ */
+static inline void writeb(uint64_t addr, uint8_t value)
+{
+ qtest_writeb(global_qtest, addr, value);
+}
+
+/**
+ * writew:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 16-bit value to guest memory.
+ */
+static inline void writew(uint64_t addr, uint16_t value)
+{
+ qtest_writew(global_qtest, addr, value);
+}
+
+/**
+ * writel:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 32-bit value to guest memory.
+ */
+static inline void writel(uint64_t addr, uint32_t value)
+{
+ qtest_writel(global_qtest, addr, value);
+}
+
+/**
+ * writeq:
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 64-bit value to guest memory.
+ */
+static inline void writeq(uint64_t addr, uint64_t value)
+{
+ qtest_writeq(global_qtest, addr, value);
+}
+
+/**
+ * readb:
+ * @addr: Guest address to read from.
+ *
+ * Reads an 8-bit value from guest memory.
+ *
+ * Returns: Value read.
+ */
+static inline uint8_t readb(uint64_t addr)
+{
+ return qtest_readb(global_qtest, addr);
+}
+
+/**
+ * readw:
+ * @addr: Guest address to read from.
+ *
+ * Reads a 16-bit value from guest memory.
+ *
+ * Returns: Value read.
+ */
+static inline uint16_t readw(uint64_t addr)
+{
+ return qtest_readw(global_qtest, addr);
+}
+
+/**
+ * readl:
+ * @addr: Guest address to read from.
+ *
+ * Reads a 32-bit value from guest memory.
+ *
+ * Returns: Value read.
+ */
+static inline uint32_t readl(uint64_t addr)
+{
+ return qtest_readl(global_qtest, addr);
+}
+
+/**
+ * readq:
+ * @addr: Guest address to read from.
+ *
+ * Reads a 64-bit value from guest memory.
+ *
+ * Returns: Value read.
+ */
+static inline uint64_t readq(uint64_t addr)
+{
+ return qtest_readq(global_qtest, addr);
+}
+
+/**
+ * memread:
+ * @addr: Guest address to read from.
+ * @data: Pointer to where memory contents will be stored.
+ * @size: Number of bytes to read.
+ *
+ * Read guest memory into a buffer.
+ */
+static inline void memread(uint64_t addr, void *data, size_t size)
+{
+ qtest_memread(global_qtest, addr, data, size);
+}
+
+/**
+ * memwrite:
+ * @addr: Guest address to write to.
+ * @data: Pointer to the bytes that will be written to guest memory.
+ * @size: Number of bytes to write.
+ *
+ * Write a buffer to guest memory.
+ */
+static inline void memwrite(uint64_t addr, const void *data, size_t size)
+{
+ qtest_memwrite(global_qtest, addr, data, size);
+}
+
+/**
+ * clock_step_next:
+ *
+ * Advance the QEMU_CLOCK_VIRTUAL to the next deadline.
+ *
+ * Returns: The current value of the QEMU_CLOCK_VIRTUAL in nanoseconds.
+ */
+static inline int64_t clock_step_next(void)
+{
+ return qtest_clock_step_next(global_qtest);
+}
+
+/**
+ * clock_step:
+ * @step: Number of nanoseconds to advance the clock by.
+ *
+ * Advance the QEMU_CLOCK_VIRTUAL by @step nanoseconds.
+ *
+ * Returns: The current value of the QEMU_CLOCK_VIRTUAL in nanoseconds.
+ */
+static inline int64_t clock_step(int64_t step)
+{
+ return qtest_clock_step(global_qtest, step);
+}
+
+#endif
diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
new file mode 100644
index 0000000000..76c9f8eade
--- /dev/null
+++ b/tests/qtest/libqtest.c
@@ -0,0 +1,1339 @@
+/*
+ * QTest
+ *
+ * Copyright IBM, Corp. 2012
+ * Copyright Red Hat, Inc. 2012
+ * Copyright SUSE LINUX Products GmbH 2013
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Paolo Bonzini <pbonzini@redhat.com>
+ * Andreas Färber <afaerber@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+
+#include "libqtest.h"
+#include "qemu-common.h"
+#include "qemu/ctype.h"
+#include "qemu/cutils.h"
+#include "qapi/error.h"
+#include "qapi/qmp/json-parser.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qjson.h"
+#include "qapi/qmp/qlist.h"
+#include "qapi/qmp/qstring.h"
+
+#define MAX_IRQ 256
+#define SOCKET_TIMEOUT 50
+#define SOCKET_MAX_FDS 16
+
+struct QTestState
+{
+ int fd;
+ int qmp_fd;
+ pid_t qemu_pid; /* our child QEMU process */
+ int wstatus;
+ int expected_status;
+ bool big_endian;
+ bool irq_level[MAX_IRQ];
+ GString *rx;
+};
+
+static GHookList abrt_hooks;
+static struct sigaction sigact_old;
+
+static int qtest_query_target_endianness(QTestState *s);
+
+static int init_socket(const char *socket_path)
+{
+ struct sockaddr_un addr;
+ int sock;
+ int ret;
+
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ g_assert_cmpint(sock, !=, -1);
+
+ addr.sun_family = AF_UNIX;
+ snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path);
+ qemu_set_cloexec(sock);
+
+ do {
+ ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));
+ } while (ret == -1 && errno == EINTR);
+ g_assert_cmpint(ret, !=, -1);
+ ret = listen(sock, 1);
+ g_assert_cmpint(ret, !=, -1);
+
+ return sock;
+}
+
+static int socket_accept(int sock)
+{
+ struct sockaddr_un addr;
+ socklen_t addrlen;
+ int ret;
+ struct timeval timeout = { .tv_sec = SOCKET_TIMEOUT,
+ .tv_usec = 0 };
+
+ setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *)&timeout,
+ sizeof(timeout));
+
+ do {
+ addrlen = sizeof(addr);
+ ret = accept(sock, (struct sockaddr *)&addr, &addrlen);
+ } while (ret == -1 && errno == EINTR);
+ if (ret == -1) {
+ fprintf(stderr, "%s failed: %s\n", __func__, strerror(errno));
+ }
+ close(sock);
+
+ return ret;
+}
+
+bool qtest_probe_child(QTestState *s)
+{
+ pid_t pid = s->qemu_pid;
+
+ if (pid != -1) {
+ pid = waitpid(pid, &s->wstatus, WNOHANG);
+ if (pid == 0) {
+ return true;
+ }
+ s->qemu_pid = -1;
+ }
+ return false;
+}
+
+void qtest_set_expected_status(QTestState *s, int status)
+{
+ s->expected_status = status;
+}
+
+static void kill_qemu(QTestState *s)
+{
+ pid_t pid = s->qemu_pid;
+ int wstatus;
+
+ /* Skip wait if qtest_probe_child already reaped. */
+ if (pid != -1) {
+ kill(pid, SIGTERM);
+ TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
+ assert(pid == s->qemu_pid);
+ }
+
+ /*
+ * Check whether qemu exited with expected exit status; anything else is
+ * fishy and should be logged with as much detail as possible.
+ */
+ wstatus = s->wstatus;
+ if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != s->expected_status) {
+ fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
+ "process but encountered exit status %d (expected %d)\n",
+ __FILE__, __LINE__, WEXITSTATUS(wstatus), s->expected_status);
+ abort();
+ } else if (WIFSIGNALED(wstatus)) {
+ int sig = WTERMSIG(wstatus);
+ const char *signame = strsignal(sig) ?: "unknown ???";
+ const char *dump = WCOREDUMP(wstatus) ? " (core dumped)" : "";
+
+ fprintf(stderr, "%s:%d: kill_qemu() detected QEMU death "
+ "from signal %d (%s)%s\n",
+ __FILE__, __LINE__, sig, signame, dump);
+ abort();
+ }
+}
+
+static void kill_qemu_hook_func(void *s)
+{
+ kill_qemu(s);
+}
+
+static void sigabrt_handler(int signo)
+{
+ g_hook_list_invoke(&abrt_hooks, FALSE);
+}
+
+static void setup_sigabrt_handler(void)
+{
+ struct sigaction sigact;
+
+ /* Catch SIGABRT to clean up on g_assert() failure */
+ sigact = (struct sigaction){
+ .sa_handler = sigabrt_handler,
+ .sa_flags = SA_RESETHAND,
+ };
+ sigemptyset(&sigact.sa_mask);
+ sigaction(SIGABRT, &sigact, &sigact_old);
+}
+
+static void cleanup_sigabrt_handler(void)
+{
+ sigaction(SIGABRT, &sigact_old, NULL);
+}
+
+void qtest_add_abrt_handler(GHookFunc fn, const void *data)
+{
+ GHook *hook;
+
+ /* Only install SIGABRT handler once */
+ if (!abrt_hooks.is_setup) {
+ g_hook_list_init(&abrt_hooks, sizeof(GHook));
+ }
+ setup_sigabrt_handler();
+
+ hook = g_hook_alloc(&abrt_hooks);
+ hook->func = fn;
+ hook->data = (void *)data;
+
+ g_hook_prepend(&abrt_hooks, hook);
+}
+
+static const char *qtest_qemu_binary(void)
+{
+ const char *qemu_bin;
+
+ qemu_bin = getenv("QTEST_QEMU_BINARY");
+ if (!qemu_bin) {
+ fprintf(stderr, "Environment variable QTEST_QEMU_BINARY required\n");
+ exit(1);
+ }
+
+ return qemu_bin;
+}
+
+QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
+{
+ QTestState *s;
+ int sock, qmpsock, i;
+ gchar *socket_path;
+ gchar *qmp_socket_path;
+ gchar *command;
+ const char *qemu_binary = qtest_qemu_binary();
+
+ s = g_new(QTestState, 1);
+
+ socket_path = g_strdup_printf("/tmp/qtest-%d.sock", getpid());
+ qmp_socket_path = g_strdup_printf("/tmp/qtest-%d.qmp", getpid());
+
+ /* It's possible that if an earlier test run crashed it might
+ * have left a stale unix socket lying around. Delete any
+ * stale old socket to avoid spurious test failures with
+ * tests/libqtest.c:70:init_socket: assertion failed (ret != -1): (-1 != -1)
+ */
+ unlink(socket_path);
+ unlink(qmp_socket_path);
+
+ sock = init_socket(socket_path);
+ qmpsock = init_socket(qmp_socket_path);
+
+ qtest_add_abrt_handler(kill_qemu_hook_func, s);
+
+ command = g_strdup_printf("exec %s "
+ "-qtest unix:%s "
+ "-qtest-log %s "
+ "-chardev socket,path=%s,id=char0 "
+ "-mon chardev=char0,mode=control "
+ "-display none "
+ "%s"
+ " -accel qtest", qemu_binary, socket_path,
+ getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null",
+ qmp_socket_path,
+ extra_args ?: "");
+
+ g_test_message("starting QEMU: %s", command);
+
+ s->wstatus = 0;
+ s->expected_status = 0;
+ s->qemu_pid = fork();
+ if (s->qemu_pid == 0) {
+ g_setenv("QEMU_AUDIO_DRV", "none", true);
+ execlp("/bin/sh", "sh", "-c", command, NULL);
+ exit(1);
+ }
+
+ g_free(command);
+ s->fd = socket_accept(sock);
+ if (s->fd >= 0) {
+ s->qmp_fd = socket_accept(qmpsock);
+ }
+ unlink(socket_path);
+ unlink(qmp_socket_path);
+ g_free(socket_path);
+ g_free(qmp_socket_path);
+
+ g_assert(s->fd >= 0 && s->qmp_fd >= 0);
+
+ s->rx = g_string_new("");
+ for (i = 0; i < MAX_IRQ; i++) {
+ s->irq_level[i] = false;
+ }
+
+ if (getenv("QTEST_STOP")) {
+ kill(s->qemu_pid, SIGSTOP);
+ }
+
+ /* ask endianness of the target */
+
+ s->big_endian = qtest_query_target_endianness(s);
+
+ return s;
+}
+
+QTestState *qtest_init(const char *extra_args)
+{
+ QTestState *s = qtest_init_without_qmp_handshake(extra_args);
+ QDict *greeting;
+
+ /* Read the QMP greeting and then do the handshake */
+ greeting = qtest_qmp_receive(s);
+ qobject_unref(greeting);
+ qobject_unref(qtest_qmp(s, "{ 'execute': 'qmp_capabilities' }"));
+
+ return s;
+}
+
+QTestState *qtest_vinitf(const char *fmt, va_list ap)
+{
+ char *args = g_strdup_vprintf(fmt, ap);
+ QTestState *s;
+
+ s = qtest_init(args);
+ g_free(args);
+ return s;
+}
+
+QTestState *qtest_initf(const char *fmt, ...)
+{
+ va_list ap;
+ QTestState *s;
+
+ va_start(ap, fmt);
+ s = qtest_vinitf(fmt, ap);
+ va_end(ap);
+ return s;
+}
+
+QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd)
+{
+ int sock_fd_init;
+ char *sock_path, sock_dir[] = "/tmp/qtest-serial-XXXXXX";
+ QTestState *qts;
+
+ g_assert_true(mkdtemp(sock_dir) != NULL);
+ sock_path = g_strdup_printf("%s/sock", sock_dir);
+
+ sock_fd_init = init_socket(sock_path);
+
+ qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0 %s",
+ sock_path, extra_args);
+
+ *sock_fd = socket_accept(sock_fd_init);
+
+ unlink(sock_path);
+ g_free(sock_path);
+ rmdir(sock_dir);
+
+ g_assert_true(*sock_fd >= 0);
+
+ return qts;
+}
+
+void qtest_quit(QTestState *s)
+{
+ g_hook_destroy_link(&abrt_hooks, g_hook_find_data(&abrt_hooks, TRUE, s));
+
+ /* Uninstall SIGABRT handler on last instance */
+ cleanup_sigabrt_handler();
+
+ kill_qemu(s);
+ close(s->fd);
+ close(s->qmp_fd);
+ g_string_free(s->rx, true);
+ g_free(s);
+}
+
+static void socket_send(int fd, const char *buf, size_t size)
+{
+ size_t offset;
+
+ offset = 0;
+ while (offset < size) {
+ ssize_t len;
+
+ len = write(fd, buf + offset, size - offset);
+ if (len == -1 && errno == EINTR) {
+ continue;
+ }
+
+ g_assert_cmpint(len, >, 0);
+
+ offset += len;
+ }
+}
+
+static void socket_sendf(int fd, const char *fmt, va_list ap)
+{
+ gchar *str = g_strdup_vprintf(fmt, ap);
+ size_t size = strlen(str);
+
+ socket_send(fd, str, size);
+ g_free(str);
+}
+
+static void GCC_FMT_ATTR(2, 3) qtest_sendf(QTestState *s, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ socket_sendf(s->fd, fmt, ap);
+ va_end(ap);
+}
+
+/* Sends a message and file descriptors to the socket.
+ * It's needed for qmp-commands like getfd/add-fd */
+static void socket_send_fds(int socket_fd, int *fds, size_t fds_num,
+ const char *buf, size_t buf_size)
+{
+ ssize_t ret;
+ struct msghdr msg = { 0 };
+ char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)] = { 0 };
+ size_t fdsize = sizeof(int) * fds_num;
+ struct cmsghdr *cmsg;
+ struct iovec iov = { .iov_base = (char *)buf, .iov_len = buf_size };
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ if (fds && fds_num > 0) {
+ g_assert_cmpuint(fds_num, <, SOCKET_MAX_FDS);
+
+ msg.msg_control = control;
+ msg.msg_controllen = CMSG_SPACE(fdsize);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(fdsize);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsg), fds, fdsize);
+ }
+
+ do {
+ ret = sendmsg(socket_fd, &msg, 0);
+ } while (ret < 0 && errno == EINTR);
+ g_assert_cmpint(ret, >, 0);
+}
+
+static GString *qtest_recv_line(QTestState *s)
+{
+ GString *line;
+ size_t offset;
+ char *eol;
+
+ while ((eol = strchr(s->rx->str, '\n')) == NULL) {
+ ssize_t len;
+ char buffer[1024];
+
+ len = read(s->fd, buffer, sizeof(buffer));
+ if (len == -1 && errno == EINTR) {
+ continue;
+ }
+
+ if (len == -1 || len == 0) {
+ fprintf(stderr, "Broken pipe\n");
+ abort();
+ }
+
+ g_string_append_len(s->rx, buffer, len);
+ }
+
+ offset = eol - s->rx->str;
+ line = g_string_new_len(s->rx->str, offset);
+ g_string_erase(s->rx, 0, offset + 1);
+
+ return line;
+}
+
+static gchar **qtest_rsp(QTestState *s, int expected_args)
+{
+ GString *line;
+ gchar **words;
+ int i;
+
+redo:
+ line = qtest_recv_line(s);
+ words = g_strsplit(line->str, " ", 0);
+ g_string_free(line, TRUE);
+
+ if (strcmp(words[0], "IRQ") == 0) {
+ long irq;
+ int ret;
+
+ g_assert(words[1] != NULL);
+ g_assert(words[2] != NULL);
+
+ ret = qemu_strtol(words[2], NULL, 0, &irq);
+ g_assert(!ret);
+ g_assert_cmpint(irq, >=, 0);
+ g_assert_cmpint(irq, <, MAX_IRQ);
+
+ if (strcmp(words[1], "raise") == 0) {
+ s->irq_level[irq] = true;
+ } else {
+ s->irq_level[irq] = false;
+ }
+
+ g_strfreev(words);
+ goto redo;
+ }
+
+ g_assert(words[0] != NULL);
+ g_assert_cmpstr(words[0], ==, "OK");
+
+ if (expected_args) {
+ for (i = 0; i < expected_args; i++) {
+ g_assert(words[i] != NULL);
+ }
+ } else {
+ g_strfreev(words);
+ }
+
+ return words;
+}
+
+static int qtest_query_target_endianness(QTestState *s)
+{
+ gchar **args;
+ int big_endian;
+
+ qtest_sendf(s, "endianness\n");
+ args = qtest_rsp(s, 1);
+ g_assert(strcmp(args[1], "big") == 0 || strcmp(args[1], "little") == 0);
+ big_endian = strcmp(args[1], "big") == 0;
+ g_strfreev(args);
+
+ return big_endian;
+}
+
+typedef struct {
+ JSONMessageParser parser;
+ QDict *response;
+} QMPResponseParser;
+
+static void qmp_response(void *opaque, QObject *obj, Error *err)
+{
+ QMPResponseParser *qmp = opaque;
+
+ assert(!obj != !err);
+
+ if (err) {
+ error_prepend(&err, "QMP JSON response parsing failed: ");
+ error_report_err(err);
+ abort();
+ }
+
+ g_assert(!qmp->response);
+ qmp->response = qobject_to(QDict, obj);
+ g_assert(qmp->response);
+}
+
+QDict *qmp_fd_receive(int fd)
+{
+ QMPResponseParser qmp;
+ bool log = getenv("QTEST_LOG") != NULL;
+
+ qmp.response = NULL;
+ json_message_parser_init(&qmp.parser, qmp_response, &qmp, NULL);
+ while (!qmp.response) {
+ ssize_t len;
+ char c;
+
+ len = read(fd, &c, 1);
+ if (len == -1 && errno == EINTR) {
+ continue;
+ }
+
+ if (len == -1 || len == 0) {
+ fprintf(stderr, "Broken pipe\n");
+ abort();
+ }
+
+ if (log) {
+ len = write(2, &c, 1);
+ }
+ json_message_parser_feed(&qmp.parser, &c, 1);
+ }
+ json_message_parser_destroy(&qmp.parser);
+
+ return qmp.response;
+}
+
+QDict *qtest_qmp_receive(QTestState *s)
+{
+ return qmp_fd_receive(s->qmp_fd);
+}
+
+/**
+ * Allow users to send a message without waiting for the reply,
+ * in the case that they choose to discard all replies up until
+ * a particular EVENT is received.
+ */
+void qmp_fd_vsend_fds(int fd, int *fds, size_t fds_num,
+ const char *fmt, va_list ap)
+{
+ QObject *qobj;
+
+ /* Going through qobject ensures we escape strings properly */
+ qobj = qobject_from_vjsonf_nofail(fmt, ap);
+
+ /* No need to send anything for an empty QObject. */
+ if (qobj) {
+ int log = getenv("QTEST_LOG") != NULL;
+ QString *qstr = qobject_to_json(qobj);
+ const char *str;
+
+ /*
+ * BUG: QMP doesn't react to input until it sees a newline, an
+ * object, or an array. Work-around: give it a newline.
+ */
+ qstring_append_chr(qstr, '\n');
+ str = qstring_get_str(qstr);
+
+ if (log) {
+ fprintf(stderr, "%s", str);
+ }
+ /* Send QMP request */
+ if (fds && fds_num > 0) {
+ socket_send_fds(fd, fds, fds_num, str, qstring_get_length(qstr));
+ } else {
+ socket_send(fd, str, qstring_get_length(qstr));
+ }
+
+ qobject_unref(qstr);
+ qobject_unref(qobj);
+ }
+}
+
+void qmp_fd_vsend(int fd, const char *fmt, va_list ap)
+{
+ qmp_fd_vsend_fds(fd, NULL, 0, fmt, ap);
+}
+
+void qtest_qmp_vsend_fds(QTestState *s, int *fds, size_t fds_num,
+ const char *fmt, va_list ap)
+{
+ qmp_fd_vsend_fds(s->qmp_fd, fds, fds_num, fmt, ap);
+}
+
+void qtest_qmp_vsend(QTestState *s, const char *fmt, va_list ap)
+{
+ qmp_fd_vsend_fds(s->qmp_fd, NULL, 0, fmt, ap);
+}
+
+QDict *qmp_fdv(int fd, const char *fmt, va_list ap)
+{
+ qmp_fd_vsend_fds(fd, NULL, 0, fmt, ap);
+
+ return qmp_fd_receive(fd);
+}
+
+QDict *qtest_vqmp_fds(QTestState *s, int *fds, size_t fds_num,
+ const char *fmt, va_list ap)
+{
+ qtest_qmp_vsend_fds(s, fds, fds_num, fmt, ap);
+
+ /* Receive reply */
+ return qtest_qmp_receive(s);
+}
+
+QDict *qtest_vqmp(QTestState *s, const char *fmt, va_list ap)
+{
+ qtest_qmp_vsend(s, fmt, ap);
+
+ /* Receive reply */
+ return qtest_qmp_receive(s);
+}
+
+QDict *qmp_fd(int fd, const char *fmt, ...)
+{
+ va_list ap;
+ QDict *response;
+
+ va_start(ap, fmt);
+ response = qmp_fdv(fd, fmt, ap);
+ va_end(ap);
+ return response;
+}
+
+void qmp_fd_send(int fd, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ qmp_fd_vsend(fd, fmt, ap);
+ va_end(ap);
+}
+
+QDict *qtest_qmp_fds(QTestState *s, int *fds, size_t fds_num,
+ const char *fmt, ...)
+{
+ va_list ap;
+ QDict *response;
+
+ va_start(ap, fmt);
+ response = qtest_vqmp_fds(s, fds, fds_num, fmt, ap);
+ va_end(ap);
+ return response;
+}
+
+QDict *qtest_qmp(QTestState *s, const char *fmt, ...)
+{
+ va_list ap;
+ QDict *response;
+
+ va_start(ap, fmt);
+ response = qtest_vqmp(s, fmt, ap);
+ va_end(ap);
+ return response;
+}
+
+void qtest_qmp_send(QTestState *s, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ qtest_qmp_vsend(s, fmt, ap);
+ va_end(ap);
+}
+
+void qmp_fd_vsend_raw(int fd, const char *fmt, va_list ap)
+{
+ bool log = getenv("QTEST_LOG") != NULL;
+ char *str = g_strdup_vprintf(fmt, ap);
+
+ if (log) {
+ fprintf(stderr, "%s", str);
+ }
+ socket_send(fd, str, strlen(str));
+ g_free(str);
+}
+
+void qmp_fd_send_raw(int fd, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ qmp_fd_vsend_raw(fd, fmt, ap);
+ va_end(ap);
+}
+
+void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ qmp_fd_vsend_raw(s->qmp_fd, fmt, ap);
+ va_end(ap);
+}
+
+QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event)
+{
+ QDict *response;
+
+ for (;;) {
+ response = qtest_qmp_receive(s);
+ if ((qdict_haskey(response, "event")) &&
+ (strcmp(qdict_get_str(response, "event"), event) == 0)) {
+ return response;
+ }
+ qobject_unref(response);
+ }
+}
+
+void qtest_qmp_eventwait(QTestState *s, const char *event)
+{
+ QDict *response;
+
+ response = qtest_qmp_eventwait_ref(s, event);
+ qobject_unref(response);
+}
+
+char *qtest_vhmp(QTestState *s, const char *fmt, va_list ap)
+{
+ char *cmd;
+ QDict *resp;
+ char *ret;
+
+ cmd = g_strdup_vprintf(fmt, ap);
+ resp = qtest_qmp(s, "{'execute': 'human-monitor-command',"
+ " 'arguments': {'command-line': %s}}",
+ cmd);
+ ret = g_strdup(qdict_get_try_str(resp, "return"));
+ while (ret == NULL && qdict_get_try_str(resp, "event")) {
+ /* Ignore asynchronous QMP events */
+ qobject_unref(resp);
+ resp = qtest_qmp_receive(s);
+ ret = g_strdup(qdict_get_try_str(resp, "return"));
+ }
+ g_assert(ret);
+ qobject_unref(resp);
+ g_free(cmd);
+ return ret;
+}
+
+char *qtest_hmp(QTestState *s, const char *fmt, ...)
+{
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = qtest_vhmp(s, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+const char *qtest_get_arch(void)
+{
+ const char *qemu = qtest_qemu_binary();
+ const char *end = strrchr(qemu, '/');
+
+ return end + strlen("/qemu-system-");
+}
+
+bool qtest_get_irq(QTestState *s, int num)
+{
+ /* dummy operation in order to make sure irq is up to date */
+ qtest_inb(s, 0);
+
+ return s->irq_level[num];
+}
+
+void qtest_module_load(QTestState *s, const char *prefix, const char *libname)
+{
+ qtest_sendf(s, "module_load %s %s\n", prefix, libname);
+ qtest_rsp(s, 0);
+}
+
+static int64_t qtest_clock_rsp(QTestState *s)
+{
+ gchar **words;
+ int64_t clock;
+ words = qtest_rsp(s, 2);
+ clock = g_ascii_strtoll(words[1], NULL, 0);
+ g_strfreev(words);
+ return clock;
+}
+
+int64_t qtest_clock_step_next(QTestState *s)
+{
+ qtest_sendf(s, "clock_step\n");
+ return qtest_clock_rsp(s);
+}
+
+int64_t qtest_clock_step(QTestState *s, int64_t step)
+{
+ qtest_sendf(s, "clock_step %"PRIi64"\n", step);
+ return qtest_clock_rsp(s);
+}
+
+int64_t qtest_clock_set(QTestState *s, int64_t val)
+{
+ qtest_sendf(s, "clock_set %"PRIi64"\n", val);
+ return qtest_clock_rsp(s);
+}
+
+void qtest_irq_intercept_out(QTestState *s, const char *qom_path)
+{
+ qtest_sendf(s, "irq_intercept_out %s\n", qom_path);
+ qtest_rsp(s, 0);
+}
+
+void qtest_irq_intercept_in(QTestState *s, const char *qom_path)
+{
+ qtest_sendf(s, "irq_intercept_in %s\n", qom_path);
+ qtest_rsp(s, 0);
+}
+
+void qtest_set_irq_in(QTestState *s, const char *qom_path, const char *name,
+ int num, int level)
+{
+ if (!name) {
+ name = "unnamed-gpio-in";
+ }
+ qtest_sendf(s, "set_irq_in %s %s %d %d\n", qom_path, name, num, level);
+ qtest_rsp(s, 0);
+}
+
+static void qtest_out(QTestState *s, const char *cmd, uint16_t addr, uint32_t value)
+{
+ qtest_sendf(s, "%s 0x%x 0x%x\n", cmd, addr, value);
+ qtest_rsp(s, 0);
+}
+
+void qtest_outb(QTestState *s, uint16_t addr, uint8_t value)
+{
+ qtest_out(s, "outb", addr, value);
+}
+
+void qtest_outw(QTestState *s, uint16_t addr, uint16_t value)
+{
+ qtest_out(s, "outw", addr, value);
+}
+
+void qtest_outl(QTestState *s, uint16_t addr, uint32_t value)
+{
+ qtest_out(s, "outl", addr, value);
+}
+
+static uint32_t qtest_in(QTestState *s, const char *cmd, uint16_t addr)
+{
+ gchar **args;
+ int ret;
+ unsigned long value;
+
+ qtest_sendf(s, "%s 0x%x\n", cmd, addr);
+ args = qtest_rsp(s, 2);
+ ret = qemu_strtoul(args[1], NULL, 0, &value);
+ g_assert(!ret && value <= UINT32_MAX);
+ g_strfreev(args);
+
+ return value;
+}
+
+uint8_t qtest_inb(QTestState *s, uint16_t addr)
+{
+ return qtest_in(s, "inb", addr);
+}
+
+uint16_t qtest_inw(QTestState *s, uint16_t addr)
+{
+ return qtest_in(s, "inw", addr);
+}
+
+uint32_t qtest_inl(QTestState *s, uint16_t addr)
+{
+ return qtest_in(s, "inl", addr);
+}
+
+static void qtest_write(QTestState *s, const char *cmd, uint64_t addr,
+ uint64_t value)
+{
+ qtest_sendf(s, "%s 0x%" PRIx64 " 0x%" PRIx64 "\n", cmd, addr, value);
+ qtest_rsp(s, 0);
+}
+
+void qtest_writeb(QTestState *s, uint64_t addr, uint8_t value)
+{
+ qtest_write(s, "writeb", addr, value);
+}
+
+void qtest_writew(QTestState *s, uint64_t addr, uint16_t value)
+{
+ qtest_write(s, "writew", addr, value);
+}
+
+void qtest_writel(QTestState *s, uint64_t addr, uint32_t value)
+{
+ qtest_write(s, "writel", addr, value);
+}
+
+void qtest_writeq(QTestState *s, uint64_t addr, uint64_t value)
+{
+ qtest_write(s, "writeq", addr, value);
+}
+
+static uint64_t qtest_read(QTestState *s, const char *cmd, uint64_t addr)
+{
+ gchar **args;
+ int ret;
+ uint64_t value;
+
+ qtest_sendf(s, "%s 0x%" PRIx64 "\n", cmd, addr);
+ args = qtest_rsp(s, 2);
+ ret = qemu_strtou64(args[1], NULL, 0, &value);
+ g_assert(!ret);
+ g_strfreev(args);
+
+ return value;
+}
+
+uint8_t qtest_readb(QTestState *s, uint64_t addr)
+{
+ return qtest_read(s, "readb", addr);
+}
+
+uint16_t qtest_readw(QTestState *s, uint64_t addr)
+{
+ return qtest_read(s, "readw", addr);
+}
+
+uint32_t qtest_readl(QTestState *s, uint64_t addr)
+{
+ return qtest_read(s, "readl", addr);
+}
+
+uint64_t qtest_readq(QTestState *s, uint64_t addr)
+{
+ return qtest_read(s, "readq", addr);
+}
+
+static int hex2nib(char ch)
+{
+ if (ch >= '0' && ch <= '9') {
+ return ch - '0';
+ } else if (ch >= 'a' && ch <= 'f') {
+ return 10 + (ch - 'a');
+ } else if (ch >= 'A' && ch <= 'F') {
+ return 10 + (ch - 'a');
+ } else {
+ return -1;
+ }
+}
+
+void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size)
+{
+ uint8_t *ptr = data;
+ gchar **args;
+ size_t i;
+
+ if (!size) {
+ return;
+ }
+
+ qtest_sendf(s, "read 0x%" PRIx64 " 0x%zx\n", addr, size);
+ args = qtest_rsp(s, 2);
+
+ for (i = 0; i < size; i++) {
+ ptr[i] = hex2nib(args[1][2 + (i * 2)]) << 4;
+ ptr[i] |= hex2nib(args[1][2 + (i * 2) + 1]);
+ }
+
+ g_strfreev(args);
+}
+
+uint64_t qtest_rtas_call(QTestState *s, const char *name,
+ uint32_t nargs, uint64_t args,
+ uint32_t nret, uint64_t ret)
+{
+ qtest_sendf(s, "rtas %s %u 0x%"PRIx64" %u 0x%"PRIx64"\n",
+ name, nargs, args, nret, ret);
+ qtest_rsp(s, 0);
+ return 0;
+}
+
+void qtest_add_func(const char *str, void (*fn)(void))
+{
+ gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
+ g_test_add_func(path, fn);
+ g_free(path);
+}
+
+void qtest_add_data_func_full(const char *str, void *data,
+ void (*fn)(const void *),
+ GDestroyNotify data_free_func)
+{
+ gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
+ g_test_add_data_func_full(path, data, fn, data_free_func);
+ g_free(path);
+}
+
+void qtest_add_data_func(const char *str, const void *data,
+ void (*fn)(const void *))
+{
+ gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str);
+ g_test_add_data_func(path, data, fn);
+ g_free(path);
+}
+
+void qtest_bufwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
+{
+ gchar *bdata;
+
+ bdata = g_base64_encode(data, size);
+ qtest_sendf(s, "b64write 0x%" PRIx64 " 0x%zx ", addr, size);
+ socket_send(s->fd, bdata, strlen(bdata));
+ socket_send(s->fd, "\n", 1);
+ qtest_rsp(s, 0);
+ g_free(bdata);
+}
+
+void qtest_bufread(QTestState *s, uint64_t addr, void *data, size_t size)
+{
+ gchar **args;
+ size_t len;
+
+ qtest_sendf(s, "b64read 0x%" PRIx64 " 0x%zx\n", addr, size);
+ args = qtest_rsp(s, 2);
+
+ g_base64_decode_inplace(args[1], &len);
+ if (size != len) {
+ fprintf(stderr, "bufread: asked for %zu bytes but decoded %zu\n",
+ size, len);
+ len = MIN(len, size);
+ }
+
+ memcpy(data, args[1], len);
+ g_strfreev(args);
+}
+
+void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
+{
+ const uint8_t *ptr = data;
+ size_t i;
+ char *enc;
+
+ if (!size) {
+ return;
+ }
+
+ enc = g_malloc(2 * size + 1);
+
+ for (i = 0; i < size; i++) {
+ sprintf(&enc[i * 2], "%02x", ptr[i]);
+ }
+
+ qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x%s\n", addr, size, enc);
+ qtest_rsp(s, 0);
+ g_free(enc);
+}
+
+void qtest_memset(QTestState *s, uint64_t addr, uint8_t pattern, size_t size)
+{
+ qtest_sendf(s, "memset 0x%" PRIx64 " 0x%zx 0x%02x\n", addr, size, pattern);
+ qtest_rsp(s, 0);
+}
+
+void qtest_qmp_assert_success(QTestState *qts, const char *fmt, ...)
+{
+ va_list ap;
+ QDict *response;
+
+ va_start(ap, fmt);
+ response = qtest_vqmp(qts, fmt, ap);
+ va_end(ap);
+
+ g_assert(response);
+ if (!qdict_haskey(response, "return")) {
+ QString *s = qobject_to_json_pretty(QOBJECT(response));
+ g_test_message("%s", qstring_get_str(s));
+ qobject_unref(s);
+ }
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+bool qtest_big_endian(QTestState *s)
+{
+ return s->big_endian;
+}
+
+static bool qtest_check_machine_version(const char *mname, const char *basename,
+ int major, int minor)
+{
+ char *newname;
+ bool is_equal;
+
+ newname = g_strdup_printf("%s-%i.%i", basename, major, minor);
+ is_equal = g_str_equal(mname, newname);
+ g_free(newname);
+
+ return is_equal;
+}
+
+static bool qtest_is_old_versioned_machine(const char *mname)
+{
+ const char *dash = strrchr(mname, '-');
+ const char *dot = strrchr(mname, '.');
+ const char *chr;
+ char *bname;
+ const int major = QEMU_VERSION_MAJOR;
+ const int minor = QEMU_VERSION_MINOR;
+ bool res = false;
+
+ if (dash && dot && dot > dash) {
+ for (chr = dash + 1; *chr; chr++) {
+ if (!qemu_isdigit(*chr) && *chr != '.') {
+ return false;
+ }
+ }
+ /*
+ * Now check if it is one of the latest versions. Check major + 1
+ * and minor + 1 versions as well, since they might already exist
+ * in the development branch.
+ */
+ bname = g_strdup(mname);
+ bname[dash - mname] = 0;
+ res = !qtest_check_machine_version(mname, bname, major + 1, 0) &&
+ !qtest_check_machine_version(mname, bname, major, minor + 1) &&
+ !qtest_check_machine_version(mname, bname, major, minor);
+ g_free(bname);
+ }
+
+ return res;
+}
+
+void qtest_cb_for_every_machine(void (*cb)(const char *machine),
+ bool skip_old_versioned)
+{
+ QDict *response, *minfo;
+ QList *list;
+ const QListEntry *p;
+ QObject *qobj;
+ QString *qstr;
+ const char *mname;
+ QTestState *qts;
+
+ qts = qtest_init("-machine none");
+ response = qtest_qmp(qts, "{ 'execute': 'query-machines' }");
+ g_assert(response);
+ list = qdict_get_qlist(response, "return");
+ g_assert(list);
+
+ for (p = qlist_first(list); p; p = qlist_next(p)) {
+ 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);
+ if (!skip_old_versioned || !qtest_is_old_versioned_machine(mname)) {
+ cb(mname);
+ }
+ }
+
+ qtest_quit(qts);
+ qobject_unref(response);
+}
+
+QDict *qtest_qmp_receive_success(QTestState *s,
+ void (*event_cb)(void *opaque,
+ const char *event,
+ QDict *data),
+ void *opaque)
+{
+ QDict *response, *ret, *data;
+ const char *event;
+
+ for (;;) {
+ response = qtest_qmp_receive(s);
+ g_assert(!qdict_haskey(response, "error"));
+ ret = qdict_get_qdict(response, "return");
+ if (ret) {
+ break;
+ }
+ event = qdict_get_str(response, "event");
+ data = qdict_get_qdict(response, "data");
+ if (event_cb) {
+ event_cb(opaque, event, data);
+ }
+ qobject_unref(response);
+ }
+
+ qobject_ref(ret);
+ qobject_unref(response);
+ return ret;
+}
+
+/*
+ * Generic hot-plugging test via the device_add QMP commands.
+ */
+void qtest_qmp_device_add_qdict(QTestState *qts, const char *drv,
+ const QDict *arguments)
+{
+ QDict *resp;
+ QDict *args = arguments ? qdict_clone_shallow(arguments) : qdict_new();
+
+ g_assert(!qdict_haskey(args, "driver"));
+ qdict_put_str(args, "driver", drv);
+ resp = qtest_qmp(qts, "{'execute': 'device_add', 'arguments': %p}", args);
+ g_assert(resp);
+ g_assert(!qdict_haskey(resp, "event")); /* We don't expect any events */
+ g_assert(!qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+}
+
+void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
+ const char *fmt, ...)
+{
+ QDict *args;
+ va_list ap;
+
+ va_start(ap, fmt);
+ args = qdict_from_vjsonf_nofail(fmt, ap);
+ va_end(ap);
+
+ g_assert(!qdict_haskey(args, "id"));
+ qdict_put_str(args, "id", id);
+
+ qtest_qmp_device_add_qdict(qts, driver, args);
+ qobject_unref(args);
+}
+
+static void device_deleted_cb(void *opaque, const char *name, QDict *data)
+{
+ bool *got_event = opaque;
+
+ g_assert_cmpstr(name, ==, "DEVICE_DELETED");
+ *got_event = true;
+}
+
+/*
+ * Generic hot-unplugging test via the device_del QMP command.
+ * Device deletion will get one response and one event. For example:
+ *
+ * {'execute': 'device_del','arguments': { 'id': 'scsi-hd'}}
+ *
+ * will get this one:
+ *
+ * {"timestamp": {"seconds": 1505289667, "microseconds": 569862},
+ * "event": "DEVICE_DELETED", "data": {"device": "scsi-hd",
+ * "path": "/machine/peripheral/scsi-hd"}}
+ *
+ * and this one:
+ *
+ * {"return": {}}
+ *
+ * But the order of arrival may vary - so we've got to detect both.
+ */
+void qtest_qmp_device_del(QTestState *qts, const char *id)
+{
+ bool got_event = false;
+ QDict *rsp;
+
+ qtest_qmp_send(qts, "{'execute': 'device_del', 'arguments': {'id': %s}}",
+ id);
+ rsp = qtest_qmp_receive_success(qts, device_deleted_cb, &got_event);
+ qobject_unref(rsp);
+ if (!got_event) {
+ rsp = qtest_qmp_receive(qts);
+ g_assert_cmpstr(qdict_get_try_str(rsp, "event"),
+ ==, "DEVICE_DELETED");
+ qobject_unref(rsp);
+ }
+}
+
+bool qmp_rsp_is_err(QDict *rsp)
+{
+ QDict *error = qdict_get_qdict(rsp, "error");
+ qobject_unref(rsp);
+ return !!error;
+}
+
+void qmp_assert_error_class(QDict *rsp, const char *class)
+{
+ QDict *error = qdict_get_qdict(rsp, "error");
+
+ g_assert_cmpstr(qdict_get_try_str(error, "class"), ==, class);
+ g_assert_nonnull(qdict_get_try_str(error, "desc"));
+ g_assert(!qdict_haskey(rsp, "return"));
+
+ qobject_unref(rsp);
+}
diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h
new file mode 100644
index 0000000000..c9e21e05b3
--- /dev/null
+++ b/tests/qtest/libqtest.h
@@ -0,0 +1,732 @@
+/*
+ * QTest
+ *
+ * Copyright IBM, Corp. 2012
+ * Copyright Red Hat, Inc. 2012
+ * Copyright SUSE LINUX Products GmbH 2013
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ * Paolo Bonzini <pbonzini@redhat.com>
+ * Andreas Färber <afaerber@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+#ifndef LIBQTEST_H
+#define LIBQTEST_H
+
+#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qdict.h"
+
+typedef struct QTestState QTestState;
+
+/**
+ * qtest_initf:
+ * @fmt...: Format for creating other arguments to pass to QEMU, formatted
+ * like sprintf().
+ *
+ * Convenience wrapper around qtest_init().
+ *
+ * Returns: #QTestState instance.
+ */
+QTestState *qtest_initf(const char *fmt, ...) GCC_FMT_ATTR(1, 2);
+
+/**
+ * qtest_vinitf:
+ * @fmt: Format for creating other arguments to pass to QEMU, formatted
+ * like vsprintf().
+ * @ap: Format arguments.
+ *
+ * Convenience wrapper around qtest_init().
+ *
+ * Returns: #QTestState instance.
+ */
+QTestState *qtest_vinitf(const char *fmt, va_list ap) GCC_FMT_ATTR(1, 0);
+
+/**
+ * qtest_init:
+ * @extra_args: other arguments to pass to QEMU. CAUTION: these
+ * arguments are subject to word splitting and shell evaluation.
+ *
+ * Returns: #QTestState instance.
+ */
+QTestState *qtest_init(const char *extra_args);
+
+/**
+ * qtest_init_without_qmp_handshake:
+ * @extra_args: other arguments to pass to QEMU. CAUTION: these
+ * arguments are subject to word splitting and shell evaluation.
+ *
+ * Returns: #QTestState instance.
+ */
+QTestState *qtest_init_without_qmp_handshake(const char *extra_args);
+
+/**
+ * qtest_init_with_serial:
+ * @extra_args: other arguments to pass to QEMU. CAUTION: these
+ * arguments are subject to word splitting and shell evaluation.
+ * @sock_fd: pointer to store the socket file descriptor for
+ * connection with serial.
+ *
+ * Returns: #QTestState instance.
+ */
+QTestState *qtest_init_with_serial(const char *extra_args, int *sock_fd);
+
+/**
+ * qtest_quit:
+ * @s: #QTestState instance to operate on.
+ *
+ * Shut down the QEMU process associated to @s.
+ */
+void qtest_quit(QTestState *s);
+
+/**
+ * qtest_qmp_fds:
+ * @s: #QTestState instance to operate on.
+ * @fds: array of file descriptors
+ * @fds_num: number of elements in @fds
+ * @fmt...: QMP message to send to qemu, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ *
+ * Sends a QMP message to QEMU with fds and returns the response.
+ */
+QDict *qtest_qmp_fds(QTestState *s, int *fds, size_t fds_num,
+ const char *fmt, ...)
+ GCC_FMT_ATTR(4, 5);
+
+/**
+ * qtest_qmp:
+ * @s: #QTestState instance to operate on.
+ * @fmt...: QMP message to send to qemu, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ *
+ * Sends a QMP message to QEMU and returns the response.
+ */
+QDict *qtest_qmp(QTestState *s, const char *fmt, ...)
+ GCC_FMT_ATTR(2, 3);
+
+/**
+ * qtest_qmp_send:
+ * @s: #QTestState instance to operate on.
+ * @fmt...: QMP message to send to qemu, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ *
+ * Sends a QMP message to QEMU and leaves the response in the stream.
+ */
+void qtest_qmp_send(QTestState *s, const char *fmt, ...)
+ GCC_FMT_ATTR(2, 3);
+
+/**
+ * qtest_qmp_send_raw:
+ * @s: #QTestState instance to operate on.
+ * @fmt...: text to send, formatted like sprintf()
+ *
+ * Sends text to the QMP monitor verbatim. Need not be valid JSON;
+ * this is useful for negative tests.
+ */
+void qtest_qmp_send_raw(QTestState *s, const char *fmt, ...)
+ GCC_FMT_ATTR(2, 3);
+
+/**
+ * qtest_vqmp_fds:
+ * @s: #QTestState instance to operate on.
+ * @fds: array of file descriptors
+ * @fds_num: number of elements in @fds
+ * @fmt: QMP message to send to QEMU, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ * @ap: QMP message arguments
+ *
+ * Sends a QMP message to QEMU with fds and returns the response.
+ */
+QDict *qtest_vqmp_fds(QTestState *s, int *fds, size_t fds_num,
+ const char *fmt, va_list ap)
+ GCC_FMT_ATTR(4, 0);
+
+/**
+ * qtest_vqmp:
+ * @s: #QTestState instance to operate on.
+ * @fmt: QMP message to send to QEMU, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ * @ap: QMP message arguments
+ *
+ * Sends a QMP message to QEMU and returns the response.
+ */
+QDict *qtest_vqmp(QTestState *s, const char *fmt, va_list ap)
+ GCC_FMT_ATTR(2, 0);
+
+/**
+ * qtest_qmp_vsend_fds:
+ * @s: #QTestState instance to operate on.
+ * @fds: array of file descriptors
+ * @fds_num: number of elements in @fds
+ * @fmt: QMP message to send to QEMU, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ * @ap: QMP message arguments
+ *
+ * Sends a QMP message to QEMU and leaves the response in the stream.
+ */
+void qtest_qmp_vsend_fds(QTestState *s, int *fds, size_t fds_num,
+ const char *fmt, va_list ap)
+ GCC_FMT_ATTR(4, 0);
+
+/**
+ * qtest_qmp_vsend:
+ * @s: #QTestState instance to operate on.
+ * @fmt: QMP message to send to QEMU, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ * @ap: QMP message arguments
+ *
+ * Sends a QMP message to QEMU and leaves the response in the stream.
+ */
+void qtest_qmp_vsend(QTestState *s, const char *fmt, va_list ap)
+ GCC_FMT_ATTR(2, 0);
+
+/**
+ * qtest_receive:
+ * @s: #QTestState instance to operate on.
+ *
+ * Reads a QMP message from QEMU and returns the response.
+ */
+QDict *qtest_qmp_receive(QTestState *s);
+
+/**
+ * qtest_qmp_eventwait:
+ * @s: #QTestState instance to operate on.
+ * @s: #event event to wait for.
+ *
+ * Continuously polls for QMP responses until it receives the desired event.
+ */
+void qtest_qmp_eventwait(QTestState *s, const char *event);
+
+/**
+ * qtest_qmp_eventwait_ref:
+ * @s: #QTestState instance to operate on.
+ * @s: #event event to wait for.
+ *
+ * Continuously polls for QMP responses until it receives the desired event.
+ * Returns a copy of the event for further investigation.
+ */
+QDict *qtest_qmp_eventwait_ref(QTestState *s, const char *event);
+
+/**
+ * qtest_qmp_receive_success:
+ * @s: #QTestState instance to operate on
+ * @event_cb: Event callback
+ * @opaque: Argument for @event_cb
+ *
+ * Poll QMP messages until a command success response is received.
+ * If @event_cb, call it for each event received, passing @opaque,
+ * the event's name and data.
+ * Return the success response's "return" member.
+ */
+QDict *qtest_qmp_receive_success(QTestState *s,
+ void (*event_cb)(void *opaque,
+ const char *name,
+ QDict *data),
+ void *opaque);
+
+/**
+ * qtest_hmp:
+ * @s: #QTestState instance to operate on.
+ * @fmt...: HMP command to send to QEMU, formats arguments like sprintf().
+ *
+ * Send HMP command to QEMU via QMP's human-monitor-command.
+ * QMP events are discarded.
+ *
+ * Returns: the command's output. The caller should g_free() it.
+ */
+char *qtest_hmp(QTestState *s, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
+
+/**
+ * qtest_hmpv:
+ * @s: #QTestState instance to operate on.
+ * @fmt: HMP command to send to QEMU, formats arguments like vsprintf().
+ * @ap: HMP command arguments
+ *
+ * Send HMP command to QEMU via QMP's human-monitor-command.
+ * QMP events are discarded.
+ *
+ * Returns: the command's output. The caller should g_free() it.
+ */
+char *qtest_vhmp(QTestState *s, const char *fmt, va_list ap)
+ GCC_FMT_ATTR(2, 0);
+
+void qtest_module_load(QTestState *s, const char *prefix, const char *libname);
+
+/**
+ * qtest_get_irq:
+ * @s: #QTestState instance to operate on.
+ * @num: Interrupt to observe.
+ *
+ * Returns: The level of the @num interrupt.
+ */
+bool qtest_get_irq(QTestState *s, int num);
+
+/**
+ * qtest_irq_intercept_in:
+ * @s: #QTestState instance to operate on.
+ * @string: QOM path of a device.
+ *
+ * Associate qtest irqs with the GPIO-in pins of the device
+ * whose path is specified by @string.
+ */
+void qtest_irq_intercept_in(QTestState *s, const char *string);
+
+/**
+ * qtest_irq_intercept_out:
+ * @s: #QTestState instance to operate on.
+ * @string: QOM path of a device.
+ *
+ * Associate qtest irqs with the GPIO-out pins of the device
+ * whose path is specified by @string.
+ */
+void qtest_irq_intercept_out(QTestState *s, const char *string);
+
+/**
+ * qtest_set_irq_in:
+ * @s: QTestState instance to operate on.
+ * @string: QOM path of a device
+ * @name: IRQ name
+ * @irq: IRQ number
+ * @level: IRQ level
+ *
+ * Force given device/irq GPIO-in pin to the given level.
+ */
+void qtest_set_irq_in(QTestState *s, const char *string, const char *name,
+ int irq, int level);
+
+/**
+ * qtest_outb:
+ * @s: #QTestState instance to operate on.
+ * @addr: I/O port to write to.
+ * @value: Value being written.
+ *
+ * Write an 8-bit value to an I/O port.
+ */
+void qtest_outb(QTestState *s, uint16_t addr, uint8_t value);
+
+/**
+ * qtest_outw:
+ * @s: #QTestState instance to operate on.
+ * @addr: I/O port to write to.
+ * @value: Value being written.
+ *
+ * Write a 16-bit value to an I/O port.
+ */
+void qtest_outw(QTestState *s, uint16_t addr, uint16_t value);
+
+/**
+ * qtest_outl:
+ * @s: #QTestState instance to operate on.
+ * @addr: I/O port to write to.
+ * @value: Value being written.
+ *
+ * Write a 32-bit value to an I/O port.
+ */
+void qtest_outl(QTestState *s, uint16_t addr, uint32_t value);
+
+/**
+ * qtest_inb:
+ * @s: #QTestState instance to operate on.
+ * @addr: I/O port to read from.
+ *
+ * Returns an 8-bit value from an I/O port.
+ */
+uint8_t qtest_inb(QTestState *s, uint16_t addr);
+
+/**
+ * qtest_inw:
+ * @s: #QTestState instance to operate on.
+ * @addr: I/O port to read from.
+ *
+ * Returns a 16-bit value from an I/O port.
+ */
+uint16_t qtest_inw(QTestState *s, uint16_t addr);
+
+/**
+ * qtest_inl:
+ * @s: #QTestState instance to operate on.
+ * @addr: I/O port to read from.
+ *
+ * Returns a 32-bit value from an I/O port.
+ */
+uint32_t qtest_inl(QTestState *s, uint16_t addr);
+
+/**
+ * qtest_writeb:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes an 8-bit value to memory.
+ */
+void qtest_writeb(QTestState *s, uint64_t addr, uint8_t value);
+
+/**
+ * qtest_writew:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 16-bit value to memory.
+ */
+void qtest_writew(QTestState *s, uint64_t addr, uint16_t value);
+
+/**
+ * qtest_writel:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 32-bit value to memory.
+ */
+void qtest_writel(QTestState *s, uint64_t addr, uint32_t value);
+
+/**
+ * qtest_writeq:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @value: Value being written.
+ *
+ * Writes a 64-bit value to memory.
+ */
+void qtest_writeq(QTestState *s, uint64_t addr, uint64_t value);
+
+/**
+ * qtest_readb:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads an 8-bit value from memory.
+ *
+ * Returns: Value read.
+ */
+uint8_t qtest_readb(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_readw:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads a 16-bit value from memory.
+ *
+ * Returns: Value read.
+ */
+uint16_t qtest_readw(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_readl:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads a 32-bit value from memory.
+ *
+ * Returns: Value read.
+ */
+uint32_t qtest_readl(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_readq:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ *
+ * Reads a 64-bit value from memory.
+ *
+ * Returns: Value read.
+ */
+uint64_t qtest_readq(QTestState *s, uint64_t addr);
+
+/**
+ * qtest_memread:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ * @data: Pointer to where memory contents will be stored.
+ * @size: Number of bytes to read.
+ *
+ * Read guest memory into a buffer.
+ */
+void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size);
+
+/**
+ * qtest_rtas_call:
+ * @s: #QTestState instance to operate on.
+ * @name: name of the command to call.
+ * @nargs: Number of args.
+ * @args: Guest address to read args from.
+ * @nret: Number of return value.
+ * @ret: Guest address to write return values to.
+ *
+ * Call an RTAS function
+ */
+uint64_t qtest_rtas_call(QTestState *s, const char *name,
+ uint32_t nargs, uint64_t args,
+ uint32_t nret, uint64_t ret);
+
+/**
+ * qtest_bufread:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to read from.
+ * @data: Pointer to where memory contents will be stored.
+ * @size: Number of bytes to read.
+ *
+ * Read guest memory into a buffer and receive using a base64 encoding.
+ */
+void qtest_bufread(QTestState *s, uint64_t addr, void *data, size_t size);
+
+/**
+ * qtest_memwrite:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @data: Pointer to the bytes that will be written to guest memory.
+ * @size: Number of bytes to write.
+ *
+ * Write a buffer to guest memory.
+ */
+void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size);
+
+/**
+ * qtest_bufwrite:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @data: Pointer to the bytes that will be written to guest memory.
+ * @size: Number of bytes to write.
+ *
+ * Write a buffer to guest memory and transmit using a base64 encoding.
+ */
+void qtest_bufwrite(QTestState *s, uint64_t addr,
+ const void *data, size_t size);
+
+/**
+ * qtest_memset:
+ * @s: #QTestState instance to operate on.
+ * @addr: Guest address to write to.
+ * @patt: Byte pattern to fill the guest memory region with.
+ * @size: Number of bytes to write.
+ *
+ * Write a pattern to guest memory.
+ */
+void qtest_memset(QTestState *s, uint64_t addr, uint8_t patt, size_t size);
+
+/**
+ * qtest_clock_step_next:
+ * @s: #QTestState instance to operate on.
+ *
+ * Advance the QEMU_CLOCK_VIRTUAL to the next deadline.
+ *
+ * Returns: The current value of the QEMU_CLOCK_VIRTUAL in nanoseconds.
+ */
+int64_t qtest_clock_step_next(QTestState *s);
+
+/**
+ * qtest_clock_step:
+ * @s: QTestState instance to operate on.
+ * @step: Number of nanoseconds to advance the clock by.
+ *
+ * Advance the QEMU_CLOCK_VIRTUAL by @step nanoseconds.
+ *
+ * Returns: The current value of the QEMU_CLOCK_VIRTUAL in nanoseconds.
+ */
+int64_t qtest_clock_step(QTestState *s, int64_t step);
+
+/**
+ * qtest_clock_set:
+ * @s: QTestState instance to operate on.
+ * @val: Nanoseconds value to advance the clock to.
+ *
+ * Advance the QEMU_CLOCK_VIRTUAL to @val nanoseconds since the VM was launched.
+ *
+ * Returns: The current value of the QEMU_CLOCK_VIRTUAL in nanoseconds.
+ */
+int64_t qtest_clock_set(QTestState *s, int64_t val);
+
+/**
+ * qtest_big_endian:
+ * @s: QTestState instance to operate on.
+ *
+ * Returns: True if the architecture under test has a big endian configuration.
+ */
+bool qtest_big_endian(QTestState *s);
+
+/**
+ * qtest_get_arch:
+ *
+ * Returns: The architecture for the QEMU executable under test.
+ */
+const char *qtest_get_arch(void);
+
+/**
+ * qtest_add_func:
+ * @str: Test case path.
+ * @fn: Test case function
+ *
+ * Add a GTester testcase with the given name and function.
+ * The path is prefixed with the architecture under test, as
+ * returned by qtest_get_arch().
+ */
+void qtest_add_func(const char *str, void (*fn)(void));
+
+/**
+ * qtest_add_data_func:
+ * @str: Test case path.
+ * @data: Test case data
+ * @fn: Test case function
+ *
+ * Add a GTester testcase with the given name, data and function.
+ * The path is prefixed with the architecture under test, as
+ * returned by qtest_get_arch().
+ */
+void qtest_add_data_func(const char *str, const void *data,
+ void (*fn)(const void *));
+
+/**
+ * qtest_add_data_func_full:
+ * @str: Test case path.
+ * @data: Test case data
+ * @fn: Test case function
+ * @data_free_func: GDestroyNotify for data
+ *
+ * Add a GTester testcase with the given name, data and function.
+ * The path is prefixed with the architecture under test, as
+ * returned by qtest_get_arch().
+ *
+ * @data is passed to @data_free_func() on test completion.
+ */
+void qtest_add_data_func_full(const char *str, void *data,
+ void (*fn)(const void *),
+ GDestroyNotify data_free_func);
+
+/**
+ * qtest_add:
+ * @testpath: Test case path
+ * @Fixture: Fixture type
+ * @tdata: Test case data
+ * @fsetup: Test case setup function
+ * @ftest: Test case function
+ * @fteardown: Test case teardown function
+ *
+ * Add a GTester testcase with the given name, data and functions.
+ * The path is prefixed with the architecture under test, as
+ * returned by qtest_get_arch().
+ */
+#define qtest_add(testpath, Fixture, tdata, fsetup, ftest, fteardown) \
+ do { \
+ char *path = g_strdup_printf("/%s/%s", qtest_get_arch(), testpath); \
+ g_test_add(path, Fixture, tdata, fsetup, ftest, fteardown); \
+ g_free(path); \
+ } while (0)
+
+void qtest_add_abrt_handler(GHookFunc fn, const void *data);
+
+/**
+ * qtest_qmp_assert_success:
+ * @qts: QTestState instance to operate on
+ * @fmt...: QMP message to send to qemu, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ *
+ * Sends a QMP message to QEMU and asserts that a 'return' key is present in
+ * the response.
+ */
+void qtest_qmp_assert_success(QTestState *qts, const char *fmt, ...)
+ GCC_FMT_ATTR(2, 3);
+
+QDict *qmp_fd_receive(int fd);
+void qmp_fd_vsend_fds(int fd, int *fds, size_t fds_num,
+ const char *fmt, va_list ap) GCC_FMT_ATTR(4, 0);
+void qmp_fd_vsend(int fd, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
+void qmp_fd_send(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
+void qmp_fd_send_raw(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
+void qmp_fd_vsend_raw(int fd, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
+QDict *qmp_fdv(int fd, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0);
+QDict *qmp_fd(int fd, const char *fmt, ...) GCC_FMT_ATTR(2, 3);
+
+/**
+ * qtest_cb_for_every_machine:
+ * @cb: Pointer to the callback function
+ * @skip_old_versioned: true if versioned old machine types should be skipped
+ *
+ * Call a callback function for every name of all available machines.
+ */
+void qtest_cb_for_every_machine(void (*cb)(const char *machine),
+ bool skip_old_versioned);
+
+/**
+ * qtest_qmp_device_add_qdict:
+ * @qts: QTestState instance to operate on
+ * @drv: Name of the device that should be added
+ * @arguments: QDict with properties for the device to intialize
+ *
+ * Generic hot-plugging test via the device_add QMP command with properties
+ * supplied in form of QDict. Use NULL for empty properties list.
+ */
+void qtest_qmp_device_add_qdict(QTestState *qts, const char *drv,
+ const QDict *arguments);
+
+/**
+ * qtest_qmp_device_add:
+ * @qts: QTestState instance to operate on
+ * @driver: Name of the device that should be added
+ * @id: Identification string
+ * @fmt...: QMP message to send to qemu, formatted like
+ * qobject_from_jsonf_nofail(). See parse_escape() for what's
+ * supported after '%'.
+ *
+ * Generic hot-plugging test via the device_add QMP command.
+ */
+void qtest_qmp_device_add(QTestState *qts, const char *driver, const char *id,
+ const char *fmt, ...) GCC_FMT_ATTR(4, 5);
+
+/**
+ * qtest_qmp_device_del:
+ * @qts: QTestState instance to operate on
+ * @id: Identification string
+ *
+ * Generic hot-unplugging test via the device_del QMP command.
+ */
+void qtest_qmp_device_del(QTestState *qts, const char *id);
+
+/**
+ * qmp_rsp_is_err:
+ * @rsp: QMP response to check for error
+ *
+ * Test @rsp for error and discard @rsp.
+ * Returns 'true' if there is error in @rsp and 'false' otherwise.
+ */
+bool qmp_rsp_is_err(QDict *rsp);
+
+/**
+ * qmp_assert_error_class:
+ * @rsp: QMP response to check for error
+ * @class: an error class
+ *
+ * Assert the response has the given error class and discard @rsp.
+ */
+void qmp_assert_error_class(QDict *rsp, const char *class);
+
+/**
+ * qtest_probe_child:
+ * @s: QTestState instance to operate on.
+ *
+ * Returns: true if the child is still alive.
+ */
+bool qtest_probe_child(QTestState *s);
+
+/**
+ * qtest_set_expected_status:
+ * @s: QTestState instance to operate on.
+ * @status: an expected exit status.
+ *
+ * Set expected exit status of the child.
+ */
+void qtest_set_expected_status(QTestState *s, int status);
+
+#endif
diff --git a/tests/qtest/m25p80-test.c b/tests/qtest/m25p80-test.c
new file mode 100644
index 0000000000..50c6b79fb3
--- /dev/null
+++ b/tests/qtest/m25p80-test.c
@@ -0,0 +1,382 @@
+/*
+ * QTest testcase for the M25P80 Flash (Using the Aspeed SPI
+ * Controller)
+ *
+ * Copyright (C) 2016 IBM Corp.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "libqtest-single.h"
+
+/*
+ * ASPEED SPI Controller registers
+ */
+#define R_CONF 0x00
+#define CONF_ENABLE_W0 (1 << 16)
+#define R_CE_CTRL 0x04
+#define CRTL_EXTENDED0 0 /* 32 bit addressing for SPI */
+#define R_CTRL0 0x10
+#define CTRL_CE_STOP_ACTIVE (1 << 2)
+#define CTRL_READMODE 0x0
+#define CTRL_FREADMODE 0x1
+#define CTRL_WRITEMODE 0x2
+#define CTRL_USERMODE 0x3
+
+#define ASPEED_FMC_BASE 0x1E620000
+#define ASPEED_FLASH_BASE 0x20000000
+
+/*
+ * Flash commands
+ */
+enum {
+ JEDEC_READ = 0x9f,
+ BULK_ERASE = 0xc7,
+ READ = 0x03,
+ PP = 0x02,
+ WREN = 0x6,
+ RESET_ENABLE = 0x66,
+ RESET_MEMORY = 0x99,
+ EN_4BYTE_ADDR = 0xB7,
+ ERASE_SECTOR = 0xd8,
+};
+
+#define FLASH_JEDEC 0x20ba19 /* n25q256a */
+#define FLASH_SIZE (32 * 1024 * 1024)
+
+#define PAGE_SIZE 256
+
+/*
+ * Use an explicit bswap for the values read/wrote to the flash region
+ * as they are BE and the Aspeed CPU is LE.
+ */
+static inline uint32_t make_be32(uint32_t data)
+{
+ return bswap32(data);
+}
+
+static void spi_conf(uint32_t value)
+{
+ uint32_t conf = readl(ASPEED_FMC_BASE + R_CONF);
+
+ conf |= value;
+ writel(ASPEED_FMC_BASE + R_CONF, conf);
+}
+
+static void spi_conf_remove(uint32_t value)
+{
+ uint32_t conf = readl(ASPEED_FMC_BASE + R_CONF);
+
+ conf &= ~value;
+ writel(ASPEED_FMC_BASE + R_CONF, conf);
+}
+
+static void spi_ce_ctrl(uint32_t value)
+{
+ uint32_t conf = readl(ASPEED_FMC_BASE + R_CE_CTRL);
+
+ conf |= value;
+ writel(ASPEED_FMC_BASE + R_CE_CTRL, conf);
+}
+
+static void spi_ctrl_setmode(uint8_t mode, uint8_t cmd)
+{
+ uint32_t ctrl = readl(ASPEED_FMC_BASE + R_CTRL0);
+ ctrl &= ~(CTRL_USERMODE | 0xff << 16);
+ ctrl |= mode | (cmd << 16);
+ writel(ASPEED_FMC_BASE + R_CTRL0, ctrl);
+}
+
+static void spi_ctrl_start_user(void)
+{
+ uint32_t ctrl = readl(ASPEED_FMC_BASE + R_CTRL0);
+
+ ctrl |= CTRL_USERMODE | CTRL_CE_STOP_ACTIVE;
+ writel(ASPEED_FMC_BASE + R_CTRL0, ctrl);
+
+ ctrl &= ~CTRL_CE_STOP_ACTIVE;
+ writel(ASPEED_FMC_BASE + R_CTRL0, ctrl);
+}
+
+static void spi_ctrl_stop_user(void)
+{
+ uint32_t ctrl = readl(ASPEED_FMC_BASE + R_CTRL0);
+
+ ctrl |= CTRL_USERMODE | CTRL_CE_STOP_ACTIVE;
+ writel(ASPEED_FMC_BASE + R_CTRL0, ctrl);
+}
+
+static void flash_reset(void)
+{
+ spi_conf(CONF_ENABLE_W0);
+
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, RESET_ENABLE);
+ writeb(ASPEED_FLASH_BASE, RESET_MEMORY);
+ spi_ctrl_stop_user();
+
+ spi_conf_remove(CONF_ENABLE_W0);
+}
+
+static void test_read_jedec(void)
+{
+ uint32_t jedec = 0x0;
+
+ spi_conf(CONF_ENABLE_W0);
+
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, JEDEC_READ);
+ jedec |= readb(ASPEED_FLASH_BASE) << 16;
+ jedec |= readb(ASPEED_FLASH_BASE) << 8;
+ jedec |= readb(ASPEED_FLASH_BASE);
+ spi_ctrl_stop_user();
+
+ flash_reset();
+
+ g_assert_cmphex(jedec, ==, FLASH_JEDEC);
+}
+
+static void read_page(uint32_t addr, uint32_t *page)
+{
+ int i;
+
+ spi_ctrl_start_user();
+
+ writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+ writeb(ASPEED_FLASH_BASE, READ);
+ writel(ASPEED_FLASH_BASE, make_be32(addr));
+
+ /* Continuous read are supported */
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ page[i] = make_be32(readl(ASPEED_FLASH_BASE));
+ }
+ spi_ctrl_stop_user();
+}
+
+static void read_page_mem(uint32_t addr, uint32_t *page)
+{
+ int i;
+
+ /* move out USER mode to use direct reads from the AHB bus */
+ spi_ctrl_setmode(CTRL_READMODE, READ);
+
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ page[i] = make_be32(readl(ASPEED_FLASH_BASE + addr + i * 4));
+ }
+}
+
+static void test_erase_sector(void)
+{
+ uint32_t some_page_addr = 0x600 * PAGE_SIZE;
+ uint32_t page[PAGE_SIZE / 4];
+ int i;
+
+ spi_conf(CONF_ENABLE_W0);
+
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, WREN);
+ writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+ writeb(ASPEED_FLASH_BASE, ERASE_SECTOR);
+ writel(ASPEED_FLASH_BASE, make_be32(some_page_addr));
+ spi_ctrl_stop_user();
+
+ /* Previous page should be full of zeroes as backend is not
+ * initialized */
+ read_page(some_page_addr - PAGE_SIZE, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, 0x0);
+ }
+
+ /* But this one was erased */
+ read_page(some_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, 0xffffffff);
+ }
+
+ flash_reset();
+}
+
+static void test_erase_all(void)
+{
+ uint32_t some_page_addr = 0x15000 * PAGE_SIZE;
+ uint32_t page[PAGE_SIZE / 4];
+ int i;
+
+ spi_conf(CONF_ENABLE_W0);
+
+ /* Check some random page. Should be full of zeroes as backend is
+ * not initialized */
+ read_page(some_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, 0x0);
+ }
+
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, WREN);
+ writeb(ASPEED_FLASH_BASE, BULK_ERASE);
+ spi_ctrl_stop_user();
+
+ /* Recheck that some random page */
+ read_page(some_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, 0xffffffff);
+ }
+
+ flash_reset();
+}
+
+static void test_write_page(void)
+{
+ uint32_t my_page_addr = 0x14000 * PAGE_SIZE; /* beyond 16MB */
+ uint32_t some_page_addr = 0x15000 * PAGE_SIZE;
+ uint32_t page[PAGE_SIZE / 4];
+ int i;
+
+ spi_conf(CONF_ENABLE_W0);
+
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+ writeb(ASPEED_FLASH_BASE, WREN);
+ writeb(ASPEED_FLASH_BASE, PP);
+ writel(ASPEED_FLASH_BASE, make_be32(my_page_addr));
+
+ /* Fill the page with its own addresses */
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ writel(ASPEED_FLASH_BASE, make_be32(my_page_addr + i * 4));
+ }
+ spi_ctrl_stop_user();
+
+ /* Check what was written */
+ read_page(my_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+ }
+
+ /* Check some other page. It should be full of 0xff */
+ read_page(some_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, 0xffffffff);
+ }
+
+ flash_reset();
+}
+
+static void test_read_page_mem(void)
+{
+ uint32_t my_page_addr = 0x14000 * PAGE_SIZE; /* beyond 16MB */
+ uint32_t some_page_addr = 0x15000 * PAGE_SIZE;
+ uint32_t page[PAGE_SIZE / 4];
+ int i;
+
+ /* Enable 4BYTE mode for controller. This is should be strapped by
+ * HW for CE0 anyhow.
+ */
+ spi_ce_ctrl(1 << CRTL_EXTENDED0);
+
+ /* Enable 4BYTE mode for flash. */
+ spi_conf(CONF_ENABLE_W0);
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+ spi_ctrl_stop_user();
+ spi_conf_remove(CONF_ENABLE_W0);
+
+ /* Check what was written */
+ read_page_mem(my_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+ }
+
+ /* Check some other page. It should be full of 0xff */
+ read_page_mem(some_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, 0xffffffff);
+ }
+
+ flash_reset();
+}
+
+static void test_write_page_mem(void)
+{
+ uint32_t my_page_addr = 0x15000 * PAGE_SIZE;
+ uint32_t page[PAGE_SIZE / 4];
+ int i;
+
+ /* Enable 4BYTE mode for controller. This is should be strapped by
+ * HW for CE0 anyhow.
+ */
+ spi_ce_ctrl(1 << CRTL_EXTENDED0);
+
+ /* Enable 4BYTE mode for flash. */
+ spi_conf(CONF_ENABLE_W0);
+ spi_ctrl_start_user();
+ writeb(ASPEED_FLASH_BASE, EN_4BYTE_ADDR);
+ writeb(ASPEED_FLASH_BASE, WREN);
+ spi_ctrl_stop_user();
+
+ /* move out USER mode to use direct writes to the AHB bus */
+ spi_ctrl_setmode(CTRL_WRITEMODE, PP);
+
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ writel(ASPEED_FLASH_BASE + my_page_addr + i * 4,
+ make_be32(my_page_addr + i * 4));
+ }
+
+ /* Check what was written */
+ read_page_mem(my_page_addr, page);
+ for (i = 0; i < PAGE_SIZE / 4; i++) {
+ g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+ }
+
+ flash_reset();
+}
+
+static char tmp_path[] = "/tmp/qtest.m25p80.XXXXXX";
+
+int main(int argc, char **argv)
+{
+ int ret;
+ int fd;
+
+ g_test_init(&argc, &argv, NULL);
+
+ fd = mkstemp(tmp_path);
+ g_assert(fd >= 0);
+ ret = ftruncate(fd, FLASH_SIZE);
+ g_assert(ret == 0);
+ close(fd);
+
+ global_qtest = qtest_initf("-m 256 -machine palmetto-bmc "
+ "-drive file=%s,format=raw,if=mtd",
+ tmp_path);
+
+ qtest_add_func("/m25p80/read_jedec", test_read_jedec);
+ qtest_add_func("/m25p80/erase_sector", test_erase_sector);
+ qtest_add_func("/m25p80/erase_all", test_erase_all);
+ qtest_add_func("/m25p80/write_page", test_write_page);
+ qtest_add_func("/m25p80/read_page_mem", test_read_page_mem);
+ qtest_add_func("/m25p80/write_page_mem", test_write_page_mem);
+
+ ret = g_test_run();
+
+ qtest_quit(global_qtest);
+ unlink(tmp_path);
+ return ret;
+}
diff --git a/tests/qtest/m48t59-test.c b/tests/qtest/m48t59-test.c
new file mode 100644
index 0000000000..b94a1230f7
--- /dev/null
+++ b/tests/qtest/m48t59-test.c
@@ -0,0 +1,269 @@
+/*
+ * QTest testcase for the M48T59 and M48T08 real-time clocks
+ *
+ * Based on MC146818 RTC test:
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+
+#define RTC_SECONDS 0x9
+#define RTC_MINUTES 0xa
+#define RTC_HOURS 0xb
+
+#define RTC_DAY_OF_WEEK 0xc
+#define RTC_DAY_OF_MONTH 0xd
+#define RTC_MONTH 0xe
+#define RTC_YEAR 0xf
+
+static uint32_t base;
+static uint16_t reg_base = 0x1ff0; /* 0x7f0 for m48t02 */
+static int base_year;
+static const char *base_machine;
+static bool use_mmio;
+
+static uint8_t cmos_read_mmio(QTestState *s, uint8_t reg)
+{
+ return qtest_readb(s, base + (uint32_t)reg_base + (uint32_t)reg);
+}
+
+static void cmos_write_mmio(QTestState *s, uint8_t reg, uint8_t val)
+{
+ uint8_t data = val;
+
+ qtest_writeb(s, base + (uint32_t)reg_base + (uint32_t)reg, data);
+}
+
+static uint8_t cmos_read_ioio(QTestState *s, uint8_t reg)
+{
+ qtest_outw(s, base + 0, reg_base + (uint16_t)reg);
+ return qtest_inb(s, base + 3);
+}
+
+static void cmos_write_ioio(QTestState *s, uint8_t reg, uint8_t val)
+{
+ qtest_outw(s, base + 0, reg_base + (uint16_t)reg);
+ qtest_outb(s, base + 3, val);
+}
+
+static uint8_t cmos_read(QTestState *s, uint8_t reg)
+{
+ if (use_mmio) {
+ return cmos_read_mmio(s, reg);
+ } else {
+ return cmos_read_ioio(s, reg);
+ }
+}
+
+static void cmos_write(QTestState *s, uint8_t reg, uint8_t val)
+{
+ if (use_mmio) {
+ cmos_write_mmio(s, reg, val);
+ } else {
+ cmos_write_ioio(s, reg, val);
+ }
+}
+
+static int bcd2dec(int value)
+{
+ return (((value >> 4) & 0x0F) * 10) + (value & 0x0F);
+}
+
+static int tm_cmp(struct tm *lhs, struct tm *rhs)
+{
+ time_t a, b;
+ struct tm d1, d2;
+
+ memcpy(&d1, lhs, sizeof(d1));
+ memcpy(&d2, rhs, sizeof(d2));
+
+ a = mktime(&d1);
+ b = mktime(&d2);
+
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+
+ return 0;
+}
+
+#if 0
+static void print_tm(struct tm *tm)
+{
+ printf("%04d-%02d-%02d %02d:%02d:%02d %+02ld\n",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_gmtoff);
+}
+#endif
+
+static void cmos_get_date_time(QTestState *s, struct tm *date)
+{
+ int sec, min, hour, mday, mon, year;
+ time_t ts;
+ struct tm dummy;
+
+ sec = cmos_read(s, RTC_SECONDS);
+ min = cmos_read(s, RTC_MINUTES);
+ hour = cmos_read(s, RTC_HOURS);
+ mday = cmos_read(s, RTC_DAY_OF_MONTH);
+ mon = cmos_read(s, RTC_MONTH);
+ year = cmos_read(s, RTC_YEAR);
+
+ sec = bcd2dec(sec);
+ min = bcd2dec(min);
+ hour = bcd2dec(hour);
+ mday = bcd2dec(mday);
+ mon = bcd2dec(mon);
+ year = bcd2dec(year);
+
+ ts = time(NULL);
+ localtime_r(&ts, &dummy);
+
+ date->tm_isdst = dummy.tm_isdst;
+ date->tm_sec = sec;
+ date->tm_min = min;
+ date->tm_hour = hour;
+ date->tm_mday = mday;
+ date->tm_mon = mon - 1;
+ date->tm_year = base_year + year - 1900;
+#ifndef __sun__
+ date->tm_gmtoff = 0;
+#endif
+
+ ts = mktime(date);
+}
+
+static QTestState *m48t59_qtest_start(void)
+{
+ return qtest_initf("-M %s -rtc clock=vm", base_machine);
+}
+
+static void bcd_check_time(void)
+{
+ struct tm start, date[4], end;
+ struct tm *datep;
+ time_t ts;
+ const int wiggle = 2;
+ QTestState *s = m48t59_qtest_start();
+
+ /*
+ * This check assumes a few things. First, we cannot guarantee that we get
+ * a consistent reading from the wall clock because we may hit an edge of
+ * the clock while reading. To work around this, we read four clock readings
+ * such that at least two of them should match. We need to assume that one
+ * reading is corrupt so we need four readings to ensure that we have at
+ * least two consecutive identical readings
+ *
+ * It's also possible that we'll cross an edge reading the host clock so
+ * simply check to make sure that the clock reading is within the period of
+ * when we expect it to be.
+ */
+
+ ts = time(NULL);
+ gmtime_r(&ts, &start);
+
+ cmos_get_date_time(s, &date[0]);
+ cmos_get_date_time(s, &date[1]);
+ cmos_get_date_time(s, &date[2]);
+ cmos_get_date_time(s, &date[3]);
+
+ ts = time(NULL);
+ gmtime_r(&ts, &end);
+
+ if (tm_cmp(&date[0], &date[1]) == 0) {
+ datep = &date[0];
+ } else if (tm_cmp(&date[1], &date[2]) == 0) {
+ datep = &date[1];
+ } else if (tm_cmp(&date[2], &date[3]) == 0) {
+ datep = &date[2];
+ } else {
+ g_assert_not_reached();
+ }
+
+ if (!(tm_cmp(&start, datep) <= 0 && tm_cmp(datep, &end) <= 0)) {
+ long t, s;
+
+ start.tm_isdst = datep->tm_isdst;
+
+ t = (long)mktime(datep);
+ s = (long)mktime(&start);
+ if (t < s) {
+ g_test_message("RTC is %ld second(s) behind wall-clock", (s - t));
+ } else {
+ g_test_message("RTC is %ld second(s) ahead of wall-clock", (t - s));
+ }
+
+ g_assert_cmpint(ABS(t - s), <=, wiggle);
+ }
+
+ qtest_quit(s);
+}
+
+/* success if no crash or abort */
+static void fuzz_registers(void)
+{
+ unsigned int i;
+ QTestState *s = m48t59_qtest_start();
+
+ for (i = 0; i < 1000; i++) {
+ uint8_t reg, val;
+
+ reg = (uint8_t)g_test_rand_int_range(0, 16);
+ val = (uint8_t)g_test_rand_int_range(0, 256);
+
+ if (reg == 7) {
+ /* watchdog setup register, may trigger system reset, skip */
+ continue;
+ }
+
+ cmos_write(s, reg, val);
+ cmos_read(s, reg);
+ }
+
+ qtest_quit(s);
+}
+
+static void base_setup(void)
+{
+ const char *arch = qtest_get_arch();
+
+ if (g_str_equal(arch, "sparc")) {
+ /* Note: For sparc64, we'd need to map-in the PCI bridge memory first */
+ base = 0x71200000;
+ base_year = 1968;
+ base_machine = "SS-5";
+ use_mmio = true;
+ } else if (g_str_equal(arch, "ppc") || g_str_equal(arch, "ppc64")) {
+ base = 0xF0000000;
+ base_year = 1968;
+ base_machine = "ref405ep";
+ use_mmio = true;
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+int main(int argc, char **argv)
+{
+ base_setup();
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (g_test_slow()) {
+ /* Do not run this in timing-sensitive environments */
+ qtest_add_func("/rtc/bcd-check-time", bcd_check_time);
+ }
+ qtest_add_func("/rtc/fuzz-registers", fuzz_registers);
+ return g_test_run();
+}
diff --git a/tests/qtest/machine-none-test.c b/tests/qtest/machine-none-test.c
new file mode 100644
index 0000000000..5953d31755
--- /dev/null
+++ b/tests/qtest/machine-none-test.c
@@ -0,0 +1,103 @@
+/*
+ * Machine 'none' tests.
+ *
+ * Copyright (c) 2018 Red Hat Inc.
+ *
+ * Authors:
+ * Igor Mammedov <imammedo@redhat.com>,
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu-common.h"
+#include "qemu/cutils.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+
+
+struct arch2cpu {
+ const char *arch;
+ const char *cpu_model;
+};
+
+static struct arch2cpu cpus_map[] = {
+ /* tested targets list */
+ { "arm", "cortex-a15" },
+ { "aarch64", "cortex-a57" },
+ { "x86_64", "qemu64,apic-id=0" },
+ { "i386", "qemu32,apic-id=0" },
+ { "alpha", "ev67" },
+ { "cris", "crisv32" },
+ { "lm32", "lm32-full" },
+ { "m68k", "m5206" },
+ /* FIXME: { "microblaze", "any" }, doesn't work with -M none -cpu any */
+ /* FIXME: { "microblazeel", "any" }, doesn't work with -M none -cpu any */
+ { "mips", "4Kc" },
+ { "mipsel", "I7200" },
+ { "mips64", "20Kc" },
+ { "mips64el", "I6500" },
+ { "moxie", "MoxieLite" },
+ { "nios2", "FIXME" },
+ { "or1k", "or1200" },
+ { "ppc", "604" },
+ { "ppc64", "power8e_v2.1" },
+ { "s390x", "qemu" },
+ { "sh4", "sh7750r" },
+ { "sh4eb", "sh7751r" },
+ { "sparc", "LEON2" },
+ { "sparc64", "Fujitsu Sparc64" },
+ { "tricore", "tc1796" },
+ { "unicore32", "UniCore-II" },
+ { "xtensa", "dc233c" },
+ { "xtensaeb", "fsf" },
+ { "hppa", "hppa" },
+ { "riscv64", "rv64gcsu-v1.10.0" },
+ { "riscv32", "rv32gcsu-v1.9.1" },
+};
+
+static const char *get_cpu_model_by_arch(const char *arch)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cpus_map); i++) {
+ if (!strcmp(arch, cpus_map[i].arch)) {
+ return cpus_map[i].cpu_model;
+ }
+ }
+ return NULL;
+}
+
+static void test_machine_cpu_cli(void)
+{
+ QDict *response;
+ const char *arch = qtest_get_arch();
+ const char *cpu_model = get_cpu_model_by_arch(arch);
+ QTestState *qts;
+
+ if (!cpu_model) {
+ if (!(!strcmp(arch, "microblaze") || !strcmp(arch, "microblazeel"))) {
+ fprintf(stderr, "WARNING: cpu name for target '%s' isn't defined,"
+ " add it to cpus_map\n", arch);
+ }
+ return; /* TODO: die here to force all targets have a test */
+ }
+ qts = qtest_initf("-machine none -cpu '%s'", cpu_model);
+
+ response = qtest_qmp(qts, "{ 'execute': 'quit' }");
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("machine/none/cpu_option", test_machine_cpu_cli);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/megasas-test.c b/tests/qtest/megasas-test.c
new file mode 100644
index 0000000000..d6796b9bd7
--- /dev/null
+++ b/tests/qtest/megasas-test.c
@@ -0,0 +1,91 @@
+/*
+ * QTest testcase for LSI MegaRAID
+ *
+ * Copyright (c) 2017 Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bswap.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QMegasas QMegasas;
+
+struct QMegasas {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *megasas_get_driver(void *obj, const char *interface)
+{
+ QMegasas *megasas = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &megasas->dev;
+ }
+
+ fprintf(stderr, "%s not present in megasas\n", interface);
+ g_assert_not_reached();
+}
+
+static void *megasas_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QMegasas *megasas = g_new0(QMegasas, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&megasas->dev, bus, addr);
+ megasas->obj.get_driver = megasas_get_driver;
+
+ return &megasas->obj;
+}
+
+/* This used to cause a NULL pointer dereference. */
+static void megasas_pd_get_info_fuzz(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QMegasas *megasas = obj;
+ QPCIDevice *dev = &megasas->dev;
+ QPCIBar bar;
+ uint32_t context[256];
+ uint64_t context_pa;
+ int i;
+
+ qpci_device_enable(dev);
+ bar = qpci_iomap(dev, 0, NULL);
+
+ memset(context, 0, sizeof(context));
+ context[0] = cpu_to_le32(0x05050505);
+ context[1] = cpu_to_le32(0x01010101);
+ for (i = 2; i < ARRAY_SIZE(context); i++) {
+ context[i] = cpu_to_le32(0x41414141);
+ }
+ context[6] = cpu_to_le32(0x02020000);
+ context[7] = cpu_to_le32(0);
+
+ context_pa = guest_alloc(alloc, sizeof(context));
+ qtest_memwrite(dev->bus->qts, context_pa, context, sizeof(context));
+ qpci_io_writel(dev, bar, 0x40, context_pa);
+}
+
+static void megasas_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0,id=scsi0",
+ .before_cmd_line = "-drive id=drv0,if=none,file=null-co://,"
+ "file.read-zeroes=on,format=raw",
+ .after_cmd_line = "-device scsi-hd,bus=scsi0.0,drive=drv0",
+ };
+
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("megasas", megasas_create);
+ qos_node_consumes("megasas", "pci-bus", &opts);
+ qos_node_produces("megasas", "pci-device");
+
+ qos_add_test("dcmd/pd-get-info/fuzz", "megasas", megasas_pd_get_info_fuzz, NULL);
+}
+libqos_init(megasas_register_nodes);
diff --git a/tests/qtest/microbit-test.c b/tests/qtest/microbit-test.c
new file mode 100644
index 0000000000..04e199ec33
--- /dev/null
+++ b/tests/qtest/microbit-test.c
@@ -0,0 +1,507 @@
+/*
+ * QTest testcase for Microbit board using the Nordic Semiconductor nRF51 SoC.
+ *
+ * nRF51:
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
+ * Product Spec: http://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.1.pdf
+ *
+ * Microbit Board: http://microbit.org/
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later. See
+ * the COPYING file in the top-level directory.
+ */
+
+
+#include "qemu/osdep.h"
+#include "exec/hwaddr.h"
+#include "libqtest.h"
+
+#include "hw/arm/nrf51.h"
+#include "hw/char/nrf51_uart.h"
+#include "hw/gpio/nrf51_gpio.h"
+#include "hw/nvram/nrf51_nvm.h"
+#include "hw/timer/nrf51_timer.h"
+#include "hw/i2c/microbit_i2c.h"
+
+static bool uart_wait_for_event(QTestState *qts, uint32_t event_addr)
+{
+ time_t now, start = time(NULL);
+
+ while (true) {
+ if (qtest_readl(qts, event_addr) == 1) {
+ qtest_writel(qts, event_addr, 0x00);
+ return true;
+ }
+
+ /* Wait at most 10 minutes */
+ now = time(NULL);
+ if (now - start > 600) {
+ break;
+ }
+ g_usleep(10000);
+ }
+
+ return false;
+}
+
+static void uart_rw_to_rxd(QTestState *qts, int sock_fd, const char *in,
+ char *out)
+{
+ int i, in_len = strlen(in);
+
+ g_assert_true(write(sock_fd, in, in_len) == in_len);
+ for (i = 0; i < in_len; i++) {
+ g_assert_true(uart_wait_for_event(qts, NRF51_UART_BASE +
+ A_UART_RXDRDY));
+ out[i] = qtest_readl(qts, NRF51_UART_BASE + A_UART_RXD);
+ }
+ out[i] = '\0';
+}
+
+static void uart_w_to_txd(QTestState *qts, const char *in)
+{
+ int i, in_len = strlen(in);
+
+ for (i = 0; i < in_len; i++) {
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_TXD, in[i]);
+ g_assert_true(uart_wait_for_event(qts, NRF51_UART_BASE +
+ A_UART_TXDRDY));
+ }
+}
+
+static void test_nrf51_uart(void)
+{
+ int sock_fd;
+ char s[10];
+ QTestState *qts = qtest_init_with_serial("-M microbit", &sock_fd);
+
+ g_assert_true(write(sock_fd, "c", 1) == 1);
+ g_assert_cmphex(qtest_readl(qts, NRF51_UART_BASE + A_UART_RXD), ==, 0x00);
+
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_ENABLE, 0x04);
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_STARTRX, 0x01);
+
+ g_assert_true(uart_wait_for_event(qts, NRF51_UART_BASE + A_UART_RXDRDY));
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_RXDRDY, 0x00);
+ g_assert_cmphex(qtest_readl(qts, NRF51_UART_BASE + A_UART_RXD), ==, 'c');
+
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_INTENSET, 0x04);
+ g_assert_cmphex(qtest_readl(qts, NRF51_UART_BASE + A_UART_INTEN), ==, 0x04);
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_INTENCLR, 0x04);
+ g_assert_cmphex(qtest_readl(qts, NRF51_UART_BASE + A_UART_INTEN), ==, 0x00);
+
+ uart_rw_to_rxd(qts, sock_fd, "hello", s);
+ g_assert_true(memcmp(s, "hello", 5) == 0);
+
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_STARTTX, 0x01);
+ uart_w_to_txd(qts, "d");
+ g_assert_true(read(sock_fd, s, 10) == 1);
+ g_assert_cmphex(s[0], ==, 'd');
+
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_SUSPEND, 0x01);
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_TXD, 'h');
+ qtest_writel(qts, NRF51_UART_BASE + A_UART_STARTTX, 0x01);
+ uart_w_to_txd(qts, "world");
+ g_assert_true(read(sock_fd, s, 10) == 5);
+ g_assert_true(memcmp(s, "world", 5) == 0);
+
+ close(sock_fd);
+
+ qtest_quit(qts);
+}
+
+/* Read a byte from I2C device at @addr from register @reg */
+static uint32_t i2c_read_byte(QTestState *qts, uint32_t addr, uint32_t reg)
+{
+ uint32_t val;
+
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_REG_ADDRESS, addr);
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_TASK_STARTTX, 1);
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_REG_TXD, reg);
+ val = qtest_readl(qts, NRF51_TWI_BASE + NRF51_TWI_EVENT_TXDSENT);
+ g_assert_cmpuint(val, ==, 1);
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_TASK_STOP, 1);
+
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_TASK_STARTRX, 1);
+ val = qtest_readl(qts, NRF51_TWI_BASE + NRF51_TWI_EVENT_RXDREADY);
+ g_assert_cmpuint(val, ==, 1);
+ val = qtest_readl(qts, NRF51_TWI_BASE + NRF51_TWI_REG_RXD);
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_TASK_STOP, 1);
+
+ return val;
+}
+
+static void test_microbit_i2c(void)
+{
+ uint32_t val;
+ QTestState *qts = qtest_init("-M microbit");
+
+ /* We don't program pins/irqs but at least enable the device */
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_REG_ENABLE, 5);
+
+ /* MMA8653 magnetometer detection */
+ val = i2c_read_byte(qts, 0x3A, 0x0D);
+ g_assert_cmpuint(val, ==, 0x5A);
+
+ val = i2c_read_byte(qts, 0x3A, 0x0D);
+ g_assert_cmpuint(val, ==, 0x5A);
+
+ /* LSM303 accelerometer detection */
+ val = i2c_read_byte(qts, 0x3C, 0x4F);
+ g_assert_cmpuint(val, ==, 0x40);
+
+ qtest_writel(qts, NRF51_TWI_BASE + NRF51_TWI_REG_ENABLE, 0);
+
+ qtest_quit(qts);
+}
+
+#define FLASH_SIZE (256 * NRF51_PAGE_SIZE)
+
+static void fill_and_erase(QTestState *qts, hwaddr base, hwaddr size,
+ uint32_t address_reg)
+{
+ hwaddr i;
+
+ /* Erase Page */
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x02);
+ qtest_writel(qts, NRF51_NVMC_BASE + address_reg, base);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ /* Check memory */
+ for (i = 0; i < size / 4; i++) {
+ g_assert_cmpuint(qtest_readl(qts, base + i * 4), ==, 0xFFFFFFFF);
+ }
+
+ /* Fill memory */
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x01);
+ for (i = 0; i < size / 4; i++) {
+ qtest_writel(qts, base + i * 4, i);
+ g_assert_cmpuint(qtest_readl(qts, base + i * 4), ==, i);
+ }
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+}
+
+static void test_nrf51_nvmc(void)
+{
+ uint32_t value;
+ hwaddr i;
+ QTestState *qts = qtest_init("-M microbit");
+
+ /* Test always ready */
+ value = qtest_readl(qts, NRF51_NVMC_BASE + NRF51_NVMC_READY);
+ g_assert_cmpuint(value & 0x01, ==, 0x01);
+
+ /* Test write-read config register */
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x03);
+ g_assert_cmpuint(qtest_readl(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG),
+ ==, 0x03);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+ g_assert_cmpuint(qtest_readl(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG),
+ ==, 0x00);
+
+ /* Test PCR0 */
+ fill_and_erase(qts, NRF51_FLASH_BASE, NRF51_PAGE_SIZE,
+ NRF51_NVMC_ERASEPCR0);
+ fill_and_erase(qts, NRF51_FLASH_BASE + NRF51_PAGE_SIZE,
+ NRF51_PAGE_SIZE, NRF51_NVMC_ERASEPCR0);
+
+ /* Test PCR1 */
+ fill_and_erase(qts, NRF51_FLASH_BASE, NRF51_PAGE_SIZE,
+ NRF51_NVMC_ERASEPCR1);
+ fill_and_erase(qts, NRF51_FLASH_BASE + NRF51_PAGE_SIZE,
+ NRF51_PAGE_SIZE, NRF51_NVMC_ERASEPCR1);
+
+ /* Erase all */
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x02);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_ERASEALL, 0x01);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x01);
+ for (i = 0; i < FLASH_SIZE / 4; i++) {
+ qtest_writel(qts, NRF51_FLASH_BASE + i * 4, i);
+ g_assert_cmpuint(qtest_readl(qts, NRF51_FLASH_BASE + i * 4), ==, i);
+ }
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x02);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_ERASEALL, 0x01);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ for (i = 0; i < FLASH_SIZE / 4; i++) {
+ g_assert_cmpuint(qtest_readl(qts, NRF51_FLASH_BASE + i * 4),
+ ==, 0xFFFFFFFF);
+ }
+
+ /* Erase UICR */
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x02);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_ERASEUICR, 0x01);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ for (i = 0; i < NRF51_UICR_SIZE / 4; i++) {
+ g_assert_cmpuint(qtest_readl(qts, NRF51_UICR_BASE + i * 4),
+ ==, 0xFFFFFFFF);
+ }
+
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x01);
+ for (i = 0; i < NRF51_UICR_SIZE / 4; i++) {
+ qtest_writel(qts, NRF51_UICR_BASE + i * 4, i);
+ g_assert_cmpuint(qtest_readl(qts, NRF51_UICR_BASE + i * 4), ==, i);
+ }
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x02);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_ERASEUICR, 0x01);
+ qtest_writel(qts, NRF51_NVMC_BASE + NRF51_NVMC_CONFIG, 0x00);
+
+ for (i = 0; i < NRF51_UICR_SIZE / 4; i++) {
+ g_assert_cmpuint(qtest_readl(qts, NRF51_UICR_BASE + i * 4),
+ ==, 0xFFFFFFFF);
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_nrf51_gpio(void)
+{
+ size_t i;
+ uint32_t actual, expected;
+
+ struct {
+ hwaddr addr;
+ uint32_t expected;
+ } const reset_state[] = {
+ {NRF51_GPIO_REG_OUT, 0x00000000}, {NRF51_GPIO_REG_OUTSET, 0x00000000},
+ {NRF51_GPIO_REG_OUTCLR, 0x00000000}, {NRF51_GPIO_REG_IN, 0x00000000},
+ {NRF51_GPIO_REG_DIR, 0x00000000}, {NRF51_GPIO_REG_DIRSET, 0x00000000},
+ {NRF51_GPIO_REG_DIRCLR, 0x00000000}
+ };
+
+ QTestState *qts = qtest_init("-M microbit");
+
+ /* Check reset state */
+ for (i = 0; i < ARRAY_SIZE(reset_state); i++) {
+ expected = reset_state[i].expected;
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + reset_state[i].addr);
+ g_assert_cmpuint(actual, ==, expected);
+ }
+
+ for (i = 0; i < NRF51_GPIO_PINS; i++) {
+ expected = 0x00000002;
+ actual = qtest_readl(qts, NRF51_GPIO_BASE +
+ NRF51_GPIO_REG_CNF_START + i * 4);
+ g_assert_cmpuint(actual, ==, expected);
+ }
+
+ /* Check dir bit consistency between dir and cnf */
+ /* Check set via DIRSET */
+ expected = 0x80000001;
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIRSET, expected);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIR);
+ g_assert_cmpuint(actual, ==, expected);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START)
+ & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_END) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+
+ /* Check clear via DIRCLR */
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIRCLR, 0x80000001);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIR);
+ g_assert_cmpuint(actual, ==, 0x00000000);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START)
+ & 0x01;
+ g_assert_cmpuint(actual, ==, 0x00);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_END) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x00);
+
+ /* Check set via DIR */
+ expected = 0x80000001;
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIR, expected);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIR);
+ g_assert_cmpuint(actual, ==, expected);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START)
+ & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_END) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+
+ /* Reset DIR */
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_DIR, 0x00000000);
+
+ /* Check Input propagates */
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0x00);
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, 0);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x00);
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, 1);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, -1);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0x02);
+
+ /* Check pull-up working */
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, 0);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b0000);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x00);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b1110);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0x02);
+
+ /* Check pull-down working */
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, 1);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b0000);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b0110);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x00);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0x02);
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, -1);
+
+ /* Check Output propagates */
+ qtest_irq_intercept_out(qts, "/machine/nrf51");
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b0011);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_OUTSET, 0x01);
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_OUTCLR, 0x01);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ /* Check self-stimulation */
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b01);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_OUTSET, 0x01);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x01);
+
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_OUTCLR, 0x01);
+ actual = qtest_readl(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_IN) & 0x01;
+ g_assert_cmpuint(actual, ==, 0x00);
+
+ /*
+ * Check short-circuit - generates an guest_error which must be checked
+ * manually as long as qtest can not scan qemu_log messages
+ */
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_CNF_START, 0b01);
+ qtest_writel(qts, NRF51_GPIO_BASE + NRF51_GPIO_REG_OUTSET, 0x01);
+ qtest_set_irq_in(qts, "/machine/nrf51", "unnamed-gpio-in", 0, 0);
+
+ qtest_quit(qts);
+}
+
+static void timer_task(QTestState *qts, hwaddr task)
+{
+ qtest_writel(qts, NRF51_TIMER_BASE + task, NRF51_TRIGGER_TASK);
+}
+
+static void timer_clear_event(QTestState *qts, hwaddr event)
+{
+ qtest_writel(qts, NRF51_TIMER_BASE + event, NRF51_EVENT_CLEAR);
+}
+
+static void timer_set_bitmode(QTestState *qts, uint8_t mode)
+{
+ qtest_writel(qts, NRF51_TIMER_BASE + NRF51_TIMER_REG_BITMODE, mode);
+}
+
+static void timer_set_prescaler(QTestState *qts, uint8_t prescaler)
+{
+ qtest_writel(qts, NRF51_TIMER_BASE + NRF51_TIMER_REG_PRESCALER, prescaler);
+}
+
+static void timer_set_cc(QTestState *qts, size_t idx, uint32_t value)
+{
+ qtest_writel(qts, NRF51_TIMER_BASE + NRF51_TIMER_REG_CC0 + idx * 4, value);
+}
+
+static void timer_assert_events(QTestState *qts, uint32_t ev0, uint32_t ev1,
+ uint32_t ev2, uint32_t ev3)
+{
+ g_assert(qtest_readl(qts, NRF51_TIMER_BASE + NRF51_TIMER_EVENT_COMPARE_0)
+ == ev0);
+ g_assert(qtest_readl(qts, NRF51_TIMER_BASE + NRF51_TIMER_EVENT_COMPARE_1)
+ == ev1);
+ g_assert(qtest_readl(qts, NRF51_TIMER_BASE + NRF51_TIMER_EVENT_COMPARE_2)
+ == ev2);
+ g_assert(qtest_readl(qts, NRF51_TIMER_BASE + NRF51_TIMER_EVENT_COMPARE_3)
+ == ev3);
+}
+
+static void test_nrf51_timer(void)
+{
+ uint32_t steps_to_overflow = 408;
+ QTestState *qts = qtest_init("-M microbit");
+
+ /* Compare Match */
+ timer_task(qts, NRF51_TIMER_TASK_STOP);
+ timer_task(qts, NRF51_TIMER_TASK_CLEAR);
+
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_0);
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_1);
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_2);
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_3);
+
+ timer_set_bitmode(qts, NRF51_TIMER_WIDTH_16); /* 16 MHz Timer */
+ timer_set_prescaler(qts, 0);
+ /* Swept over in first step */
+ timer_set_cc(qts, 0, 2);
+ /* Barely miss on first step */
+ timer_set_cc(qts, 1, 162);
+ /* Spot on on third step */
+ timer_set_cc(qts, 2, 480);
+
+ timer_assert_events(qts, 0, 0, 0, 0);
+
+ timer_task(qts, NRF51_TIMER_TASK_START);
+ qtest_clock_step(qts, 10000);
+ timer_assert_events(qts, 1, 0, 0, 0);
+
+ /* Swept over on first overflow */
+ timer_set_cc(qts, 3, 114);
+
+ qtest_clock_step(qts, 10000);
+ timer_assert_events(qts, 1, 1, 0, 0);
+
+ qtest_clock_step(qts, 10000);
+ timer_assert_events(qts, 1, 1, 1, 0);
+
+ /* Wrap time until internal counter overflows */
+ while (steps_to_overflow--) {
+ timer_assert_events(qts, 1, 1, 1, 0);
+ qtest_clock_step(qts, 10000);
+ }
+
+ timer_assert_events(qts, 1, 1, 1, 1);
+
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_0);
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_1);
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_2);
+ timer_clear_event(qts, NRF51_TIMER_EVENT_COMPARE_3);
+ timer_assert_events(qts, 0, 0, 0, 0);
+
+ timer_task(qts, NRF51_TIMER_TASK_STOP);
+
+ /* Test Proposal: Stop/Shutdown */
+ /* Test Proposal: Shortcut Compare -> Clear */
+ /* Test Proposal: Shortcut Compare -> Stop */
+ /* Test Proposal: Counter Mode */
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/microbit/nrf51/uart", test_nrf51_uart);
+ qtest_add_func("/microbit/nrf51/gpio", test_nrf51_gpio);
+ qtest_add_func("/microbit/nrf51/nvmc", test_nrf51_nvmc);
+ qtest_add_func("/microbit/nrf51/timer", test_nrf51_timer);
+ qtest_add_func("/microbit/microbit/i2c", test_microbit_i2c);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/migration-helpers.c b/tests/qtest/migration-helpers.c
new file mode 100644
index 0000000000..516093b39a
--- /dev/null
+++ b/tests/qtest/migration-helpers.c
@@ -0,0 +1,167 @@
+/*
+ * QTest migration helpers
+ *
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ * based on the vhost-user-test.c that is:
+ * Copyright (c) 2014 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/qmp/qjson.h"
+
+#include "migration-helpers.h"
+
+bool got_stop;
+
+static void stop_cb(void *opaque, const char *name, QDict *data)
+{
+ if (!strcmp(name, "STOP")) {
+ got_stop = true;
+ }
+}
+
+/*
+ * Events can get in the way of responses we are actually waiting for.
+ */
+QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...)
+{
+ va_list ap;
+
+ va_start(ap, command);
+ qtest_qmp_vsend_fds(who, &fd, 1, command, ap);
+ va_end(ap);
+
+ return qtest_qmp_receive_success(who, stop_cb, NULL);
+}
+
+/*
+ * Events can get in the way of responses we are actually waiting for.
+ */
+QDict *wait_command(QTestState *who, const char *command, ...)
+{
+ va_list ap;
+
+ va_start(ap, command);
+ qtest_qmp_vsend(who, command, ap);
+ va_end(ap);
+
+ return qtest_qmp_receive_success(who, stop_cb, NULL);
+}
+
+/*
+ * Send QMP command "migrate".
+ * Arguments are built from @fmt... (formatted like
+ * qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
+ */
+void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
+{
+ va_list ap;
+ QDict *args, *rsp;
+
+ va_start(ap, fmt);
+ args = qdict_from_vjsonf_nofail(fmt, ap);
+ va_end(ap);
+
+ g_assert(!qdict_haskey(args, "uri"));
+ qdict_put_str(args, "uri", uri);
+
+ rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args);
+
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+}
+
+/*
+ * Note: caller is responsible to free the returned object via
+ * qobject_unref() after use
+ */
+QDict *migrate_query(QTestState *who)
+{
+ return wait_command(who, "{ 'execute': 'query-migrate' }");
+}
+
+/*
+ * Note: caller is responsible to free the returned object via
+ * g_free() after use
+ */
+static gchar *migrate_query_status(QTestState *who)
+{
+ QDict *rsp_return = migrate_query(who);
+ gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
+
+ g_assert(status);
+ qobject_unref(rsp_return);
+
+ return status;
+}
+
+static bool check_migration_status(QTestState *who, const char *goal,
+ const char **ungoals)
+{
+ bool ready;
+ char *current_status;
+ const char **ungoal;
+
+ current_status = migrate_query_status(who);
+ ready = strcmp(current_status, goal) == 0;
+ if (!ungoals) {
+ g_assert_cmpstr(current_status, !=, "failed");
+ /*
+ * If looking for a state other than completed,
+ * completion of migration would cause the test to
+ * hang.
+ */
+ if (strcmp(goal, "completed") != 0) {
+ g_assert_cmpstr(current_status, !=, "completed");
+ }
+ } else {
+ for (ungoal = ungoals; *ungoal; ungoal++) {
+ g_assert_cmpstr(current_status, !=, *ungoal);
+ }
+ }
+ g_free(current_status);
+ return ready;
+}
+
+void wait_for_migration_status(QTestState *who,
+ const char *goal, const char **ungoals)
+{
+ while (!check_migration_status(who, goal, ungoals)) {
+ usleep(1000);
+ }
+}
+
+void wait_for_migration_complete(QTestState *who)
+{
+ wait_for_migration_status(who, "completed", NULL);
+}
+
+void wait_for_migration_fail(QTestState *from, bool allow_active)
+{
+ QDict *rsp_return;
+ char *status;
+ bool failed;
+
+ do {
+ status = migrate_query_status(from);
+ bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
+ (allow_active && !strcmp(status, "active"));
+ if (!result) {
+ fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
+ __func__, status, allow_active);
+ }
+ g_assert(result);
+ failed = !strcmp(status, "failed");
+ g_free(status);
+ } while (!failed);
+
+ /* Is the machine currently running? */
+ rsp_return = wait_command(from, "{ 'execute': 'query-status' }");
+ g_assert(qdict_haskey(rsp_return, "running"));
+ g_assert(qdict_get_bool(rsp_return, "running"));
+ qobject_unref(rsp_return);
+}
diff --git a/tests/qtest/migration-helpers.h b/tests/qtest/migration-helpers.h
new file mode 100644
index 0000000000..a11808b3b7
--- /dev/null
+++ b/tests/qtest/migration-helpers.h
@@ -0,0 +1,37 @@
+/*
+ * QTest migration helpers
+ *
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ * based on the vhost-user-test.c that is:
+ * Copyright (c) 2014 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+#ifndef MIGRATION_HELPERS_H_
+#define MIGRATION_HELPERS_H_
+
+#include "libqtest.h"
+
+extern bool got_stop;
+
+GCC_FMT_ATTR(3, 4)
+QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...);
+
+GCC_FMT_ATTR(2, 3)
+QDict *wait_command(QTestState *who, const char *command, ...);
+
+GCC_FMT_ATTR(3, 4)
+void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
+
+QDict *migrate_query(QTestState *who);
+
+void wait_for_migration_status(QTestState *who,
+ const char *goal, const char **ungoals);
+
+void wait_for_migration_complete(QTestState *who);
+
+void wait_for_migration_fail(QTestState *from, bool allow_active);
+
+#endif /* MIGRATION_HELPERS_H_ */
diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c
new file mode 100644
index 0000000000..53afec4395
--- /dev/null
+++ b/tests/qtest/migration-test.c
@@ -0,0 +1,1281 @@
+/*
+ * QTest testcase for migration
+ *
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ * based on the vhost-user-test.c that is:
+ * Copyright (c) 2014 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/range.h"
+#include "qemu/sockets.h"
+#include "chardev/char.h"
+#include "qapi/qapi-visit-sockets.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qobject-output-visitor.h"
+
+#include "migration-helpers.h"
+#include "migration/migration-test.h"
+
+/* TODO actually test the results and get rid of this */
+#define qtest_qmp_discard_response(...) qobject_unref(qtest_qmp(__VA_ARGS__))
+
+unsigned start_address;
+unsigned end_address;
+static bool uffd_feature_thread_id;
+
+#if defined(__linux__)
+#include <sys/syscall.h>
+#include <sys/vfs.h>
+#endif
+
+#if defined(__linux__) && defined(__NR_userfaultfd) && defined(CONFIG_EVENTFD)
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <linux/userfaultfd.h>
+
+static bool ufd_version_check(void)
+{
+ struct uffdio_api api_struct;
+ uint64_t ioctl_mask;
+
+ int ufd = syscall(__NR_userfaultfd, O_CLOEXEC);
+
+ if (ufd == -1) {
+ g_test_message("Skipping test: userfaultfd not available");
+ return false;
+ }
+
+ api_struct.api = UFFD_API;
+ api_struct.features = 0;
+ if (ioctl(ufd, UFFDIO_API, &api_struct)) {
+ g_test_message("Skipping test: UFFDIO_API failed");
+ return false;
+ }
+ uffd_feature_thread_id = api_struct.features & UFFD_FEATURE_THREAD_ID;
+
+ ioctl_mask = (__u64)1 << _UFFDIO_REGISTER |
+ (__u64)1 << _UFFDIO_UNREGISTER;
+ if ((api_struct.ioctls & ioctl_mask) != ioctl_mask) {
+ g_test_message("Skipping test: Missing userfault feature");
+ return false;
+ }
+
+ return true;
+}
+
+#else
+static bool ufd_version_check(void)
+{
+ g_test_message("Skipping test: Userfault not available (builtdtime)");
+ return false;
+}
+
+#endif
+
+static const char *tmpfs;
+
+/* The boot file modifies memory area in [start_address, end_address)
+ * repeatedly. It outputs a 'B' at a fixed rate while it's still running.
+ */
+#include "tests/migration/i386/a-b-bootblock.h"
+#include "tests/migration/aarch64/a-b-kernel.h"
+#include "tests/migration/s390x/a-b-bios.h"
+
+static void init_bootfile(const char *bootpath, void *content, size_t len)
+{
+ FILE *bootfile = fopen(bootpath, "wb");
+
+ g_assert_cmpint(fwrite(content, len, 1, bootfile), ==, 1);
+ fclose(bootfile);
+}
+
+/*
+ * Wait for some output in the serial output file,
+ * we get an 'A' followed by an endless string of 'B's
+ * but on the destination we won't have the A.
+ */
+static void wait_for_serial(const char *side)
+{
+ char *serialpath = g_strdup_printf("%s/%s", tmpfs, side);
+ FILE *serialfile = fopen(serialpath, "r");
+ const char *arch = qtest_get_arch();
+ int started = (strcmp(side, "src_serial") == 0 &&
+ strcmp(arch, "ppc64") == 0) ? 0 : 1;
+
+ g_free(serialpath);
+ do {
+ int readvalue = fgetc(serialfile);
+
+ if (!started) {
+ /* SLOF prints its banner before starting test,
+ * to ignore it, mark the start of the test with '_',
+ * ignore all characters until this marker
+ */
+ switch (readvalue) {
+ case '_':
+ started = 1;
+ break;
+ case EOF:
+ fseek(serialfile, 0, SEEK_SET);
+ usleep(1000);
+ break;
+ }
+ continue;
+ }
+ switch (readvalue) {
+ case 'A':
+ /* Fine */
+ break;
+
+ case 'B':
+ /* It's alive! */
+ fclose(serialfile);
+ return;
+
+ case EOF:
+ started = (strcmp(side, "src_serial") == 0 &&
+ strcmp(arch, "ppc64") == 0) ? 0 : 1;
+ fseek(serialfile, 0, SEEK_SET);
+ usleep(1000);
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected %d on %s serial\n", readvalue, side);
+ g_assert_not_reached();
+ }
+ } while (true);
+}
+
+/*
+ * It's tricky to use qemu's migration event capability with qtest,
+ * events suddenly appearing confuse the qmp()/hmp() responses.
+ */
+
+static int64_t read_ram_property_int(QTestState *who, const char *property)
+{
+ QDict *rsp_return, *rsp_ram;
+ int64_t result;
+
+ rsp_return = migrate_query(who);
+ if (!qdict_haskey(rsp_return, "ram")) {
+ /* Still in setup */
+ result = 0;
+ } else {
+ rsp_ram = qdict_get_qdict(rsp_return, "ram");
+ result = qdict_get_try_int(rsp_ram, property, 0);
+ }
+ qobject_unref(rsp_return);
+ return result;
+}
+
+static int64_t read_migrate_property_int(QTestState *who, const char *property)
+{
+ QDict *rsp_return;
+ int64_t result;
+
+ rsp_return = migrate_query(who);
+ result = qdict_get_try_int(rsp_return, property, 0);
+ qobject_unref(rsp_return);
+ return result;
+}
+
+static uint64_t get_migration_pass(QTestState *who)
+{
+ return read_ram_property_int(who, "dirty-sync-count");
+}
+
+static void read_blocktime(QTestState *who)
+{
+ QDict *rsp_return;
+
+ rsp_return = migrate_query(who);
+ g_assert(qdict_haskey(rsp_return, "postcopy-blocktime"));
+ qobject_unref(rsp_return);
+}
+
+static void wait_for_migration_pass(QTestState *who)
+{
+ uint64_t initial_pass = get_migration_pass(who);
+ uint64_t pass;
+
+ /* Wait for the 1st sync */
+ while (!got_stop && !initial_pass) {
+ usleep(1000);
+ initial_pass = get_migration_pass(who);
+ }
+
+ do {
+ usleep(1000);
+ pass = get_migration_pass(who);
+ } while (pass == initial_pass && !got_stop);
+}
+
+static void check_guests_ram(QTestState *who)
+{
+ /* Our ASM test will have been incrementing one byte from each page from
+ * start_address to < end_address in order. This gives us a constraint
+ * that any page's byte should be equal or less than the previous pages
+ * byte (mod 256); and they should all be equal except for one transition
+ * at the point where we meet the incrementer. (We're running this with
+ * the guest stopped).
+ */
+ unsigned address;
+ uint8_t first_byte;
+ uint8_t last_byte;
+ bool hit_edge = false;
+ int bad = 0;
+
+ qtest_memread(who, start_address, &first_byte, 1);
+ last_byte = first_byte;
+
+ for (address = start_address + TEST_MEM_PAGE_SIZE; address < end_address;
+ address += TEST_MEM_PAGE_SIZE)
+ {
+ uint8_t b;
+ qtest_memread(who, address, &b, 1);
+ if (b != last_byte) {
+ if (((b + 1) % 256) == last_byte && !hit_edge) {
+ /* This is OK, the guest stopped at the point of
+ * incrementing the previous page but didn't get
+ * to us yet.
+ */
+ hit_edge = true;
+ last_byte = b;
+ } else {
+ bad++;
+ if (bad <= 10) {
+ fprintf(stderr, "Memory content inconsistency at %x"
+ " first_byte = %x last_byte = %x current = %x"
+ " hit_edge = %x\n",
+ address, first_byte, last_byte, b, hit_edge);
+ }
+ }
+ }
+ }
+ if (bad >= 10) {
+ fprintf(stderr, "and in another %d pages", bad - 10);
+ }
+ g_assert(bad == 0);
+}
+
+static void cleanup(const char *filename)
+{
+ char *path = g_strdup_printf("%s/%s", tmpfs, filename);
+
+ unlink(path);
+ g_free(path);
+}
+
+static char *SocketAddress_to_str(SocketAddress *addr)
+{
+ switch (addr->type) {
+ case SOCKET_ADDRESS_TYPE_INET:
+ return g_strdup_printf("tcp:%s:%s",
+ addr->u.inet.host,
+ addr->u.inet.port);
+ case SOCKET_ADDRESS_TYPE_UNIX:
+ return g_strdup_printf("unix:%s",
+ addr->u.q_unix.path);
+ case SOCKET_ADDRESS_TYPE_FD:
+ return g_strdup_printf("fd:%s", addr->u.fd.str);
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ return g_strdup_printf("tcp:%s:%s",
+ addr->u.vsock.cid,
+ addr->u.vsock.port);
+ default:
+ return g_strdup("unknown address type");
+ }
+}
+
+static char *migrate_get_socket_address(QTestState *who, const char *parameter)
+{
+ QDict *rsp;
+ char *result;
+ Error *local_err = NULL;
+ SocketAddressList *addrs;
+ Visitor *iv = NULL;
+ QObject *object;
+
+ rsp = migrate_query(who);
+ object = qdict_get(rsp, parameter);
+
+ iv = qobject_input_visitor_new(object);
+ visit_type_SocketAddressList(iv, NULL, &addrs, &local_err);
+ visit_free(iv);
+
+ /* we are only using a single address */
+ result = SocketAddress_to_str(addrs->value);
+
+ qapi_free_SocketAddressList(addrs);
+ qobject_unref(rsp);
+ return result;
+}
+
+static long long migrate_get_parameter_int(QTestState *who,
+ const char *parameter)
+{
+ QDict *rsp;
+ long long result;
+
+ rsp = wait_command(who, "{ 'execute': 'query-migrate-parameters' }");
+ result = qdict_get_int(rsp, parameter);
+ qobject_unref(rsp);
+ return result;
+}
+
+static void migrate_check_parameter_int(QTestState *who, const char *parameter,
+ long long value)
+{
+ long long result;
+
+ result = migrate_get_parameter_int(who, parameter);
+ g_assert_cmpint(result, ==, value);
+}
+
+static void migrate_set_parameter_int(QTestState *who, const char *parameter,
+ long long value)
+{
+ QDict *rsp;
+
+ rsp = qtest_qmp(who,
+ "{ 'execute': 'migrate-set-parameters',"
+ "'arguments': { %s: %lld } }",
+ parameter, value);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ migrate_check_parameter_int(who, parameter, value);
+}
+
+static void migrate_pause(QTestState *who)
+{
+ QDict *rsp;
+
+ rsp = wait_command(who, "{ 'execute': 'migrate-pause' }");
+ qobject_unref(rsp);
+}
+
+static void migrate_continue(QTestState *who, const char *state)
+{
+ QDict *rsp;
+
+ rsp = wait_command(who,
+ "{ 'execute': 'migrate-continue',"
+ " 'arguments': { 'state': %s } }",
+ state);
+ qobject_unref(rsp);
+}
+
+static void migrate_recover(QTestState *who, const char *uri)
+{
+ QDict *rsp;
+
+ rsp = wait_command(who,
+ "{ 'execute': 'migrate-recover', "
+ " 'id': 'recover-cmd', "
+ " 'arguments': { 'uri': %s } }",
+ uri);
+ qobject_unref(rsp);
+}
+
+static void migrate_set_capability(QTestState *who, const char *capability,
+ bool value)
+{
+ QDict *rsp;
+
+ rsp = qtest_qmp(who,
+ "{ 'execute': 'migrate-set-capabilities',"
+ "'arguments': { "
+ "'capabilities': [ { "
+ "'capability': %s, 'state': %i } ] } }",
+ capability, value);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+}
+
+static void migrate_postcopy_start(QTestState *from, QTestState *to)
+{
+ QDict *rsp;
+
+ rsp = wait_command(from, "{ 'execute': 'migrate-start-postcopy' }");
+ qobject_unref(rsp);
+
+ if (!got_stop) {
+ qtest_qmp_eventwait(from, "STOP");
+ }
+
+ qtest_qmp_eventwait(to, "RESUME");
+}
+
+typedef struct {
+ bool hide_stderr;
+ bool use_shmem;
+ char *opts_source;
+ char *opts_target;
+} MigrateStart;
+
+static MigrateStart *migrate_start_new(void)
+{
+ MigrateStart *args = g_new0(MigrateStart, 1);
+
+ args->opts_source = g_strdup("");
+ args->opts_target = g_strdup("");
+ return args;
+}
+
+static void migrate_start_destroy(MigrateStart *args)
+{
+ g_free(args->opts_source);
+ g_free(args->opts_target);
+ g_free(args);
+}
+
+static int test_migrate_start(QTestState **from, QTestState **to,
+ const char *uri, MigrateStart *args)
+{
+ gchar *arch_source, *arch_target;
+ gchar *cmd_source, *cmd_target;
+ const gchar *ignore_stderr;
+ char *bootpath = NULL;
+ char *shmem_opts;
+ char *shmem_path;
+ const char *arch = qtest_get_arch();
+ const char *machine_opts = NULL;
+ const char *memory_size;
+
+ if (args->use_shmem) {
+ if (!g_file_test("/dev/shm", G_FILE_TEST_IS_DIR)) {
+ g_test_skip("/dev/shm is not supported");
+ return -1;
+ }
+ }
+
+ got_stop = false;
+ bootpath = g_strdup_printf("%s/bootsect", tmpfs);
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ /* the assembled x86 boot sector should be exactly one sector large */
+ assert(sizeof(x86_bootsect) == 512);
+ init_bootfile(bootpath, x86_bootsect, sizeof(x86_bootsect));
+ memory_size = "150M";
+ arch_source = g_strdup_printf("-drive file=%s,format=raw", bootpath);
+ arch_target = g_strdup(arch_source);
+ start_address = X86_TEST_MEM_START;
+ end_address = X86_TEST_MEM_END;
+ } else if (g_str_equal(arch, "s390x")) {
+ init_bootfile(bootpath, s390x_elf, sizeof(s390x_elf));
+ memory_size = "128M";
+ arch_source = g_strdup_printf("-bios %s", bootpath);
+ arch_target = g_strdup(arch_source);
+ start_address = S390_TEST_MEM_START;
+ end_address = S390_TEST_MEM_END;
+ } else if (strcmp(arch, "ppc64") == 0) {
+ machine_opts = "vsmt=8";
+ memory_size = "256M";
+ arch_source = g_strdup_printf("-nodefaults "
+ "-prom-env 'use-nvramrc?=true' -prom-env "
+ "'nvramrc=hex .\" _\" begin %x %x "
+ "do i c@ 1 + i c! 1000 +loop .\" B\" 0 "
+ "until'", end_address, start_address);
+ arch_target = g_strdup("");
+ start_address = PPC_TEST_MEM_START;
+ end_address = PPC_TEST_MEM_END;
+ } else if (strcmp(arch, "aarch64") == 0) {
+ init_bootfile(bootpath, aarch64_kernel, sizeof(aarch64_kernel));
+ machine_opts = "virt,gic-version=max";
+ memory_size = "150M";
+ arch_source = g_strdup_printf("-cpu max "
+ "-kernel %s",
+ bootpath);
+ arch_target = g_strdup(arch_source);
+ start_address = ARM_TEST_MEM_START;
+ end_address = ARM_TEST_MEM_END;
+
+ g_assert(sizeof(aarch64_kernel) <= ARM_TEST_MAX_KERNEL_SIZE);
+ } else {
+ g_assert_not_reached();
+ }
+
+ g_free(bootpath);
+
+ if (args->hide_stderr) {
+ ignore_stderr = "2>/dev/null";
+ } else {
+ ignore_stderr = "";
+ }
+
+ if (args->use_shmem) {
+ shmem_path = g_strdup_printf("/dev/shm/qemu-%d", getpid());
+ shmem_opts = g_strdup_printf(
+ "-object memory-backend-file,id=mem0,size=%s"
+ ",mem-path=%s,share=on -numa node,memdev=mem0",
+ memory_size, shmem_path);
+ } else {
+ shmem_path = NULL;
+ shmem_opts = g_strdup("");
+ }
+
+ cmd_source = g_strdup_printf("-accel kvm -accel tcg%s%s "
+ "-name source,debug-threads=on "
+ "-m %s "
+ "-serial file:%s/src_serial "
+ "%s %s %s %s",
+ machine_opts ? " -machine " : "",
+ machine_opts ? machine_opts : "",
+ memory_size, tmpfs,
+ arch_source, shmem_opts, args->opts_source,
+ ignore_stderr);
+ g_free(arch_source);
+ *from = qtest_init(cmd_source);
+ g_free(cmd_source);
+
+ cmd_target = g_strdup_printf("-accel kvm -accel tcg%s%s "
+ "-name target,debug-threads=on "
+ "-m %s "
+ "-serial file:%s/dest_serial "
+ "-incoming %s "
+ "%s %s %s %s",
+ machine_opts ? " -machine " : "",
+ machine_opts ? machine_opts : "",
+ memory_size, tmpfs, uri,
+ arch_target, shmem_opts,
+ args->opts_target, ignore_stderr);
+ g_free(arch_target);
+ *to = qtest_init(cmd_target);
+ g_free(cmd_target);
+
+ g_free(shmem_opts);
+ /*
+ * Remove shmem file immediately to avoid memory leak in test failed case.
+ * It's valid becase QEMU has already opened this file
+ */
+ if (args->use_shmem) {
+ unlink(shmem_path);
+ g_free(shmem_path);
+ }
+
+ migrate_start_destroy(args);
+ return 0;
+}
+
+static void test_migrate_end(QTestState *from, QTestState *to, bool test_dest)
+{
+ unsigned char dest_byte_a, dest_byte_b, dest_byte_c, dest_byte_d;
+
+ qtest_quit(from);
+
+ if (test_dest) {
+ qtest_memread(to, start_address, &dest_byte_a, 1);
+
+ /* Destination still running, wait for a byte to change */
+ do {
+ qtest_memread(to, start_address, &dest_byte_b, 1);
+ usleep(1000 * 10);
+ } while (dest_byte_a == dest_byte_b);
+
+ qtest_qmp_discard_response(to, "{ 'execute' : 'stop'}");
+
+ /* With it stopped, check nothing changes */
+ qtest_memread(to, start_address, &dest_byte_c, 1);
+ usleep(1000 * 200);
+ qtest_memread(to, start_address, &dest_byte_d, 1);
+ g_assert_cmpint(dest_byte_c, ==, dest_byte_d);
+
+ check_guests_ram(to);
+ }
+
+ qtest_quit(to);
+
+ cleanup("bootsect");
+ cleanup("migsocket");
+ cleanup("src_serial");
+ cleanup("dest_serial");
+}
+
+static void deprecated_set_downtime(QTestState *who, const double value)
+{
+ QDict *rsp;
+
+ rsp = qtest_qmp(who,
+ "{ 'execute': 'migrate_set_downtime',"
+ " 'arguments': { 'value': %f } }", value);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ migrate_check_parameter_int(who, "downtime-limit", value * 1000);
+}
+
+static void deprecated_set_speed(QTestState *who, long long value)
+{
+ QDict *rsp;
+
+ rsp = qtest_qmp(who, "{ 'execute': 'migrate_set_speed',"
+ "'arguments': { 'value': %lld } }", value);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ migrate_check_parameter_int(who, "max-bandwidth", value);
+}
+
+static void deprecated_set_cache_size(QTestState *who, long long value)
+{
+ QDict *rsp;
+
+ rsp = qtest_qmp(who, "{ 'execute': 'migrate-set-cache-size',"
+ "'arguments': { 'value': %lld } }", value);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+ migrate_check_parameter_int(who, "xbzrle-cache-size", value);
+}
+
+static void test_deprecated(void)
+{
+ QTestState *from;
+
+ from = qtest_init("-machine none");
+
+ deprecated_set_downtime(from, 0.12345);
+ deprecated_set_speed(from, 12345);
+ deprecated_set_cache_size(from, 4096);
+
+ qtest_quit(from);
+}
+
+static int migrate_postcopy_prepare(QTestState **from_ptr,
+ QTestState **to_ptr,
+ MigrateStart *args)
+{
+ char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
+ QTestState *from, *to;
+
+ if (test_migrate_start(&from, &to, uri, args)) {
+ return -1;
+ }
+
+ migrate_set_capability(from, "postcopy-ram", true);
+ migrate_set_capability(to, "postcopy-ram", true);
+ migrate_set_capability(to, "postcopy-blocktime", true);
+
+ /* We want to pick a speed slow enough that the test completes
+ * quickly, but that it doesn't complete precopy even on a slow
+ * machine, so also set the downtime.
+ */
+ migrate_set_parameter_int(from, "max-bandwidth", 30000000);
+ migrate_set_parameter_int(from, "downtime-limit", 1);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ migrate_qmp(from, uri, "{}");
+ g_free(uri);
+
+ wait_for_migration_pass(from);
+
+ *from_ptr = from;
+ *to_ptr = to;
+
+ return 0;
+}
+
+static void migrate_postcopy_complete(QTestState *from, QTestState *to)
+{
+ wait_for_migration_complete(from);
+
+ /* Make sure we get at least one "B" on destination */
+ wait_for_serial("dest_serial");
+
+ if (uffd_feature_thread_id) {
+ read_blocktime(to);
+ }
+
+ test_migrate_end(from, to, true);
+}
+
+static void test_postcopy(void)
+{
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+
+ if (migrate_postcopy_prepare(&from, &to, args)) {
+ return;
+ }
+ migrate_postcopy_start(from, to);
+ migrate_postcopy_complete(from, to);
+}
+
+static void test_postcopy_recovery(void)
+{
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+ char *uri;
+
+ args->hide_stderr = true;
+
+ if (migrate_postcopy_prepare(&from, &to, args)) {
+ return;
+ }
+
+ /* Turn postcopy speed down, 4K/s is slow enough on any machines */
+ migrate_set_parameter_int(from, "max-postcopy-bandwidth", 4096);
+
+ /* Now we start the postcopy */
+ migrate_postcopy_start(from, to);
+
+ /*
+ * Wait until postcopy is really started; we can only run the
+ * migrate-pause command during a postcopy
+ */
+ wait_for_migration_status(from, "postcopy-active", NULL);
+
+ /*
+ * Manually stop the postcopy migration. This emulates a network
+ * failure with the migration socket
+ */
+ migrate_pause(from);
+
+ /*
+ * Wait for destination side to reach postcopy-paused state. The
+ * migrate-recover command can only succeed if destination machine
+ * is in the paused state
+ */
+ wait_for_migration_status(to, "postcopy-paused",
+ (const char * []) { "failed", "active",
+ "completed", NULL });
+
+ /*
+ * Create a new socket to emulate a new channel that is different
+ * from the broken migration channel; tell the destination to
+ * listen to the new port
+ */
+ uri = g_strdup_printf("unix:%s/migsocket-recover", tmpfs);
+ migrate_recover(to, uri);
+
+ /*
+ * Try to rebuild the migration channel using the resume flag and
+ * the newly created channel
+ */
+ wait_for_migration_status(from, "postcopy-paused",
+ (const char * []) { "failed", "active",
+ "completed", NULL });
+ migrate_qmp(from, uri, "{'resume': true}");
+ g_free(uri);
+
+ /* Restore the postcopy bandwidth to unlimited */
+ migrate_set_parameter_int(from, "max-postcopy-bandwidth", 0);
+
+ migrate_postcopy_complete(from, to);
+}
+
+static void test_baddest(void)
+{
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+
+ args->hide_stderr = true;
+
+ if (test_migrate_start(&from, &to, "tcp:0:0", args)) {
+ return;
+ }
+ migrate_qmp(from, "tcp:0:0", "{}");
+ wait_for_migration_fail(from, false);
+ test_migrate_end(from, to, false);
+}
+
+static void test_precopy_unix(void)
+{
+ char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+
+ if (test_migrate_start(&from, &to, uri, args)) {
+ return;
+ }
+
+ /* We want to pick a speed slow enough that the test completes
+ * quickly, but that it doesn't complete precopy even on a slow
+ * machine, so also set the downtime.
+ */
+ /* 1 ms should make it not converge*/
+ migrate_set_parameter_int(from, "downtime-limit", 1);
+ /* 1GB/s */
+ migrate_set_parameter_int(from, "max-bandwidth", 1000000000);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ migrate_qmp(from, uri, "{}");
+
+ wait_for_migration_pass(from);
+
+ /* 300 ms should converge */
+ migrate_set_parameter_int(from, "downtime-limit", 300);
+
+ if (!got_stop) {
+ qtest_qmp_eventwait(from, "STOP");
+ }
+
+ qtest_qmp_eventwait(to, "RESUME");
+
+ wait_for_serial("dest_serial");
+ wait_for_migration_complete(from);
+
+ test_migrate_end(from, to, true);
+ g_free(uri);
+}
+
+#if 0
+/* Currently upset on aarch64 TCG */
+static void test_ignore_shared(void)
+{
+ char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
+ QTestState *from, *to;
+
+ if (test_migrate_start(&from, &to, uri, false, true, NULL, NULL)) {
+ return;
+ }
+
+ migrate_set_capability(from, "x-ignore-shared", true);
+ migrate_set_capability(to, "x-ignore-shared", true);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ migrate_qmp(from, uri, "{}");
+
+ wait_for_migration_pass(from);
+
+ if (!got_stop) {
+ qtest_qmp_eventwait(from, "STOP");
+ }
+
+ qtest_qmp_eventwait(to, "RESUME");
+
+ wait_for_serial("dest_serial");
+ wait_for_migration_complete(from);
+
+ /* Check whether shared RAM has been really skipped */
+ g_assert_cmpint(read_ram_property_int(from, "transferred"), <, 1024 * 1024);
+
+ test_migrate_end(from, to, true);
+ g_free(uri);
+}
+#endif
+
+static void test_xbzrle(const char *uri)
+{
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+
+ if (test_migrate_start(&from, &to, uri, args)) {
+ return;
+ }
+
+ /*
+ * We want to pick a speed slow enough that the test completes
+ * quickly, but that it doesn't complete precopy even on a slow
+ * machine, so also set the downtime.
+ */
+ /* 1 ms should make it not converge*/
+ migrate_set_parameter_int(from, "downtime-limit", 1);
+ /* 1GB/s */
+ migrate_set_parameter_int(from, "max-bandwidth", 1000000000);
+
+ migrate_set_parameter_int(from, "xbzrle-cache-size", 33554432);
+
+ migrate_set_capability(from, "xbzrle", "true");
+ migrate_set_capability(to, "xbzrle", "true");
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ migrate_qmp(from, uri, "{}");
+
+ wait_for_migration_pass(from);
+
+ /* 300ms should converge */
+ migrate_set_parameter_int(from, "downtime-limit", 300);
+
+ if (!got_stop) {
+ qtest_qmp_eventwait(from, "STOP");
+ }
+ qtest_qmp_eventwait(to, "RESUME");
+
+ wait_for_serial("dest_serial");
+ wait_for_migration_complete(from);
+
+ test_migrate_end(from, to, true);
+}
+
+static void test_xbzrle_unix(void)
+{
+ char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
+
+ test_xbzrle(uri);
+ g_free(uri);
+}
+
+static void test_precopy_tcp(void)
+{
+ MigrateStart *args = migrate_start_new();
+ char *uri;
+ QTestState *from, *to;
+
+ if (test_migrate_start(&from, &to, "tcp:127.0.0.1:0", args)) {
+ return;
+ }
+
+ /*
+ * We want to pick a speed slow enough that the test completes
+ * quickly, but that it doesn't complete precopy even on a slow
+ * machine, so also set the downtime.
+ */
+ /* 1 ms should make it not converge*/
+ migrate_set_parameter_int(from, "downtime-limit", 1);
+ /* 1GB/s */
+ migrate_set_parameter_int(from, "max-bandwidth", 1000000000);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ uri = migrate_get_socket_address(to, "socket-address");
+
+ migrate_qmp(from, uri, "{}");
+
+ wait_for_migration_pass(from);
+
+ /* 300ms should converge */
+ migrate_set_parameter_int(from, "downtime-limit", 300);
+
+ if (!got_stop) {
+ qtest_qmp_eventwait(from, "STOP");
+ }
+ qtest_qmp_eventwait(to, "RESUME");
+
+ wait_for_serial("dest_serial");
+ wait_for_migration_complete(from);
+
+ test_migrate_end(from, to, true);
+ g_free(uri);
+}
+
+static void test_migrate_fd_proto(void)
+{
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+ int ret;
+ int pair[2];
+ QDict *rsp;
+ const char *error_desc;
+
+ if (test_migrate_start(&from, &to, "defer", args)) {
+ return;
+ }
+
+ /*
+ * We want to pick a speed slow enough that the test completes
+ * quickly, but that it doesn't complete precopy even on a slow
+ * machine, so also set the downtime.
+ */
+ /* 1 ms should make it not converge */
+ migrate_set_parameter_int(from, "downtime-limit", 1);
+ /* 1GB/s */
+ migrate_set_parameter_int(from, "max-bandwidth", 1000000000);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ /* Create two connected sockets for migration */
+ ret = socketpair(PF_LOCAL, SOCK_STREAM, 0, pair);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Send the 1st socket to the target */
+ rsp = wait_command_fd(to, pair[0],
+ "{ 'execute': 'getfd',"
+ " 'arguments': { 'fdname': 'fd-mig' }}");
+ qobject_unref(rsp);
+ close(pair[0]);
+
+ /* Start incoming migration from the 1st socket */
+ rsp = wait_command(to, "{ 'execute': 'migrate-incoming',"
+ " 'arguments': { 'uri': 'fd:fd-mig' }}");
+ qobject_unref(rsp);
+
+ /* Send the 2nd socket to the target */
+ rsp = wait_command_fd(from, pair[1],
+ "{ 'execute': 'getfd',"
+ " 'arguments': { 'fdname': 'fd-mig' }}");
+ qobject_unref(rsp);
+ close(pair[1]);
+
+ /* Start migration to the 2nd socket*/
+ migrate_qmp(from, "fd:fd-mig", "{}");
+
+ wait_for_migration_pass(from);
+
+ /* 300ms should converge */
+ migrate_set_parameter_int(from, "downtime-limit", 300);
+
+ if (!got_stop) {
+ qtest_qmp_eventwait(from, "STOP");
+ }
+ qtest_qmp_eventwait(to, "RESUME");
+
+ /* Test closing fds */
+ /* We assume, that QEMU removes named fd from its list,
+ * so this should fail */
+ rsp = qtest_qmp(from, "{ 'execute': 'closefd',"
+ " 'arguments': { 'fdname': 'fd-mig' }}");
+ g_assert_true(qdict_haskey(rsp, "error"));
+ error_desc = qdict_get_str(qdict_get_qdict(rsp, "error"), "desc");
+ g_assert_cmpstr(error_desc, ==, "File descriptor named 'fd-mig' not found");
+ qobject_unref(rsp);
+
+ rsp = qtest_qmp(to, "{ 'execute': 'closefd',"
+ " 'arguments': { 'fdname': 'fd-mig' }}");
+ g_assert_true(qdict_haskey(rsp, "error"));
+ error_desc = qdict_get_str(qdict_get_qdict(rsp, "error"), "desc");
+ g_assert_cmpstr(error_desc, ==, "File descriptor named 'fd-mig' not found");
+ qobject_unref(rsp);
+
+ /* Complete migration */
+ wait_for_serial("dest_serial");
+ wait_for_migration_complete(from);
+ test_migrate_end(from, to, true);
+}
+
+static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
+{
+ char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
+ QTestState *from, *to;
+
+ if (test_migrate_start(&from, &to, uri, args)) {
+ return;
+ }
+
+ /*
+ * UUID validation is at the begin of migration. So, the main process of
+ * migration is not interesting for us here. Thus, set huge downtime for
+ * very fast migration.
+ */
+ migrate_set_parameter_int(from, "downtime-limit", 1000000);
+ migrate_set_capability(from, "validate-uuid", true);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ migrate_qmp(from, uri, "{}");
+
+ if (should_fail) {
+ qtest_set_expected_status(to, 1);
+ wait_for_migration_fail(from, true);
+ } else {
+ wait_for_migration_complete(from);
+ }
+
+ test_migrate_end(from, to, false);
+ g_free(uri);
+}
+
+static void test_validate_uuid(void)
+{
+ MigrateStart *args = migrate_start_new();
+
+ args->opts_source = g_strdup("-uuid 11111111-1111-1111-1111-111111111111");
+ args->opts_target = g_strdup("-uuid 11111111-1111-1111-1111-111111111111");
+ do_test_validate_uuid(args, false);
+}
+
+static void test_validate_uuid_error(void)
+{
+ MigrateStart *args = migrate_start_new();
+
+ args->opts_source = g_strdup("-uuid 11111111-1111-1111-1111-111111111111");
+ args->opts_target = g_strdup("-uuid 22222222-2222-2222-2222-222222222222");
+ args->hide_stderr = true;
+ do_test_validate_uuid(args, true);
+}
+
+static void test_validate_uuid_src_not_set(void)
+{
+ MigrateStart *args = migrate_start_new();
+
+ args->opts_target = g_strdup("-uuid 22222222-2222-2222-2222-222222222222");
+ args->hide_stderr = true;
+ do_test_validate_uuid(args, false);
+}
+
+static void test_validate_uuid_dst_not_set(void)
+{
+ MigrateStart *args = migrate_start_new();
+
+ args->opts_source = g_strdup("-uuid 11111111-1111-1111-1111-111111111111");
+ args->hide_stderr = true;
+ do_test_validate_uuid(args, false);
+}
+
+static void test_migrate_auto_converge(void)
+{
+ char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs);
+ MigrateStart *args = migrate_start_new();
+ QTestState *from, *to;
+ int64_t remaining, percentage;
+
+ /*
+ * We want the test to be stable and as fast as possible.
+ * E.g., with 1Gb/s bandwith migration may pass without throttling,
+ * so we need to decrease a bandwidth.
+ */
+ const int64_t init_pct = 5, inc_pct = 50, max_pct = 95;
+ const int64_t max_bandwidth = 400000000; /* ~400Mb/s */
+ const int64_t downtime_limit = 250; /* 250ms */
+ /*
+ * We migrate through unix-socket (> 500Mb/s).
+ * Thus, expected migration speed ~= bandwidth limit (< 500Mb/s).
+ * So, we can predict expected_threshold
+ */
+ const int64_t expected_threshold = max_bandwidth * downtime_limit / 1000;
+
+ if (test_migrate_start(&from, &to, uri, args)) {
+ return;
+ }
+
+ migrate_set_capability(from, "auto-converge", true);
+ migrate_set_parameter_int(from, "cpu-throttle-initial", init_pct);
+ migrate_set_parameter_int(from, "cpu-throttle-increment", inc_pct);
+ migrate_set_parameter_int(from, "max-cpu-throttle", max_pct);
+
+ /*
+ * Set the initial parameters so that the migration could not converge
+ * without throttling.
+ */
+ migrate_set_parameter_int(from, "downtime-limit", 1);
+ migrate_set_parameter_int(from, "max-bandwidth", 100000000); /* ~100Mb/s */
+
+ /* To check remaining size after precopy */
+ migrate_set_capability(from, "pause-before-switchover", true);
+
+ /* Wait for the first serial output from the source */
+ wait_for_serial("src_serial");
+
+ migrate_qmp(from, uri, "{}");
+
+ /* Wait for throttling begins */
+ percentage = 0;
+ while (percentage == 0) {
+ percentage = read_migrate_property_int(from, "cpu-throttle-percentage");
+ usleep(100);
+ g_assert_false(got_stop);
+ }
+ /* The first percentage of throttling should be equal to init_pct */
+ g_assert_cmpint(percentage, ==, init_pct);
+ /* Now, when we tested that throttling works, let it converge */
+ migrate_set_parameter_int(from, "downtime-limit", downtime_limit);
+ migrate_set_parameter_int(from, "max-bandwidth", max_bandwidth);
+
+ /*
+ * Wait for pre-switchover status to check last throttle percentage
+ * and remaining. These values will be zeroed later
+ */
+ wait_for_migration_status(from, "pre-switchover", NULL);
+
+ /* The final percentage of throttling shouldn't be greater than max_pct */
+ percentage = read_migrate_property_int(from, "cpu-throttle-percentage");
+ g_assert_cmpint(percentage, <=, max_pct);
+
+ remaining = read_ram_property_int(from, "remaining");
+ g_assert_cmpint(remaining, <, expected_threshold);
+
+ migrate_continue(from, "pre-switchover");
+
+ qtest_qmp_eventwait(to, "RESUME");
+
+ wait_for_serial("dest_serial");
+ wait_for_migration_complete(from);
+
+ g_free(uri);
+
+ test_migrate_end(from, to, true);
+}
+
+int main(int argc, char **argv)
+{
+ char template[] = "/tmp/migration-test-XXXXXX";
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (!ufd_version_check()) {
+ return g_test_run();
+ }
+
+ /*
+ * On ppc64, the test only works with kvm-hv, but not with kvm-pr and TCG
+ * is touchy due to race conditions on dirty bits (especially on PPC for
+ * some reason)
+ */
+ if (g_str_equal(qtest_get_arch(), "ppc64") &&
+ (access("/sys/module/kvm_hv", F_OK) ||
+ access("/dev/kvm", R_OK | W_OK))) {
+ g_test_message("Skipping test: kvm_hv not available");
+ return g_test_run();
+ }
+
+ /*
+ * Similar to ppc64, s390x seems to be touchy with TCG, so disable it
+ * there until the problems are resolved
+ */
+ if (g_str_equal(qtest_get_arch(), "s390x")) {
+#if defined(HOST_S390X)
+ if (access("/dev/kvm", R_OK | W_OK)) {
+ g_test_message("Skipping test: kvm not available");
+ return g_test_run();
+ }
+#else
+ g_test_message("Skipping test: Need s390x host to work properly");
+ return g_test_run();
+#endif
+ }
+
+ tmpfs = mkdtemp(template);
+ if (!tmpfs) {
+ g_test_message("mkdtemp on path (%s): %s", template, strerror(errno));
+ }
+ g_assert(tmpfs);
+
+ module_call_init(MODULE_INIT_QOM);
+
+ qtest_add_func("/migration/postcopy/unix", test_postcopy);
+ qtest_add_func("/migration/postcopy/recovery", test_postcopy_recovery);
+ qtest_add_func("/migration/deprecated", test_deprecated);
+ qtest_add_func("/migration/bad_dest", test_baddest);
+ qtest_add_func("/migration/precopy/unix", test_precopy_unix);
+ qtest_add_func("/migration/precopy/tcp", test_precopy_tcp);
+ /* qtest_add_func("/migration/ignore_shared", test_ignore_shared); */
+ qtest_add_func("/migration/xbzrle/unix", test_xbzrle_unix);
+ qtest_add_func("/migration/fd_proto", test_migrate_fd_proto);
+ qtest_add_func("/migration/validate_uuid", test_validate_uuid);
+ qtest_add_func("/migration/validate_uuid_error", test_validate_uuid_error);
+ qtest_add_func("/migration/validate_uuid_src_not_set",
+ test_validate_uuid_src_not_set);
+ qtest_add_func("/migration/validate_uuid_dst_not_set",
+ test_validate_uuid_dst_not_set);
+
+ qtest_add_func("/migration/auto_converge", test_migrate_auto_converge);
+
+ ret = g_test_run();
+
+ g_assert_cmpint(ret, ==, 0);
+
+ ret = rmdir(tmpfs);
+ if (ret != 0) {
+ g_test_message("unable to rmdir: path (%s): %s",
+ tmpfs, strerror(errno));
+ }
+
+ return ret;
+}
diff --git a/tests/qtest/modules-test.c b/tests/qtest/modules-test.c
new file mode 100644
index 0000000000..88217686e1
--- /dev/null
+++ b/tests/qtest/modules-test.c
@@ -0,0 +1,74 @@
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+const char common_args[] = "-nodefaults -machine none";
+
+static void test_modules_load(const void *data)
+{
+ QTestState *qts;
+ const char **args = (const char **)data;
+
+ qts = qtest_init(common_args);
+ qtest_module_load(qts, args[0], args[1]);
+ qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *modules[] = {
+#ifdef CONFIG_CURL
+ "block-", "curl",
+#endif
+#ifdef CONFIG_GLUSTERFS
+ "block-", "gluster",
+#endif
+#ifdef CONFIG_LIBISCSI
+ "block-", "iscsi",
+#endif
+#ifdef CONFIG_LIBNFS
+ "block-", "nfs",
+#endif
+#ifdef CONFIG_LIBSSH
+ "block-", "ssh",
+#endif
+#ifdef CONFIG_RBD
+ "block-", "rbd",
+#endif
+#ifdef CONFIG_AUDIO_ALSA
+ "audio-", "alsa",
+#endif
+#ifdef CONFIG_AUDIO_OSS
+ "audio-", "oss",
+#endif
+#ifdef CONFIG_AUDIO_PA
+ "audio-", "pa",
+#endif
+#ifdef CONFIG_AUDIO_SDL
+ "audio-", "sdl",
+#endif
+#ifdef CONFIG_CURSES
+ "ui-", "curses",
+#endif
+#if defined(CONFIG_GTK) && defined(CONFIG_VTE)
+ "ui-", "gtk",
+#endif
+#ifdef CONFIG_SDL
+ "ui-", "sdl",
+#endif
+#if defined(CONFIG_SPICE) && defined(CONFIG_GIO)
+ "ui-", "spice-app",
+#endif
+ };
+ int i;
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (i = 0; i < G_N_ELEMENTS(modules); i += 2) {
+ char *testname = g_strdup_printf("/module/load/%s%s",
+ modules[i], modules[i + 1]);
+ qtest_add_data_func(testname, modules + i, test_modules_load);
+ g_free(testname);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/ne2000-test.c b/tests/qtest/ne2000-test.c
new file mode 100644
index 0000000000..3fc0e555d5
--- /dev/null
+++ b/tests/qtest/ne2000-test.c
@@ -0,0 +1,58 @@
+/*
+ * QTest testcase for ne2000 NIC
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QNe2k_pci QNe2k_pci;
+
+struct QNe2k_pci {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *ne2k_pci_get_driver(void *obj, const char *interface)
+{
+ QNe2k_pci *ne2k_pci = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &ne2k_pci->dev;
+ }
+
+ fprintf(stderr, "%s not present in ne2k_pci\n", interface);
+ g_assert_not_reached();
+}
+
+static void *ne2k_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QNe2k_pci *ne2k_pci = g_new0(QNe2k_pci, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&ne2k_pci->dev, bus, addr);
+ ne2k_pci->obj.get_driver = ne2k_pci_get_driver;
+
+ return &ne2k_pci->obj;
+}
+
+static void ne2000_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("ne2k_pci", ne2k_pci_create);
+ qos_node_consumes("ne2k_pci", "pci-bus", &opts);
+ qos_node_produces("ne2k_pci", "pci-device");
+}
+
+libqos_init(ne2000_register_nodes);
diff --git a/tests/qtest/numa-test.c b/tests/qtest/numa-test.c
new file mode 100644
index 0000000000..17dd807d2a
--- /dev/null
+++ b/tests/qtest/numa-test.c
@@ -0,0 +1,574 @@
+/*
+ * NUMA configuration test cases
+ *
+ * Copyright (c) 2017 Red Hat Inc.
+ * Authors:
+ * Igor Mammedov <imammedo@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+
+static char *make_cli(const char *generic_cli, const char *test_cli)
+{
+ return g_strdup_printf("%s %s", generic_cli ? generic_cli : "", test_cli);
+}
+
+static void test_mon_explicit(const void *data)
+{
+ char *s;
+ char *cli;
+ QTestState *qts;
+
+ cli = make_cli(data, "-smp 8 "
+ "-numa node,nodeid=0,cpus=0-3 "
+ "-numa node,nodeid=1,cpus=4-7 ");
+ qts = qtest_init(cli);
+
+ s = qtest_hmp(qts, "info numa");
+ g_assert(strstr(s, "node 0 cpus: 0 1 2 3"));
+ g_assert(strstr(s, "node 1 cpus: 4 5 6 7"));
+ g_free(s);
+
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static void test_mon_default(const void *data)
+{
+ char *s;
+ char *cli;
+ QTestState *qts;
+
+ cli = make_cli(data, "-smp 8 -numa node -numa node");
+ qts = qtest_init(cli);
+
+ s = qtest_hmp(qts, "info numa");
+ g_assert(strstr(s, "node 0 cpus: 0 2 4 6"));
+ g_assert(strstr(s, "node 1 cpus: 1 3 5 7"));
+ g_free(s);
+
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static void test_mon_partial(const void *data)
+{
+ char *s;
+ char *cli;
+ QTestState *qts;
+
+ cli = make_cli(data, "-smp 8 "
+ "-numa node,nodeid=0,cpus=0-1 "
+ "-numa node,nodeid=1,cpus=4-5 ");
+ qts = qtest_init(cli);
+
+ s = qtest_hmp(qts, "info numa");
+ g_assert(strstr(s, "node 0 cpus: 0 1 2 3 6 7"));
+ g_assert(strstr(s, "node 1 cpus: 4 5"));
+ g_free(s);
+
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static QList *get_cpus(QTestState *qts, QDict **resp)
+{
+ *resp = qtest_qmp(qts, "{ 'execute': 'query-cpus' }");
+ g_assert(*resp);
+ g_assert(qdict_haskey(*resp, "return"));
+ return qdict_get_qlist(*resp, "return");
+}
+
+static void test_query_cpus(const void *data)
+{
+ char *cli;
+ QDict *resp;
+ QList *cpus;
+ QObject *e;
+ QTestState *qts;
+
+ cli = make_cli(data, "-smp 8 -numa node,cpus=0-3 -numa node,cpus=4-7");
+ qts = qtest_init(cli);
+ cpus = get_cpus(qts, &resp);
+ g_assert(cpus);
+
+ while ((e = qlist_pop(cpus))) {
+ QDict *cpu, *props;
+ int64_t cpu_idx, node;
+
+ cpu = qobject_to(QDict, e);
+ g_assert(qdict_haskey(cpu, "CPU"));
+ g_assert(qdict_haskey(cpu, "props"));
+
+ cpu_idx = qdict_get_int(cpu, "CPU");
+ props = qdict_get_qdict(cpu, "props");
+ g_assert(qdict_haskey(props, "node-id"));
+ node = qdict_get_int(props, "node-id");
+ if (cpu_idx >= 0 && cpu_idx < 4) {
+ g_assert_cmpint(node, ==, 0);
+ } else {
+ g_assert_cmpint(node, ==, 1);
+ }
+ qobject_unref(e);
+ }
+
+ qobject_unref(resp);
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static void pc_numa_cpu(const void *data)
+{
+ char *cli;
+ QDict *resp;
+ QList *cpus;
+ QObject *e;
+ QTestState *qts;
+
+ cli = make_cli(data, "-cpu pentium -smp 8,sockets=2,cores=2,threads=2 "
+ "-numa node,nodeid=0 -numa node,nodeid=1 "
+ "-numa cpu,node-id=1,socket-id=0 "
+ "-numa cpu,node-id=0,socket-id=1,core-id=0 "
+ "-numa cpu,node-id=0,socket-id=1,core-id=1,thread-id=0 "
+ "-numa cpu,node-id=1,socket-id=1,core-id=1,thread-id=1");
+ qts = qtest_init(cli);
+ cpus = get_cpus(qts, &resp);
+ g_assert(cpus);
+
+ while ((e = qlist_pop(cpus))) {
+ QDict *cpu, *props;
+ int64_t socket, core, thread, node;
+
+ cpu = qobject_to(QDict, e);
+ g_assert(qdict_haskey(cpu, "props"));
+ props = qdict_get_qdict(cpu, "props");
+
+ g_assert(qdict_haskey(props, "node-id"));
+ node = qdict_get_int(props, "node-id");
+ g_assert(qdict_haskey(props, "socket-id"));
+ socket = qdict_get_int(props, "socket-id");
+ g_assert(qdict_haskey(props, "core-id"));
+ core = qdict_get_int(props, "core-id");
+ g_assert(qdict_haskey(props, "thread-id"));
+ thread = qdict_get_int(props, "thread-id");
+
+ if (socket == 0) {
+ g_assert_cmpint(node, ==, 1);
+ } else if (socket == 1 && core == 0) {
+ g_assert_cmpint(node, ==, 0);
+ } else if (socket == 1 && core == 1 && thread == 0) {
+ g_assert_cmpint(node, ==, 0);
+ } else if (socket == 1 && core == 1 && thread == 1) {
+ g_assert_cmpint(node, ==, 1);
+ } else {
+ g_assert(false);
+ }
+ qobject_unref(e);
+ }
+
+ qobject_unref(resp);
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static void spapr_numa_cpu(const void *data)
+{
+ char *cli;
+ QDict *resp;
+ QList *cpus;
+ QObject *e;
+ QTestState *qts;
+
+ cli = make_cli(data, "-smp 4,cores=4 "
+ "-numa node,nodeid=0 -numa node,nodeid=1 "
+ "-numa cpu,node-id=0,core-id=0 "
+ "-numa cpu,node-id=0,core-id=1 "
+ "-numa cpu,node-id=0,core-id=2 "
+ "-numa cpu,node-id=1,core-id=3");
+ qts = qtest_init(cli);
+ cpus = get_cpus(qts, &resp);
+ g_assert(cpus);
+
+ while ((e = qlist_pop(cpus))) {
+ QDict *cpu, *props;
+ int64_t core, node;
+
+ cpu = qobject_to(QDict, e);
+ g_assert(qdict_haskey(cpu, "props"));
+ props = qdict_get_qdict(cpu, "props");
+
+ g_assert(qdict_haskey(props, "node-id"));
+ node = qdict_get_int(props, "node-id");
+ g_assert(qdict_haskey(props, "core-id"));
+ core = qdict_get_int(props, "core-id");
+
+ if (core >= 0 && core < 3) {
+ g_assert_cmpint(node, ==, 0);
+ } else if (core == 3) {
+ g_assert_cmpint(node, ==, 1);
+ } else {
+ g_assert(false);
+ }
+ qobject_unref(e);
+ }
+
+ qobject_unref(resp);
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static void aarch64_numa_cpu(const void *data)
+{
+ char *cli;
+ QDict *resp;
+ QList *cpus;
+ QObject *e;
+ QTestState *qts;
+
+ cli = make_cli(data, "-smp 2 "
+ "-numa node,nodeid=0 -numa node,nodeid=1 "
+ "-numa cpu,node-id=1,thread-id=0 "
+ "-numa cpu,node-id=0,thread-id=1");
+ qts = qtest_init(cli);
+ cpus = get_cpus(qts, &resp);
+ g_assert(cpus);
+
+ while ((e = qlist_pop(cpus))) {
+ QDict *cpu, *props;
+ int64_t thread, node;
+
+ cpu = qobject_to(QDict, e);
+ g_assert(qdict_haskey(cpu, "props"));
+ props = qdict_get_qdict(cpu, "props");
+
+ g_assert(qdict_haskey(props, "node-id"));
+ node = qdict_get_int(props, "node-id");
+ g_assert(qdict_haskey(props, "thread-id"));
+ thread = qdict_get_int(props, "thread-id");
+
+ if (thread == 0) {
+ g_assert_cmpint(node, ==, 1);
+ } else if (thread == 1) {
+ g_assert_cmpint(node, ==, 0);
+ } else {
+ g_assert(false);
+ }
+ qobject_unref(e);
+ }
+
+ qobject_unref(resp);
+ qtest_quit(qts);
+ g_free(cli);
+}
+
+static void pc_dynamic_cpu_cfg(const void *data)
+{
+ QObject *e;
+ QDict *resp;
+ QList *cpus;
+ QTestState *qs;
+
+ qs = qtest_initf("%s -nodefaults --preconfig -smp 2",
+ data ? (char *)data : "");
+
+ /* create 2 numa nodes */
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'node', 'nodeid': 0 } }")));
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'node', 'nodeid': 1 } }")));
+
+ /* map 2 cpus in non default reverse order
+ * i.e socket1->node0, socket0->node1
+ */
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'cpu', 'node-id': 0, 'socket-id': 1 } }")));
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'cpu', 'node-id': 1, 'socket-id': 0 } }")));
+
+ /* let machine initialization to complete and run */
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }")));
+ qtest_qmp_eventwait(qs, "RESUME");
+
+ /* check that CPUs are mapped as expected */
+ resp = qtest_qmp(qs, "{ 'execute': 'query-hotpluggable-cpus'}");
+ g_assert(qdict_haskey(resp, "return"));
+ cpus = qdict_get_qlist(resp, "return");
+ g_assert(cpus);
+ while ((e = qlist_pop(cpus))) {
+ const QDict *cpu, *props;
+ int64_t socket, node;
+
+ cpu = qobject_to(QDict, e);
+ g_assert(qdict_haskey(cpu, "props"));
+ props = qdict_get_qdict(cpu, "props");
+
+ g_assert(qdict_haskey(props, "node-id"));
+ node = qdict_get_int(props, "node-id");
+ g_assert(qdict_haskey(props, "socket-id"));
+ socket = qdict_get_int(props, "socket-id");
+
+ if (socket == 0) {
+ g_assert_cmpint(node, ==, 1);
+ } else if (socket == 1) {
+ g_assert_cmpint(node, ==, 0);
+ } else {
+ g_assert(false);
+ }
+ qobject_unref(e);
+ }
+ qobject_unref(resp);
+
+ qtest_quit(qs);
+}
+
+static void pc_hmat_build_cfg(const void *data)
+{
+ QTestState *qs = qtest_initf("%s -nodefaults --preconfig -machine hmat=on "
+ "-smp 2,sockets=2 "
+ "-m 128M,slots=2,maxmem=1G "
+ "-object memory-backend-ram,size=64M,id=m0 "
+ "-object memory-backend-ram,size=64M,id=m1 "
+ "-numa node,nodeid=0,memdev=m0 "
+ "-numa node,nodeid=1,memdev=m1,initiator=0 "
+ "-numa cpu,node-id=0,socket-id=0 "
+ "-numa cpu,node-id=0,socket-id=1",
+ data ? (char *)data : "");
+
+ /* Fail: Initiator should be less than the number of nodes */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 2, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\" } }")));
+
+ /* Fail: Target should be less than the number of nodes */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 2,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\" } }")));
+
+ /* Fail: Initiator should contain cpu */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 1, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\" } }")));
+
+ /* Fail: Data-type mismatch */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"write-latency\","
+ " 'bandwidth': 524288000 } }")));
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"read-bandwidth\","
+ " 'latency': 5 } }")));
+
+ /* Fail: Bandwidth should be 1MB (1048576) aligned */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-bandwidth\","
+ " 'bandwidth': 1048575 } }")));
+
+ /* Configuring HMAT bandwidth and latency details */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\","
+ " 'latency': 1 } }"))); /* 1 ns */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\","
+ " 'latency': 5 } }"))); /* Fail: Duplicate configuration */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-bandwidth\","
+ " 'bandwidth': 68717379584 } }"))); /* 65534 MB/s */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 1,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\","
+ " 'latency': 65534 } }"))); /* 65534 ns */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 1,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-bandwidth\","
+ " 'bandwidth': 34358689792 } }"))); /* 32767 MB/s */
+
+ /* Fail: node_id should be less than the number of nodes */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 2, 'size': 10240,"
+ " 'level': 1, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+
+ /* Fail: level should be less than HMAT_LB_LEVELS (4) */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 4, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+
+ /* Fail: associativity option should be 'none', if level is 0 */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 0, 'associativity': \"direct\", 'policy': \"none\","
+ " 'line': 0 } }")));
+ /* Fail: policy option should be 'none', if level is 0 */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 0, 'associativity': \"none\", 'policy': \"write-back\","
+ " 'line': 0 } }")));
+ /* Fail: line option should be 0, if level is 0 */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 0, 'associativity': \"none\", 'policy': \"none\","
+ " 'line': 8 } }")));
+
+ /* Configuring HMAT memory side cache attributes */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 1, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 1, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }"))); /* Fail: Duplicate configuration */
+ /* Fail: The size of level 2 size should be small than level 1 */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 2, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+ /* Fail: The size of level 0 size should be larger than level 1 */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 0, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 1, 'size': 10240,"
+ " 'level': 1, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+
+ /* let machine initialization to complete and run */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs,
+ "{ 'execute': 'x-exit-preconfig' }")));
+ qtest_qmp_eventwait(qs, "RESUME");
+
+ qtest_quit(qs);
+}
+
+static void pc_hmat_off_cfg(const void *data)
+{
+ QTestState *qs = qtest_initf("%s -nodefaults --preconfig "
+ "-smp 2,sockets=2 "
+ "-m 128M,slots=2,maxmem=1G "
+ "-object memory-backend-ram,size=64M,id=m0 "
+ "-object memory-backend-ram,size=64M,id=m1 "
+ "-numa node,nodeid=0,memdev=m0",
+ data ? (char *)data : "");
+
+ /*
+ * Fail: Enable HMAT with -machine hmat=on
+ * before using any of hmat specific options
+ */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'node', 'nodeid': 1, 'memdev': \"m1\","
+ " 'initiator': 0 } }")));
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'node', 'nodeid': 1, 'memdev': \"m1\" } }")));
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\","
+ " 'latency': 1 } }")));
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 1, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+
+ /* let machine initialization to complete and run */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs,
+ "{ 'execute': 'x-exit-preconfig' }")));
+ qtest_qmp_eventwait(qs, "RESUME");
+
+ qtest_quit(qs);
+}
+
+static void pc_hmat_erange_cfg(const void *data)
+{
+ QTestState *qs = qtest_initf("%s -nodefaults --preconfig -machine hmat=on "
+ "-smp 2,sockets=2 "
+ "-m 128M,slots=2,maxmem=1G "
+ "-object memory-backend-ram,size=64M,id=m0 "
+ "-object memory-backend-ram,size=64M,id=m1 "
+ "-numa node,nodeid=0,memdev=m0 "
+ "-numa node,nodeid=1,memdev=m1,initiator=0 "
+ "-numa cpu,node-id=0,socket-id=0 "
+ "-numa cpu,node-id=0,socket-id=1",
+ data ? (char *)data : "");
+
+ /* Can't store the compressed latency */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\","
+ " 'latency': 1 } }"))); /* 1 ns */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 1,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-latency\","
+ " 'latency': 65535 } }"))); /* 65535 ns */
+
+ /* Test the 0 input (bandwidth not provided) */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 0,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-bandwidth\","
+ " 'bandwidth': 0 } }"))); /* 0 MB/s */
+ /* Fail: bandwidth should be provided before memory side cache attributes */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-cache', 'node-id': 0, 'size': 10240,"
+ " 'level': 1, 'associativity': \"direct\", 'policy': \"write-back\","
+ " 'line': 8 } }")));
+
+ /* Can't store the compressed bandwidth */
+ g_assert_true(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'set-numa-node',"
+ " 'arguments': { 'type': 'hmat-lb', 'initiator': 0, 'target': 1,"
+ " 'hierarchy': \"memory\", 'data-type': \"access-bandwidth\","
+ " 'bandwidth': 68718428160 } }"))); /* 65535 MB/s */
+
+ /* let machine initialization to complete and run */
+ g_assert_false(qmp_rsp_is_err(qtest_qmp(qs,
+ "{ 'execute': 'x-exit-preconfig' }")));
+ qtest_qmp_eventwait(qs, "RESUME");
+
+ qtest_quit(qs);
+}
+
+int main(int argc, char **argv)
+{
+ const char *args = NULL;
+ const char *arch = qtest_get_arch();
+
+ if (strcmp(arch, "aarch64") == 0) {
+ args = "-machine virt";
+ }
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/numa/mon/default", args, test_mon_default);
+ qtest_add_data_func("/numa/mon/cpus/explicit", args, test_mon_explicit);
+ qtest_add_data_func("/numa/mon/cpus/partial", args, test_mon_partial);
+ qtest_add_data_func("/numa/qmp/cpus/query-cpus", args, test_query_cpus);
+
+ if (!strcmp(arch, "i386") || !strcmp(arch, "x86_64")) {
+ qtest_add_data_func("/numa/pc/cpu/explicit", args, pc_numa_cpu);
+ qtest_add_data_func("/numa/pc/dynamic/cpu", args, pc_dynamic_cpu_cfg);
+ qtest_add_data_func("/numa/pc/hmat/build", args, pc_hmat_build_cfg);
+ qtest_add_data_func("/numa/pc/hmat/off", args, pc_hmat_off_cfg);
+ qtest_add_data_func("/numa/pc/hmat/erange", args, pc_hmat_erange_cfg);
+ }
+
+ if (!strcmp(arch, "ppc64")) {
+ qtest_add_data_func("/numa/spapr/cpu/explicit", args, spapr_numa_cpu);
+ }
+
+ if (!strcmp(arch, "aarch64")) {
+ qtest_add_data_func("/numa/aarch64/cpu/explicit", args,
+ aarch64_numa_cpu);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/nvme-test.c b/tests/qtest/nvme-test.c
new file mode 100644
index 0000000000..ff0442150c
--- /dev/null
+++ b/tests/qtest/nvme-test.c
@@ -0,0 +1,88 @@
+/*
+ * QTest testcase for NVMe
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "libqtest.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QNvme QNvme;
+
+struct QNvme {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *nvme_get_driver(void *obj, const char *interface)
+{
+ QNvme *nvme = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &nvme->dev;
+ }
+
+ fprintf(stderr, "%s not present in nvme\n", interface);
+ g_assert_not_reached();
+}
+
+static void *nvme_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QNvme *nvme = g_new0(QNvme, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&nvme->dev, bus, addr);
+ nvme->obj.get_driver = nvme_get_driver;
+
+ return &nvme->obj;
+}
+
+/* This used to cause a NULL pointer dereference. */
+static void nvmetest_oob_cmb_test(void *obj, void *data, QGuestAllocator *alloc)
+{
+ const int cmb_bar_size = 2 * MiB;
+ QNvme *nvme = obj;
+ QPCIDevice *pdev = &nvme->dev;
+ QPCIBar bar;
+
+ qpci_device_enable(pdev);
+ bar = qpci_iomap(pdev, 2, NULL);
+
+ qpci_io_writel(pdev, bar, 0, 0xccbbaa99);
+ g_assert_cmpint(qpci_io_readb(pdev, bar, 0), ==, 0x99);
+ g_assert_cmpint(qpci_io_readw(pdev, bar, 0), ==, 0xaa99);
+
+ /* Test partially out-of-bounds accesses. */
+ qpci_io_writel(pdev, bar, cmb_bar_size - 1, 0x44332211);
+ g_assert_cmpint(qpci_io_readb(pdev, bar, cmb_bar_size - 1), ==, 0x11);
+ g_assert_cmpint(qpci_io_readw(pdev, bar, cmb_bar_size - 1), !=, 0x2211);
+ g_assert_cmpint(qpci_io_readl(pdev, bar, cmb_bar_size - 1), !=, 0x44332211);
+}
+
+static void nvme_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0,drive=drv0,serial=foo",
+ .before_cmd_line = "-drive id=drv0,if=none,file=null-co://,"
+ "file.read-zeroes=on,format=raw",
+ };
+
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("nvme", nvme_create);
+ qos_node_consumes("nvme", "pci-bus", &opts);
+ qos_node_produces("nvme", "pci-device");
+
+ qos_add_test("oob-cmb-access", "nvme", nvmetest_oob_cmb_test, &(QOSGraphTestOptions) {
+ .edge.extra_device_opts = "cmb_size_mb=2"
+ });
+}
+
+libqos_init(nvme_register_nodes);
diff --git a/tests/qtest/pca9552-test.c b/tests/qtest/pca9552-test.c
new file mode 100644
index 0000000000..4b800d3c3e
--- /dev/null
+++ b/tests/qtest/pca9552-test.c
@@ -0,0 +1,93 @@
+/*
+ * QTest testcase for the PCA9552 LED blinker
+ *
+ * Copyright (c) 2017-2018, IBM Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "hw/misc/pca9552_regs.h"
+
+#define PCA9552_TEST_ID "pca9552-test"
+#define PCA9552_TEST_ADDR 0x60
+
+static void pca9552_init(QI2CDevice *i2cdev)
+{
+ /* Switch on LEDs 0 and 12 */
+ i2c_set8(i2cdev, PCA9552_LS0, 0x54);
+ i2c_set8(i2cdev, PCA9552_LS3, 0x54);
+}
+
+static void receive_autoinc(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t resp;
+ uint8_t reg = PCA9552_LS0 | PCA9552_AUTOINC;
+
+ pca9552_init(i2cdev);
+
+ i2c_send(i2cdev, &reg, 1);
+
+ /* PCA9552_LS0 */
+ i2c_recv(i2cdev, &resp, 1);
+ g_assert_cmphex(resp, ==, 0x54);
+
+ /* PCA9552_LS1 */
+ i2c_recv(i2cdev, &resp, 1);
+ g_assert_cmphex(resp, ==, 0x55);
+
+ /* PCA9552_LS2 */
+ i2c_recv(i2cdev, &resp, 1);
+ g_assert_cmphex(resp, ==, 0x55);
+
+ /* PCA9552_LS3 */
+ i2c_recv(i2cdev, &resp, 1);
+ g_assert_cmphex(resp, ==, 0x54);
+}
+
+static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t value;
+
+ value = i2c_get8(i2cdev, PCA9552_LS0);
+ g_assert_cmphex(value, ==, 0x55);
+
+ value = i2c_get8(i2cdev, PCA9552_INPUT0);
+ g_assert_cmphex(value, ==, 0x0);
+
+ pca9552_init(i2cdev);
+
+ value = i2c_get8(i2cdev, PCA9552_LS0);
+ g_assert_cmphex(value, ==, 0x54);
+
+ value = i2c_get8(i2cdev, PCA9552_INPUT0);
+ g_assert_cmphex(value, ==, 0x01);
+
+ value = i2c_get8(i2cdev, PCA9552_LS3);
+ g_assert_cmphex(value, ==, 0x54);
+
+ value = i2c_get8(i2cdev, PCA9552_INPUT1);
+ g_assert_cmphex(value, ==, 0x10);
+}
+
+static void pca9552_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "address=0x60"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { 0x60 });
+
+ qos_node_create_driver("pca9552", i2c_device_create);
+ qos_node_consumes("pca9552", "i2c-bus", &opts);
+
+ qos_add_test("tx-rx", "pca9552", send_and_receive, NULL);
+ qos_add_test("rx-autoinc", "pca9552", receive_autoinc, NULL);
+}
+libqos_init(pca9552_register_nodes);
diff --git a/tests/qtest/pci-test.c b/tests/qtest/pci-test.c
new file mode 100644
index 0000000000..4b2092b949
--- /dev/null
+++ b/tests/qtest/pci-test.c
@@ -0,0 +1,26 @@
+/*
+ * QTest testcase for PCI
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void nop(void *obj, void *data, QGuestAllocator *alloc)
+{
+}
+
+static void register_pci_test(void)
+{
+ qos_add_test("nop", "pci-device", nop, NULL);
+}
+
+libqos_init(register_pci_test);
diff --git a/tests/qtest/pcnet-test.c b/tests/qtest/pcnet-test.c
new file mode 100644
index 0000000000..900944fa7e
--- /dev/null
+++ b/tests/qtest/pcnet-test.c
@@ -0,0 +1,58 @@
+/*
+ * QTest testcase for PC-Net NIC
+ *
+ * Copyright (c) 2013-2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QPCNet QPCNet;
+
+struct QPCNet {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *pcnet_get_driver(void *obj, const char *interface)
+{
+ QPCNet *pcnet = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &pcnet->dev;
+ }
+
+ fprintf(stderr, "%s not present in pcnet\n", interface);
+ g_assert_not_reached();
+}
+
+static void *pcnet_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QPCNet *pcnet = g_new0(QPCNet, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&pcnet->dev, bus, addr);
+ pcnet->obj.get_driver = pcnet_get_driver;
+
+ return &pcnet->obj;
+}
+
+static void pcnet_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("pcnet", pcnet_create);
+ qos_node_consumes("pcnet", "pci-bus", &opts);
+ qos_node_produces("pcnet", "pci-device");
+}
+
+libqos_init(pcnet_register_nodes);
diff --git a/tests/qtest/pflash-cfi02-test.c b/tests/qtest/pflash-cfi02-test.c
new file mode 100644
index 0000000000..17aa669b2e
--- /dev/null
+++ b/tests/qtest/pflash-cfi02-test.c
@@ -0,0 +1,681 @@
+/*
+ * QTest testcase for parallel flash with AMD command set
+ *
+ * Copyright (c) 2019 Stephen Checkoway
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+/*
+ * To test the pflash_cfi02 device, we run QEMU with the musicpal machine with
+ * a pflash drive. This enables us to test some flash configurations, but not
+ * all. In particular, we're limited to a 16-bit wide flash device.
+ */
+
+#define MP_FLASH_SIZE_MAX (32 * 1024 * 1024)
+#define BASE_ADDR (0x100000000ULL - MP_FLASH_SIZE_MAX)
+
+#define UNIFORM_FLASH_SIZE (8 * 1024 * 1024)
+#define UNIFORM_FLASH_SECTOR_SIZE (64 * 1024)
+
+/* Use a newtype to keep flash addresses separate from byte addresses. */
+typedef struct {
+ uint64_t addr;
+} faddr;
+#define FLASH_ADDR(x) ((faddr) { .addr = (x) })
+
+#define CFI_ADDR FLASH_ADDR(0x55)
+#define UNLOCK0_ADDR FLASH_ADDR(0x555)
+#define UNLOCK1_ADDR FLASH_ADDR(0x2AA)
+
+#define CFI_CMD 0x98
+#define UNLOCK0_CMD 0xAA
+#define UNLOCK1_CMD 0x55
+#define SECOND_UNLOCK_CMD 0x80
+#define AUTOSELECT_CMD 0x90
+#define RESET_CMD 0xF0
+#define PROGRAM_CMD 0xA0
+#define SECTOR_ERASE_CMD 0x30
+#define CHIP_ERASE_CMD 0x10
+#define UNLOCK_BYPASS_CMD 0x20
+#define UNLOCK_BYPASS_RESET_CMD 0x00
+#define ERASE_SUSPEND_CMD 0xB0
+#define ERASE_RESUME_CMD SECTOR_ERASE_CMD
+
+typedef struct {
+ int bank_width;
+
+ /* Nonuniform block size. */
+ int nb_blocs[4];
+ int sector_len[4];
+
+ QTestState *qtest;
+} FlashConfig;
+
+static char image_path[] = "/tmp/qtest.XXXXXX";
+
+/*
+ * The pflash implementation allows some parameters to be unspecified. We want
+ * to test those configurations but we also need to know the real values in
+ * our testing code. So after we launch qemu, we'll need a new FlashConfig
+ * with the correct values filled in.
+ */
+static FlashConfig expand_config_defaults(const FlashConfig *c)
+{
+ FlashConfig ret = *c;
+
+ if (ret.bank_width == 0) {
+ ret.bank_width = 2;
+ }
+ if (ret.nb_blocs[0] == 0 && ret.sector_len[0] == 0) {
+ ret.sector_len[0] = UNIFORM_FLASH_SECTOR_SIZE;
+ ret.nb_blocs[0] = UNIFORM_FLASH_SIZE / UNIFORM_FLASH_SECTOR_SIZE;
+ }
+
+ /* XXX: Limitations of test harness. */
+ assert(ret.bank_width == 2);
+ return ret;
+}
+
+/*
+ * Return a bit mask suitable for extracting the least significant
+ * status/query response from an interleaved response.
+ */
+static inline uint64_t device_mask(const FlashConfig *c)
+{
+ return (uint64_t)-1;
+}
+
+/*
+ * Return a bit mask exactly as long as the bank_width.
+ */
+static inline uint64_t bank_mask(const FlashConfig *c)
+{
+ if (c->bank_width == 8) {
+ return (uint64_t)-1;
+ }
+ return (1ULL << (c->bank_width * 8)) - 1ULL;
+}
+
+static inline void flash_write(const FlashConfig *c, uint64_t byte_addr,
+ uint64_t data)
+{
+ /* Sanity check our tests. */
+ assert((data & ~bank_mask(c)) == 0);
+ uint64_t addr = BASE_ADDR + byte_addr;
+ switch (c->bank_width) {
+ case 1:
+ qtest_writeb(c->qtest, addr, data);
+ break;
+ case 2:
+ qtest_writew(c->qtest, addr, data);
+ break;
+ case 4:
+ qtest_writel(c->qtest, addr, data);
+ break;
+ case 8:
+ qtest_writeq(c->qtest, addr, data);
+ break;
+ default:
+ abort();
+ }
+}
+
+static inline uint64_t flash_read(const FlashConfig *c, uint64_t byte_addr)
+{
+ uint64_t addr = BASE_ADDR + byte_addr;
+ switch (c->bank_width) {
+ case 1:
+ return qtest_readb(c->qtest, addr);
+ case 2:
+ return qtest_readw(c->qtest, addr);
+ case 4:
+ return qtest_readl(c->qtest, addr);
+ case 8:
+ return qtest_readq(c->qtest, addr);
+ default:
+ abort();
+ }
+}
+
+/*
+ * Convert a flash address expressed in the maximum width of the device as a
+ * byte address.
+ */
+static inline uint64_t as_byte_addr(const FlashConfig *c, faddr flash_addr)
+{
+ /*
+ * Command addresses are always given as addresses in the maximum
+ * supported bus size for the flash chip. So an x8/x16 chip in x8 mode
+ * uses addresses 0xAAA and 0x555 to unlock because the least significant
+ * bit is ignored. (0x555 rather than 0x554 is traditional.)
+ *
+ * In general we need to multiply by the maximum device width.
+ */
+ return flash_addr.addr * c->bank_width;
+}
+
+/*
+ * Return the command value or expected status replicated across all devices.
+ */
+static inline uint64_t replicate(const FlashConfig *c, uint64_t data)
+{
+ /* Sanity check our tests. */
+ assert((data & ~device_mask(c)) == 0);
+ return data;
+}
+
+static inline void flash_cmd(const FlashConfig *c, faddr cmd_addr,
+ uint8_t cmd)
+{
+ flash_write(c, as_byte_addr(c, cmd_addr), replicate(c, cmd));
+}
+
+static inline uint64_t flash_query(const FlashConfig *c, faddr query_addr)
+{
+ return flash_read(c, as_byte_addr(c, query_addr));
+}
+
+static inline uint64_t flash_query_1(const FlashConfig *c, faddr query_addr)
+{
+ return flash_query(c, query_addr) & device_mask(c);
+}
+
+static void unlock(const FlashConfig *c)
+{
+ flash_cmd(c, UNLOCK0_ADDR, UNLOCK0_CMD);
+ flash_cmd(c, UNLOCK1_ADDR, UNLOCK1_CMD);
+}
+
+static void reset(const FlashConfig *c)
+{
+ flash_cmd(c, FLASH_ADDR(0), RESET_CMD);
+}
+
+static void sector_erase(const FlashConfig *c, uint64_t byte_addr)
+{
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
+ unlock(c);
+ flash_write(c, byte_addr, replicate(c, SECTOR_ERASE_CMD));
+}
+
+static void wait_for_completion(const FlashConfig *c, uint64_t byte_addr)
+{
+ /* If DQ6 is toggling, step the clock and ensure the toggle stops. */
+ const uint64_t dq6 = replicate(c, 0x40);
+ if ((flash_read(c, byte_addr) & dq6) ^ (flash_read(c, byte_addr) & dq6)) {
+ /* Wait for erase or program to finish. */
+ qtest_clock_step_next(c->qtest);
+ /* Ensure that DQ6 has stopped toggling. */
+ g_assert_cmphex(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
+ }
+}
+
+static void bypass_program(const FlashConfig *c, uint64_t byte_addr,
+ uint16_t data)
+{
+ flash_cmd(c, UNLOCK0_ADDR, PROGRAM_CMD);
+ flash_write(c, byte_addr, data);
+ /*
+ * Data isn't valid until DQ6 stops toggling. We don't model this as
+ * writes are immediate, but if this changes in the future, we can wait
+ * until the program is complete.
+ */
+ wait_for_completion(c, byte_addr);
+}
+
+static void program(const FlashConfig *c, uint64_t byte_addr, uint16_t data)
+{
+ unlock(c);
+ bypass_program(c, byte_addr, data);
+}
+
+static void chip_erase(const FlashConfig *c)
+{
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD);
+}
+
+static void erase_suspend(const FlashConfig *c)
+{
+ flash_cmd(c, FLASH_ADDR(0), ERASE_SUSPEND_CMD);
+}
+
+static void erase_resume(const FlashConfig *c)
+{
+ flash_cmd(c, FLASH_ADDR(0), ERASE_RESUME_CMD);
+}
+
+/*
+ * Test flash commands with a variety of device geometry.
+ */
+static void test_geometry(const void *opaque)
+{
+ const FlashConfig *config = opaque;
+ QTestState *qtest;
+ qtest = qtest_initf("-M musicpal"
+ " -drive if=pflash,file=%s,format=raw,copy-on-read"
+ /* Device geometry properties. */
+ " -global driver=cfi.pflash02,"
+ "property=num-blocks0,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=sector-length0,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=num-blocks1,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=sector-length1,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=num-blocks2,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=sector-length2,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=num-blocks3,value=%d"
+ " -global driver=cfi.pflash02,"
+ "property=sector-length3,value=%d",
+ image_path,
+ config->nb_blocs[0],
+ config->sector_len[0],
+ config->nb_blocs[1],
+ config->sector_len[1],
+ config->nb_blocs[2],
+ config->sector_len[2],
+ config->nb_blocs[3],
+ config->sector_len[3]);
+ FlashConfig explicit_config = expand_config_defaults(config);
+ explicit_config.qtest = qtest;
+ const FlashConfig *c = &explicit_config;
+
+ /* Check the IDs. */
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+ if (c->bank_width >= 2) {
+ /*
+ * XXX: The ID returned by the musicpal flash chip is 16 bits which
+ * wouldn't happen with an 8-bit device. It would probably be best to
+ * prohibit addresses larger than the device width in pflash_cfi02.c,
+ * but then we couldn't test smaller device widths at all.
+ */
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(1)), ==,
+ replicate(c, 0x236D));
+ }
+ reset(c);
+
+ /* Check the erase blocks. */
+ flash_cmd(c, CFI_ADDR, CFI_CMD);
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y'));
+
+ /* Num erase regions. */
+ int nb_erase_regions = flash_query_1(c, FLASH_ADDR(0x2C));
+ g_assert_cmphex(nb_erase_regions, ==,
+ !!c->nb_blocs[0] + !!c->nb_blocs[1] + !!c->nb_blocs[2] +
+ !!c->nb_blocs[3]);
+
+ /* Check device length. */
+ uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27));
+ g_assert_cmphex(device_len, ==, UNIFORM_FLASH_SIZE);
+
+ /* Check that erase suspend to read/write is supported. */
+ uint16_t pri = flash_query_1(c, FLASH_ADDR(0x15)) +
+ (flash_query_1(c, FLASH_ADDR(0x16)) << 8);
+ g_assert_cmpint(pri, >=, 0x2D + 4 * nb_erase_regions);
+ g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 0)), ==, replicate(c, 'P'));
+ g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 1)), ==, replicate(c, 'R'));
+ g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 2)), ==, replicate(c, 'I'));
+ g_assert_cmpint(flash_query_1(c, FLASH_ADDR(pri + 6)), ==, 2); /* R/W */
+ reset(c);
+
+ const uint64_t dq7 = replicate(c, 0x80);
+ const uint64_t dq6 = replicate(c, 0x40);
+ const uint64_t dq3 = replicate(c, 0x08);
+ const uint64_t dq2 = replicate(c, 0x04);
+
+ uint64_t byte_addr = 0;
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ uint64_t base = 0x2D + 4 * region;
+ flash_cmd(c, CFI_ADDR, CFI_CMD);
+ uint32_t nb_sectors = flash_query_1(c, FLASH_ADDR(base + 0)) +
+ (flash_query_1(c, FLASH_ADDR(base + 1)) << 8) + 1;
+ uint32_t sector_len = (flash_query_1(c, FLASH_ADDR(base + 2)) << 8) +
+ (flash_query_1(c, FLASH_ADDR(base + 3)) << 16);
+ g_assert_cmphex(nb_sectors, ==, c->nb_blocs[region]);
+ g_assert_cmphex(sector_len, ==, c->sector_len[region]);
+ reset(c);
+
+ /* Erase and program sector. */
+ for (uint32_t i = 0; i < nb_sectors; ++i) {
+ sector_erase(c, byte_addr);
+
+ /* Check that DQ3 is 0. */
+ g_assert_cmphex(flash_read(c, byte_addr) & dq3, ==, 0);
+ qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */
+
+ /* Check that DQ3 is 1. */
+ uint64_t status0 = flash_read(c, byte_addr);
+ g_assert_cmphex(status0 & dq3, ==, dq3);
+
+ /* DQ7 is 0 during an erase. */
+ g_assert_cmphex(status0 & dq7, ==, 0);
+ uint64_t status1 = flash_read(c, byte_addr);
+
+ /* DQ6 toggles during an erase. */
+ g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6);
+
+ /* Wait for erase to complete. */
+ wait_for_completion(c, byte_addr);
+
+ /* Ensure DQ6 has stopped toggling. */
+ g_assert_cmphex(flash_read(c, byte_addr), ==,
+ flash_read(c, byte_addr));
+
+ /* Now the data should be valid. */
+ g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c));
+
+ /* Program a bit pattern. */
+ program(c, byte_addr, 0x55);
+ g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
+ program(c, byte_addr, 0xA5);
+ g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x05);
+ byte_addr += sector_len;
+ }
+ }
+
+ /* Erase the chip. */
+ chip_erase(c);
+ /* Read toggle. */
+ uint64_t status0 = flash_read(c, 0);
+ /* DQ7 is 0 during an erase. */
+ g_assert_cmphex(status0 & dq7, ==, 0);
+ uint64_t status1 = flash_read(c, 0);
+ /* DQ6 toggles during an erase. */
+ g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6);
+ /* Wait for erase to complete. */
+ qtest_clock_step_next(c->qtest);
+ /* Ensure DQ6 has stopped toggling. */
+ g_assert_cmphex(flash_read(c, 0), ==, flash_read(c, 0));
+ /* Now the data should be valid. */
+
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ for (uint32_t i = 0; i < c->nb_blocs[region]; ++i) {
+ uint64_t byte_addr = i * c->sector_len[region];
+ g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c));
+ }
+ }
+
+ /* Unlock bypass */
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, UNLOCK_BYPASS_CMD);
+ bypass_program(c, 0 * c->bank_width, 0x01);
+ bypass_program(c, 1 * c->bank_width, 0x23);
+ bypass_program(c, 2 * c->bank_width, 0x45);
+ /*
+ * Test that bypass programming, unlike normal programming can use any
+ * address for the PROGRAM_CMD.
+ */
+ flash_cmd(c, FLASH_ADDR(3 * c->bank_width), PROGRAM_CMD);
+ flash_write(c, 3 * c->bank_width, 0x67);
+ wait_for_completion(c, 3 * c->bank_width);
+ flash_cmd(c, FLASH_ADDR(0), UNLOCK_BYPASS_RESET_CMD);
+ bypass_program(c, 4 * c->bank_width, 0x89); /* Should fail. */
+ g_assert_cmphex(flash_read(c, 0 * c->bank_width), ==, 0x01);
+ g_assert_cmphex(flash_read(c, 1 * c->bank_width), ==, 0x23);
+ g_assert_cmphex(flash_read(c, 2 * c->bank_width), ==, 0x45);
+ g_assert_cmphex(flash_read(c, 3 * c->bank_width), ==, 0x67);
+ g_assert_cmphex(flash_read(c, 4 * c->bank_width), ==, bank_mask(c));
+
+ /* Test ignored high order bits of address. */
+ flash_cmd(c, FLASH_ADDR(0x5555), UNLOCK0_CMD);
+ flash_cmd(c, FLASH_ADDR(0x2AAA), UNLOCK1_CMD);
+ flash_cmd(c, FLASH_ADDR(0x5555), AUTOSELECT_CMD);
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+ reset(c);
+
+ /*
+ * Program a word on each sector, erase one or two sectors per region, and
+ * verify that all of those, and only those, are erased.
+ */
+ byte_addr = 0;
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ for (int i = 0; i < config->nb_blocs[region]; ++i) {
+ program(c, byte_addr, 0);
+ byte_addr += config->sector_len[region];
+ }
+ }
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
+ unlock(c);
+ byte_addr = 0;
+ const uint64_t erase_cmd = replicate(c, SECTOR_ERASE_CMD);
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ flash_write(c, byte_addr, erase_cmd);
+ if (c->nb_blocs[region] > 1) {
+ flash_write(c, byte_addr + c->sector_len[region], erase_cmd);
+ }
+ byte_addr += c->sector_len[region] * c->nb_blocs[region];
+ }
+
+ qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */
+ wait_for_completion(c, 0);
+ byte_addr = 0;
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ for (int i = 0; i < config->nb_blocs[region]; ++i) {
+ if (i < 2) {
+ g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c));
+ } else {
+ g_assert_cmphex(flash_read(c, byte_addr), ==, 0);
+ }
+ byte_addr += config->sector_len[region];
+ }
+ }
+
+ /* Test erase suspend/resume during erase timeout. */
+ sector_erase(c, 0);
+ /*
+ * Check that DQ 3 is 0 and DQ6 and DQ2 are toggling in the sector being
+ * erased as well as in a sector not being erased.
+ */
+ byte_addr = c->sector_len[0];
+ status0 = flash_read(c, 0);
+ status1 = flash_read(c, 0);
+ g_assert_cmpint(status0 & dq3, ==, 0);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ status0 = flash_read(c, byte_addr);
+ status1 = flash_read(c, byte_addr);
+ g_assert_cmpint(status0 & dq3, ==, 0);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+
+ /*
+ * Check that after suspending, DQ6 does not toggle but DQ2 does toggle in
+ * an erase suspended sector but that neither toggle (we should be
+ * getting data) in a sector not being erased.
+ */
+ erase_suspend(c);
+ status0 = flash_read(c, 0);
+ status1 = flash_read(c, 0);
+ g_assert_cmpint(status0 & dq6, ==, status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
+
+ /* Check that after resuming, DQ3 is 1 and DQ6 and DQ2 toggle. */
+ erase_resume(c);
+ status0 = flash_read(c, 0);
+ status1 = flash_read(c, 0);
+ g_assert_cmpint(status0 & dq3, ==, dq3);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ status0 = flash_read(c, byte_addr);
+ status1 = flash_read(c, byte_addr);
+ g_assert_cmpint(status0 & dq3, ==, dq3);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ wait_for_completion(c, 0);
+
+ /* Repeat this process but this time suspend after the timeout. */
+ sector_erase(c, 0);
+ qtest_clock_step_next(c->qtest);
+ /*
+ * Check that DQ 3 is 1 and DQ6 and DQ2 are toggling in the sector being
+ * erased as well as in a sector not being erased.
+ */
+ byte_addr = c->sector_len[0];
+ status0 = flash_read(c, 0);
+ status1 = flash_read(c, 0);
+ g_assert_cmpint(status0 & dq3, ==, dq3);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ status0 = flash_read(c, byte_addr);
+ status1 = flash_read(c, byte_addr);
+ g_assert_cmpint(status0 & dq3, ==, dq3);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+
+ /*
+ * Check that after suspending, DQ6 does not toggle but DQ2 does toggle in
+ * an erase suspended sector but that neither toggle (we should be
+ * getting data) in a sector not being erased.
+ */
+ erase_suspend(c);
+ status0 = flash_read(c, 0);
+ status1 = flash_read(c, 0);
+ g_assert_cmpint(status0 & dq6, ==, status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
+
+ /* Check that after resuming, DQ3 is 1 and DQ6 and DQ2 toggle. */
+ erase_resume(c);
+ status0 = flash_read(c, 0);
+ status1 = flash_read(c, 0);
+ g_assert_cmpint(status0 & dq3, ==, dq3);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ status0 = flash_read(c, byte_addr);
+ status1 = flash_read(c, byte_addr);
+ g_assert_cmpint(status0 & dq3, ==, dq3);
+ g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+ g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2);
+ wait_for_completion(c, 0);
+
+ qtest_quit(qtest);
+}
+
+/*
+ * Test that
+ * 1. enter autoselect mode;
+ * 2. enter CFI mode; and then
+ * 3. exit CFI mode
+ * leaves the flash device in autoselect mode.
+ */
+static void test_cfi_in_autoselect(const void *opaque)
+{
+ const FlashConfig *config = opaque;
+ QTestState *qtest;
+ qtest = qtest_initf("-M musicpal"
+ " -drive if=pflash,file=%s,format=raw,copy-on-read",
+ image_path);
+ FlashConfig explicit_config = expand_config_defaults(config);
+ explicit_config.qtest = qtest;
+ const FlashConfig *c = &explicit_config;
+
+ /* 1. Enter autoselect. */
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+
+ /* 2. Enter CFI. */
+ flash_cmd(c, CFI_ADDR, CFI_CMD);
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y'));
+
+ /* 3. Exit CFI. */
+ reset(c);
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+
+ qtest_quit(qtest);
+}
+
+static void cleanup(void *opaque)
+{
+ unlink(image_path);
+}
+
+/*
+ * XXX: Tests are limited to bank_width = 2 for now because that's what
+ * hw/arm/musicpal.c has.
+ */
+static const FlashConfig configuration[] = {
+ /* One x16 device. */
+ {
+ .bank_width = 2,
+ },
+ /* Nonuniform sectors (top boot). */
+ {
+ .bank_width = 2,
+ .nb_blocs = { 127, 1, 2, 1 },
+ .sector_len = { 0x10000, 0x08000, 0x02000, 0x04000 },
+ },
+ /* Nonuniform sectors (bottom boot). */
+ {
+ .bank_width = 2,
+ .nb_blocs = { 1, 2, 1, 127 },
+ .sector_len = { 0x04000, 0x02000, 0x08000, 0x10000 },
+ },
+};
+
+int main(int argc, char **argv)
+{
+ int fd = mkstemp(image_path);
+ if (fd == -1) {
+ g_printerr("Failed to create temporary file %s: %s\n", image_path,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (ftruncate(fd, UNIFORM_FLASH_SIZE) < 0) {
+ int error_code = errno;
+ close(fd);
+ unlink(image_path);
+ g_printerr("Failed to truncate file %s to %u MB: %s\n", image_path,
+ UNIFORM_FLASH_SIZE, strerror(error_code));
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+
+ qtest_add_abrt_handler(cleanup, NULL);
+ g_test_init(&argc, &argv, NULL);
+
+ size_t nb_configurations = sizeof configuration / sizeof configuration[0];
+ for (size_t i = 0; i < nb_configurations; ++i) {
+ const FlashConfig *config = &configuration[i];
+ char *path = g_strdup_printf("pflash-cfi02"
+ "/geometry/%dx%x-%dx%x-%dx%x-%dx%x"
+ "/%d",
+ config->nb_blocs[0],
+ config->sector_len[0],
+ config->nb_blocs[1],
+ config->sector_len[1],
+ config->nb_blocs[2],
+ config->sector_len[2],
+ config->nb_blocs[3],
+ config->sector_len[3],
+ config->bank_width);
+ qtest_add_data_func(path, config, test_geometry);
+ g_free(path);
+ }
+
+ qtest_add_data_func("pflash-cfi02/cfi-in-autoselect", &configuration[0],
+ test_cfi_in_autoselect);
+ int result = g_test_run();
+ cleanup(NULL);
+ return result;
+}
diff --git a/tests/qtest/pnv-xscom-test.c b/tests/qtest/pnv-xscom-test.c
new file mode 100644
index 0000000000..2c46d5cf6d
--- /dev/null
+++ b/tests/qtest/pnv-xscom-test.c
@@ -0,0 +1,153 @@
+/*
+ * QTest testcase for PowerNV XSCOM bus
+ *
+ * Copyright (c) 2016, IBM Corporation.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+
+typedef enum PnvChipType {
+ PNV_CHIP_POWER8E, /* AKA Murano (default) */
+ PNV_CHIP_POWER8, /* AKA Venice */
+ PNV_CHIP_POWER8NVL, /* AKA Naples */
+ PNV_CHIP_POWER9, /* AKA Nimbus */
+} PnvChipType;
+
+typedef struct PnvChip {
+ PnvChipType chip_type;
+ const char *cpu_model;
+ uint64_t xscom_base;
+ uint64_t cfam_id;
+ uint32_t first_core;
+} PnvChip;
+
+static const PnvChip pnv_chips[] = {
+ {
+ .chip_type = PNV_CHIP_POWER8,
+ .cpu_model = "POWER8",
+ .xscom_base = 0x0003fc0000000000ull,
+ .cfam_id = 0x220ea04980000000ull,
+ .first_core = 0x1,
+ }, {
+ .chip_type = PNV_CHIP_POWER8NVL,
+ .cpu_model = "POWER8NVL",
+ .xscom_base = 0x0003fc0000000000ull,
+ .cfam_id = 0x120d304980000000ull,
+ .first_core = 0x1,
+ },
+ {
+ .chip_type = PNV_CHIP_POWER9,
+ .cpu_model = "POWER9",
+ .xscom_base = 0x000603fc00000000ull,
+ .cfam_id = 0x220d104900008000ull,
+ .first_core = 0x0,
+ },
+};
+
+static uint64_t pnv_xscom_addr(const PnvChip *chip, uint32_t pcba)
+{
+ uint64_t addr = chip->xscom_base;
+
+ if (chip->chip_type == PNV_CHIP_POWER9) {
+ addr |= ((uint64_t) pcba << 3);
+ } else {
+ addr |= (((uint64_t) pcba << 4) & ~0xffull) |
+ (((uint64_t) pcba << 3) & 0x78);
+ }
+ return addr;
+}
+
+static uint64_t pnv_xscom_read(QTestState *qts, const PnvChip *chip,
+ uint32_t pcba)
+{
+ return qtest_readq(qts, pnv_xscom_addr(chip, pcba));
+}
+
+static void test_xscom_cfam_id(QTestState *qts, const PnvChip *chip)
+{
+ uint64_t f000f = pnv_xscom_read(qts, chip, 0xf000f);
+
+ g_assert_cmphex(f000f, ==, chip->cfam_id);
+}
+
+static void test_cfam_id(const void *data)
+{
+ const PnvChip *chip = data;
+ const char *machine = "powernv8";
+ QTestState *qts;
+
+ if (chip->chip_type == PNV_CHIP_POWER9) {
+ machine = "powernv9";
+ }
+
+ qts = qtest_initf("-M %s -accel tcg -cpu %s",
+ machine, chip->cpu_model);
+ test_xscom_cfam_id(qts, chip);
+ qtest_quit(qts);
+}
+
+
+#define PNV_XSCOM_EX_CORE_BASE 0x10000000ull
+#define PNV_XSCOM_EX_BASE(core) \
+ (PNV_XSCOM_EX_CORE_BASE | ((uint64_t)(core) << 24))
+#define PNV_XSCOM_P9_EC_BASE(core) \
+ ((uint64_t)(((core) & 0x1F) + 0x20) << 24)
+
+#define PNV_XSCOM_EX_DTS_RESULT0 0x50000
+
+static void test_xscom_core(QTestState *qts, const PnvChip *chip)
+{
+ uint32_t first_core_dts0 = PNV_XSCOM_EX_DTS_RESULT0;
+ uint64_t dts0;
+
+ if (chip->chip_type != PNV_CHIP_POWER9) {
+ first_core_dts0 |= PNV_XSCOM_EX_BASE(chip->first_core);
+ } else {
+ first_core_dts0 |= PNV_XSCOM_P9_EC_BASE(chip->first_core);
+ }
+
+ dts0 = pnv_xscom_read(qts, chip, first_core_dts0);
+
+ g_assert_cmphex(dts0, ==, 0x26f024f023f0000ull);
+}
+
+static void test_core(const void *data)
+{
+ const PnvChip *chip = data;
+ QTestState *qts;
+ const char *machine = "powernv8";
+
+ if (chip->chip_type == PNV_CHIP_POWER9) {
+ machine = "powernv9";
+ }
+
+ qts = qtest_initf("-M %s -accel tcg -cpu %s",
+ machine, chip->cpu_model);
+ test_xscom_core(qts, chip);
+ qtest_quit(qts);
+}
+
+static void add_test(const char *name, void (*test)(const void *data))
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pnv_chips); i++) {
+ char *tname = g_strdup_printf("pnv-xscom/%s/%s", name,
+ pnv_chips[i].cpu_model);
+ qtest_add_data_func(tname, &pnv_chips[i], test);
+ g_free(tname);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ add_test("cfam_id", test_cfam_id);
+ add_test("core", test_core);
+ return g_test_run();
+}
diff --git a/tests/qtest/prom-env-test.c b/tests/qtest/prom-env-test.c
new file mode 100644
index 0000000000..9be52c766f
--- /dev/null
+++ b/tests/qtest/prom-env-test.c
@@ -0,0 +1,104 @@
+/*
+ * Test Open-Firmware-based machines.
+ *
+ * Copyright (c) 2016, 2017 Red Hat Inc.
+ *
+ * Author:
+ * Thomas Huth <thuth@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2
+ * or later. See the COPYING file in the top-level directory.
+ *
+ * This test is used to check that some Open Firmware based machines (i.e.
+ * OpenBIOS or SLOF) can be started successfully in TCG mode. To do this, we
+ * first put some Forth code into the "boot-command" Open Firmware environment
+ * variable. This Forth code writes a well-known magic value to a known location
+ * in memory. Then we start the guest so that the firmware can boot and finally
+ * run the Forth code.
+ * The testing code here then can finally check whether the value has been
+ * successfully written into the guest memory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define MAGIC 0xcafec0de
+#define ADDRESS 0x4000
+
+static void check_guest_memory(QTestState *qts)
+{
+ uint32_t signature;
+ int i;
+
+ /* Poll until code has run and modified memory. Wait at most 600 seconds */
+ for (i = 0; i < 60000; ++i) {
+ signature = qtest_readl(qts, ADDRESS);
+ if (signature == MAGIC) {
+ break;
+ }
+ g_usleep(10000);
+ }
+
+ g_assert_cmphex(signature, ==, MAGIC);
+}
+
+static void test_machine(const void *machine)
+{
+ const char *extra_args = "";
+ QTestState *qts;
+
+ /*
+ * The pseries firmware boots much faster without the default
+ * devices, it also needs Spectre/Meltdown workarounds disabled to
+ * avoid warnings with TCG
+ */
+ if (strcmp(machine, "pseries") == 0) {
+ extra_args = "-nodefaults"
+ " -machine cap-cfpc=broken,cap-sbbc=broken,cap-ibs=broken";
+ }
+
+ qts = qtest_initf("-M %s -accel tcg %s -prom-env 'use-nvramrc?=true' "
+ "-prom-env 'nvramrc=%x %x l!' ", (const char *)machine,
+ extra_args, MAGIC, ADDRESS);
+ check_guest_memory(qts);
+ qtest_quit(qts);
+}
+
+static void add_tests(const char *machines[])
+{
+ int i;
+ 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);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ const char *sparc_machines[] = { "SPARCbook", "Voyager", "SS-20", NULL };
+ const char *sparc64_machines[] = { "sun4u", NULL };
+ const char *ppc_machines[] = { "mac99", "g3beige", NULL };
+ const char *arch = qtest_get_arch();
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (!strcmp(arch, "ppc")) {
+ add_tests(ppc_machines);
+ } else if (!strcmp(arch, "ppc64")) {
+ add_tests(ppc_machines);
+ if (g_test_slow()) {
+ qtest_add_data_func("prom-env/pseries", "pseries", test_machine);
+ }
+ } else if (!strcmp(arch, "sparc")) {
+ add_tests(sparc_machines);
+ } else if (!strcmp(arch, "sparc64")) {
+ add_tests(sparc64_machines);
+ } else {
+ g_assert_not_reached();
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/pvpanic-test.c b/tests/qtest/pvpanic-test.c
new file mode 100644
index 0000000000..ff9176adf3
--- /dev/null
+++ b/tests/qtest/pvpanic-test.c
@@ -0,0 +1,49 @@
+/*
+ * QTest testcase for PV Panic
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+
+static void test_panic(void)
+{
+ uint8_t val;
+ QDict *response, *data;
+ QTestState *qts;
+
+ qts = qtest_init("-device pvpanic");
+
+ val = qtest_inb(qts, 0x505);
+ g_assert_cmpuint(val, ==, 1);
+
+ qtest_outb(qts, 0x505, 0x1);
+
+ response = qtest_qmp_receive(qts);
+ g_assert(qdict_haskey(response, "event"));
+ g_assert_cmpstr(qdict_get_str(response, "event"), ==, "GUEST_PANICKED");
+ g_assert(qdict_haskey(response, "data"));
+ data = qdict_get_qdict(response, "data");
+ g_assert(qdict_haskey(data, "action"));
+ g_assert_cmpstr(qdict_get_str(data, "action"), ==, "pause");
+ qobject_unref(response);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/pvpanic/panic", test_panic);
+
+ ret = g_test_run();
+
+ return ret;
+}
diff --git a/tests/qtest/pxe-test.c b/tests/qtest/pxe-test.c
new file mode 100644
index 0000000000..f68d0aadbb
--- /dev/null
+++ b/tests/qtest/pxe-test.c
@@ -0,0 +1,152 @@
+/*
+ * PXE test cases.
+ *
+ * Copyright (c) 2016, 2017 Red Hat Inc.
+ *
+ * Authors:
+ * Michael S. Tsirkin <mst@redhat.com>,
+ * Victor Kaplansky <victork@redhat.com>
+ * Thomas Huth <thuth@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "qemu-common.h"
+#include "libqtest.h"
+#include "boot-sector.h"
+
+#define NETNAME "net0"
+
+static char disk[] = "tests/pxe-test-disk-XXXXXX";
+
+typedef struct testdef {
+ const char *machine; /* Machine type */
+ const char *model; /* NIC device model */
+ const char *extra; /* Any additional parameters */
+} testdef_t;
+
+static testdef_t x86_tests[] = {
+ { "pc", "e1000" },
+ { "pc", "virtio-net-pci" },
+ { "q35", "e1000e" },
+ { "q35", "virtio-net-pci", },
+ { NULL },
+};
+
+static testdef_t x86_tests_slow[] = {
+ { "pc", "ne2k_pci", },
+ { "pc", "i82550", },
+ { "pc", "rtl8139" },
+ { "pc", "vmxnet3" },
+ { NULL },
+};
+
+static testdef_t ppc64_tests[] = {
+ { "pseries", "spapr-vlan",
+ "-machine cap-cfpc=broken,cap-sbbc=broken,cap-ibs=broken,vsmt=8" },
+ { "pseries", "virtio-net-pci",
+ "-machine cap-cfpc=broken,cap-sbbc=broken,cap-ibs=broken,vsmt=8" },
+ { NULL },
+};
+
+static testdef_t ppc64_tests_slow[] = {
+ { "pseries", "e1000",
+ "-machine cap-cfpc=broken,cap-sbbc=broken,cap-ibs=broken,vsmt=8" },
+ { NULL },
+};
+
+static testdef_t s390x_tests[] = {
+ { "s390-ccw-virtio", "virtio-net-ccw" },
+ { NULL },
+};
+
+static void test_pxe_one(const testdef_t *test, bool ipv6)
+{
+ QTestState *qts;
+ char *args;
+ const char *extra = test->extra;
+
+ if (!extra) {
+ extra = "";
+ }
+
+ args = g_strdup_printf(
+ "-accel kvm -accel tcg -machine %s -nodefaults -boot order=n "
+ "-netdev user,id=" NETNAME ",tftp=./,bootfile=%s,ipv4=%s,ipv6=%s "
+ "-device %s,bootindex=1,netdev=" NETNAME " %s",
+ test->machine, disk, ipv6 ? "off" : "on", ipv6 ? "on" : "off",
+ test->model, extra);
+
+ qts = qtest_init(args);
+ boot_sector_test(qts);
+ qtest_quit(qts);
+ g_free(args);
+}
+
+static void test_pxe_ipv4(gconstpointer data)
+{
+ const testdef_t *test = data;
+
+ test_pxe_one(test, false);
+}
+
+static void test_pxe_ipv6(gconstpointer data)
+{
+ const testdef_t *test = data;
+
+ test_pxe_one(test, true);
+}
+
+static void test_batch(const testdef_t *tests, bool ipv6)
+{
+ int i;
+
+ for (i = 0; tests[i].machine; i++) {
+ const testdef_t *test = &tests[i];
+ char *testname;
+
+ testname = g_strdup_printf("pxe/ipv4/%s/%s",
+ test->machine, test->model);
+ qtest_add_data_func(testname, test, test_pxe_ipv4);
+ g_free(testname);
+
+ if (ipv6) {
+ testname = g_strdup_printf("pxe/ipv6/%s/%s",
+ test->machine, test->model);
+ qtest_add_data_func(testname, test, test_pxe_ipv6);
+ g_free(testname);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ const char *arch = qtest_get_arch();
+
+ ret = boot_sector_init(disk);
+ if(ret)
+ return ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ test_batch(x86_tests, false);
+ if (g_test_slow()) {
+ test_batch(x86_tests_slow, false);
+ }
+ } else if (strcmp(arch, "ppc64") == 0) {
+ test_batch(ppc64_tests, g_test_slow());
+ if (g_test_slow()) {
+ test_batch(ppc64_tests_slow, true);
+ }
+ } else if (g_str_equal(arch, "s390x")) {
+ test_batch(s390x_tests, g_test_slow());
+ }
+ ret = g_test_run();
+ boot_sector_cleanup(disk);
+ return ret;
+}
diff --git a/tests/qtest/q35-test.c b/tests/qtest/q35-test.c
new file mode 100644
index 0000000000..a68183d513
--- /dev/null
+++ b/tests/qtest/q35-test.c
@@ -0,0 +1,201 @@
+/*
+ * QTest testcase for Q35 northbridge
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * Author: Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "hw/pci-host/q35.h"
+#include "qapi/qmp/qdict.h"
+
+#define TSEG_SIZE_TEST_GUEST_RAM_MBYTES 128
+
+/* @esmramc_tseg_sz: ESMRAMC.TSEG_SZ bitmask for selecting the requested TSEG
+ * size. Must be a subset of
+ * MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_MASK.
+ *
+ * @extended_tseg_mbytes: Size of the extended TSEG. Only consulted if
+ * @esmramc_tseg_sz equals
+ * MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_MASK precisely.
+ *
+ * @expected_tseg_mbytes: Expected guest-visible TSEG size in megabytes,
+ * matching @esmramc_tseg_sz and @extended_tseg_mbytes
+ * above.
+ */
+struct TsegSizeArgs {
+ uint8_t esmramc_tseg_sz;
+ uint16_t extended_tseg_mbytes;
+ uint16_t expected_tseg_mbytes;
+};
+typedef struct TsegSizeArgs TsegSizeArgs;
+
+static const TsegSizeArgs tseg_1mb = {
+ .esmramc_tseg_sz = MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_1MB,
+ .extended_tseg_mbytes = 0,
+ .expected_tseg_mbytes = 1,
+};
+static const TsegSizeArgs tseg_2mb = {
+ .esmramc_tseg_sz = MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_2MB,
+ .extended_tseg_mbytes = 0,
+ .expected_tseg_mbytes = 2,
+};
+static const TsegSizeArgs tseg_8mb = {
+ .esmramc_tseg_sz = MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_8MB,
+ .extended_tseg_mbytes = 0,
+ .expected_tseg_mbytes = 8,
+};
+static const TsegSizeArgs tseg_ext_16mb = {
+ .esmramc_tseg_sz = MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_MASK,
+ .extended_tseg_mbytes = 16,
+ .expected_tseg_mbytes = 16,
+};
+
+static void smram_set_bit(QPCIDevice *pcidev, uint8_t mask, bool enabled)
+{
+ uint8_t smram;
+
+ smram = qpci_config_readb(pcidev, MCH_HOST_BRIDGE_SMRAM);
+ if (enabled) {
+ smram |= mask;
+ } else {
+ smram &= ~mask;
+ }
+ qpci_config_writeb(pcidev, MCH_HOST_BRIDGE_SMRAM, smram);
+}
+
+static bool smram_test_bit(QPCIDevice *pcidev, uint8_t mask)
+{
+ uint8_t smram;
+
+ smram = qpci_config_readb(pcidev, MCH_HOST_BRIDGE_SMRAM);
+ return smram & mask;
+}
+
+static void test_smram_lock(void)
+{
+ QPCIBus *pcibus;
+ QPCIDevice *pcidev;
+ QDict *response;
+ QTestState *qts;
+
+ qts = qtest_init("-M q35");
+
+ pcibus = qpci_new_pc(qts, NULL);
+ g_assert(pcibus != NULL);
+
+ pcidev = qpci_device_find(pcibus, 0);
+ g_assert(pcidev != NULL);
+
+ /* check open is settable */
+ smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, false);
+ g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == false);
+ smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, true);
+ g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == true);
+
+ /* lock, check open is cleared & not settable */
+ smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_LCK, true);
+ g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == false);
+ smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, true);
+ g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == false);
+
+ /* reset */
+ response = qtest_qmp(qts, "{'execute': 'system_reset', 'arguments': {} }");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ /* check open is settable again */
+ smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, false);
+ g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == false);
+ smram_set_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN, true);
+ g_assert(smram_test_bit(pcidev, MCH_HOST_BRIDGE_SMRAM_D_OPEN) == true);
+
+ g_free(pcidev);
+ qpci_free_pc(pcibus);
+
+ qtest_quit(qts);
+}
+
+static void test_tseg_size(const void *data)
+{
+ const TsegSizeArgs *args = data;
+ QPCIBus *pcibus;
+ QPCIDevice *pcidev;
+ uint8_t smram_val;
+ uint8_t esmramc_val;
+ uint32_t ram_offs;
+ QTestState *qts;
+
+ if (args->esmramc_tseg_sz == MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_MASK) {
+ qts = qtest_initf("-M q35 -m %uM -global mch.extended-tseg-mbytes=%u",
+ TSEG_SIZE_TEST_GUEST_RAM_MBYTES,
+ args->extended_tseg_mbytes);
+ } else {
+ qts = qtest_initf("-M q35 -m %uM", TSEG_SIZE_TEST_GUEST_RAM_MBYTES);
+ }
+
+ /* locate the DRAM controller */
+ pcibus = qpci_new_pc(qts, NULL);
+ g_assert(pcibus != NULL);
+ pcidev = qpci_device_find(pcibus, 0);
+ g_assert(pcidev != NULL);
+
+ /* Set TSEG size. Restrict TSEG visibility to SMM by setting T_EN. */
+ esmramc_val = qpci_config_readb(pcidev, MCH_HOST_BRIDGE_ESMRAMC);
+ esmramc_val &= ~MCH_HOST_BRIDGE_ESMRAMC_TSEG_SZ_MASK;
+ esmramc_val |= args->esmramc_tseg_sz;
+ esmramc_val |= MCH_HOST_BRIDGE_ESMRAMC_T_EN;
+ qpci_config_writeb(pcidev, MCH_HOST_BRIDGE_ESMRAMC, esmramc_val);
+
+ /* Enable TSEG by setting G_SMRAME. Close TSEG by setting D_CLS. */
+ smram_val = qpci_config_readb(pcidev, MCH_HOST_BRIDGE_SMRAM);
+ smram_val &= ~(MCH_HOST_BRIDGE_SMRAM_D_OPEN |
+ MCH_HOST_BRIDGE_SMRAM_D_LCK);
+ smram_val |= (MCH_HOST_BRIDGE_SMRAM_D_CLS |
+ MCH_HOST_BRIDGE_SMRAM_G_SMRAME);
+ qpci_config_writeb(pcidev, MCH_HOST_BRIDGE_SMRAM, smram_val);
+
+ /* lock TSEG */
+ smram_val |= MCH_HOST_BRIDGE_SMRAM_D_LCK;
+ qpci_config_writeb(pcidev, MCH_HOST_BRIDGE_SMRAM, smram_val);
+
+ /* Now check that the byte right before the TSEG is r/w, and that the first
+ * byte in the TSEG always reads as 0xff.
+ */
+ ram_offs = (TSEG_SIZE_TEST_GUEST_RAM_MBYTES - args->expected_tseg_mbytes) *
+ 1024 * 1024 - 1;
+ g_assert_cmpint(qtest_readb(qts, ram_offs), ==, 0);
+ qtest_writeb(qts, ram_offs, 1);
+ g_assert_cmpint(qtest_readb(qts, ram_offs), ==, 1);
+
+ ram_offs++;
+ g_assert_cmpint(qtest_readb(qts, ram_offs), ==, 0xff);
+ qtest_writeb(qts, ram_offs, 1);
+ g_assert_cmpint(qtest_readb(qts, ram_offs), ==, 0xff);
+
+ g_free(pcidev);
+ qpci_free_pc(pcibus);
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/q35/smram/lock", test_smram_lock);
+
+ qtest_add_data_func("/q35/tseg-size/1mb", &tseg_1mb, test_tseg_size);
+ qtest_add_data_func("/q35/tseg-size/2mb", &tseg_2mb, test_tseg_size);
+ qtest_add_data_func("/q35/tseg-size/8mb", &tseg_8mb, test_tseg_size);
+ qtest_add_data_func("/q35/tseg-size/ext/16mb", &tseg_ext_16mb,
+ test_tseg_size);
+ return g_test_run();
+}
diff --git a/tests/qtest/qmp-cmd-test.c b/tests/qtest/qmp-cmd-test.c
new file mode 100644
index 0000000000..9f5228cd99
--- /dev/null
+++ b/tests/qtest/qmp-cmd-test.c
@@ -0,0 +1,234 @@
+/*
+ * QMP command test cases
+ *
+ * Copyright (c) 2017 Red Hat Inc.
+ *
+ * Authors:
+ * Markus Armbruster <armbru@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qapi/error.h"
+#include "qapi/qapi-visit-introspect.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qobject-input-visitor.h"
+
+const char common_args[] = "-nodefaults -machine none";
+
+/* Query smoke tests */
+
+static int query_error_class(const char *cmd)
+{
+ static struct {
+ const char *cmd;
+ int err_class;
+ } fails[] = {
+ /* Success depends on build configuration: */
+#ifndef CONFIG_SPICE
+ { "query-spice", ERROR_CLASS_COMMAND_NOT_FOUND },
+#endif
+#ifndef CONFIG_VNC
+ { "query-vnc", ERROR_CLASS_GENERIC_ERROR },
+ { "query-vnc-servers", ERROR_CLASS_GENERIC_ERROR },
+#endif
+#ifndef CONFIG_REPLICATION
+ { "query-xen-replication-status", ERROR_CLASS_COMMAND_NOT_FOUND },
+#endif
+ /* Likewise, and require special QEMU command-line arguments: */
+ { "query-acpi-ospm-status", ERROR_CLASS_GENERIC_ERROR },
+ { "query-balloon", ERROR_CLASS_DEVICE_NOT_ACTIVE },
+ { "query-hotpluggable-cpus", ERROR_CLASS_GENERIC_ERROR },
+ { "query-vm-generation-id", ERROR_CLASS_GENERIC_ERROR },
+ { NULL, -1 }
+ };
+ int i;
+
+ for (i = 0; fails[i].cmd; i++) {
+ if (!strcmp(cmd, fails[i].cmd)) {
+ return fails[i].err_class;
+ }
+ }
+ return -1;
+}
+
+static void test_query(const void *data)
+{
+ const char *cmd = data;
+ int expected_error_class = query_error_class(cmd);
+ QDict *resp, *error;
+ const char *error_class;
+ QTestState *qts;
+
+ qts = qtest_init(common_args);
+
+ resp = qtest_qmp(qts, "{ 'execute': %s }", cmd);
+ error = qdict_get_qdict(resp, "error");
+ error_class = error ? qdict_get_str(error, "class") : NULL;
+
+ if (expected_error_class < 0) {
+ g_assert(qdict_haskey(resp, "return"));
+ } else {
+ g_assert(error);
+ g_assert_cmpint(qapi_enum_parse(&QapiErrorClass_lookup, error_class,
+ -1, &error_abort),
+ ==, expected_error_class);
+ }
+ qobject_unref(resp);
+
+ qtest_quit(qts);
+}
+
+static bool query_is_blacklisted(const char *cmd)
+{
+ const char *blacklist[] = {
+ /* Not actually queries: */
+ "add-fd",
+ /* Success depends on target arch: */
+ "query-cpu-definitions", /* arm, i386, ppc, s390x */
+ "query-gic-capabilities", /* arm */
+ /* Success depends on target-specific build configuration: */
+ "query-pci", /* CONFIG_PCI */
+ /* Success depends on launching SEV guest */
+ "query-sev-launch-measure",
+ /* Success depends on Host or Hypervisor SEV support */
+ "query-sev",
+ "query-sev-capabilities",
+ NULL
+ };
+ int i;
+
+ for (i = 0; blacklist[i]; i++) {
+ if (!strcmp(cmd, blacklist[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+typedef struct {
+ SchemaInfoList *list;
+ GHashTable *hash;
+} QmpSchema;
+
+static void qmp_schema_init(QmpSchema *schema)
+{
+ QDict *resp;
+ Visitor *qiv;
+ SchemaInfoList *tail;
+ QTestState *qts;
+
+ qts = qtest_init(common_args);
+
+ resp = qtest_qmp(qts, "{ 'execute': 'query-qmp-schema' }");
+
+ qiv = qobject_input_visitor_new(qdict_get(resp, "return"));
+ visit_type_SchemaInfoList(qiv, NULL, &schema->list, &error_abort);
+ visit_free(qiv);
+
+ qobject_unref(resp);
+ qtest_quit(qts);
+
+ schema->hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ /* Build @schema: hash table mapping entity name to SchemaInfo */
+ for (tail = schema->list; tail; tail = tail->next) {
+ g_hash_table_insert(schema->hash, tail->value->name, tail->value);
+ }
+}
+
+static SchemaInfo *qmp_schema_lookup(QmpSchema *schema, const char *name)
+{
+ return g_hash_table_lookup(schema->hash, name);
+}
+
+static void qmp_schema_cleanup(QmpSchema *schema)
+{
+ qapi_free_SchemaInfoList(schema->list);
+ g_hash_table_destroy(schema->hash);
+}
+
+static bool object_type_has_mandatory_members(SchemaInfo *type)
+{
+ SchemaInfoObjectMemberList *tail;
+
+ g_assert(type->meta_type == SCHEMA_META_TYPE_OBJECT);
+
+ for (tail = type->u.object.members; tail; tail = tail->next) {
+ if (!tail->value->has_q_default) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void add_query_tests(QmpSchema *schema)
+{
+ SchemaInfoList *tail;
+ SchemaInfo *si, *arg_type, *ret_type;
+ char *test_name;
+
+ /* Test the query-like commands */
+ for (tail = schema->list; tail; tail = tail->next) {
+ si = tail->value;
+ if (si->meta_type != SCHEMA_META_TYPE_COMMAND) {
+ continue;
+ }
+
+ if (query_is_blacklisted(si->name)) {
+ continue;
+ }
+
+ arg_type = qmp_schema_lookup(schema, si->u.command.arg_type);
+ if (object_type_has_mandatory_members(arg_type)) {
+ continue;
+ }
+
+ ret_type = qmp_schema_lookup(schema, si->u.command.ret_type);
+ if (ret_type->meta_type == SCHEMA_META_TYPE_OBJECT
+ && !ret_type->u.object.members) {
+ continue;
+ }
+
+ test_name = g_strdup_printf("qmp/%s", si->name);
+ qtest_add_data_func(test_name, si->name, test_query);
+ g_free(test_name);
+ }
+}
+
+static void test_object_add_without_props(void)
+{
+ QTestState *qts;
+ QDict *resp;
+
+ qts = qtest_init(common_args);
+ resp = qtest_qmp(qts, "{'execute': 'object-add', 'arguments':"
+ " {'qom-type': 'memory-backend-ram', 'id': 'ram1' } }");
+ g_assert_nonnull(resp);
+ qmp_assert_error_class(resp, "GenericError");
+ qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+ QmpSchema schema;
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qmp_schema_init(&schema);
+ add_query_tests(&schema);
+
+ qtest_add_func("qmp/object-add-without-props",
+ test_object_add_without_props);
+ /* TODO: add coverage of generic object-add failure modes */
+
+ ret = g_test_run();
+
+ qmp_schema_cleanup(&schema);
+ return ret;
+}
diff --git a/tests/qtest/qmp-test.c b/tests/qtest/qmp-test.c
new file mode 100644
index 0000000000..1b0eb69832
--- /dev/null
+++ b/tests/qtest/qmp-test.c
@@ -0,0 +1,344 @@
+/*
+ * QMP protocol test cases
+ *
+ * Copyright (c) 2017-2018 Red Hat Inc.
+ *
+ * Authors:
+ * Markus Armbruster <armbru@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qapi/error.h"
+#include "qapi/qapi-visit-misc.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qmp/qstring.h"
+
+const char common_args[] = "-nodefaults -machine none";
+
+static void test_version(QObject *version)
+{
+ Visitor *v;
+ VersionInfo *vinfo;
+
+ g_assert(version);
+ v = qobject_input_visitor_new(version);
+ visit_type_VersionInfo(v, "version", &vinfo, &error_abort);
+ qapi_free_VersionInfo(vinfo);
+ visit_free(v);
+}
+
+static void assert_recovered(QTestState *qts)
+{
+ QDict *resp;
+
+ resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd' }");
+ qmp_assert_error_class(resp, "CommandNotFound");
+}
+
+static void test_malformed(QTestState *qts)
+{
+ QDict *resp;
+
+ /* syntax error */
+ qtest_qmp_send_raw(qts, "{]\n");
+ resp = qtest_qmp_receive(qts);
+ qmp_assert_error_class(resp, "GenericError");
+ assert_recovered(qts);
+
+ /* lexical error: impossible byte outside string */
+ qtest_qmp_send_raw(qts, "{\xFF");
+ resp = qtest_qmp_receive(qts);
+ qmp_assert_error_class(resp, "GenericError");
+ assert_recovered(qts);
+
+ /* lexical error: funny control character outside string */
+ qtest_qmp_send_raw(qts, "{\x01");
+ resp = qtest_qmp_receive(qts);
+ qmp_assert_error_class(resp, "GenericError");
+ assert_recovered(qts);
+
+ /* lexical error: impossible byte in string */
+ qtest_qmp_send_raw(qts, "{'bad \xFF");
+ resp = qtest_qmp_receive(qts);
+ qmp_assert_error_class(resp, "GenericError");
+ assert_recovered(qts);
+
+ /* lexical error: control character in string */
+ qtest_qmp_send_raw(qts, "{'execute': 'nonexistent', 'id':'\n");
+ resp = qtest_qmp_receive(qts);
+ qmp_assert_error_class(resp, "GenericError");
+ assert_recovered(qts);
+
+ /* lexical error: interpolation */
+ qtest_qmp_send_raw(qts, "%%p");
+ resp = qtest_qmp_receive(qts);
+ qmp_assert_error_class(resp, "GenericError");
+ assert_recovered(qts);
+
+ /* Not even a dictionary */
+ resp = qtest_qmp(qts, "null");
+ qmp_assert_error_class(resp, "GenericError");
+
+ /* No "execute" key */
+ resp = qtest_qmp(qts, "{}");
+ qmp_assert_error_class(resp, "GenericError");
+
+ /* "execute" isn't a string */
+ resp = qtest_qmp(qts, "{ 'execute': true }");
+ qmp_assert_error_class(resp, "GenericError");
+
+ /* "arguments" isn't a dictionary */
+ resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'arguments': [] }");
+ qmp_assert_error_class(resp, "GenericError");
+
+ /* extra key */
+ resp = qtest_qmp(qts, "{ 'execute': 'no-such-cmd', 'extra': true }");
+ qmp_assert_error_class(resp, "GenericError");
+}
+
+static void test_qmp_protocol(void)
+{
+ QDict *resp, *q, *ret;
+ QList *capabilities;
+ QTestState *qts;
+
+ qts = qtest_init_without_qmp_handshake(common_args);
+
+ /* Test greeting */
+ resp = qtest_qmp_receive(qts);
+ q = qdict_get_qdict(resp, "QMP");
+ g_assert(q);
+ test_version(qdict_get(q, "version"));
+ capabilities = qdict_get_qlist(q, "capabilities");
+ g_assert(capabilities);
+ qobject_unref(resp);
+
+ /* Test valid command before handshake */
+ resp = qtest_qmp(qts, "{ 'execute': 'query-version' }");
+ qmp_assert_error_class(resp, "CommandNotFound");
+
+ /* Test malformed commands before handshake */
+ test_malformed(qts);
+
+ /* Test handshake */
+ resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }");
+ ret = qdict_get_qdict(resp, "return");
+ g_assert(ret && !qdict_size(ret));
+ qobject_unref(resp);
+
+ /* Test repeated handshake */
+ resp = qtest_qmp(qts, "{ 'execute': 'qmp_capabilities' }");
+ qmp_assert_error_class(resp, "CommandNotFound");
+
+ /* Test valid command */
+ resp = qtest_qmp(qts, "{ 'execute': 'query-version' }");
+ test_version(qdict_get(resp, "return"));
+ qobject_unref(resp);
+
+ /* Test malformed commands */
+ test_malformed(qts);
+
+ /* Test 'id' */
+ resp = qtest_qmp(qts, "{ 'execute': 'query-name', 'id': 'cookie#1' }");
+ ret = qdict_get_qdict(resp, "return");
+ g_assert(ret);
+ g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, "cookie#1");
+ qobject_unref(resp);
+
+ /* Test command failure with 'id' */
+ resp = qtest_qmp(qts, "{ 'execute': 'human-monitor-command', 'id': 2 }");
+ g_assert_cmpint(qdict_get_int(resp, "id"), ==, 2);
+ qmp_assert_error_class(resp, "GenericError");
+
+ qtest_quit(qts);
+}
+
+/* Out-of-band tests */
+
+char tmpdir[] = "/tmp/qmp-test-XXXXXX";
+char *fifo_name;
+
+static void setup_blocking_cmd(void)
+{
+ if (!mkdtemp(tmpdir)) {
+ g_error("mkdtemp: %s", strerror(errno));
+ }
+ fifo_name = g_strdup_printf("%s/fifo", tmpdir);
+ if (mkfifo(fifo_name, 0666)) {
+ g_error("mkfifo: %s", strerror(errno));
+ }
+}
+
+static void cleanup_blocking_cmd(void)
+{
+ unlink(fifo_name);
+ rmdir(tmpdir);
+}
+
+static void send_cmd_that_blocks(QTestState *s, const char *id)
+{
+ qtest_qmp_send(s, "{ 'execute': 'blockdev-add', 'id': %s,"
+ " 'arguments': {"
+ " 'driver': 'blkdebug', 'node-name': %s,"
+ " 'config': %s,"
+ " 'image': { 'driver': 'null-co', 'read-zeroes': true } } }",
+ id, id, fifo_name);
+}
+
+static void unblock_blocked_cmd(void)
+{
+ int fd = open(fifo_name, O_WRONLY);
+ g_assert(fd >= 0);
+ close(fd);
+}
+
+static void send_oob_cmd_that_fails(QTestState *s, const char *id)
+{
+ qtest_qmp_send(s, "{ 'exec-oob': 'migrate-pause', 'id': %s }", id);
+}
+
+static void recv_cmd_id(QTestState *s, const char *id)
+{
+ QDict *resp = qtest_qmp_receive(s);
+
+ g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, id);
+ qobject_unref(resp);
+}
+
+static void test_qmp_oob(void)
+{
+ QTestState *qts;
+ QDict *resp, *q;
+ const QListEntry *entry;
+ QList *capabilities;
+ QString *qstr;
+
+ qts = qtest_init_without_qmp_handshake(common_args);
+
+ /* Check the greeting message. */
+ resp = qtest_qmp_receive(qts);
+ q = qdict_get_qdict(resp, "QMP");
+ g_assert(q);
+ capabilities = qdict_get_qlist(q, "capabilities");
+ g_assert(capabilities && !qlist_empty(capabilities));
+ entry = qlist_first(capabilities);
+ g_assert(entry);
+ qstr = qobject_to(QString, entry->value);
+ g_assert(qstr);
+ g_assert_cmpstr(qstring_get_str(qstr), ==, "oob");
+ qobject_unref(resp);
+
+ /* Try a fake capability, it should fail. */
+ resp = qtest_qmp(qts,
+ "{ 'execute': 'qmp_capabilities', "
+ " 'arguments': { 'enable': [ 'cap-does-not-exist' ] } }");
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+
+ /* Now, enable OOB in current QMP session, it should succeed. */
+ resp = qtest_qmp(qts,
+ "{ 'execute': 'qmp_capabilities', "
+ " 'arguments': { 'enable': [ 'oob' ] } }");
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+
+ /*
+ * Try any command that does not support OOB but with OOB flag. We
+ * should get failure.
+ */
+ resp = qtest_qmp(qts, "{ 'exec-oob': 'query-cpus' }");
+ g_assert(qdict_haskey(resp, "error"));
+ qobject_unref(resp);
+
+ /* OOB command overtakes slow in-band command */
+ setup_blocking_cmd();
+ send_cmd_that_blocks(qts, "ib-blocks-1");
+ qtest_qmp_send(qts, "{ 'execute': 'query-name', 'id': 'ib-quick-1' }");
+ send_oob_cmd_that_fails(qts, "oob-1");
+ recv_cmd_id(qts, "oob-1");
+ unblock_blocked_cmd();
+ recv_cmd_id(qts, "ib-blocks-1");
+ recv_cmd_id(qts, "ib-quick-1");
+
+ /* Even malformed in-band command fails in-band */
+ send_cmd_that_blocks(qts, "blocks-2");
+ qtest_qmp_send(qts, "{ 'id': 'err-2' }");
+ unblock_blocked_cmd();
+ recv_cmd_id(qts, "blocks-2");
+ recv_cmd_id(qts, "err-2");
+ cleanup_blocking_cmd();
+
+ qtest_quit(qts);
+}
+
+/* Preconfig tests */
+
+static void test_qmp_preconfig(void)
+{
+ QDict *rsp, *ret;
+ QTestState *qs = qtest_initf("%s --preconfig", common_args);
+
+ /* preconfig state */
+ /* enabled commands, no error expected */
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-commands' }")));
+
+ /* forbidden commands, expected error */
+ g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }")));
+
+ /* check that query-status returns preconfig state */
+ rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }");
+ ret = qdict_get_qdict(rsp, "return");
+ g_assert(ret);
+ g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "preconfig");
+ qobject_unref(rsp);
+
+ /* exit preconfig state */
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }")));
+ qtest_qmp_eventwait(qs, "RESUME");
+
+ /* check that query-status returns running state */
+ rsp = qtest_qmp(qs, "{ 'execute': 'query-status' }");
+ ret = qdict_get_qdict(rsp, "return");
+ g_assert(ret);
+ g_assert_cmpstr(qdict_get_try_str(ret, "status"), ==, "running");
+ qobject_unref(rsp);
+
+ /* check that x-exit-preconfig returns error after exiting preconfig */
+ g_assert(qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'x-exit-preconfig' }")));
+
+ /* enabled commands, no error expected */
+ g_assert(!qmp_rsp_is_err(qtest_qmp(qs, "{ 'execute': 'query-cpus' }")));
+
+ qtest_quit(qs);
+}
+
+static void test_qmp_missing_any_arg(void)
+{
+ QTestState *qts;
+ QDict *resp;
+
+ qts = qtest_init(common_args);
+ resp = qtest_qmp(qts, "{'execute': 'qom-set', 'arguments':"
+ " { 'path': '/machine', 'property': 'rtc-time' } }");
+ g_assert_nonnull(resp);
+ qmp_assert_error_class(resp, "GenericError");
+ qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("qmp/protocol", test_qmp_protocol);
+ qtest_add_func("qmp/oob", test_qmp_oob);
+ qtest_add_func("qmp/preconfig", test_qmp_preconfig);
+ qtest_add_func("qmp/missing-any-arg", test_qmp_missing_any_arg);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/qom-test.c b/tests/qtest/qom-test.c
new file mode 100644
index 0000000000..4f94cc678c
--- /dev/null
+++ b/tests/qtest/qom-test.c
@@ -0,0 +1,127 @@
+/*
+ * QTest testcase for QOM
+ *
+ * Copyright (c) 2013 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu-common.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+#include "qemu/cutils.h"
+#include "libqtest.h"
+
+static const char *blacklist_x86[] = {
+ "xenfv", "xenpv", NULL
+};
+
+static const struct {
+ const char *arch;
+ const char **machine;
+} blacklists[] = {
+ { "i386", blacklist_x86 },
+ { "x86_64", blacklist_x86 },
+};
+
+static bool is_blacklisted(const char *arch, const char *mach)
+{
+ int i;
+ const char **p;
+
+ for (i = 0; i < ARRAY_SIZE(blacklists); i++) {
+ if (!strcmp(blacklists[i].arch, arch)) {
+ for (p = blacklists[i].machine; *p; p++) {
+ if (!strcmp(*p, mach)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static void test_properties(QTestState *qts, const char *path, bool recurse)
+{
+ char *child_path;
+ QDict *response, *tuple, *tmp;
+ QList *list;
+ QListEntry *entry;
+
+ g_test_message("Obtaining properties of %s", path);
+ response = qtest_qmp(qts, "{ 'execute': 'qom-list',"
+ " 'arguments': { 'path': %s } }", path);
+ g_assert(response);
+
+ if (!recurse) {
+ qobject_unref(response);
+ return;
+ }
+
+ g_assert(qdict_haskey(response, "return"));
+ list = qobject_to(QList, qdict_get(response, "return"));
+ QLIST_FOREACH_ENTRY(list, entry) {
+ tuple = qobject_to(QDict, qlist_entry_obj(entry));
+ bool is_child = strstart(qdict_get_str(tuple, "type"), "child<", NULL);
+ bool is_link = strstart(qdict_get_str(tuple, "type"), "link<", NULL);
+
+ if (is_child || is_link) {
+ child_path = g_strdup_printf("%s/%s",
+ path, qdict_get_str(tuple, "name"));
+ test_properties(qts, child_path, is_child);
+ g_free(child_path);
+ } else {
+ const char *prop = qdict_get_str(tuple, "name");
+ g_test_message("Testing property %s.%s", path, prop);
+ tmp = qtest_qmp(qts,
+ "{ 'execute': 'qom-get',"
+ " 'arguments': { 'path': %s, 'property': %s } }",
+ path, prop);
+ /* qom-get may fail but should not, e.g., segfault. */
+ g_assert(tmp);
+ qobject_unref(tmp);
+ }
+ }
+ qobject_unref(response);
+}
+
+static void test_machine(gconstpointer data)
+{
+ const char *machine = data;
+ QDict *response;
+ QTestState *qts;
+
+ qts = qtest_initf("-machine %s", machine);
+
+ test_properties(qts, "/machine", true);
+
+ response = qtest_qmp(qts, "{ 'execute': 'quit' }");
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+
+ qtest_quit(qts);
+ g_free((void *)machine);
+}
+
+static void add_machine_test_case(const char *mname)
+{
+ const char *arch = qtest_get_arch();
+
+ if (!is_blacklisted(arch, mname)) {
+ char *path = g_strdup_printf("qom/%s", mname);
+ qtest_add_data_func(path, g_strdup(mname), test_machine);
+ g_free(path);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_cb_for_every_machine(add_machine_test_case, g_test_quick());
+
+ return g_test_run();
+}
diff --git a/tests/qtest/qos-test.c b/tests/qtest/qos-test.c
new file mode 100644
index 0000000000..fd70d73ea5
--- /dev/null
+++ b/tests/qtest/qos-test.c
@@ -0,0 +1,449 @@
+/*
+ * libqos driver framework
+ *
+ * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu/osdep.h"
+#include <getopt.h>
+#include "libqtest-single.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qbool.h"
+#include "qapi/qmp/qstring.h"
+#include "qemu/module.h"
+#include "qapi/qmp/qlist.h"
+#include "libqos/malloc.h"
+#include "libqos/qgraph.h"
+#include "libqos/qgraph_internal.h"
+
+static char *old_path;
+
+static void apply_to_node(const char *name, bool is_machine, bool is_abstract)
+{
+ char *machine_name = NULL;
+ if (is_machine) {
+ const char *arch = qtest_get_arch();
+ machine_name = g_strconcat(arch, "/", name, NULL);
+ name = machine_name;
+ }
+ qos_graph_node_set_availability(name, true);
+ if (is_abstract) {
+ qos_delete_cmd_line(name);
+ }
+ g_free(machine_name);
+}
+
+/**
+ * apply_to_qlist(): using QMP queries QEMU for a list of
+ * machines and devices available, and sets the respective node
+ * as true. If a node is found, also all its produced and contained
+ * child are marked available.
+ *
+ * See qos_graph_node_set_availability() for more info
+ */
+static void apply_to_qlist(QList *list, bool is_machine)
+{
+ const QListEntry *p;
+ const char *name;
+ bool abstract;
+ QDict *minfo;
+ QObject *qobj;
+ QString *qstr;
+ QBool *qbool;
+
+ for (p = qlist_first(list); p; p = qlist_next(p)) {
+ minfo = qobject_to(QDict, qlist_entry_obj(p));
+ qobj = qdict_get(minfo, "name");
+ qstr = qobject_to(QString, qobj);
+ name = qstring_get_str(qstr);
+
+ qobj = qdict_get(minfo, "abstract");
+ if (qobj) {
+ qbool = qobject_to(QBool, qobj);
+ abstract = qbool_get_bool(qbool);
+ } else {
+ abstract = false;
+ }
+
+ apply_to_node(name, is_machine, abstract);
+ qobj = qdict_get(minfo, "alias");
+ if (qobj) {
+ qstr = qobject_to(QString, qobj);
+ name = qstring_get_str(qstr);
+ apply_to_node(name, is_machine, abstract);
+ }
+ }
+}
+
+/**
+ * qos_set_machines_devices_available(): sets availability of qgraph
+ * machines and devices.
+ *
+ * This function firstly starts QEMU with "-machine none" option,
+ * and then executes the QMP protocol asking for the list of devices
+ * and machines available.
+ *
+ * for each of these items, it looks up the corresponding qgraph node,
+ * setting it as available. The list currently returns all devices that
+ * are either machines or QEDGE_CONSUMED_BY other nodes.
+ * Therefore, in order to mark all other nodes, it recursively sets
+ * all its QEDGE_CONTAINS and QEDGE_PRODUCES child as available too.
+ */
+static void qos_set_machines_devices_available(void)
+{
+ QDict *response;
+ QDict *args = qdict_new();
+ QList *list;
+
+ qtest_start("-machine none");
+ response = qmp("{ 'execute': 'query-machines' }");
+ list = qdict_get_qlist(response, "return");
+
+ apply_to_qlist(list, true);
+
+ qobject_unref(response);
+
+ qdict_put_bool(args, "abstract", true);
+ qdict_put_str(args, "implements", "device");
+
+ response = qmp("{'execute': 'qom-list-types',"
+ " 'arguments': %p }", args);
+ g_assert(qdict_haskey(response, "return"));
+ list = qdict_get_qlist(response, "return");
+
+ apply_to_qlist(list, false);
+
+ qtest_end();
+ qobject_unref(response);
+}
+
+static QGuestAllocator *get_machine_allocator(QOSGraphObject *obj)
+{
+ return obj->get_driver(obj, "memory");
+}
+
+static void restart_qemu_or_continue(char *path)
+{
+ /* compares the current command line with the
+ * one previously executed: if they are the same,
+ * don't restart QEMU, if they differ, stop previous
+ * QEMU subprocess (if active) and start over with
+ * the new command line
+ */
+ if (g_strcmp0(old_path, path)) {
+ qtest_end();
+ qos_invalidate_command_line();
+ old_path = g_strdup(path);
+ qtest_start(path);
+ } else { /* if cmd line is the same, reset the guest */
+ qobject_unref(qmp("{ 'execute': 'system_reset' }"));
+ qmp_eventwait("RESET");
+ }
+}
+
+void qos_invalidate_command_line(void)
+{
+ g_free(old_path);
+ old_path = NULL;
+}
+
+/**
+ * allocate_objects(): given an array of nodes @arg,
+ * walks the path invoking all constructors and
+ * passing the corresponding parameter in order to
+ * continue the objects allocation.
+ * Once the test is reached, return the object it consumes.
+ *
+ * Since the machine and QEDGE_CONSUMED_BY nodes allocate
+ * memory in the constructor, g_test_queue_destroy is used so
+ * that after execution they can be safely free'd. (The test's
+ * ->before callback is also welcome to use g_test_queue_destroy).
+ *
+ * Note: as specified in walk_path() too, @arg is an array of
+ * char *, where arg[0] is a pointer to the command line
+ * string that will be used to properly start QEMU when executing
+ * the test, and the remaining elements represent the actual objects
+ * that will be allocated.
+ */
+static void *allocate_objects(QTestState *qts, char **path, QGuestAllocator **p_alloc)
+{
+ int current = 0;
+ QGuestAllocator *alloc;
+ QOSGraphObject *parent = NULL;
+ QOSGraphEdge *edge;
+ QOSGraphNode *node;
+ void *edge_arg;
+ void *obj;
+
+ node = qos_graph_get_node(path[current]);
+ g_assert(node->type == QNODE_MACHINE);
+
+ obj = qos_machine_new(node, qts);
+ qos_object_queue_destroy(obj);
+
+ alloc = get_machine_allocator(obj);
+ if (p_alloc) {
+ *p_alloc = alloc;
+ }
+
+ for (;;) {
+ if (node->type != QNODE_INTERFACE) {
+ qos_object_start_hw(obj);
+ parent = obj;
+ }
+
+ /* follow edge and get object for next node constructor */
+ current++;
+ edge = qos_graph_get_edge(path[current - 1], path[current]);
+ node = qos_graph_get_node(path[current]);
+
+ if (node->type == QNODE_TEST) {
+ g_assert(qos_graph_edge_get_type(edge) == QEDGE_CONSUMED_BY);
+ return obj;
+ }
+
+ switch (qos_graph_edge_get_type(edge)) {
+ case QEDGE_PRODUCES:
+ obj = parent->get_driver(parent, path[current]);
+ break;
+
+ case QEDGE_CONSUMED_BY:
+ edge_arg = qos_graph_edge_get_arg(edge);
+ obj = qos_driver_new(node, obj, alloc, edge_arg);
+ qos_object_queue_destroy(obj);
+ break;
+
+ case QEDGE_CONTAINS:
+ obj = parent->get_device(parent, path[current]);
+ break;
+ }
+ }
+}
+
+/* The argument to run_one_test, which is the test function that is registered
+ * with GTest, is a vector of strings. The first item is the initial command
+ * line (before it is modified by the test's "before" function), the remaining
+ * items are node names forming the path to the test node.
+ */
+static char **current_path;
+
+const char *qos_get_current_command_line(void)
+{
+ return current_path[0];
+}
+
+void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc)
+{
+ return allocate_objects(qts, current_path + 1, p_alloc);
+}
+
+/**
+ * run_one_test(): given an array of nodes @arg,
+ * walks the path invoking all constructors and
+ * passing the corresponding parameter in order to
+ * continue the objects allocation.
+ * Once the test is reached, its function is executed.
+ *
+ * Since the machine and QEDGE_CONSUMED_BY nodes allocate
+ * memory in the constructor, g_test_queue_destroy is used so
+ * that after execution they can be safely free'd. The test's
+ * ->before callback is also welcome to use g_test_queue_destroy.
+ *
+ * Note: as specified in walk_path() too, @arg is an array of
+ * char *, where arg[0] is a pointer to the command line
+ * string that will be used to properly start QEMU when executing
+ * the test, and the remaining elements represent the actual objects
+ * that will be allocated.
+ *
+ * The order of execution is the following:
+ * 1) @before test function as defined in the given QOSGraphTestOptions
+ * 2) start QEMU
+ * 3) call all nodes constructor and get_driver/get_device depending on edge,
+ * start the hardware (*_device_enable functions)
+ * 4) start test
+ */
+static void run_one_test(const void *arg)
+{
+ QOSGraphNode *test_node;
+ QGuestAllocator *alloc = NULL;
+ void *obj;
+ char **path = (char **) arg;
+ GString *cmd_line = g_string_new(path[0]);
+ void *test_arg;
+
+ /* Before test */
+ current_path = path;
+ test_node = qos_graph_get_node(path[(g_strv_length(path) - 1)]);
+ test_arg = test_node->u.test.arg;
+ if (test_node->u.test.before) {
+ test_arg = test_node->u.test.before(cmd_line, test_arg);
+ }
+
+ restart_qemu_or_continue(cmd_line->str);
+ g_string_free(cmd_line, true);
+
+ obj = qos_allocate_objects(global_qtest, &alloc);
+ test_node->u.test.function(obj, test_arg, alloc);
+}
+
+static void subprocess_run_one_test(const void *arg)
+{
+ const gchar *path = arg;
+ g_test_trap_subprocess(path, 0, 0);
+ g_test_trap_assert_passed();
+}
+
+/*
+ * in this function, 2 path will be built:
+ * path_str, a one-string path (ex "pc/i440FX-pcihost/...")
+ * path_vec, a string-array path (ex [0] = "pc", [1] = "i440FX-pcihost").
+ *
+ * path_str will be only used to build the test name, and won't need the
+ * architecture name at beginning, since it will be added by qtest_add_func().
+ *
+ * path_vec is used to allocate all constructors of the path nodes.
+ * Each name in this array except position 0 must correspond to a valid
+ * QOSGraphNode name.
+ * Position 0 is special, initially contains just the <machine> name of
+ * the node, (ex for "x86_64/pc" it will be "pc"), used to build the test
+ * path (see below). After it will contain the command line used to start
+ * qemu with all required devices.
+ *
+ * Note that the machine node name must be with format <arch>/<machine>
+ * (ex "x86_64/pc"), because it will identify the node "x86_64/pc"
+ * and start QEMU with "-M pc". For this reason,
+ * when building path_str, path_vec
+ * initially contains the <machine> at position 0 ("pc"),
+ * and the node name at position 1 (<arch>/<machine>)
+ * ("x86_64/pc"), followed by the rest of the nodes.
+ */
+static void walk_path(QOSGraphNode *orig_path, int len)
+{
+ QOSGraphNode *path;
+ QOSGraphEdge *edge;
+
+ /* etype set to QEDGE_CONSUMED_BY so that machine can add to the command line */
+ QOSEdgeType etype = QEDGE_CONSUMED_BY;
+
+ /* twice QOS_PATH_MAX_ELEMENT_SIZE since each edge can have its arg */
+ char **path_vec = g_new0(char *, (QOS_PATH_MAX_ELEMENT_SIZE * 2));
+ int path_vec_size = 0;
+
+ char *after_cmd, *before_cmd, *after_device;
+ GString *after_device_str = g_string_new("");
+ char *node_name = orig_path->name, *path_str;
+
+ GString *cmd_line = g_string_new("");
+ GString *cmd_line2 = g_string_new("");
+
+ path = qos_graph_get_node(node_name); /* root */
+ node_name = qos_graph_edge_get_dest(path->path_edge); /* machine name */
+
+ path_vec[path_vec_size++] = node_name;
+ path_vec[path_vec_size++] = qos_get_machine_type(node_name);
+
+ for (;;) {
+ path = qos_graph_get_node(node_name);
+ if (!path->path_edge) {
+ break;
+ }
+
+ node_name = qos_graph_edge_get_dest(path->path_edge);
+
+ /* append node command line + previous edge command line */
+ if (path->command_line && etype == QEDGE_CONSUMED_BY) {
+ g_string_append(cmd_line, path->command_line);
+ g_string_append(cmd_line, after_device_str->str);
+ g_string_truncate(after_device_str, 0);
+ }
+
+ path_vec[path_vec_size++] = qos_graph_edge_get_name(path->path_edge);
+ /* detect if edge has command line args */
+ after_cmd = qos_graph_edge_get_after_cmd_line(path->path_edge);
+ after_device = qos_graph_edge_get_extra_device_opts(path->path_edge);
+ before_cmd = qos_graph_edge_get_before_cmd_line(path->path_edge);
+ edge = qos_graph_get_edge(path->name, node_name);
+ etype = qos_graph_edge_get_type(edge);
+
+ if (before_cmd) {
+ g_string_append(cmd_line, before_cmd);
+ }
+ if (after_cmd) {
+ g_string_append(cmd_line2, after_cmd);
+ }
+ if (after_device) {
+ g_string_append(after_device_str, after_device);
+ }
+ }
+
+ path_vec[path_vec_size++] = NULL;
+ g_string_append(cmd_line, after_device_str->str);
+ g_string_free(after_device_str, true);
+
+ g_string_append(cmd_line, cmd_line2->str);
+ g_string_free(cmd_line2, true);
+
+ /* here position 0 has <arch>/<machine>, position 1 has <machine>.
+ * The path must not have the <arch>, qtest_add_data_func adds it.
+ */
+ path_str = g_strjoinv("/", path_vec + 1);
+
+ /* put arch/machine in position 1 so run_one_test can do its work
+ * and add the command line at position 0.
+ */
+ path_vec[1] = path_vec[0];
+ path_vec[0] = g_string_free(cmd_line, false);
+
+ if (path->u.test.subprocess) {
+ gchar *subprocess_path = g_strdup_printf("/%s/%s/subprocess",
+ qtest_get_arch(), path_str);
+ qtest_add_data_func(path_str, subprocess_path, subprocess_run_one_test);
+ g_test_add_data_func(subprocess_path, path_vec, run_one_test);
+ } else {
+ qtest_add_data_func(path_str, path_vec, run_one_test);
+ }
+
+ g_free(path_str);
+}
+
+
+
+/**
+ * main(): heart of the qgraph framework.
+ *
+ * - Initializes the glib test framework
+ * - Creates the graph by invoking the various _init constructors
+ * - Starts QEMU to mark the available devices
+ * - Walks the graph, and each path is added to
+ * the glib test framework (walk_path)
+ * - Runs the tests, calling allocate_object() and allocating the
+ * machine/drivers/test objects
+ * - Cleans up everything
+ */
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qos_graph_init();
+ module_call_init(MODULE_INIT_QOM);
+ module_call_init(MODULE_INIT_LIBQOS);
+ qos_set_machines_devices_available();
+
+ qos_graph_foreach_test_path(walk_path);
+ g_test_run();
+ qtest_end();
+ qos_graph_destroy();
+ g_free(old_path);
+ return 0;
+}
diff --git a/tests/qtest/rtas-test.c b/tests/qtest/rtas-test.c
new file mode 100644
index 0000000000..167b42db38
--- /dev/null
+++ b/tests/qtest/rtas-test.c
@@ -0,0 +1,40 @@
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "libqtest.h"
+
+#include "libqos/libqos-spapr.h"
+#include "libqos/rtas.h"
+
+static void test_rtas_get_time_of_day(void)
+{
+ QOSState *qs;
+ struct tm tm;
+ uint32_t ns;
+ uint64_t ret;
+ time_t t1, t2;
+
+ qs = qtest_spapr_boot("-machine pseries");
+
+ t1 = time(NULL);
+ ret = qrtas_get_time_of_day(qs->qts, &qs->alloc, &tm, &ns);
+ g_assert_cmpint(ret, ==, 0);
+ t2 = mktimegm(&tm);
+ g_assert(t2 - t1 < 5); /* 5 sec max to run the test */
+
+ qtest_shutdown(qs);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *arch = qtest_get_arch();
+
+ g_test_init(&argc, &argv, NULL);
+
+ if (strcmp(arch, "ppc64")) {
+ g_printerr("RTAS requires ppc64-softmmu/qemu-system-ppc64\n");
+ exit(EXIT_FAILURE);
+ }
+ qtest_add_func("rtas/get-time-of-day", test_rtas_get_time_of_day);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/rtc-test.c b/tests/qtest/rtc-test.c
new file mode 100644
index 0000000000..c7af34f6b1
--- /dev/null
+++ b/tests/qtest/rtc-test.c
@@ -0,0 +1,720 @@
+/*
+ * QTest testcase for the MC146818 real-time clock
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest-single.h"
+#include "qemu/timer.h"
+#include "hw/rtc/mc146818rtc.h"
+#include "hw/rtc/mc146818rtc_regs.h"
+
+#define UIP_HOLD_LENGTH (8 * NANOSECONDS_PER_SECOND / 32768)
+
+static uint8_t base = 0x70;
+
+static int bcd2dec(int value)
+{
+ return (((value >> 4) & 0x0F) * 10) + (value & 0x0F);
+}
+
+static uint8_t cmos_read(uint8_t reg)
+{
+ outb(base + 0, reg);
+ return inb(base + 1);
+}
+
+static void cmos_write(uint8_t reg, uint8_t val)
+{
+ outb(base + 0, reg);
+ outb(base + 1, val);
+}
+
+static int tm_cmp(struct tm *lhs, struct tm *rhs)
+{
+ time_t a, b;
+ struct tm d1, d2;
+
+ memcpy(&d1, lhs, sizeof(d1));
+ memcpy(&d2, rhs, sizeof(d2));
+
+ a = mktime(&d1);
+ b = mktime(&d2);
+
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+
+ return 0;
+}
+
+#if 0
+static void print_tm(struct tm *tm)
+{
+ printf("%04d-%02d-%02d %02d:%02d:%02d\n",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_gmtoff);
+}
+#endif
+
+static void cmos_get_date_time(struct tm *date)
+{
+ int base_year = 2000, hour_offset;
+ int sec, min, hour, mday, mon, year;
+ time_t ts;
+ struct tm dummy;
+
+ sec = cmos_read(RTC_SECONDS);
+ min = cmos_read(RTC_MINUTES);
+ hour = cmos_read(RTC_HOURS);
+ mday = cmos_read(RTC_DAY_OF_MONTH);
+ mon = cmos_read(RTC_MONTH);
+ year = cmos_read(RTC_YEAR);
+
+ if ((cmos_read(RTC_REG_B) & REG_B_DM) == 0) {
+ sec = bcd2dec(sec);
+ min = bcd2dec(min);
+ hour = bcd2dec(hour);
+ mday = bcd2dec(mday);
+ mon = bcd2dec(mon);
+ year = bcd2dec(year);
+ hour_offset = 80;
+ } else {
+ hour_offset = 0x80;
+ }
+
+ if ((cmos_read(0x0B) & REG_B_24H) == 0) {
+ if (hour >= hour_offset) {
+ hour -= hour_offset;
+ hour += 12;
+ }
+ }
+
+ ts = time(NULL);
+ localtime_r(&ts, &dummy);
+
+ date->tm_isdst = dummy.tm_isdst;
+ date->tm_sec = sec;
+ date->tm_min = min;
+ date->tm_hour = hour;
+ date->tm_mday = mday;
+ date->tm_mon = mon - 1;
+ date->tm_year = base_year + year - 1900;
+#ifndef __sun__
+ date->tm_gmtoff = 0;
+#endif
+
+ ts = mktime(date);
+}
+
+static void check_time(int wiggle)
+{
+ struct tm start, date[4], end;
+ struct tm *datep;
+ time_t ts;
+
+ /*
+ * This check assumes a few things. First, we cannot guarantee that we get
+ * a consistent reading from the wall clock because we may hit an edge of
+ * the clock while reading. To work around this, we read four clock readings
+ * such that at least two of them should match. We need to assume that one
+ * reading is corrupt so we need four readings to ensure that we have at
+ * least two consecutive identical readings
+ *
+ * It's also possible that we'll cross an edge reading the host clock so
+ * simply check to make sure that the clock reading is within the period of
+ * when we expect it to be.
+ */
+
+ ts = time(NULL);
+ gmtime_r(&ts, &start);
+
+ cmos_get_date_time(&date[0]);
+ cmos_get_date_time(&date[1]);
+ cmos_get_date_time(&date[2]);
+ cmos_get_date_time(&date[3]);
+
+ ts = time(NULL);
+ gmtime_r(&ts, &end);
+
+ if (tm_cmp(&date[0], &date[1]) == 0) {
+ datep = &date[0];
+ } else if (tm_cmp(&date[1], &date[2]) == 0) {
+ datep = &date[1];
+ } else if (tm_cmp(&date[2], &date[3]) == 0) {
+ datep = &date[2];
+ } else {
+ g_assert_not_reached();
+ }
+
+ if (!(tm_cmp(&start, datep) <= 0 && tm_cmp(datep, &end) <= 0)) {
+ long t, s;
+
+ start.tm_isdst = datep->tm_isdst;
+
+ t = (long)mktime(datep);
+ s = (long)mktime(&start);
+ if (t < s) {
+ g_test_message("RTC is %ld second(s) behind wall-clock", (s - t));
+ } else {
+ g_test_message("RTC is %ld second(s) ahead of wall-clock", (t - s));
+ }
+
+ g_assert_cmpint(ABS(t - s), <=, wiggle);
+ }
+}
+
+static int wiggle = 2;
+
+static void set_year_20xx(void)
+{
+ /* Set BCD mode */
+ cmos_write(RTC_REG_B, REG_B_24H);
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_YEAR, 0x11);
+ cmos_write(RTC_CENTURY, 0x20);
+ cmos_write(RTC_MONTH, 0x02);
+ cmos_write(RTC_DAY_OF_MONTH, 0x02);
+ cmos_write(RTC_HOURS, 0x02);
+ cmos_write(RTC_MINUTES, 0x04);
+ cmos_write(RTC_SECONDS, 0x58);
+ cmos_write(RTC_REG_A, 0x26);
+
+ g_assert_cmpint(cmos_read(RTC_HOURS), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MINUTES), ==, 0x04);
+ g_assert_cmpint(cmos_read(RTC_SECONDS), >=, 0x58);
+ g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_YEAR), ==, 0x11);
+ g_assert_cmpint(cmos_read(RTC_CENTURY), ==, 0x20);
+
+ if (sizeof(time_t) == 4) {
+ return;
+ }
+
+ /* Set a date in 2080 to ensure there is no year-2038 overflow. */
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_YEAR, 0x80);
+ cmos_write(RTC_REG_A, 0x26);
+
+ g_assert_cmpint(cmos_read(RTC_HOURS), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MINUTES), ==, 0x04);
+ g_assert_cmpint(cmos_read(RTC_SECONDS), >=, 0x58);
+ g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_YEAR), ==, 0x80);
+ g_assert_cmpint(cmos_read(RTC_CENTURY), ==, 0x20);
+
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_YEAR, 0x11);
+ cmos_write(RTC_REG_A, 0x26);
+
+ g_assert_cmpint(cmos_read(RTC_HOURS), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MINUTES), ==, 0x04);
+ g_assert_cmpint(cmos_read(RTC_SECONDS), >=, 0x58);
+ g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_YEAR), ==, 0x11);
+ g_assert_cmpint(cmos_read(RTC_CENTURY), ==, 0x20);
+}
+
+static void set_year_1980(void)
+{
+ /* Set BCD mode */
+ cmos_write(RTC_REG_B, REG_B_24H);
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_YEAR, 0x80);
+ cmos_write(RTC_CENTURY, 0x19);
+ cmos_write(RTC_MONTH, 0x02);
+ cmos_write(RTC_DAY_OF_MONTH, 0x02);
+ cmos_write(RTC_HOURS, 0x02);
+ cmos_write(RTC_MINUTES, 0x04);
+ cmos_write(RTC_SECONDS, 0x58);
+ cmos_write(RTC_REG_A, 0x26);
+
+ g_assert_cmpint(cmos_read(RTC_HOURS), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MINUTES), ==, 0x04);
+ g_assert_cmpint(cmos_read(RTC_SECONDS), >=, 0x58);
+ g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_MONTH), ==, 0x02);
+ g_assert_cmpint(cmos_read(RTC_YEAR), ==, 0x80);
+ g_assert_cmpint(cmos_read(RTC_CENTURY), ==, 0x19);
+}
+
+static void bcd_check_time(void)
+{
+ /* Set BCD mode */
+ cmos_write(RTC_REG_B, REG_B_24H);
+ check_time(wiggle);
+}
+
+static void dec_check_time(void)
+{
+ /* Set DEC mode */
+ cmos_write(RTC_REG_B, REG_B_24H | REG_B_DM);
+ check_time(wiggle);
+}
+
+static void alarm_time(void)
+{
+ struct tm now;
+ time_t ts;
+ int i;
+
+ ts = time(NULL);
+ gmtime_r(&ts, &now);
+
+ /* set DEC mode */
+ cmos_write(RTC_REG_B, REG_B_24H | REG_B_DM);
+
+ g_assert(!get_irq(RTC_ISA_IRQ));
+ cmos_read(RTC_REG_C);
+
+ now.tm_sec = (now.tm_sec + 2) % 60;
+ cmos_write(RTC_SECONDS_ALARM, now.tm_sec);
+ cmos_write(RTC_MINUTES_ALARM, RTC_ALARM_DONT_CARE);
+ cmos_write(RTC_HOURS_ALARM, RTC_ALARM_DONT_CARE);
+ cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) | REG_B_AIE);
+
+ for (i = 0; i < 2 + wiggle; i++) {
+ if (get_irq(RTC_ISA_IRQ)) {
+ break;
+ }
+
+ clock_step(1000000000);
+ }
+
+ g_assert(get_irq(RTC_ISA_IRQ));
+ g_assert((cmos_read(RTC_REG_C) & REG_C_AF) != 0);
+ g_assert(cmos_read(RTC_REG_C) == 0);
+}
+
+static void set_time_regs(int h, int m, int s)
+{
+ cmos_write(RTC_HOURS, h);
+ cmos_write(RTC_MINUTES, m);
+ cmos_write(RTC_SECONDS, s);
+}
+
+static void set_time(int mode, int h, int m, int s)
+{
+ cmos_write(RTC_REG_B, mode);
+ cmos_write(RTC_REG_A, 0x76);
+ set_time_regs(h, m, s);
+ cmos_write(RTC_REG_A, 0x26);
+}
+
+static void set_datetime_bcd(int h, int min, int s, int d, int m, int y)
+{
+ cmos_write(RTC_HOURS, h);
+ cmos_write(RTC_MINUTES, min);
+ cmos_write(RTC_SECONDS, s);
+ cmos_write(RTC_YEAR, y & 0xFF);
+ cmos_write(RTC_CENTURY, y >> 8);
+ cmos_write(RTC_MONTH, m);
+ cmos_write(RTC_DAY_OF_MONTH, d);
+}
+
+static void set_datetime_dec(int h, int min, int s, int d, int m, int y)
+{
+ cmos_write(RTC_HOURS, h);
+ cmos_write(RTC_MINUTES, min);
+ cmos_write(RTC_SECONDS, s);
+ cmos_write(RTC_YEAR, y % 100);
+ cmos_write(RTC_CENTURY, y / 100);
+ cmos_write(RTC_MONTH, m);
+ cmos_write(RTC_DAY_OF_MONTH, d);
+}
+
+static void set_datetime(int mode, int h, int min, int s, int d, int m, int y)
+{
+ cmos_write(RTC_REG_B, mode);
+
+ cmos_write(RTC_REG_A, 0x76);
+ if (mode & REG_B_DM) {
+ set_datetime_dec(h, min, s, d, m, y);
+ } else {
+ set_datetime_bcd(h, min, s, d, m, y);
+ }
+ cmos_write(RTC_REG_A, 0x26);
+}
+
+#define assert_time(h, m, s) \
+ do { \
+ g_assert_cmpint(cmos_read(RTC_HOURS), ==, h); \
+ g_assert_cmpint(cmos_read(RTC_MINUTES), ==, m); \
+ g_assert_cmpint(cmos_read(RTC_SECONDS), ==, s); \
+ } while(0)
+
+#define assert_datetime_bcd(h, min, s, d, m, y) \
+ do { \
+ g_assert_cmpint(cmos_read(RTC_HOURS), ==, h); \
+ g_assert_cmpint(cmos_read(RTC_MINUTES), ==, min); \
+ g_assert_cmpint(cmos_read(RTC_SECONDS), ==, s); \
+ g_assert_cmpint(cmos_read(RTC_DAY_OF_MONTH), ==, d); \
+ g_assert_cmpint(cmos_read(RTC_MONTH), ==, m); \
+ g_assert_cmpint(cmos_read(RTC_YEAR), ==, (y & 0xFF)); \
+ g_assert_cmpint(cmos_read(RTC_CENTURY), ==, (y >> 8)); \
+ } while(0)
+
+static void basic_12h_bcd(void)
+{
+ /* set BCD 12 hour mode */
+ set_time(0, 0x81, 0x59, 0x00);
+ clock_step(1000000000LL);
+ assert_time(0x81, 0x59, 0x01);
+ clock_step(59000000000LL);
+ assert_time(0x82, 0x00, 0x00);
+
+ /* test BCD wraparound */
+ set_time(0, 0x09, 0x59, 0x59);
+ clock_step(60000000000LL);
+ assert_time(0x10, 0x00, 0x59);
+
+ /* 12 AM -> 1 AM */
+ set_time(0, 0x12, 0x59, 0x59);
+ clock_step(1000000000LL);
+ assert_time(0x01, 0x00, 0x00);
+
+ /* 12 PM -> 1 PM */
+ set_time(0, 0x92, 0x59, 0x59);
+ clock_step(1000000000LL);
+ assert_time(0x81, 0x00, 0x00);
+
+ /* 11 AM -> 12 PM */
+ set_time(0, 0x11, 0x59, 0x59);
+ clock_step(1000000000LL);
+ assert_time(0x92, 0x00, 0x00);
+ /* TODO: test day wraparound */
+
+ /* 11 PM -> 12 AM */
+ set_time(0, 0x91, 0x59, 0x59);
+ clock_step(1000000000LL);
+ assert_time(0x12, 0x00, 0x00);
+ /* TODO: test day wraparound */
+}
+
+static void basic_12h_dec(void)
+{
+ /* set decimal 12 hour mode */
+ set_time(REG_B_DM, 0x81, 59, 0);
+ clock_step(1000000000LL);
+ assert_time(0x81, 59, 1);
+ clock_step(59000000000LL);
+ assert_time(0x82, 0, 0);
+
+ /* 12 PM -> 1 PM */
+ set_time(REG_B_DM, 0x8c, 59, 59);
+ clock_step(1000000000LL);
+ assert_time(0x81, 0, 0);
+
+ /* 12 AM -> 1 AM */
+ set_time(REG_B_DM, 0x0c, 59, 59);
+ clock_step(1000000000LL);
+ assert_time(0x01, 0, 0);
+
+ /* 11 AM -> 12 PM */
+ set_time(REG_B_DM, 0x0b, 59, 59);
+ clock_step(1000000000LL);
+ assert_time(0x8c, 0, 0);
+
+ /* 11 PM -> 12 AM */
+ set_time(REG_B_DM, 0x8b, 59, 59);
+ clock_step(1000000000LL);
+ assert_time(0x0c, 0, 0);
+ /* TODO: test day wraparound */
+}
+
+static void basic_24h_bcd(void)
+{
+ /* set BCD 24 hour mode */
+ set_time(REG_B_24H, 0x09, 0x59, 0x00);
+ clock_step(1000000000LL);
+ assert_time(0x09, 0x59, 0x01);
+ clock_step(59000000000LL);
+ assert_time(0x10, 0x00, 0x00);
+
+ /* test BCD wraparound */
+ set_time(REG_B_24H, 0x09, 0x59, 0x00);
+ clock_step(60000000000LL);
+ assert_time(0x10, 0x00, 0x00);
+
+ /* TODO: test day wraparound */
+ set_time(REG_B_24H, 0x23, 0x59, 0x00);
+ clock_step(60000000000LL);
+ assert_time(0x00, 0x00, 0x00);
+}
+
+static void basic_24h_dec(void)
+{
+ /* set decimal 24 hour mode */
+ set_time(REG_B_24H | REG_B_DM, 9, 59, 0);
+ clock_step(1000000000LL);
+ assert_time(9, 59, 1);
+ clock_step(59000000000LL);
+ assert_time(10, 0, 0);
+
+ /* test BCD wraparound */
+ set_time(REG_B_24H | REG_B_DM, 9, 59, 0);
+ clock_step(60000000000LL);
+ assert_time(10, 0, 0);
+
+ /* TODO: test day wraparound */
+ set_time(REG_B_24H | REG_B_DM, 23, 59, 0);
+ clock_step(60000000000LL);
+ assert_time(0, 0, 0);
+}
+
+static void am_pm_alarm(void)
+{
+ cmos_write(RTC_MINUTES_ALARM, 0xC0);
+ cmos_write(RTC_SECONDS_ALARM, 0xC0);
+
+ /* set BCD 12 hour mode */
+ cmos_write(RTC_REG_B, 0);
+
+ /* Set time and alarm hour. */
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_HOURS_ALARM, 0x82);
+ cmos_write(RTC_HOURS, 0x81);
+ cmos_write(RTC_MINUTES, 0x59);
+ cmos_write(RTC_SECONDS, 0x00);
+ cmos_read(RTC_REG_C);
+ cmos_write(RTC_REG_A, 0x26);
+
+ /* Check that alarm triggers when AM/PM is set. */
+ clock_step(60000000000LL);
+ g_assert(cmos_read(RTC_HOURS) == 0x82);
+ g_assert((cmos_read(RTC_REG_C) & REG_C_AF) != 0);
+
+ /*
+ * Each of the following two tests takes over 60 seconds due to the time
+ * needed to report the PIT interrupts. Unfortunately, our PIT device
+ * model keeps counting even when GATE=0, so we cannot simply disable
+ * it in main().
+ */
+ if (g_test_quick()) {
+ return;
+ }
+
+ /* set DEC 12 hour mode */
+ cmos_write(RTC_REG_B, REG_B_DM);
+
+ /* Set time and alarm hour. */
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_HOURS_ALARM, 0x82);
+ cmos_write(RTC_HOURS, 3);
+ cmos_write(RTC_MINUTES, 0);
+ cmos_write(RTC_SECONDS, 0);
+ cmos_read(RTC_REG_C);
+ cmos_write(RTC_REG_A, 0x26);
+
+ /* Check that alarm triggers. */
+ clock_step(3600 * 11 * 1000000000LL);
+ g_assert(cmos_read(RTC_HOURS) == 0x82);
+ g_assert((cmos_read(RTC_REG_C) & REG_C_AF) != 0);
+
+ /* Same as above, with inverted HOURS and HOURS_ALARM. */
+ cmos_write(RTC_REG_A, 0x76);
+ cmos_write(RTC_HOURS_ALARM, 2);
+ cmos_write(RTC_HOURS, 3);
+ cmos_write(RTC_MINUTES, 0);
+ cmos_write(RTC_SECONDS, 0);
+ cmos_read(RTC_REG_C);
+ cmos_write(RTC_REG_A, 0x26);
+
+ /* Check that alarm does not trigger if hours differ only by AM/PM. */
+ clock_step(3600 * 11 * 1000000000LL);
+ g_assert(cmos_read(RTC_HOURS) == 0x82);
+ g_assert((cmos_read(RTC_REG_C) & REG_C_AF) == 0);
+}
+
+/* success if no crash or abort */
+static void fuzz_registers(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < 1000; i++) {
+ uint8_t reg, val;
+
+ reg = (uint8_t)g_test_rand_int_range(0, 16);
+ val = (uint8_t)g_test_rand_int_range(0, 256);
+
+ cmos_write(reg, val);
+ cmos_read(reg);
+ }
+}
+
+static void register_b_set_flag(void)
+{
+ if (cmos_read(RTC_REG_A) & REG_A_UIP) {
+ clock_step(UIP_HOLD_LENGTH + NANOSECONDS_PER_SECOND / 5);
+ }
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, ==, 0);
+
+ /* Enable binary-coded decimal (BCD) mode and SET flag in Register B*/
+ cmos_write(RTC_REG_B, REG_B_24H | REG_B_SET);
+
+ set_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ assert_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ /* Since SET flag is still enabled, time does not advance. */
+ clock_step(1000000000LL);
+ assert_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ /* Disable SET flag in Register B */
+ cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) & ~REG_B_SET);
+
+ assert_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ /* Since SET flag is disabled, the clock now advances. */
+ clock_step(1000000000LL);
+ assert_datetime_bcd(0x02, 0x04, 0x59, 0x02, 0x02, 0x2011);
+}
+
+static void divider_reset(void)
+{
+ /* Enable binary-coded decimal (BCD) mode in Register B*/
+ cmos_write(RTC_REG_B, REG_B_24H);
+
+ /* Enter divider reset */
+ cmos_write(RTC_REG_A, 0x76);
+ set_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ assert_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ /* Since divider reset flag is still enabled, these are equality checks. */
+ clock_step(1000000000LL);
+ assert_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ /* The first update ends 500 ms after divider reset */
+ cmos_write(RTC_REG_A, 0x26);
+ clock_step(500000000LL - UIP_HOLD_LENGTH - 1);
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, ==, 0);
+ assert_datetime_bcd(0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ clock_step(1);
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, !=, 0);
+ clock_step(UIP_HOLD_LENGTH);
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, ==, 0);
+
+ assert_datetime_bcd(0x02, 0x04, 0x59, 0x02, 0x02, 0x2011);
+}
+
+static void uip_stuck(void)
+{
+ set_datetime(REG_B_24H, 0x02, 0x04, 0x58, 0x02, 0x02, 0x2011);
+
+ /* The first update ends 500 ms after divider reset */
+ (void)cmos_read(RTC_REG_C);
+ clock_step(500000000LL);
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, ==, 0);
+ assert_datetime_bcd(0x02, 0x04, 0x59, 0x02, 0x02, 0x2011);
+
+ /* UF is now set. */
+ cmos_write(RTC_HOURS_ALARM, 0x02);
+ cmos_write(RTC_MINUTES_ALARM, 0xC0);
+ cmos_write(RTC_SECONDS_ALARM, 0xC0);
+
+ /* Because the alarm will fire soon, reading register A will latch UIP. */
+ clock_step(1000000000LL - UIP_HOLD_LENGTH / 2);
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, !=, 0);
+
+ /* Move the alarm far away. This must not cause UIP to remain stuck! */
+ cmos_write(RTC_HOURS_ALARM, 0x03);
+ clock_step(UIP_HOLD_LENGTH);
+ g_assert_cmpint(cmos_read(RTC_REG_A) & REG_A_UIP, ==, 0);
+}
+
+#define RTC_PERIOD_CODE1 13 /* 8 Hz */
+#define RTC_PERIOD_CODE2 15 /* 2 Hz */
+
+#define RTC_PERIOD_TEST_NR 50
+
+static uint64_t wait_periodic_interrupt(uint64_t real_time)
+{
+ while (!get_irq(RTC_ISA_IRQ)) {
+ real_time = clock_step_next();
+ }
+
+ g_assert((cmos_read(RTC_REG_C) & REG_C_PF) != 0);
+ return real_time;
+}
+
+static void periodic_timer(void)
+{
+ int i;
+ uint64_t period_clocks, period_time, start_time, real_time;
+
+ /* disable all interrupts. */
+ cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) &
+ ~(REG_B_PIE | REG_B_AIE | REG_B_UIE));
+ cmos_write(RTC_REG_A, RTC_PERIOD_CODE1);
+ /* enable periodic interrupt after properly configure the period. */
+ cmos_write(RTC_REG_B, cmos_read(RTC_REG_B) | REG_B_PIE);
+
+ start_time = real_time = clock_step_next();
+
+ for (i = 0; i < RTC_PERIOD_TEST_NR; i++) {
+ cmos_write(RTC_REG_A, RTC_PERIOD_CODE1);
+ real_time = wait_periodic_interrupt(real_time);
+ cmos_write(RTC_REG_A, RTC_PERIOD_CODE2);
+ real_time = wait_periodic_interrupt(real_time);
+ }
+
+ period_clocks = periodic_period_to_clock(RTC_PERIOD_CODE1) +
+ periodic_period_to_clock(RTC_PERIOD_CODE2);
+ period_clocks *= RTC_PERIOD_TEST_NR;
+ period_time = periodic_clock_to_ns(period_clocks);
+
+ real_time -= start_time;
+ g_assert_cmpint(ABS((int64_t)(real_time - period_time)), <=,
+ NANOSECONDS_PER_SECOND * 0.5);
+}
+
+int main(int argc, char **argv)
+{
+ QTestState *s = NULL;
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ s = qtest_start("-rtc clock=vm");
+ qtest_irq_intercept_in(s, "ioapic");
+
+ qtest_add_func("/rtc/check-time/bcd", bcd_check_time);
+ qtest_add_func("/rtc/check-time/dec", dec_check_time);
+ qtest_add_func("/rtc/alarm/interrupt", alarm_time);
+ qtest_add_func("/rtc/alarm/am-pm", am_pm_alarm);
+ qtest_add_func("/rtc/basic/dec-24h", basic_24h_dec);
+ qtest_add_func("/rtc/basic/bcd-24h", basic_24h_bcd);
+ qtest_add_func("/rtc/basic/dec-12h", basic_12h_dec);
+ qtest_add_func("/rtc/basic/bcd-12h", basic_12h_bcd);
+ qtest_add_func("/rtc/set-year/20xx", set_year_20xx);
+ qtest_add_func("/rtc/set-year/1980", set_year_1980);
+ qtest_add_func("/rtc/update/register_b_set_flag", register_b_set_flag);
+ qtest_add_func("/rtc/update/divider-reset", divider_reset);
+ qtest_add_func("/rtc/update/uip-stuck", uip_stuck);
+ qtest_add_func("/rtc/misc/fuzz-registers", fuzz_registers);
+ qtest_add_func("/rtc/periodic/interrupt", periodic_timer);
+
+ ret = g_test_run();
+
+ if (s) {
+ qtest_quit(s);
+ }
+
+ return ret;
+}
diff --git a/tests/qtest/rtl8139-test.c b/tests/qtest/rtl8139-test.c
new file mode 100644
index 0000000000..4506049264
--- /dev/null
+++ b/tests/qtest/rtl8139-test.c
@@ -0,0 +1,211 @@
+/*
+ * QTest testcase for Realtek 8139 NIC
+ *
+ * Copyright (c) 2013-2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/pci-pc.h"
+#include "qemu/timer.h"
+#include "qemu-common.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void nop(void)
+{
+}
+
+#define CLK 33333333
+
+static QPCIBus *pcibus;
+static QPCIDevice *dev;
+static QPCIBar dev_bar;
+
+static void save_fn(QPCIDevice *dev, int devfn, void *data)
+{
+ QPCIDevice **pdev = (QPCIDevice **) data;
+
+ *pdev = dev;
+}
+
+static QPCIDevice *get_device(void)
+{
+ QPCIDevice *dev;
+
+ pcibus = qpci_new_pc(global_qtest, NULL);
+ qpci_device_foreach(pcibus, 0x10ec, 0x8139, save_fn, &dev);
+ g_assert(dev != NULL);
+
+ return dev;
+}
+
+#define PORT(name, len, val) \
+static unsigned __attribute__((unused)) in_##name(void) \
+{ \
+ unsigned res = qpci_io_read##len(dev, dev_bar, (val)); \
+ g_test_message("*%s -> %x", #name, res); \
+ return res; \
+} \
+static void out_##name(unsigned v) \
+{ \
+ g_test_message("%x -> *%s", v, #name); \
+ qpci_io_write##len(dev, dev_bar, (val), v); \
+}
+
+PORT(Timer, l, 0x48)
+PORT(IntrMask, w, 0x3c)
+PORT(IntrStatus, w, 0x3E)
+PORT(TimerInt, l, 0x54)
+
+#define fatal(...) do { g_test_message(__VA_ARGS__); g_assert(0); } while (0)
+
+static void test_timer(void)
+{
+ const unsigned from = 0.95 * CLK;
+ const unsigned to = 1.6 * CLK;
+ unsigned prev, curr, next;
+ unsigned cnt, diff;
+
+ out_IntrMask(0);
+
+ in_IntrStatus();
+ in_Timer();
+ in_Timer();
+
+ /* Test 1. test counter continue and continue */
+ out_TimerInt(0); /* disable timer */
+ out_IntrStatus(0x4000);
+ out_Timer(12345); /* reset timer to 0 */
+ curr = in_Timer();
+ if (curr > 0.1 * CLK) {
+ fatal("time too big %u\n", curr);
+ }
+ for (cnt = 0; ; ) {
+ clock_step(1 * NANOSECONDS_PER_SECOND);
+ prev = curr;
+ curr = in_Timer();
+
+ /* test skip is in a specific range */
+ diff = (curr-prev) & 0xffffffffu;
+ if (diff < from || diff > to) {
+ fatal("Invalid diff %u (%u-%u)\n", diff, from, to);
+ }
+ if (curr < prev && ++cnt == 3) {
+ break;
+ }
+ }
+
+ /* Test 2. Check we didn't get an interrupt with TimerInt == 0 */
+ if (in_IntrStatus() & 0x4000) {
+ fatal("got an interrupt\n");
+ }
+
+ /* Test 3. Setting TimerInt to 1 and Timer to 0 get interrupt */
+ out_TimerInt(1);
+ out_Timer(0);
+ clock_step(40);
+ if ((in_IntrStatus() & 0x4000) == 0) {
+ fatal("we should have an interrupt here!\n");
+ }
+
+ /* Test 3. Check acknowledge */
+ out_IntrStatus(0x4000);
+ if (in_IntrStatus() & 0x4000) {
+ fatal("got an interrupt\n");
+ }
+
+ /* Test. Status set after Timer reset */
+ out_Timer(0);
+ out_TimerInt(0);
+ out_IntrStatus(0x4000);
+ curr = in_Timer();
+ out_TimerInt(curr + 0.5 * CLK);
+ clock_step(1 * NANOSECONDS_PER_SECOND);
+ out_Timer(0);
+ if ((in_IntrStatus() & 0x4000) == 0) {
+ fatal("we should have an interrupt here!\n");
+ }
+
+ /* Test. Status set after TimerInt reset */
+ out_Timer(0);
+ out_TimerInt(0);
+ out_IntrStatus(0x4000);
+ curr = in_Timer();
+ out_TimerInt(curr + 0.5 * CLK);
+ clock_step(1 * NANOSECONDS_PER_SECOND);
+ out_TimerInt(0);
+ if ((in_IntrStatus() & 0x4000) == 0) {
+ fatal("we should have an interrupt here!\n");
+ }
+
+ /* Test 4. Increment TimerInt we should see an interrupt */
+ curr = in_Timer();
+ next = curr + 5.0 * CLK;
+ out_TimerInt(next);
+ for (cnt = 0; ; ) {
+ clock_step(1 * NANOSECONDS_PER_SECOND);
+ prev = curr;
+ curr = in_Timer();
+ diff = (curr-prev) & 0xffffffffu;
+ if (diff < from || diff > to) {
+ fatal("Invalid diff %u (%u-%u)\n", diff, from, to);
+ }
+ if (cnt < 3 && curr > next) {
+ if ((in_IntrStatus() & 0x4000) == 0) {
+ fatal("we should have an interrupt here!\n");
+ }
+ out_IntrStatus(0x4000);
+ next = curr + 5.0 * CLK;
+ out_TimerInt(next);
+ if (++cnt == 3) {
+ out_TimerInt(1);
+ }
+ /* Test 5. Second time we pass from 0 should see an interrupt */
+ } else if (cnt >= 3 && curr < prev) {
+ /* here we should have an interrupt */
+ if ((in_IntrStatus() & 0x4000) == 0) {
+ fatal("we should have an interrupt here!\n");
+ }
+ out_IntrStatus(0x4000);
+ if (++cnt == 5) {
+ break;
+ }
+ }
+ }
+
+ g_test_message("Everythink is ok!");
+}
+
+
+static void test_init(void)
+{
+ uint64_t barsize;
+
+ dev = get_device();
+
+ dev_bar = qpci_iomap(dev, 0, &barsize);
+
+ qpci_device_enable(dev);
+
+ test_timer();
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ qtest_start("-device rtl8139");
+
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/rtl8139/nop", nop);
+ qtest_add_func("/rtl8139/timer", test_init);
+
+ ret = g_test_run();
+
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/sdhci-test.c b/tests/qtest/sdhci-test.c
new file mode 100644
index 0000000000..6275e7626c
--- /dev/null
+++ b/tests/qtest/sdhci-test.c
@@ -0,0 +1,111 @@
+/*
+ * QTest testcase for SDHCI controllers
+ *
+ * Written by Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/registerfields.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/pci-pc.h"
+#include "hw/pci/pci.h"
+#include "libqos/qgraph.h"
+#include "libqos/sdhci.h"
+
+#define SDHC_CAPAB 0x40
+FIELD(SDHC_CAPAB, BASECLKFREQ, 8, 8); /* since v2 */
+FIELD(SDHC_CAPAB, SDMA, 22, 1);
+FIELD(SDHC_CAPAB, SDR, 32, 3); /* since v3 */
+FIELD(SDHC_CAPAB, DRIVER, 36, 3); /* since v3 */
+#define SDHC_HCVER 0xFE
+
+static void check_specs_version(QSDHCI *s, uint8_t version)
+{
+ uint32_t v;
+
+ v = s->readw(s, SDHC_HCVER);
+ v &= 0xff;
+ v += 1;
+ g_assert_cmpuint(v, ==, version);
+}
+
+static void check_capab_capareg(QSDHCI *s, uint64_t expec_capab)
+{
+ uint64_t capab;
+
+ capab = s->readq(s, SDHC_CAPAB);
+ g_assert_cmphex(capab, ==, expec_capab);
+}
+
+static void check_capab_readonly(QSDHCI *s)
+{
+ const uint64_t vrand = 0x123456789abcdef;
+ uint64_t capab0, capab1;
+
+ capab0 = s->readq(s, SDHC_CAPAB);
+ g_assert_cmpuint(capab0, !=, vrand);
+
+ s->writeq(s, SDHC_CAPAB, vrand);
+ capab1 = s->readq(s, SDHC_CAPAB);
+ g_assert_cmpuint(capab1, !=, vrand);
+ g_assert_cmpuint(capab1, ==, capab0);
+}
+
+static void check_capab_baseclock(QSDHCI *s, uint8_t expec_freq)
+{
+ uint64_t capab, capab_freq;
+
+ if (!expec_freq) {
+ return;
+ }
+ capab = s->readq(s, SDHC_CAPAB);
+ capab_freq = FIELD_EX64(capab, SDHC_CAPAB, BASECLKFREQ);
+ g_assert_cmpuint(capab_freq, ==, expec_freq);
+}
+
+static void check_capab_sdma(QSDHCI *s, bool supported)
+{
+ uint64_t capab, capab_sdma;
+
+ capab = s->readq(s, SDHC_CAPAB);
+ capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
+ g_assert_cmpuint(capab_sdma, ==, supported);
+}
+
+static void check_capab_v3(QSDHCI *s, uint8_t version)
+{
+ uint64_t capab, capab_v3;
+
+ if (version < 3) {
+ /* before v3 those fields are RESERVED */
+ capab = s->readq(s, SDHC_CAPAB);
+ capab_v3 = FIELD_EX64(capab, SDHC_CAPAB, SDR);
+ g_assert_cmpuint(capab_v3, ==, 0);
+ capab_v3 = FIELD_EX64(capab, SDHC_CAPAB, DRIVER);
+ g_assert_cmpuint(capab_v3, ==, 0);
+ }
+}
+
+static void test_registers(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QSDHCI *s = obj;
+
+ check_specs_version(s, s->props.version);
+ check_capab_capareg(s, s->props.capab.reg);
+ check_capab_readonly(s);
+ check_capab_v3(s, s->props.version);
+ check_capab_sdma(s, s->props.capab.sdma);
+ check_capab_baseclock(s, s->props.baseclock);
+}
+
+static void register_sdhci_test(void)
+{
+ qos_add_test("registers", "sdhci", test_registers, NULL);
+}
+
+libqos_init(register_sdhci_test);
diff --git a/tests/qtest/spapr-phb-test.c b/tests/qtest/spapr-phb-test.c
new file mode 100644
index 0000000000..093dc22f2f
--- /dev/null
+++ b/tests/qtest/spapr-phb-test.c
@@ -0,0 +1,32 @@
+/*
+ * QTest testcase for SPAPR PHB
+ *
+ * Authors:
+ * Alexey Kardashevskiy <aik@ozlabs.ru>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests,
+ * for example by producing pci-bus.
+ */
+static void test_phb_device(void *obj, void *data, QGuestAllocator *alloc)
+{
+}
+
+static void register_phb_test(void)
+{
+ qos_add_test("spapr-phb-test", "ppc64/pseries",
+ test_phb_device, &(QOSGraphTestOptions) {
+ .edge.before_cmd_line = "-device spapr-pci-host-bridge"
+ ",index=30",
+ });
+}
+
+libqos_init(register_phb_test);
diff --git a/tests/qtest/tco-test.c b/tests/qtest/tco-test.c
new file mode 100644
index 0000000000..254f735370
--- /dev/null
+++ b/tests/qtest/tco-test.c
@@ -0,0 +1,469 @@
+/*
+ * QEMU ICH9 TCO emulation tests
+ *
+ * Copyright (c) 2015 Paulo Alcantara <pcacjr@zytor.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+#include "qapi/qmp/qdict.h"
+#include "hw/pci/pci_regs.h"
+#include "hw/i386/ich9.h"
+#include "hw/acpi/ich9.h"
+#include "hw/acpi/tco.h"
+
+#define RCBA_BASE_ADDR 0xfed1c000
+#define PM_IO_BASE_ADDR 0xb000
+
+enum {
+ TCO_RLD_DEFAULT = 0x0000,
+ TCO_DAT_IN_DEFAULT = 0x00,
+ TCO_DAT_OUT_DEFAULT = 0x00,
+ TCO1_STS_DEFAULT = 0x0000,
+ TCO2_STS_DEFAULT = 0x0000,
+ TCO1_CNT_DEFAULT = 0x0000,
+ TCO2_CNT_DEFAULT = 0x0008,
+ TCO_MESSAGE1_DEFAULT = 0x00,
+ TCO_MESSAGE2_DEFAULT = 0x00,
+ TCO_WDCNT_DEFAULT = 0x00,
+ TCO_TMR_DEFAULT = 0x0004,
+ SW_IRQ_GEN_DEFAULT = 0x03,
+};
+
+#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6)
+#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10)
+
+typedef struct {
+ const char *args;
+ bool noreboot;
+ QPCIDevice *dev;
+ QPCIBar tco_io_bar;
+ QPCIBus *bus;
+ QTestState *qts;
+} TestData;
+
+static void test_end(TestData *d)
+{
+ g_free(d->dev);
+ qpci_free_pc(d->bus);
+ qtest_quit(d->qts);
+}
+
+static void test_init(TestData *d)
+{
+ QTestState *qs;
+
+ qs = qtest_initf("-machine q35 %s %s",
+ d->noreboot ? "" : "-global ICH9-LPC.noreboot=false",
+ !d->args ? "" : d->args);
+ qtest_irq_intercept_in(qs, "ioapic");
+
+ d->bus = qpci_new_pc(qs, NULL);
+ d->dev = qpci_device_find(d->bus, QPCI_DEVFN(0x1f, 0x00));
+ g_assert(d->dev != NULL);
+
+ qpci_device_enable(d->dev);
+
+ /* set ACPI PM I/O space base address */
+ qpci_config_writel(d->dev, ICH9_LPC_PMBASE, PM_IO_BASE_ADDR | 0x1);
+ /* enable ACPI I/O */
+ qpci_config_writeb(d->dev, ICH9_LPC_ACPI_CTRL, 0x80);
+ /* set Root Complex BAR */
+ qpci_config_writel(d->dev, ICH9_LPC_RCBA, RCBA_BASE_ADDR | 0x1);
+
+ d->tco_io_bar = qpci_legacy_iomap(d->dev, PM_IO_BASE_ADDR + 0x60);
+ d->qts = qs;
+}
+
+static void stop_tco(const TestData *d)
+{
+ uint32_t val;
+
+ val = qpci_io_readw(d->dev, d->tco_io_bar, TCO1_CNT);
+ val |= TCO_TMR_HLT;
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO1_CNT, val);
+}
+
+static void start_tco(const TestData *d)
+{
+ uint32_t val;
+
+ val = qpci_io_readw(d->dev, d->tco_io_bar, TCO1_CNT);
+ val &= ~TCO_TMR_HLT;
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO1_CNT, val);
+}
+
+static void load_tco(const TestData *d)
+{
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO_RLD, 4);
+}
+
+static void set_tco_timeout(const TestData *d, uint16_t ticks)
+{
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO_TMR, ticks);
+}
+
+static void clear_tco_status(const TestData *d)
+{
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO1_STS, 0x0008);
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO2_STS, 0x0002);
+ qpci_io_writew(d->dev, d->tco_io_bar, TCO2_STS, 0x0004);
+}
+
+static void reset_on_second_timeout(const TestData *td, bool enable)
+{
+ uint32_t val;
+
+ val = qtest_readl(td->qts, RCBA_BASE_ADDR + ICH9_CC_GCS);
+ if (enable) {
+ val &= ~ICH9_CC_GCS_NO_REBOOT;
+ } else {
+ val |= ICH9_CC_GCS_NO_REBOOT;
+ }
+ qtest_writel(td->qts, RCBA_BASE_ADDR + ICH9_CC_GCS, val);
+}
+
+static void test_tco_defaults(void)
+{
+ TestData d;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO_RLD), ==,
+ TCO_RLD_DEFAULT);
+ /* TCO_DAT_IN & TCO_DAT_OUT */
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO_DAT_IN), ==,
+ (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT);
+ /* TCO1_STS & TCO2_STS */
+ g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_bar, TCO1_STS), ==,
+ (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT);
+ /* TCO1_CNT & TCO2_CNT */
+ g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_bar, TCO1_CNT), ==,
+ (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT);
+ /* TCO_MESSAGE1 & TCO_MESSAGE2 */
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO_MESSAGE1), ==,
+ (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT);
+ g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_bar, TCO_WDCNT), ==,
+ TCO_WDCNT_DEFAULT);
+ g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_bar, SW_IRQ_GEN), ==,
+ SW_IRQ_GEN_DEFAULT);
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO_TMR), ==,
+ TCO_TMR_DEFAULT);
+ test_end(&d);
+}
+
+static void test_tco_timeout(void)
+{
+ TestData d;
+ const uint16_t ticks = TCO_SECS_TO_TICKS(4);
+ uint32_t val;
+ int ret;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+
+ stop_tco(&d);
+ clear_tco_status(&d);
+ reset_on_second_timeout(&d, false);
+ set_tco_timeout(&d, ticks);
+ load_tco(&d);
+ start_tco(&d);
+ qtest_clock_step(d.qts, ticks * TCO_TICK_NSEC);
+
+ /* test first timeout */
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS);
+ ret = val & TCO_TIMEOUT ? 1 : 0;
+ g_assert(ret == 1);
+
+ /* test clearing timeout bit */
+ val |= TCO_TIMEOUT;
+ qpci_io_writew(d.dev, d.tco_io_bar, TCO1_STS, val);
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS);
+ ret = val & TCO_TIMEOUT ? 1 : 0;
+ g_assert(ret == 0);
+
+ /* test second timeout */
+ qtest_clock_step(d.qts, ticks * TCO_TICK_NSEC);
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS);
+ ret = val & TCO_TIMEOUT ? 1 : 0;
+ g_assert(ret == 1);
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO2_STS);
+ ret = val & TCO_SECOND_TO_STS ? 1 : 0;
+ g_assert(ret == 1);
+
+ stop_tco(&d);
+ test_end(&d);
+}
+
+static void test_tco_max_timeout(void)
+{
+ TestData d;
+ const uint16_t ticks = 0xffff;
+ uint32_t val;
+ int ret;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+
+ stop_tco(&d);
+ clear_tco_status(&d);
+ reset_on_second_timeout(&d, false);
+ set_tco_timeout(&d, ticks);
+ load_tco(&d);
+ start_tco(&d);
+ qtest_clock_step(d.qts, ((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC);
+
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO_RLD);
+ g_assert_cmpint(val & TCO_RLD_MASK, ==, 1);
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS);
+ ret = val & TCO_TIMEOUT ? 1 : 0;
+ g_assert(ret == 0);
+ qtest_clock_step(d.qts, TCO_TICK_NSEC);
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS);
+ ret = val & TCO_TIMEOUT ? 1 : 0;
+ g_assert(ret == 1);
+
+ stop_tco(&d);
+ test_end(&d);
+}
+
+static QDict *get_watchdog_action(const TestData *td)
+{
+ QDict *ev = qtest_qmp_eventwait_ref(td->qts, "WATCHDOG");
+ QDict *data;
+
+ data = qdict_get_qdict(ev, "data");
+ qobject_ref(data);
+ qobject_unref(ev);
+ return data;
+}
+
+static void test_tco_second_timeout_pause(void)
+{
+ TestData td;
+ const uint16_t ticks = TCO_SECS_TO_TICKS(32);
+ QDict *ad;
+
+ td.args = "-watchdog-action pause";
+ td.noreboot = false;
+ test_init(&td);
+
+ stop_tco(&td);
+ clear_tco_status(&td);
+ reset_on_second_timeout(&td, true);
+ set_tco_timeout(&td, TCO_SECS_TO_TICKS(16));
+ load_tco(&td);
+ start_tco(&td);
+ qtest_clock_step(td.qts, ticks * TCO_TICK_NSEC * 2);
+ ad = get_watchdog_action(&td);
+ g_assert(!strcmp(qdict_get_str(ad, "action"), "pause"));
+ qobject_unref(ad);
+
+ stop_tco(&td);
+ test_end(&td);
+}
+
+static void test_tco_second_timeout_reset(void)
+{
+ TestData td;
+ const uint16_t ticks = TCO_SECS_TO_TICKS(16);
+ QDict *ad;
+
+ td.args = "-watchdog-action reset";
+ td.noreboot = false;
+ test_init(&td);
+
+ stop_tco(&td);
+ clear_tco_status(&td);
+ reset_on_second_timeout(&td, true);
+ set_tco_timeout(&td, TCO_SECS_TO_TICKS(16));
+ load_tco(&td);
+ start_tco(&td);
+ qtest_clock_step(td.qts, ticks * TCO_TICK_NSEC * 2);
+ ad = get_watchdog_action(&td);
+ g_assert(!strcmp(qdict_get_str(ad, "action"), "reset"));
+ qobject_unref(ad);
+
+ stop_tco(&td);
+ test_end(&td);
+}
+
+static void test_tco_second_timeout_shutdown(void)
+{
+ TestData td;
+ const uint16_t ticks = TCO_SECS_TO_TICKS(128);
+ QDict *ad;
+
+ td.args = "-watchdog-action shutdown";
+ td.noreboot = false;
+ test_init(&td);
+
+ stop_tco(&td);
+ clear_tco_status(&td);
+ reset_on_second_timeout(&td, true);
+ set_tco_timeout(&td, ticks);
+ load_tco(&td);
+ start_tco(&td);
+ qtest_clock_step(td.qts, ticks * TCO_TICK_NSEC * 2);
+ ad = get_watchdog_action(&td);
+ g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown"));
+ qobject_unref(ad);
+
+ stop_tco(&td);
+ test_end(&td);
+}
+
+static void test_tco_second_timeout_none(void)
+{
+ TestData td;
+ const uint16_t ticks = TCO_SECS_TO_TICKS(256);
+ QDict *ad;
+
+ td.args = "-watchdog-action none";
+ td.noreboot = false;
+ test_init(&td);
+
+ stop_tco(&td);
+ clear_tco_status(&td);
+ reset_on_second_timeout(&td, true);
+ set_tco_timeout(&td, ticks);
+ load_tco(&td);
+ start_tco(&td);
+ qtest_clock_step(td.qts, ticks * TCO_TICK_NSEC * 2);
+ ad = get_watchdog_action(&td);
+ g_assert(!strcmp(qdict_get_str(ad, "action"), "none"));
+ qobject_unref(ad);
+
+ stop_tco(&td);
+ test_end(&td);
+}
+
+static void test_tco_ticks_counter(void)
+{
+ TestData d;
+ uint16_t ticks = TCO_SECS_TO_TICKS(8);
+ uint16_t rld;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+
+ stop_tco(&d);
+ clear_tco_status(&d);
+ reset_on_second_timeout(&d, false);
+ set_tco_timeout(&d, ticks);
+ load_tco(&d);
+ start_tco(&d);
+
+ do {
+ rld = qpci_io_readw(d.dev, d.tco_io_bar, TCO_RLD) & TCO_RLD_MASK;
+ g_assert_cmpint(rld, ==, ticks);
+ qtest_clock_step(d.qts, TCO_TICK_NSEC);
+ ticks--;
+ } while (!(qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS) & TCO_TIMEOUT));
+
+ stop_tco(&d);
+ test_end(&d);
+}
+
+static void test_tco1_control_bits(void)
+{
+ TestData d;
+ uint16_t val;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+
+ val = TCO_LOCK;
+ qpci_io_writew(d.dev, d.tco_io_bar, TCO1_CNT, val);
+ val &= ~TCO_LOCK;
+ qpci_io_writew(d.dev, d.tco_io_bar, TCO1_CNT, val);
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO1_CNT), ==,
+ TCO_LOCK);
+ test_end(&d);
+}
+
+static void test_tco1_status_bits(void)
+{
+ TestData d;
+ uint16_t ticks = 8;
+ uint16_t val;
+ int ret;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+
+ stop_tco(&d);
+ clear_tco_status(&d);
+ reset_on_second_timeout(&d, false);
+ set_tco_timeout(&d, ticks);
+ load_tco(&d);
+ start_tco(&d);
+ qtest_clock_step(d.qts, ticks * TCO_TICK_NSEC);
+
+ qpci_io_writeb(d.dev, d.tco_io_bar, TCO_DAT_IN, 0);
+ qpci_io_writeb(d.dev, d.tco_io_bar, TCO_DAT_OUT, 0);
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS);
+ ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0;
+ g_assert(ret == 1);
+ qpci_io_writew(d.dev, d.tco_io_bar, TCO1_STS, val);
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO1_STS), ==, 0);
+ test_end(&d);
+}
+
+static void test_tco2_status_bits(void)
+{
+ TestData d;
+ uint16_t ticks = 8;
+ uint16_t val;
+ int ret;
+
+ d.args = NULL;
+ d.noreboot = true;
+ test_init(&d);
+
+ stop_tco(&d);
+ clear_tco_status(&d);
+ reset_on_second_timeout(&d, true);
+ set_tco_timeout(&d, ticks);
+ load_tco(&d);
+ start_tco(&d);
+ qtest_clock_step(d.qts, ticks * TCO_TICK_NSEC * 2);
+
+ val = qpci_io_readw(d.dev, d.tco_io_bar, TCO2_STS);
+ ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0;
+ g_assert(ret == 1);
+ qpci_io_writew(d.dev, d.tco_io_bar, TCO2_STS, val);
+ g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_bar, TCO2_STS), ==, 0);
+ test_end(&d);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("tco/defaults", test_tco_defaults);
+ qtest_add_func("tco/timeout/no_action", test_tco_timeout);
+ qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout);
+ qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause);
+ qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset);
+ qtest_add_func("tco/second_timeout/shutdown",
+ test_tco_second_timeout_shutdown);
+ qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none);
+ qtest_add_func("tco/counter", test_tco_ticks_counter);
+ qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits);
+ qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits);
+ qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits);
+ return g_test_run();
+}
diff --git a/tests/qtest/test-arm-mptimer.c b/tests/qtest/test-arm-mptimer.c
new file mode 100644
index 0000000000..7a56d56da9
--- /dev/null
+++ b/tests/qtest/test-arm-mptimer.c
@@ -0,0 +1,1090 @@
+/*
+ * QTest testcase for the ARM MPTimer
+ *
+ * Copyright (c) 2016 Dmitry Osipenko <digetx@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "libqtest-single.h"
+
+#define TIMER_BLOCK_SCALE(s) ((((s) & 0xff) + 1) * 10)
+
+#define TIMER_BLOCK_STEP(scaler, steps_nb) \
+ clock_step(TIMER_BLOCK_SCALE(scaler) * (int64_t)(steps_nb) + 1)
+
+#define TIMER_BASE_PHYS 0x1e000600
+
+#define TIMER_LOAD 0x00
+#define TIMER_COUNTER 0x04
+#define TIMER_CONTROL 0x08
+#define TIMER_INTSTAT 0x0C
+
+#define TIMER_CONTROL_ENABLE (1 << 0)
+#define TIMER_CONTROL_PERIODIC (1 << 1)
+#define TIMER_CONTROL_IT_ENABLE (1 << 2)
+#define TIMER_CONTROL_PRESCALER(p) (((p) & 0xff) << 8)
+
+#define PERIODIC 1
+#define ONESHOT 0
+#define NOSCALE 0
+
+static int nonscaled = NOSCALE;
+static int scaled = 122;
+
+static void timer_load(uint32_t load)
+{
+ writel(TIMER_BASE_PHYS + TIMER_LOAD, load);
+}
+
+static void timer_start(int periodic, uint32_t scale)
+{
+ uint32_t ctl = TIMER_CONTROL_ENABLE | TIMER_CONTROL_PRESCALER(scale);
+
+ if (periodic) {
+ ctl |= TIMER_CONTROL_PERIODIC;
+ }
+
+ writel(TIMER_BASE_PHYS + TIMER_CONTROL, ctl);
+}
+
+static void timer_stop(void)
+{
+ writel(TIMER_BASE_PHYS + TIMER_CONTROL, 0);
+}
+
+static void timer_int_clr(void)
+{
+ writel(TIMER_BASE_PHYS + TIMER_INTSTAT, 1);
+}
+
+static void timer_reset(void)
+{
+ timer_stop();
+ timer_load(0);
+ timer_int_clr();
+}
+
+static uint32_t timer_get_and_clr_int_sts(void)
+{
+ uint32_t int_sts = readl(TIMER_BASE_PHYS + TIMER_INTSTAT);
+
+ if (int_sts) {
+ timer_int_clr();
+ }
+
+ return int_sts;
+}
+
+static uint32_t timer_counter(void)
+{
+ return readl(TIMER_BASE_PHYS + TIMER_COUNTER);
+}
+
+static void timer_set_counter(uint32_t value)
+{
+ writel(TIMER_BASE_PHYS + TIMER_COUNTER, value);
+}
+
+static void test_timer_oneshot(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(9999999);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 9999);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+ g_assert_cmpuint(timer_counter(), ==, 9990000);
+
+ TIMER_BLOCK_STEP(scaler, 9990000);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ TIMER_BLOCK_STEP(scaler, 9990000);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_pause(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(999999999);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 999);
+
+ g_assert_cmpuint(timer_counter(), ==, 999999000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(scaler, 9000);
+
+ g_assert_cmpuint(timer_counter(), ==, 999990000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_stop();
+
+ g_assert_cmpuint(timer_counter(), ==, 999990000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(scaler, 90000);
+
+ g_assert_cmpuint(timer_counter(), ==, 999990000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 999990000);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ TIMER_BLOCK_STEP(scaler, 999990000);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+}
+
+static void test_timer_reload(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 90000);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 90000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_load(UINT32_MAX);
+
+ TIMER_BLOCK_STEP(scaler, 90000);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 90000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_periodic(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+ int repeat = 10;
+
+ timer_reset();
+ timer_load(100);
+ timer_start(PERIODIC, scaler);
+
+ while (repeat--) {
+ clock_step(TIMER_BLOCK_SCALE(scaler) * (101 + repeat) + 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 100 - repeat);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ clock_step(TIMER_BLOCK_SCALE(scaler) * (101 - repeat) - 1);
+ }
+}
+
+static void test_timer_oneshot_to_periodic(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(10000);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1000);
+
+ g_assert_cmpuint(timer_counter(), ==, 9000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 14001);
+
+ g_assert_cmpuint(timer_counter(), ==, 5000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+}
+
+static void test_timer_periodic_to_oneshot(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(99999999);
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 999);
+
+ g_assert_cmpuint(timer_counter(), ==, 99999000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 99999009);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+}
+
+static void test_timer_prescaler(void)
+{
+ timer_reset();
+ timer_load(9999999);
+ timer_start(ONESHOT, NOSCALE);
+
+ TIMER_BLOCK_STEP(NOSCALE, 9999998);
+
+ g_assert_cmpuint(timer_counter(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(NOSCALE, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ timer_reset();
+ timer_load(9999999);
+ timer_start(ONESHOT, 0xAB);
+
+ TIMER_BLOCK_STEP(0xAB, 9999998);
+
+ g_assert_cmpuint(timer_counter(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(0xAB, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+}
+
+static void test_timer_prescaler_on_the_fly(void)
+{
+ timer_reset();
+ timer_load(9999999);
+ timer_start(ONESHOT, NOSCALE);
+
+ TIMER_BLOCK_STEP(NOSCALE, 999);
+
+ g_assert_cmpuint(timer_counter(), ==, 9999000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(ONESHOT, 0xAB);
+
+ TIMER_BLOCK_STEP(0xAB, 9000);
+
+ g_assert_cmpuint(timer_counter(), ==, 9990000);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_set_oneshot_counter_to_0(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_set_counter(0);
+
+ TIMER_BLOCK_STEP(scaler, 10);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_set_periodic_counter_to_0(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_set_counter(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - (scaler ? 0 : 1));
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ timer_reset();
+ timer_set_counter(UINT32_MAX);
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_set_counter(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_noload_oneshot(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_noload_periodic(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_zero_load_oneshot(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_zero_load_periodic(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_zero_load_oneshot_to_nonzero(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ timer_load(999);
+
+ TIMER_BLOCK_STEP(scaler, 1001);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+}
+
+static void test_timer_zero_load_periodic_to_nonzero(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+ int i;
+
+ timer_reset();
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ timer_load(1999999);
+
+ for (i = 1; i < 10; i++) {
+ TIMER_BLOCK_STEP(scaler, 2000001);
+
+ g_assert_cmpuint(timer_counter(), ==, 1999999 - i);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+ }
+}
+
+static void test_timer_nonzero_load_oneshot_to_zero(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ timer_load(UINT32_MAX);
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_nonzero_load_periodic_to_zero(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ timer_load(UINT32_MAX);
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_set_periodic_counter_on_the_fly(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(UINT32_MAX / 2);
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX / 2 - 100);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_set_counter(UINT32_MAX);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 100);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_enable_and_set_counter(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ timer_set_counter(UINT32_MAX);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 100);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_set_counter_and_enable(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_set_counter(UINT32_MAX);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 100);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_set_counter_disabled(void)
+{
+ timer_reset();
+ timer_set_counter(999999999);
+
+ TIMER_BLOCK_STEP(NOSCALE, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 999999999);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_load_disabled(void)
+{
+ timer_reset();
+ timer_load(999999999);
+
+ TIMER_BLOCK_STEP(NOSCALE, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 999999999);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_oneshot_with_counter_0_on_start(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(999);
+ timer_set_counter(0);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_periodic_with_counter_0_on_start(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+ int i;
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_set_counter(0);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX + (scaler ? 1 : 0) - 100);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX + (scaler ? 1 : 0) - 200);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_reset();
+ timer_load(1999999);
+ timer_set_counter(0);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ for (i = 2 - (!!scaler ? 1 : 0); i < 10; i++) {
+ TIMER_BLOCK_STEP(scaler, 2000001);
+
+ g_assert_cmpuint(timer_counter(), ==, 1999999 - i);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+ }
+}
+
+static void test_periodic_counter(gconstpointer arg)
+{
+ const int test_load = 10;
+ int scaler = *((int *) arg);
+ int test_val;
+
+ timer_reset();
+ timer_load(test_load);
+ timer_start(PERIODIC, scaler);
+
+ clock_step(1);
+
+ for (test_val = 0; test_val <= test_load; test_val++) {
+ clock_step(TIMER_BLOCK_SCALE(scaler) * test_load);
+ g_assert_cmpint(timer_counter(), ==, test_val);
+ }
+}
+
+static void test_timer_set_counter_periodic_with_zero_load(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_start(PERIODIC, scaler);
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ timer_set_counter(999);
+
+ TIMER_BLOCK_STEP(scaler, 999);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_set_oneshot_load_to_0(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 100);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_set_periodic_load_to_0(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, UINT32_MAX - 100);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_load(0);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+ g_assert_cmpuint(timer_counter(), ==, 0);
+}
+
+static void test_deferred_trigger(void)
+{
+ int mode = ONESHOT;
+
+again:
+ timer_reset();
+ timer_start(mode, 255);
+
+ clock_step(100);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ timer_reset();
+ timer_load(2);
+ timer_start(mode, 255);
+
+ clock_step(100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(mode, 255);
+
+ clock_step(100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_set_counter(0);
+
+ clock_step(100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ timer_reset();
+ timer_load(UINT32_MAX);
+ timer_start(mode, 255);
+
+ clock_step(100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_load(0);
+
+ clock_step(100);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+
+ if (mode == ONESHOT) {
+ mode = PERIODIC;
+ goto again;
+ }
+}
+
+static void test_timer_zero_load_mode_switch(gconstpointer arg)
+{
+ int scaler = *((int *) arg);
+
+ timer_reset();
+ timer_load(0);
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ timer_start(ONESHOT, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ timer_start(PERIODIC, scaler);
+
+ TIMER_BLOCK_STEP(scaler, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, !!scaler);
+}
+
+static void test_timer_zero_load_prescaled_periodic_to_nonscaled_oneshot(void)
+{
+ timer_reset();
+ timer_load(0);
+ timer_start(PERIODIC, 255);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ timer_start(ONESHOT, NOSCALE);
+
+ TIMER_BLOCK_STEP(NOSCALE, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(NOSCALE, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_zero_load_prescaled_oneshot_to_nonscaled_periodic(void)
+{
+ timer_reset();
+ timer_load(0);
+ timer_start(ONESHOT, 255);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(PERIODIC, NOSCALE);
+
+ TIMER_BLOCK_STEP(NOSCALE, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_zero_load_nonscaled_oneshot_to_prescaled_periodic(void)
+{
+ timer_reset();
+ timer_load(0);
+ timer_start(ONESHOT, NOSCALE);
+
+ TIMER_BLOCK_STEP(NOSCALE, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(PERIODIC, 255);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+static void test_timer_zero_load_nonscaled_periodic_to_prescaled_oneshot(void)
+{
+ timer_reset();
+ timer_load(0);
+ timer_start(PERIODIC, NOSCALE);
+
+ TIMER_BLOCK_STEP(NOSCALE, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ timer_start(ONESHOT, 255);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 1);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+
+ TIMER_BLOCK_STEP(255, 1);
+
+ g_assert_cmpuint(timer_counter(), ==, 0);
+ g_assert_cmpuint(timer_get_and_clr_int_sts(), ==, 0);
+}
+
+/*
+ * Add a qtest test that comes in two versions: one with
+ * a timer scaler setting, and one with the timer nonscaled.
+ */
+static void add_scaler_test(const char *str, bool scale,
+ void (*fn)(const void *))
+{
+ char *name;
+ int *scaler = scale ? &scaled : &nonscaled;
+
+ name = g_strdup_printf("%s=%d", str, *scaler);
+ qtest_add_data_func(name, scaler, fn);
+ g_free(name);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ int scale;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("mptimer/deferred_trigger", test_deferred_trigger);
+ qtest_add_func("mptimer/load_disabled", test_timer_load_disabled);
+ qtest_add_func("mptimer/set_counter_disabled", test_timer_set_counter_disabled);
+ qtest_add_func("mptimer/zero_load_prescaled_periodic_to_nonscaled_oneshot",
+ test_timer_zero_load_prescaled_periodic_to_nonscaled_oneshot);
+ qtest_add_func("mptimer/zero_load_prescaled_oneshot_to_nonscaled_periodic",
+ test_timer_zero_load_prescaled_oneshot_to_nonscaled_periodic);
+ qtest_add_func("mptimer/zero_load_nonscaled_oneshot_to_prescaled_periodic",
+ test_timer_zero_load_nonscaled_oneshot_to_prescaled_periodic);
+ qtest_add_func("mptimer/zero_load_nonscaled_periodic_to_prescaled_oneshot",
+ test_timer_zero_load_nonscaled_periodic_to_prescaled_oneshot);
+ qtest_add_func("mptimer/prescaler", test_timer_prescaler);
+ qtest_add_func("mptimer/prescaler_on_the_fly", test_timer_prescaler_on_the_fly);
+
+ for (scale = 0; scale < 2; scale++) {
+ add_scaler_test("mptimer/oneshot scaler",
+ scale, test_timer_oneshot);
+ add_scaler_test("mptimer/pause scaler",
+ scale, test_timer_pause);
+ add_scaler_test("mptimer/reload scaler",
+ scale, test_timer_reload);
+ add_scaler_test("mptimer/periodic scaler",
+ scale, test_timer_periodic);
+ add_scaler_test("mptimer/oneshot_to_periodic scaler",
+ scale, test_timer_oneshot_to_periodic);
+ add_scaler_test("mptimer/periodic_to_oneshot scaler",
+ scale, test_timer_periodic_to_oneshot);
+ add_scaler_test("mptimer/set_oneshot_counter_to_0 scaler",
+ scale, test_timer_set_oneshot_counter_to_0);
+ add_scaler_test("mptimer/set_periodic_counter_to_0 scaler",
+ scale, test_timer_set_periodic_counter_to_0);
+ add_scaler_test("mptimer/noload_oneshot scaler",
+ scale, test_timer_noload_oneshot);
+ add_scaler_test("mptimer/noload_periodic scaler",
+ scale, test_timer_noload_periodic);
+ add_scaler_test("mptimer/zero_load_oneshot scaler",
+ scale, test_timer_zero_load_oneshot);
+ add_scaler_test("mptimer/zero_load_periodic scaler",
+ scale, test_timer_zero_load_periodic);
+ add_scaler_test("mptimer/zero_load_oneshot_to_nonzero scaler",
+ scale, test_timer_zero_load_oneshot_to_nonzero);
+ add_scaler_test("mptimer/zero_load_periodic_to_nonzero scaler",
+ scale, test_timer_zero_load_periodic_to_nonzero);
+ add_scaler_test("mptimer/nonzero_load_oneshot_to_zero scaler",
+ scale, test_timer_nonzero_load_oneshot_to_zero);
+ add_scaler_test("mptimer/nonzero_load_periodic_to_zero scaler",
+ scale, test_timer_nonzero_load_periodic_to_zero);
+ add_scaler_test("mptimer/set_periodic_counter_on_the_fly scaler",
+ scale, test_timer_set_periodic_counter_on_the_fly);
+ add_scaler_test("mptimer/enable_and_set_counter scaler",
+ scale, test_timer_enable_and_set_counter);
+ add_scaler_test("mptimer/set_counter_and_enable scaler",
+ scale, test_timer_set_counter_and_enable);
+ add_scaler_test("mptimer/oneshot_with_counter_0_on_start scaler",
+ scale, test_timer_oneshot_with_counter_0_on_start);
+ add_scaler_test("mptimer/periodic_with_counter_0_on_start scaler",
+ scale, test_timer_periodic_with_counter_0_on_start);
+ add_scaler_test("mptimer/periodic_counter scaler",
+ scale, test_periodic_counter);
+ add_scaler_test("mptimer/set_counter_periodic_with_zero_load scaler",
+ scale, test_timer_set_counter_periodic_with_zero_load);
+ add_scaler_test("mptimer/set_oneshot_load_to_0 scaler",
+ scale, test_timer_set_oneshot_load_to_0);
+ add_scaler_test("mptimer/set_periodic_load_to_0 scaler",
+ scale, test_timer_set_periodic_load_to_0);
+ add_scaler_test("mptimer/zero_load_mode_switch scaler",
+ scale, test_timer_zero_load_mode_switch);
+ }
+
+ qtest_start("-machine vexpress-a9");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/test-filter-mirror.c b/tests/qtest/test-filter-mirror.c
new file mode 100644
index 0000000000..1e3ced84a9
--- /dev/null
+++ b/tests/qtest/test-filter-mirror.c
@@ -0,0 +1,94 @@
+/*
+ * QTest testcase for filter-mirror
+ *
+ * Copyright (c) 2016 FUJITSU LIMITED
+ * Author: Zhang Chen <zhangchen.fnst@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/iov.h"
+#include "qemu/sockets.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(qs, ...) qobject_unref(qtest_qmp(qs, __VA_ARGS__))
+
+static void test_mirror(void)
+{
+ int send_sock[2], recv_sock[2];
+ uint32_t ret = 0, len = 0;
+ char send_buf[] = "Hello! filter-mirror~";
+ char *recv_buf;
+ uint32_t size = sizeof(send_buf);
+ size = htonl(size);
+ const char *devstr = "e1000";
+ QTestState *qts;
+
+ if (g_str_equal(qtest_get_arch(), "s390x")) {
+ devstr = "virtio-net-ccw";
+ }
+
+ ret = socketpair(PF_UNIX, SOCK_STREAM, 0, send_sock);
+ g_assert_cmpint(ret, !=, -1);
+
+ ret = socketpair(PF_UNIX, SOCK_STREAM, 0, recv_sock);
+ g_assert_cmpint(ret, !=, -1);
+
+ qts = qtest_initf(
+ "-netdev socket,id=qtest-bn0,fd=%d "
+ "-device %s,netdev=qtest-bn0,id=qtest-e0 "
+ "-chardev socket,id=mirror0,fd=%d "
+ "-object filter-mirror,id=qtest-f0,netdev=qtest-bn0,queue=tx,outdev=mirror0 "
+ , send_sock[1], devstr, recv_sock[1]);
+
+ struct iovec iov[] = {
+ {
+ .iov_base = &size,
+ .iov_len = sizeof(size),
+ }, {
+ .iov_base = send_buf,
+ .iov_len = sizeof(send_buf),
+ },
+ };
+
+ /* send a qmp command to guarantee that 'connected' is setting to true. */
+ qmp_discard_response(qts, "{ 'execute' : 'query-status'}");
+ ret = iov_send(send_sock[0], iov, 2, 0, sizeof(size) + sizeof(send_buf));
+ g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
+ close(send_sock[0]);
+
+ ret = qemu_recv(recv_sock[0], &len, sizeof(len), 0);
+ g_assert_cmpint(ret, ==, sizeof(len));
+ len = ntohl(len);
+
+ g_assert_cmpint(len, ==, sizeof(send_buf));
+ recv_buf = g_malloc(len);
+ ret = qemu_recv(recv_sock[0], recv_buf, len, 0);
+ g_assert_cmpstr(recv_buf, ==, send_buf);
+
+ g_free(recv_buf);
+ close(send_sock[0]);
+ close(send_sock[1]);
+ close(recv_sock[0]);
+ close(recv_sock[1]);
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/netfilter/mirror", test_mirror);
+ ret = g_test_run();
+
+ return ret;
+}
diff --git a/tests/qtest/test-filter-redirector.c b/tests/qtest/test-filter-redirector.c
new file mode 100644
index 0000000000..e4d53220fd
--- /dev/null
+++ b/tests/qtest/test-filter-redirector.c
@@ -0,0 +1,219 @@
+/*
+ * QTest testcase for filter-redirector
+ *
+ * Copyright (c) 2016 FUJITSU LIMITED
+ * Author: Zhang Chen <zhangchen.fnst@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ *
+ * Case 1, tx traffic flow:
+ *
+ * qemu side | test side
+ * |
+ * +---------+ | +-------+
+ * | backend <---------------+ sock0 |
+ * +----+----+ | +-------+
+ * | |
+ * +----v----+ +-------+ |
+ * | rd0 +->+chardev| |
+ * +---------+ +---+---+ |
+ * | |
+ * +---------+ | |
+ * | rd1 <------+ |
+ * +----+----+ |
+ * | |
+ * +----v----+ | +-------+
+ * | rd2 +--------------->sock1 |
+ * +---------+ | +-------+
+ * +
+ *
+ * --------------------------------------
+ * Case 2, rx traffic flow
+ * qemu side | test side
+ * |
+ * +---------+ | +-------+
+ * | backend +---------------> sock1 |
+ * +----^----+ | +-------+
+ * | |
+ * +----+----+ +-------+ |
+ * | rd0 +<-+chardev| |
+ * +---------+ +---+---+ |
+ * ^ |
+ * +---------+ | |
+ * | rd1 +------+ |
+ * +----^----+ |
+ * | |
+ * +----+----+ | +-------+
+ * | rd2 <---------------+sock0 |
+ * +---------+ | +-------+
+ * +
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/iov.h"
+#include "qemu/sockets.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(qs, ...) qobject_unref(qtest_qmp(qs, __VA_ARGS__))
+
+static const char *get_devstr(void)
+{
+ if (g_str_equal(qtest_get_arch(), "s390x")) {
+ return "virtio-net-ccw";
+ }
+
+ return "rtl8139";
+}
+
+
+static void test_redirector_tx(void)
+{
+ int backend_sock[2], recv_sock;
+ uint32_t ret = 0, len = 0;
+ char send_buf[] = "Hello!!";
+ char sock_path0[] = "filter-redirector0.XXXXXX";
+ char sock_path1[] = "filter-redirector1.XXXXXX";
+ char *recv_buf;
+ uint32_t size = sizeof(send_buf);
+ size = htonl(size);
+ QTestState *qts;
+
+ ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock);
+ g_assert_cmpint(ret, !=, -1);
+
+ ret = mkstemp(sock_path0);
+ g_assert_cmpint(ret, !=, -1);
+ ret = mkstemp(sock_path1);
+ g_assert_cmpint(ret, !=, -1);
+
+ qts = qtest_initf(
+ "-netdev socket,id=qtest-bn0,fd=%d "
+ "-device %s,netdev=qtest-bn0,id=qtest-e0 "
+ "-chardev socket,id=redirector0,path=%s,server,nowait "
+ "-chardev socket,id=redirector1,path=%s,server,nowait "
+ "-chardev socket,id=redirector2,path=%s "
+ "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0,"
+ "queue=tx,outdev=redirector0 "
+ "-object filter-redirector,id=qtest-f1,netdev=qtest-bn0,"
+ "queue=tx,indev=redirector2 "
+ "-object filter-redirector,id=qtest-f2,netdev=qtest-bn0,"
+ "queue=tx,outdev=redirector1 ", backend_sock[1], get_devstr(),
+ sock_path0, sock_path1, sock_path0);
+
+ recv_sock = unix_connect(sock_path1, NULL);
+ g_assert_cmpint(recv_sock, !=, -1);
+
+ /* send a qmp command to guarantee that 'connected' is setting to true. */
+ qmp_discard_response(qts, "{ 'execute' : 'query-status'}");
+
+ struct iovec iov[] = {
+ {
+ .iov_base = &size,
+ .iov_len = sizeof(size),
+ }, {
+ .iov_base = send_buf,
+ .iov_len = sizeof(send_buf),
+ },
+ };
+
+ ret = iov_send(backend_sock[0], iov, 2, 0, sizeof(size) + sizeof(send_buf));
+ g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
+ close(backend_sock[0]);
+
+ ret = qemu_recv(recv_sock, &len, sizeof(len), 0);
+ g_assert_cmpint(ret, ==, sizeof(len));
+ len = ntohl(len);
+
+ g_assert_cmpint(len, ==, sizeof(send_buf));
+ recv_buf = g_malloc(len);
+ ret = qemu_recv(recv_sock, recv_buf, len, 0);
+ g_assert_cmpstr(recv_buf, ==, send_buf);
+
+ g_free(recv_buf);
+ close(recv_sock);
+ unlink(sock_path0);
+ unlink(sock_path1);
+ qtest_quit(qts);
+}
+
+static void test_redirector_rx(void)
+{
+ int backend_sock[2], send_sock;
+ uint32_t ret = 0, len = 0;
+ char send_buf[] = "Hello!!";
+ char sock_path0[] = "filter-redirector0.XXXXXX";
+ char sock_path1[] = "filter-redirector1.XXXXXX";
+ char *recv_buf;
+ uint32_t size = sizeof(send_buf);
+ size = htonl(size);
+ QTestState *qts;
+
+ ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock);
+ g_assert_cmpint(ret, !=, -1);
+
+ ret = mkstemp(sock_path0);
+ g_assert_cmpint(ret, !=, -1);
+ ret = mkstemp(sock_path1);
+ g_assert_cmpint(ret, !=, -1);
+
+ qts = qtest_initf(
+ "-netdev socket,id=qtest-bn0,fd=%d "
+ "-device %s,netdev=qtest-bn0,id=qtest-e0 "
+ "-chardev socket,id=redirector0,path=%s,server,nowait "
+ "-chardev socket,id=redirector1,path=%s,server,nowait "
+ "-chardev socket,id=redirector2,path=%s "
+ "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0,"
+ "queue=rx,indev=redirector0 "
+ "-object filter-redirector,id=qtest-f1,netdev=qtest-bn0,"
+ "queue=rx,outdev=redirector2 "
+ "-object filter-redirector,id=qtest-f2,netdev=qtest-bn0,"
+ "queue=rx,indev=redirector1 ", backend_sock[1], get_devstr(),
+ sock_path0, sock_path1, sock_path0);
+
+ struct iovec iov[] = {
+ {
+ .iov_base = &size,
+ .iov_len = sizeof(size),
+ }, {
+ .iov_base = send_buf,
+ .iov_len = sizeof(send_buf),
+ },
+ };
+
+ send_sock = unix_connect(sock_path1, NULL);
+ g_assert_cmpint(send_sock, !=, -1);
+ /* send a qmp command to guarantee that 'connected' is setting to true. */
+ qmp_discard_response(qts, "{ 'execute' : 'query-status'}");
+
+ ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf));
+ g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
+
+ ret = qemu_recv(backend_sock[0], &len, sizeof(len), 0);
+ g_assert_cmpint(ret, ==, sizeof(len));
+ len = ntohl(len);
+
+ g_assert_cmpint(len, ==, sizeof(send_buf));
+ recv_buf = g_malloc(len);
+ ret = qemu_recv(backend_sock[0], recv_buf, len, 0);
+ g_assert_cmpstr(recv_buf, ==, send_buf);
+
+ close(send_sock);
+ g_free(recv_buf);
+ unlink(sock_path0);
+ unlink(sock_path1);
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/netfilter/redirector_tx", test_redirector_tx);
+ qtest_add_func("/netfilter/redirector_rx", test_redirector_rx);
+ return g_test_run();
+}
diff --git a/tests/qtest/test-hmp.c b/tests/qtest/test-hmp.c
new file mode 100644
index 0000000000..5029c4d2c9
--- /dev/null
+++ b/tests/qtest/test-hmp.c
@@ -0,0 +1,171 @@
+/*
+ * Test HMP commands.
+ *
+ * Copyright (c) 2017 Red Hat Inc.
+ *
+ * Author:
+ * Thomas Huth <thuth@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2
+ * or later. See the COPYING file in the top-level directory.
+ *
+ * This test calls some HMP commands for all machines that the current
+ * QEMU binary provides, to check whether they terminate successfully
+ * (i.e. do not crash QEMU).
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+static int verbose;
+
+static const char *hmp_cmds[] = {
+ "announce_self",
+ "boot_set ndc",
+ "chardev-add null,id=testchardev1",
+ "chardev-send-break testchardev1",
+ "chardev-change testchardev1 ringbuf",
+ "chardev-remove testchardev1",
+ "commit all",
+ "cpu-add 1",
+ "cpu 0",
+ "device_add ?",
+ "device_add usb-mouse,id=mouse1",
+ "drive_add ignored format=help",
+ "mouse_button 7",
+ "mouse_move 10 10",
+ "mouse_button 0",
+ "device_del mouse1",
+ "dump-guest-memory /dev/null 0 4096",
+ "dump-guest-memory /dev/null",
+ "gdbserver",
+ "gva2gpa 0",
+ "hostfwd_add tcp::43210-:43210",
+ "hostfwd_remove tcp::43210-:43210",
+ "i /w 0",
+ "log all",
+ "log none",
+ "memsave 0 4096 \"/dev/null\"",
+ "migrate_set_cache_size 1",
+ "migrate_set_downtime 1",
+ "migrate_set_speed 1",
+ "netdev_add user,id=net1",
+ "set_link net1 off",
+ "set_link net1 on",
+ "netdev_del net1",
+ "nmi",
+ "o /w 0 0x1234",
+ "object_add memory-backend-ram,id=mem1,size=256M",
+ "object_del mem1",
+ "pmemsave 0 4096 \"/dev/null\"",
+ "p $pc + 8",
+ "qom-list /",
+ "qom-set /machine initrd test",
+ "screendump /dev/null",
+ "sendkey x",
+ "singlestep on",
+ "wavcapture /dev/null",
+ "stopcapture 0",
+ "sum 0 512",
+ "x /8i 0x100",
+ "xp /16x 0",
+ NULL
+};
+
+/* Run through the list of pre-defined commands */
+static void test_commands(QTestState *qts)
+{
+ char *response;
+ int i;
+
+ for (i = 0; hmp_cmds[i] != NULL; i++) {
+ response = qtest_hmp(qts, "%s", hmp_cmds[i]);
+ if (verbose) {
+ fprintf(stderr,
+ "\texecute HMP command: %s\n"
+ "\tresult : %s\n",
+ hmp_cmds[i], response);
+ }
+ g_free(response);
+ }
+
+}
+
+/* Run through all info commands and call them blindly (without arguments) */
+static void test_info_commands(QTestState *qts)
+{
+ char *resp, *info, *info_buf, *endp;
+
+ info_buf = info = qtest_hmp(qts, "help info");
+
+ while (*info) {
+ /* Extract the info command, ignore parameters and description */
+ g_assert(strncmp(info, "info ", 5) == 0);
+ endp = strchr(&info[5], ' ');
+ g_assert(endp != NULL);
+ *endp = '\0';
+ /* Now run the info command */
+ if (verbose) {
+ fprintf(stderr, "\t%s\n", info);
+ }
+ resp = qtest_hmp(qts, "%s", info);
+ g_free(resp);
+ /* And move forward to the next line */
+ info = strchr(endp + 1, '\n');
+ if (!info) {
+ break;
+ }
+ info += 1;
+ }
+
+ g_free(info_buf);
+}
+
+static void test_machine(gconstpointer data)
+{
+ const char *machine = data;
+ char *args;
+ QTestState *qts;
+
+ args = g_strdup_printf("-S -M %s", machine);
+ qts = qtest_init(args);
+
+ test_info_commands(qts);
+ test_commands(qts);
+
+ qtest_quit(qts);
+ g_free(args);
+ g_free((void *)data);
+}
+
+static void add_machine_test_case(const char *mname)
+{
+ char *path;
+
+ /* Ignore blacklisted machines that have known problems */
+ if (!strcmp("xenfv", mname) || !strcmp("xenpv", mname)) {
+ return;
+ }
+
+ path = g_strdup_printf("hmp/%s", mname);
+ qtest_add_data_func(path, g_strdup(mname), test_machine);
+ g_free(path);
+}
+
+int main(int argc, char **argv)
+{
+ char *v_env = getenv("V");
+
+ if (v_env && *v_env >= '2') {
+ verbose = true;
+ }
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_cb_for_every_machine(add_machine_test_case, g_test_quick());
+
+ /* as none machine has no memory by default, add a test case with memory */
+ qtest_add_data_func("hmp/none+2MB", g_strdup("none -m 2"), test_machine);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/test-netfilter.c b/tests/qtest/test-netfilter.c
new file mode 100644
index 0000000000..22927ee6ab
--- /dev/null
+++ b/tests/qtest/test-netfilter.c
@@ -0,0 +1,210 @@
+/*
+ * QTest testcase for netfilter
+ *
+ * Copyright (c) 2015 FUJITSU LIMITED
+ * Author: Yang Hongyang <yanghy@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * later. See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qapi/qmp/qdict.h"
+
+/* add a netfilter to a netdev and then remove it */
+static void add_one_netfilter(void)
+{
+ QDict *response;
+
+ response = qmp("{'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'filter-buffer',"
+ " 'id': 'qtest-f0',"
+ " 'props': {"
+ " 'netdev': 'qtest-bn0',"
+ " 'queue': 'rx',"
+ " 'interval': 1000"
+ "}}}");
+
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'object-del',"
+ " 'arguments': {"
+ " 'id': 'qtest-f0'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+}
+
+/* add a netfilter to a netdev and then remove the netdev */
+static void remove_netdev_with_one_netfilter(void)
+{
+ QDict *response;
+
+ response = qmp("{'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'filter-buffer',"
+ " 'id': 'qtest-f0',"
+ " 'props': {"
+ " 'netdev': 'qtest-bn0',"
+ " 'queue': 'rx',"
+ " 'interval': 1000"
+ "}}}");
+
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'netdev_del',"
+ " 'arguments': {"
+ " 'id': 'qtest-bn0'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ /* add back the netdev */
+ response = qmp("{'execute': 'netdev_add',"
+ " 'arguments': {"
+ " 'type': 'user',"
+ " 'id': 'qtest-bn0'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+}
+
+/* add multi(2) netfilters to a netdev and then remove them */
+static void add_multi_netfilter(void)
+{
+ QDict *response;
+
+ response = qmp("{'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'filter-buffer',"
+ " 'id': 'qtest-f0',"
+ " 'props': {"
+ " 'netdev': 'qtest-bn0',"
+ " 'queue': 'rx',"
+ " 'interval': 1000"
+ "}}}");
+
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'filter-buffer',"
+ " 'id': 'qtest-f1',"
+ " 'props': {"
+ " 'netdev': 'qtest-bn0',"
+ " 'queue': 'rx',"
+ " 'interval': 1000"
+ "}}}");
+
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'object-del',"
+ " 'arguments': {"
+ " 'id': 'qtest-f0'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'object-del',"
+ " 'arguments': {"
+ " 'id': 'qtest-f1'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+}
+
+/* add multi(2) netfilters to a netdev and then remove the netdev */
+static void remove_netdev_with_multi_netfilter(void)
+{
+ QDict *response;
+
+ response = qmp("{'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'filter-buffer',"
+ " 'id': 'qtest-f0',"
+ " 'props': {"
+ " 'netdev': 'qtest-bn0',"
+ " 'queue': 'rx',"
+ " 'interval': 1000"
+ "}}}");
+
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'object-add',"
+ " 'arguments': {"
+ " 'qom-type': 'filter-buffer',"
+ " 'id': 'qtest-f1',"
+ " 'props': {"
+ " 'netdev': 'qtest-bn0',"
+ " 'queue': 'rx',"
+ " 'interval': 1000"
+ "}}}");
+
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ response = qmp("{'execute': 'netdev_del',"
+ " 'arguments': {"
+ " 'id': 'qtest-bn0'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+
+ /* add back the netdev */
+ response = qmp("{'execute': 'netdev_add',"
+ " 'arguments': {"
+ " 'type': 'user',"
+ " 'id': 'qtest-bn0'"
+ "}}");
+ g_assert(response);
+ g_assert(!qdict_haskey(response, "error"));
+ qobject_unref(response);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ char *args;
+ const char *devstr = "e1000";
+
+ if (g_str_equal(qtest_get_arch(), "s390x")) {
+ devstr = "virtio-net-ccw";
+ }
+
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/netfilter/addremove_one", add_one_netfilter);
+ qtest_add_func("/netfilter/remove_netdev_one",
+ remove_netdev_with_one_netfilter);
+ qtest_add_func("/netfilter/addremove_multi", add_multi_netfilter);
+ qtest_add_func("/netfilter/remove_netdev_multi",
+ remove_netdev_with_multi_netfilter);
+
+ args = g_strdup_printf("-netdev user,id=qtest-bn0 "
+ "-device %s,netdev=qtest-bn0", devstr);
+ qtest_start(args);
+ ret = g_test_run();
+
+ qtest_end();
+ g_free(args);
+
+ return ret;
+}
diff --git a/tests/qtest/test-x86-cpuid-compat.c b/tests/qtest/test-x86-cpuid-compat.c
new file mode 100644
index 0000000000..772287bdb4
--- /dev/null
+++ b/tests/qtest/test-x86-cpuid-compat.c
@@ -0,0 +1,381 @@
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qlist.h"
+#include "qapi/qmp/qnum.h"
+#include "qapi/qmp/qbool.h"
+#include "libqtest-single.h"
+
+static char *get_cpu0_qom_path(void)
+{
+ QDict *resp;
+ QList *ret;
+ QDict *cpu0;
+ char *path;
+
+ resp = qmp("{'execute': 'query-cpus', 'arguments': {}}");
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_qlist(resp, "return");
+
+ cpu0 = qobject_to(QDict, qlist_peek(ret));
+ path = g_strdup(qdict_get_str(cpu0, "qom_path"));
+ qobject_unref(resp);
+ return path;
+}
+
+static QObject *qom_get(const char *path, const char *prop)
+{
+ QDict *resp = qmp("{ 'execute': 'qom-get',"
+ " 'arguments': { 'path': %s,"
+ " 'property': %s } }",
+ path, prop);
+ QObject *ret = qdict_get(resp, "return");
+ qobject_ref(ret);
+ qobject_unref(resp);
+ return ret;
+}
+
+static bool qom_get_bool(const char *path, const char *prop)
+{
+ QBool *value = qobject_to(QBool, qom_get(path, prop));
+ bool b = qbool_get_bool(value);
+
+ qobject_unref(value);
+ return b;
+}
+
+typedef struct CpuidTestArgs {
+ const char *cmdline;
+ const char *property;
+ int64_t expected_value;
+} CpuidTestArgs;
+
+static void test_cpuid_prop(const void *data)
+{
+ const CpuidTestArgs *args = data;
+ char *path;
+ QNum *value;
+ int64_t val;
+
+ qtest_start(args->cmdline);
+ path = get_cpu0_qom_path();
+ value = qobject_to(QNum, qom_get(path, args->property));
+ g_assert(qnum_get_try_int(value, &val));
+ g_assert_cmpint(val, ==, args->expected_value);
+ qtest_end();
+
+ qobject_unref(value);
+ g_free(path);
+}
+
+static void add_cpuid_test(const char *name, const char *cmdline,
+ const char *property, int64_t expected_value)
+{
+ CpuidTestArgs *args = g_new0(CpuidTestArgs, 1);
+ args->cmdline = cmdline;
+ args->property = property;
+ args->expected_value = expected_value;
+ qtest_add_data_func(name, args, test_cpuid_prop);
+}
+
+
+/* Parameters to a add_feature_test() test case */
+typedef struct FeatureTestArgs {
+ /* cmdline to start QEMU */
+ const char *cmdline;
+ /*
+ * cpuid-input-eax and cpuid-input-ecx values to look for,
+ * in "feature-words" and "filtered-features" properties.
+ */
+ uint32_t in_eax, in_ecx;
+ /* The register name to look for, in the X86CPUFeatureWordInfo array */
+ const char *reg;
+ /* The bit to check in X86CPUFeatureWordInfo.features */
+ int bitnr;
+ /* The expected value for the bit in (X86CPUFeatureWordInfo.features) */
+ bool expected_value;
+} FeatureTestArgs;
+
+/* Get the value for a feature word in a X86CPUFeatureWordInfo list */
+static uint32_t get_feature_word(QList *features, uint32_t eax, uint32_t ecx,
+ const char *reg)
+{
+ const QListEntry *e;
+
+ for (e = qlist_first(features); e; e = qlist_next(e)) {
+ QDict *w = qobject_to(QDict, qlist_entry_obj(e));
+ const char *rreg = qdict_get_str(w, "cpuid-register");
+ uint32_t reax = qdict_get_int(w, "cpuid-input-eax");
+ bool has_ecx = qdict_haskey(w, "cpuid-input-ecx");
+ uint32_t recx = 0;
+ int64_t val;
+
+ if (has_ecx) {
+ recx = qdict_get_int(w, "cpuid-input-ecx");
+ }
+ if (eax == reax && (!has_ecx || ecx == recx) && !strcmp(rreg, reg)) {
+ g_assert(qnum_get_try_int(qobject_to(QNum,
+ qdict_get(w, "features")),
+ &val));
+ return val;
+ }
+ }
+ return 0;
+}
+
+static void test_feature_flag(const void *data)
+{
+ const FeatureTestArgs *args = data;
+ char *path;
+ QList *present, *filtered;
+ uint32_t value;
+
+ qtest_start(args->cmdline);
+ path = get_cpu0_qom_path();
+ present = qobject_to(QList, qom_get(path, "feature-words"));
+ filtered = qobject_to(QList, qom_get(path, "filtered-features"));
+ value = get_feature_word(present, args->in_eax, args->in_ecx, args->reg);
+ value |= get_feature_word(filtered, args->in_eax, args->in_ecx, args->reg);
+ qtest_end();
+
+ g_assert(!!(value & (1U << args->bitnr)) == args->expected_value);
+
+ qobject_unref(present);
+ qobject_unref(filtered);
+ g_free(path);
+}
+
+/*
+ * Add test case to ensure that a given feature flag is set in
+ * either "feature-words" or "filtered-features", when running QEMU
+ * using cmdline
+ */
+static FeatureTestArgs *add_feature_test(const char *name, const char *cmdline,
+ uint32_t eax, uint32_t ecx,
+ const char *reg, int bitnr,
+ bool expected_value)
+{
+ FeatureTestArgs *args = g_new0(FeatureTestArgs, 1);
+ args->cmdline = cmdline;
+ args->in_eax = eax;
+ args->in_ecx = ecx;
+ args->reg = reg;
+ args->bitnr = bitnr;
+ args->expected_value = expected_value;
+ qtest_add_data_func(name, args, test_feature_flag);
+ return args;
+}
+
+static void test_plus_minus_subprocess(void)
+{
+ char *path;
+
+ /* Rules:
+ * 1)"-foo" overrides "+foo"
+ * 2) "[+-]foo" overrides "foo=..."
+ * 3) Old feature names with underscores (e.g. "sse4_2")
+ * should keep working
+ *
+ * Note: rules 1 and 2 are planned to be removed soon, and
+ * should generate a warning.
+ */
+ qtest_start("-cpu pentium,-fpu,+fpu,-mce,mce=on,+cx8,cx8=off,+sse4_1,sse4_2=on");
+ path = get_cpu0_qom_path();
+
+ g_assert_false(qom_get_bool(path, "fpu"));
+ g_assert_false(qom_get_bool(path, "mce"));
+ g_assert_true(qom_get_bool(path, "cx8"));
+
+ /* Test both the original and the alias feature names: */
+ g_assert_true(qom_get_bool(path, "sse4-1"));
+ g_assert_true(qom_get_bool(path, "sse4.1"));
+
+ g_assert_true(qom_get_bool(path, "sse4-2"));
+ g_assert_true(qom_get_bool(path, "sse4.2"));
+
+ qtest_end();
+ g_free(path);
+}
+
+static void test_plus_minus(void)
+{
+ g_test_trap_subprocess("/x86/cpuid/parsing-plus-minus/subprocess", 0, 0);
+ g_test_trap_assert_passed();
+ g_test_trap_assert_stderr("*Ambiguous CPU model string. "
+ "Don't mix both \"-mce\" and \"mce=on\"*");
+ g_test_trap_assert_stderr("*Ambiguous CPU model string. "
+ "Don't mix both \"+cx8\" and \"cx8=off\"*");
+ g_test_trap_assert_stdout("");
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/x86/cpuid/parsing-plus-minus/subprocess",
+ test_plus_minus_subprocess);
+ g_test_add_func("/x86/cpuid/parsing-plus-minus", test_plus_minus);
+
+ /* Original level values for CPU models: */
+ add_cpuid_test("x86/cpuid/phenom/level",
+ "-cpu phenom", "level", 5);
+ add_cpuid_test("x86/cpuid/Conroe/level",
+ "-cpu Conroe", "level", 10);
+ add_cpuid_test("x86/cpuid/SandyBridge/level",
+ "-cpu SandyBridge", "level", 0xd);
+ add_cpuid_test("x86/cpuid/486/xlevel",
+ "-cpu 486", "xlevel", 0);
+ add_cpuid_test("x86/cpuid/core2duo/xlevel",
+ "-cpu core2duo", "xlevel", 0x80000008);
+ add_cpuid_test("x86/cpuid/phenom/xlevel",
+ "-cpu phenom", "xlevel", 0x8000001A);
+ add_cpuid_test("x86/cpuid/athlon/xlevel",
+ "-cpu athlon", "xlevel", 0x80000008);
+
+ /* If level is not large enough, it should increase automatically: */
+ /* CPUID[6].EAX: */
+ add_cpuid_test("x86/cpuid/auto-level/phenom/arat",
+ "-cpu 486,+arat", "level", 6);
+ /* CPUID[EAX=7,ECX=0].EBX: */
+ add_cpuid_test("x86/cpuid/auto-level/phenom/fsgsbase",
+ "-cpu phenom,+fsgsbase", "level", 7);
+ /* CPUID[EAX=7,ECX=0].ECX: */
+ add_cpuid_test("x86/cpuid/auto-level/phenom/avx512vbmi",
+ "-cpu phenom,+avx512vbmi", "level", 7);
+ /* CPUID[EAX=0xd,ECX=1].EAX: */
+ add_cpuid_test("x86/cpuid/auto-level/phenom/xsaveopt",
+ "-cpu phenom,+xsaveopt", "level", 0xd);
+ /* CPUID[8000_0001].EDX: */
+ add_cpuid_test("x86/cpuid/auto-xlevel/486/3dnow",
+ "-cpu 486,+3dnow", "xlevel", 0x80000001);
+ /* CPUID[8000_0001].ECX: */
+ add_cpuid_test("x86/cpuid/auto-xlevel/486/sse4a",
+ "-cpu 486,+sse4a", "xlevel", 0x80000001);
+ /* CPUID[8000_0007].EDX: */
+ add_cpuid_test("x86/cpuid/auto-xlevel/486/invtsc",
+ "-cpu 486,+invtsc", "xlevel", 0x80000007);
+ /* CPUID[8000_000A].EDX: */
+ add_cpuid_test("x86/cpuid/auto-xlevel/486/npt",
+ "-cpu 486,+npt", "xlevel", 0x8000000A);
+ /* CPUID[C000_0001].EDX: */
+ add_cpuid_test("x86/cpuid/auto-xlevel2/phenom/xstore",
+ "-cpu phenom,+xstore", "xlevel2", 0xC0000001);
+ /* SVM needs CPUID[0x8000000A] */
+ add_cpuid_test("x86/cpuid/auto-xlevel/athlon/svm",
+ "-cpu athlon,+svm", "xlevel", 0x8000000A);
+
+
+ /* If level is already large enough, it shouldn't change: */
+ add_cpuid_test("x86/cpuid/auto-level/SandyBridge/multiple",
+ "-cpu SandyBridge,+arat,+fsgsbase,+avx512vbmi",
+ "level", 0xd);
+ /* If level is explicitly set, it shouldn't change: */
+ add_cpuid_test("x86/cpuid/auto-level/486/fixed/0xF",
+ "-cpu 486,level=0xF,+arat,+fsgsbase,+avx512vbmi,+xsaveopt",
+ "level", 0xF);
+ add_cpuid_test("x86/cpuid/auto-level/486/fixed/2",
+ "-cpu 486,level=2,+arat,+fsgsbase,+avx512vbmi,+xsaveopt",
+ "level", 2);
+ add_cpuid_test("x86/cpuid/auto-level/486/fixed/0",
+ "-cpu 486,level=0,+arat,+fsgsbase,+avx512vbmi,+xsaveopt",
+ "level", 0);
+
+ /* if xlevel is already large enough, it shouldn't change: */
+ add_cpuid_test("x86/cpuid/auto-xlevel/phenom/3dnow",
+ "-cpu phenom,+3dnow,+sse4a,+invtsc,+npt,+svm",
+ "xlevel", 0x8000001A);
+ /* If xlevel is explicitly set, it shouldn't change: */
+ add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/80000002",
+ "-cpu 486,xlevel=0x80000002,+3dnow,+sse4a,+invtsc,+npt,+svm",
+ "xlevel", 0x80000002);
+ add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/8000001A",
+ "-cpu 486,xlevel=0x8000001A,+3dnow,+sse4a,+invtsc,+npt,+svm",
+ "xlevel", 0x8000001A);
+ add_cpuid_test("x86/cpuid/auto-xlevel/phenom/fixed/0",
+ "-cpu 486,xlevel=0,+3dnow,+sse4a,+invtsc,+npt,+svm",
+ "xlevel", 0);
+
+ /* if xlevel2 is already large enough, it shouldn't change: */
+ add_cpuid_test("x86/cpuid/auto-xlevel2/486/fixed",
+ "-cpu 486,xlevel2=0xC0000002,+xstore",
+ "xlevel2", 0xC0000002);
+
+ /* Check compatibility of old machine-types that didn't
+ * auto-increase level/xlevel/xlevel2: */
+
+ add_cpuid_test("x86/cpuid/auto-level/pc-2.7",
+ "-machine pc-i440fx-2.7 -cpu 486,+arat,+avx512vbmi,+xsaveopt",
+ "level", 1);
+ add_cpuid_test("x86/cpuid/auto-xlevel/pc-2.7",
+ "-machine pc-i440fx-2.7 -cpu 486,+3dnow,+sse4a,+invtsc,+npt,+svm",
+ "xlevel", 0);
+ add_cpuid_test("x86/cpuid/auto-xlevel2/pc-2.7",
+ "-machine pc-i440fx-2.7 -cpu 486,+xstore",
+ "xlevel2", 0);
+ /*
+ * QEMU 1.4.0 had auto-level enabled for CPUID[7], already,
+ * and the compat code that sets default level shouldn't
+ * disable the auto-level=7 code:
+ */
+ add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-1.4/off",
+ "-machine pc-i440fx-1.4 -cpu Nehalem",
+ "level", 2);
+ add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-1.5/on",
+ "-machine pc-i440fx-1.4 -cpu Nehalem,+smap",
+ "level", 7);
+ add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/off",
+ "-machine pc-i440fx-2.3 -cpu Penryn",
+ "level", 4);
+ add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/on",
+ "-machine pc-i440fx-2.3 -cpu Penryn,+erms",
+ "level", 7);
+ add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/off",
+ "-machine pc-i440fx-2.9 -cpu Conroe",
+ "level", 10);
+ add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/on",
+ "-machine pc-i440fx-2.9 -cpu Conroe,+erms",
+ "level", 10);
+
+ /*
+ * xlevel doesn't have any feature that triggers auto-level
+ * code on old machine-types. Just check that the compat code
+ * is working correctly:
+ */
+ add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.3",
+ "-machine pc-i440fx-2.3 -cpu SandyBridge",
+ "xlevel", 0x8000000a);
+ add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-off",
+ "-machine pc-i440fx-2.4 -cpu SandyBridge,",
+ "xlevel", 0x80000008);
+ add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-on",
+ "-machine pc-i440fx-2.4 -cpu SandyBridge,+npt",
+ "xlevel", 0x80000008);
+
+ /* Test feature parsing */
+ add_feature_test("x86/cpuid/features/plus",
+ "-cpu 486,+arat",
+ 6, 0, "EAX", 2, true);
+ add_feature_test("x86/cpuid/features/minus",
+ "-cpu pentium,-mmx",
+ 1, 0, "EDX", 23, false);
+ add_feature_test("x86/cpuid/features/on",
+ "-cpu 486,arat=on",
+ 6, 0, "EAX", 2, true);
+ add_feature_test("x86/cpuid/features/off",
+ "-cpu pentium,mmx=off",
+ 1, 0, "EDX", 23, false);
+ add_feature_test("x86/cpuid/features/max-plus-invtsc",
+ "-cpu max,+invtsc",
+ 0x80000007, 0, "EDX", 8, true);
+ add_feature_test("x86/cpuid/features/max-invtsc-on",
+ "-cpu max,invtsc=on",
+ 0x80000007, 0, "EDX", 8, true);
+ add_feature_test("x86/cpuid/features/max-minus-mmx",
+ "-cpu max,-mmx",
+ 1, 0, "EDX", 23, false);
+ add_feature_test("x86/cpuid/features/max-invtsc-on,mmx=off",
+ "-cpu max,mmx=off",
+ 1, 0, "EDX", 23, false);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/tmp105-test.c b/tests/qtest/tmp105-test.c
new file mode 100644
index 0000000000..f930a96b83
--- /dev/null
+++ b/tests/qtest/tmp105-test.c
@@ -0,0 +1,120 @@
+/*
+ * QTest testcase for the TMP105 temperature sensor
+ *
+ * Copyright (c) 2012 Andreas Färber
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "qapi/qmp/qdict.h"
+#include "hw/misc/tmp105_regs.h"
+
+#define TMP105_TEST_ID "tmp105-test"
+#define TMP105_TEST_ADDR 0x49
+
+static int qmp_tmp105_get_temperature(const char *id)
+{
+ QDict *response;
+ int ret;
+
+ response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+ "'property': 'temperature' } }", id);
+ g_assert(qdict_haskey(response, "return"));
+ ret = qdict_get_int(response, "return");
+ qobject_unref(response);
+ return ret;
+}
+
+static void qmp_tmp105_set_temperature(const char *id, int value)
+{
+ QDict *response;
+
+ response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+ "'property': 'temperature', 'value': %d } }", id, value);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+#define TMP105_PRECISION (1000/16)
+static void send_and_receive(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ value = qmp_tmp105_get_temperature(TMP105_TEST_ID);
+ g_assert_cmpuint(value, ==, 0);
+
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0);
+
+ qmp_tmp105_set_temperature(TMP105_TEST_ID, 20000);
+ value = qmp_tmp105_get_temperature(TMP105_TEST_ID);
+ g_assert_cmpuint(value, ==, 20000);
+
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0x1400);
+
+ qmp_tmp105_set_temperature(TMP105_TEST_ID, 20938); /* 20 + 15/16 */
+ value = qmp_tmp105_get_temperature(TMP105_TEST_ID);
+ g_assert_cmpuint(value, >=, 20938 - TMP105_PRECISION/2);
+ g_assert_cmpuint(value, <, 20938 + TMP105_PRECISION/2);
+
+ /* Set config */
+ i2c_set8(i2cdev, TMP105_REG_CONFIG, 0x60);
+ value = i2c_get8(i2cdev, TMP105_REG_CONFIG);
+ g_assert_cmphex(value, ==, 0x60);
+
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0x14f0);
+
+ /* Set precision to 9, 10, 11 bits. */
+ i2c_set8(i2cdev, TMP105_REG_CONFIG, 0x00);
+ g_assert_cmphex(i2c_get8(i2cdev, TMP105_REG_CONFIG), ==, 0x00);
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0x1480);
+
+ i2c_set8(i2cdev, TMP105_REG_CONFIG, 0x20);
+ g_assert_cmphex(i2c_get8(i2cdev, TMP105_REG_CONFIG), ==, 0x20);
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0x14c0);
+
+ i2c_set8(i2cdev, TMP105_REG_CONFIG, 0x40);
+ g_assert_cmphex(i2c_get8(i2cdev, TMP105_REG_CONFIG), ==, 0x40);
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0x14e0);
+
+ /* stored precision remains the same */
+ value = qmp_tmp105_get_temperature(TMP105_TEST_ID);
+ g_assert_cmpuint(value, >=, 20938 - TMP105_PRECISION/2);
+ g_assert_cmpuint(value, <, 20938 + TMP105_PRECISION/2);
+
+ i2c_set8(i2cdev, TMP105_REG_CONFIG, 0x60);
+ g_assert_cmphex(i2c_get8(i2cdev, TMP105_REG_CONFIG), ==, 0x60);
+ value = i2c_get16(i2cdev, TMP105_REG_TEMPERATURE);
+ g_assert_cmphex(value, ==, 0x14f0);
+
+ i2c_set16(i2cdev, TMP105_REG_T_LOW, 0x1234);
+ g_assert_cmphex(i2c_get16(i2cdev, TMP105_REG_T_LOW), ==, 0x1234);
+ i2c_set16(i2cdev, TMP105_REG_T_HIGH, 0x4231);
+ g_assert_cmphex(i2c_get16(i2cdev, TMP105_REG_T_HIGH), ==, 0x4231);
+}
+
+static void tmp105_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" TMP105_TEST_ID ",address=0x49"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { 0x49 });
+
+ qos_node_create_driver("tmp105", i2c_device_create);
+ qos_node_consumes("tmp105", "i2c-bus", &opts);
+
+ qos_add_test("tx-rx", "tmp105", send_and_receive, NULL);
+}
+libqos_init(tmp105_register_nodes);
diff --git a/tests/qtest/tpm-crb-swtpm-test.c b/tests/qtest/tpm-crb-swtpm-test.c
new file mode 100644
index 0000000000..2c4fb8ae29
--- /dev/null
+++ b/tests/qtest/tpm-crb-swtpm-test.c
@@ -0,0 +1,67 @@
+/*
+ * QTest testcase for TPM CRB talking to external swtpm and swtpm migration
+ *
+ * Copyright (c) 2018 IBM Corporation
+ * with parts borrowed from migration-test.c that is:
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "tpm-tests.h"
+
+typedef struct TestState {
+ char *src_tpm_path;
+ char *dst_tpm_path;
+ char *uri;
+} TestState;
+
+static void tpm_crb_swtpm_test(const void *data)
+{
+ const TestState *ts = data;
+
+ tpm_test_swtpm_test(ts->src_tpm_path, tpm_util_crb_transfer, "tpm-crb");
+}
+
+static void tpm_crb_swtpm_migration_test(const void *data)
+{
+ const TestState *ts = data;
+
+ tpm_test_swtpm_migration_test(ts->src_tpm_path, ts->dst_tpm_path, ts->uri,
+ tpm_util_crb_transfer, "tpm-crb");
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ TestState ts = { 0 };
+
+ ts.src_tpm_path = g_dir_make_tmp("qemu-tpm-crb-swtpm-test.XXXXXX", NULL);
+ ts.dst_tpm_path = g_dir_make_tmp("qemu-tpm-crb-swtpm-test.XXXXXX", NULL);
+ ts.uri = g_strdup_printf("unix:%s/migsocket", ts.src_tpm_path);
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/tpm/crb-swtpm/test", &ts, tpm_crb_swtpm_test);
+ qtest_add_data_func("/tpm/crb-swtpm-migration/test", &ts,
+ tpm_crb_swtpm_migration_test);
+ ret = g_test_run();
+
+ g_rmdir(ts.dst_tpm_path);
+ g_free(ts.dst_tpm_path);
+ g_rmdir(ts.src_tpm_path);
+ g_free(ts.src_tpm_path);
+ g_free(ts.uri);
+
+ return ret;
+}
diff --git a/tests/qtest/tpm-crb-test.c b/tests/qtest/tpm-crb-test.c
new file mode 100644
index 0000000000..632fb7fbd8
--- /dev/null
+++ b/tests/qtest/tpm-crb-test.c
@@ -0,0 +1,179 @@
+/*
+ * QTest testcase for TPM CRB
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "hw/acpi/tpm.h"
+#include "io/channel-socket.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "tpm-emu.h"
+
+#define TPM_CMD "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00"
+
+static void tpm_crb_test(const void *data)
+{
+ const TestState *s = data;
+ uint32_t intfid = readl(TPM_CRB_ADDR_BASE + A_CRB_INTF_ID);
+ uint32_t csize = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE);
+ uint64_t caddr = readq(TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
+ uint32_t rsize = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_SIZE);
+ uint64_t raddr = readq(TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
+ uint8_t locstate = readb(TPM_CRB_ADDR_BASE + A_CRB_LOC_STATE);
+ uint32_t locctrl = readl(TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL);
+ uint32_t locsts = readl(TPM_CRB_ADDR_BASE + A_CRB_LOC_STS);
+ uint32_t sts = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceType), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceVersion), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapLocality), ==, 0);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapCRBIdleBypass), ==, 0);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapDataXferSizeSupport),
+ ==, 3);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapFIFO), ==, 0);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapCRB), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceSelector), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, RID), ==, 0);
+
+ g_assert_cmpint(csize, >=, 128);
+ g_assert_cmpint(rsize, >=, 128);
+ g_assert_cmpint(caddr, >, TPM_CRB_ADDR_BASE);
+ g_assert_cmpint(raddr, >, TPM_CRB_ADDR_BASE);
+
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, tpmEstablished), ==, 1);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, locAssigned), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, activeLocality), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, reserved), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, tpmRegValidSts), ==, 1);
+
+ g_assert_cmpint(locctrl, ==, 0);
+
+ g_assert_cmpint(FIELD_EX32(locsts, CRB_LOC_STS, Granted), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locsts, CRB_LOC_STS, beenSeized), ==, 0);
+
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmIdle), ==, 1);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmSts), ==, 0);
+
+ /* request access to locality 0 */
+ writeb(TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 1);
+
+ /* granted bit must be set now */
+ locsts = readl(TPM_CRB_ADDR_BASE + A_CRB_LOC_STS);
+ g_assert_cmpint(FIELD_EX32(locsts, CRB_LOC_STS, Granted), ==, 1);
+ g_assert_cmpint(FIELD_EX32(locsts, CRB_LOC_STS, beenSeized), ==, 0);
+
+ /* we must have an assigned locality */
+ locstate = readb(TPM_CRB_ADDR_BASE + A_CRB_LOC_STATE);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, tpmEstablished), ==, 1);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, locAssigned), ==, 1);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, activeLocality), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, reserved), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, tpmRegValidSts), ==, 1);
+
+ /* set into ready state */
+ writel(TPM_CRB_ADDR_BASE + A_CRB_CTRL_REQ, 1);
+
+ /* TPM must not be in the idle state */
+ sts = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmIdle), ==, 0);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmSts), ==, 0);
+
+ memwrite(caddr, TPM_CMD, sizeof(TPM_CMD));
+
+ uint32_t start = 1;
+ uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ writel(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
+ do {
+ start = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ if ((start & 1) == 0) {
+ break;
+ }
+ } while (g_get_monotonic_time() < end_time);
+ start = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ g_assert_cmpint(start & 1, ==, 0);
+
+ /* TPM must still not be in the idle state */
+ sts = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmIdle), ==, 0);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmSts), ==, 0);
+
+ struct tpm_hdr tpm_msg;
+ memread(raddr, &tpm_msg, sizeof(tpm_msg));
+ g_assert_cmpmem(&tpm_msg, sizeof(tpm_msg), s->tpm_msg, sizeof(*s->tpm_msg));
+
+ /* set TPM into idle state */
+ writel(TPM_CRB_ADDR_BASE + A_CRB_CTRL_REQ, 2);
+
+ /* idle state must be indicated now */
+ sts = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmIdle), ==, 1);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_CTRL_STS, tpmSts), ==, 0);
+
+ /* relinquish locality */
+ writel(TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 2);
+
+ /* Granted flag must be cleared */
+ sts = readl(TPM_CRB_ADDR_BASE + A_CRB_LOC_STS);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_LOC_STS, Granted), ==, 0);
+ g_assert_cmpint(FIELD_EX32(sts, CRB_LOC_STS, beenSeized), ==, 0);
+
+ /* no locality may be assigned */
+ locstate = readb(TPM_CRB_ADDR_BASE + A_CRB_LOC_STATE);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, tpmEstablished), ==, 1);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, locAssigned), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, activeLocality), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, reserved), ==, 0);
+ g_assert_cmpint(FIELD_EX32(locstate, CRB_LOC_STATE, tpmRegValidSts), ==, 1);
+
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ char *args, *tmp_path = g_dir_make_tmp("qemu-tpm-crb-test.XXXXXX", NULL);
+ GThread *thread;
+ TestState test;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ test.addr = g_new0(SocketAddress, 1);
+ test.addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL);
+ g_mutex_init(&test.data_mutex);
+ g_cond_init(&test.data_cond);
+ test.data_cond_signal = false;
+
+ thread = g_thread_new(NULL, tpm_emu_ctrl_thread, &test);
+ tpm_emu_test_wait_cond(&test);
+
+ args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device tpm-crb,tpmdev=dev",
+ test.addr->u.q_unix.path);
+ qtest_start(args);
+
+ qtest_add_data_func("/tpm-crb/test", &test, tpm_crb_test);
+ ret = g_test_run();
+
+ qtest_end();
+
+ g_thread_join(thread);
+ g_unlink(test.addr->u.q_unix.path);
+ qapi_free_SocketAddress(test.addr);
+ g_rmdir(tmp_path);
+ g_free(tmp_path);
+ g_free(args);
+ return ret;
+}
diff --git a/tests/qtest/tpm-emu.c b/tests/qtest/tpm-emu.c
new file mode 100644
index 0000000000..c43ac4aef8
--- /dev/null
+++ b/tests/qtest/tpm-emu.c
@@ -0,0 +1,183 @@
+/*
+ * Minimal TPM emulator for TPM test cases
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "hw/tpm/tpm_ioctl.h"
+#include "io/channel-socket.h"
+#include "qapi/error.h"
+#include "tpm-emu.h"
+
+void tpm_emu_test_wait_cond(TestState *s)
+{
+ gint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+
+ g_mutex_lock(&s->data_mutex);
+
+ if (!s->data_cond_signal &&
+ !g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+ g_assert_not_reached();
+ }
+
+ s->data_cond_signal = false;
+
+ g_mutex_unlock(&s->data_mutex);
+}
+
+static void *tpm_emu_tpm_thread(void *data)
+{
+ TestState *s = data;
+ QIOChannel *ioc = s->tpm_ioc;
+
+ s->tpm_msg = g_new(struct tpm_hdr, 1);
+ while (true) {
+ int minhlen = sizeof(s->tpm_msg->tag) + sizeof(s->tpm_msg->len);
+
+ if (!qio_channel_read(ioc, (char *)s->tpm_msg, minhlen, &error_abort)) {
+ break;
+ }
+ s->tpm_msg->tag = be16_to_cpu(s->tpm_msg->tag);
+ s->tpm_msg->len = be32_to_cpu(s->tpm_msg->len);
+ g_assert_cmpint(s->tpm_msg->len, >=, minhlen);
+ g_assert_cmpint(s->tpm_msg->tag, ==, TPM2_ST_NO_SESSIONS);
+
+ s->tpm_msg = g_realloc(s->tpm_msg, s->tpm_msg->len);
+ qio_channel_read(ioc, (char *)&s->tpm_msg->code,
+ s->tpm_msg->len - minhlen, &error_abort);
+ s->tpm_msg->code = be32_to_cpu(s->tpm_msg->code);
+
+ /* reply error */
+ s->tpm_msg->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
+ s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr));
+ s->tpm_msg->code = cpu_to_be32(TPM_RC_FAILURE);
+ qio_channel_write(ioc, (char *)s->tpm_msg, be32_to_cpu(s->tpm_msg->len),
+ &error_abort);
+ }
+
+ g_free(s->tpm_msg);
+ s->tpm_msg = NULL;
+ object_unref(OBJECT(s->tpm_ioc));
+ return NULL;
+}
+
+void *tpm_emu_ctrl_thread(void *data)
+{
+ TestState *s = data;
+ QIOChannelSocket *lioc = qio_channel_socket_new();
+ QIOChannel *ioc;
+
+ qio_channel_socket_listen_sync(lioc, s->addr, 1, &error_abort);
+
+ g_mutex_lock(&s->data_mutex);
+ s->data_cond_signal = true;
+ g_mutex_unlock(&s->data_mutex);
+ g_cond_signal(&s->data_cond);
+
+ qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN);
+ ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+ g_assert(ioc);
+
+ {
+ uint32_t cmd = 0;
+ struct iovec iov = { .iov_base = &cmd, .iov_len = sizeof(cmd) };
+ int *pfd = NULL;
+ size_t nfd = 0;
+
+ qio_channel_readv_full(ioc, &iov, 1, &pfd, &nfd, &error_abort);
+ cmd = be32_to_cpu(cmd);
+ g_assert_cmpint(cmd, ==, CMD_SET_DATAFD);
+ g_assert_cmpint(nfd, ==, 1);
+ s->tpm_ioc = QIO_CHANNEL(qio_channel_socket_new_fd(*pfd, &error_abort));
+ g_free(pfd);
+
+ cmd = 0;
+ qio_channel_write(ioc, (char *)&cmd, sizeof(cmd), &error_abort);
+
+ s->emu_tpm_thread = g_thread_new(NULL, tpm_emu_tpm_thread, s);
+ }
+
+ while (true) {
+ uint32_t cmd;
+ ssize_t ret;
+
+ ret = qio_channel_read(ioc, (char *)&cmd, sizeof(cmd), NULL);
+ if (ret <= 0) {
+ break;
+ }
+
+ cmd = be32_to_cpu(cmd);
+ switch (cmd) {
+ case CMD_GET_CAPABILITY: {
+ ptm_cap cap = cpu_to_be64(0x3fff);
+ qio_channel_write(ioc, (char *)&cap, sizeof(cap), &error_abort);
+ break;
+ }
+ case CMD_INIT: {
+ ptm_init init;
+ qio_channel_read(ioc, (char *)&init.u.req, sizeof(init.u.req),
+ &error_abort);
+ init.u.resp.tpm_result = 0;
+ qio_channel_write(ioc, (char *)&init.u.resp, sizeof(init.u.resp),
+ &error_abort);
+ break;
+ }
+ case CMD_SHUTDOWN: {
+ ptm_res res = 0;
+ qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort);
+ /* the tpm data thread is expected to finish now */
+ g_thread_join(s->emu_tpm_thread);
+ break;
+ }
+ case CMD_STOP: {
+ ptm_res res = 0;
+ qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort);
+ break;
+ }
+ case CMD_SET_BUFFERSIZE: {
+ ptm_setbuffersize sbs;
+ qio_channel_read(ioc, (char *)&sbs.u.req, sizeof(sbs.u.req),
+ &error_abort);
+ sbs.u.resp.buffersize = sbs.u.req.buffersize ?: cpu_to_be32(4096);
+ sbs.u.resp.tpm_result = 0;
+ sbs.u.resp.minsize = cpu_to_be32(128);
+ sbs.u.resp.maxsize = cpu_to_be32(4096);
+ qio_channel_write(ioc, (char *)&sbs.u.resp, sizeof(sbs.u.resp),
+ &error_abort);
+ break;
+ }
+ case CMD_SET_LOCALITY: {
+ ptm_loc loc;
+ /* Note: this time it's not u.req / u.resp... */
+ qio_channel_read(ioc, (char *)&loc, sizeof(loc), &error_abort);
+ g_assert_cmpint(loc.u.req.loc, ==, 0);
+ loc.u.resp.tpm_result = 0;
+ qio_channel_write(ioc, (char *)&loc, sizeof(loc), &error_abort);
+ break;
+ }
+ case CMD_GET_TPMESTABLISHED: {
+ ptm_est est = {
+ .u.resp.bit = 0,
+ };
+ qio_channel_write(ioc, (char *)&est, sizeof(est), &error_abort);
+ break;
+ }
+ default:
+ g_debug("unimplemented %u", cmd);
+ g_assert_not_reached();
+ }
+ }
+
+ object_unref(OBJECT(ioc));
+ object_unref(OBJECT(lioc));
+ return NULL;
+}
diff --git a/tests/qtest/tpm-emu.h b/tests/qtest/tpm-emu.h
new file mode 100644
index 0000000000..a4f1d64226
--- /dev/null
+++ b/tests/qtest/tpm-emu.h
@@ -0,0 +1,39 @@
+/*
+ * Minimal TPM emulator for TPM test cases
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef TESTS_TPM_EMU_H
+#define TESTS_TPM_EMU_H
+
+#define TPM_RC_FAILURE 0x101
+#define TPM2_ST_NO_SESSIONS 0x8001
+
+struct tpm_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t code; /*ordinal/error */
+ char buffer[];
+} QEMU_PACKED;
+
+typedef struct TestState {
+ GMutex data_mutex;
+ GCond data_cond;
+ bool data_cond_signal;
+ SocketAddress *addr;
+ QIOChannel *tpm_ioc;
+ GThread *emu_tpm_thread;
+ struct tpm_hdr *tpm_msg;
+} TestState;
+
+void tpm_emu_test_wait_cond(TestState *s);
+void *tpm_emu_ctrl_thread(void *data);
+
+#endif /* TESTS_TPM_EMU_H */
diff --git a/tests/qtest/tpm-tests.c b/tests/qtest/tpm-tests.c
new file mode 100644
index 0000000000..6e45a0ba85
--- /dev/null
+++ b/tests/qtest/tpm-tests.c
@@ -0,0 +1,136 @@
+/*
+ * QTest TPM commont test code
+ *
+ * Copyright (c) 2018 IBM Corporation
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "libqtest-single.h"
+#include "tpm-tests.h"
+
+static bool
+tpm_test_swtpm_skip(void)
+{
+ if (!tpm_util_swtpm_has_tpm2()) {
+ g_test_skip("swtpm not in PATH or missing --tpm2 support");
+ return true;
+ }
+
+ return false;
+}
+
+void tpm_test_swtpm_test(const char *src_tpm_path, tx_func *tx,
+ const char *ifmodel)
+{
+ char *args = NULL;
+ QTestState *s;
+ SocketAddress *addr = NULL;
+ gboolean succ;
+ GPid swtpm_pid;
+ GError *error = NULL;
+
+ if (tpm_test_swtpm_skip()) {
+ return;
+ }
+
+ succ = tpm_util_swtpm_start(src_tpm_path, &swtpm_pid, &addr, &error);
+ g_assert_true(succ);
+
+ args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device %s,tpmdev=dev",
+ addr->u.q_unix.path, ifmodel);
+
+ s = qtest_start(args);
+ g_free(args);
+
+ tpm_util_startup(s, tx);
+ tpm_util_pcrextend(s, tx);
+
+ unsigned char tpm_pcrread_resp[] =
+ "\x80\x01\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00"
+ "\x00\x01\x00\x0b\x03\x00\x04\x00\x00\x00\x00\x01\x00\x20\xf6\x85"
+ "\x98\xe5\x86\x8d\xe6\x8b\x97\x29\x99\x60\xf2\x71\x7d\x17\x67\x89"
+ "\xa4\x2f\x9a\xae\xa8\xc7\xb7\xaa\x79\xa8\x62\x56\xc1\xde";
+ tpm_util_pcrread(s, tx, tpm_pcrread_resp,
+ sizeof(tpm_pcrread_resp));
+
+ qtest_end();
+ tpm_util_swtpm_kill(swtpm_pid);
+
+ if (addr) {
+ g_unlink(addr->u.q_unix.path);
+ qapi_free_SocketAddress(addr);
+ }
+}
+
+void tpm_test_swtpm_migration_test(const char *src_tpm_path,
+ const char *dst_tpm_path,
+ const char *uri, tx_func *tx,
+ const char *ifmodel)
+{
+ gboolean succ;
+ GPid src_tpm_pid, dst_tpm_pid;
+ SocketAddress *src_tpm_addr = NULL, *dst_tpm_addr = NULL;
+ GError *error = NULL;
+ QTestState *src_qemu, *dst_qemu;
+
+ if (tpm_test_swtpm_skip()) {
+ return;
+ }
+
+ succ = tpm_util_swtpm_start(src_tpm_path, &src_tpm_pid,
+ &src_tpm_addr, &error);
+ g_assert_true(succ);
+
+ succ = tpm_util_swtpm_start(dst_tpm_path, &dst_tpm_pid,
+ &dst_tpm_addr, &error);
+ g_assert_true(succ);
+
+ tpm_util_migration_start_qemu(&src_qemu, &dst_qemu,
+ src_tpm_addr, dst_tpm_addr, uri,
+ ifmodel);
+
+ tpm_util_startup(src_qemu, tx);
+ tpm_util_pcrextend(src_qemu, tx);
+
+ unsigned char tpm_pcrread_resp[] =
+ "\x80\x01\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00"
+ "\x00\x01\x00\x0b\x03\x00\x04\x00\x00\x00\x00\x01\x00\x20\xf6\x85"
+ "\x98\xe5\x86\x8d\xe6\x8b\x97\x29\x99\x60\xf2\x71\x7d\x17\x67\x89"
+ "\xa4\x2f\x9a\xae\xa8\xc7\xb7\xaa\x79\xa8\x62\x56\xc1\xde";
+ tpm_util_pcrread(src_qemu, tx, tpm_pcrread_resp,
+ sizeof(tpm_pcrread_resp));
+
+ tpm_util_migrate(src_qemu, uri);
+ tpm_util_wait_for_migration_complete(src_qemu);
+
+ tpm_util_pcrread(dst_qemu, tx, tpm_pcrread_resp,
+ sizeof(tpm_pcrread_resp));
+
+ qtest_quit(dst_qemu);
+ qtest_quit(src_qemu);
+
+ tpm_util_swtpm_kill(dst_tpm_pid);
+ if (dst_tpm_addr) {
+ g_unlink(dst_tpm_addr->u.q_unix.path);
+ qapi_free_SocketAddress(dst_tpm_addr);
+ }
+
+ tpm_util_swtpm_kill(src_tpm_pid);
+ if (src_tpm_addr) {
+ g_unlink(src_tpm_addr->u.q_unix.path);
+ qapi_free_SocketAddress(src_tpm_addr);
+ }
+}
diff --git a/tests/qtest/tpm-tests.h b/tests/qtest/tpm-tests.h
new file mode 100644
index 0000000000..b97688fe75
--- /dev/null
+++ b/tests/qtest/tpm-tests.h
@@ -0,0 +1,26 @@
+/*
+ * QTest TPM commont test code
+ *
+ * Copyright (c) 2018 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef TESTS_TPM_TESTS_H
+#define TESTS_TPM_TESTS_H
+
+#include "tpm-util.h"
+
+void tpm_test_swtpm_test(const char *src_tpm_path, tx_func *tx,
+ const char *ifmodel);
+
+void tpm_test_swtpm_migration_test(const char *src_tpm_path,
+ const char *dst_tpm_path,
+ const char *uri, tx_func *tx,
+ const char *ifmodel);
+
+#endif /* TESTS_TPM_TESTS_H */
diff --git a/tests/qtest/tpm-tis-swtpm-test.c b/tests/qtest/tpm-tis-swtpm-test.c
new file mode 100644
index 0000000000..9f58a3a92b
--- /dev/null
+++ b/tests/qtest/tpm-tis-swtpm-test.c
@@ -0,0 +1,67 @@
+/*
+ * QTest testcase for TPM TIS talking to external swtpm and swtpm migration
+ *
+ * Copyright (c) 2018 IBM Corporation
+ * with parts borrowed from migration-test.c that is:
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "tpm-tests.h"
+
+typedef struct TestState {
+ char *src_tpm_path;
+ char *dst_tpm_path;
+ char *uri;
+} TestState;
+
+static void tpm_tis_swtpm_test(const void *data)
+{
+ const TestState *ts = data;
+
+ tpm_test_swtpm_test(ts->src_tpm_path, tpm_util_tis_transfer, "tpm-tis");
+}
+
+static void tpm_tis_swtpm_migration_test(const void *data)
+{
+ const TestState *ts = data;
+
+ tpm_test_swtpm_migration_test(ts->src_tpm_path, ts->dst_tpm_path, ts->uri,
+ tpm_util_tis_transfer, "tpm-tis");
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ TestState ts = { 0 };
+
+ ts.src_tpm_path = g_dir_make_tmp("qemu-tpm-tis-swtpm-test.XXXXXX", NULL);
+ ts.dst_tpm_path = g_dir_make_tmp("qemu-tpm-tis-swtpm-test.XXXXXX", NULL);
+ ts.uri = g_strdup_printf("unix:%s/migsocket", ts.src_tpm_path);
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/tpm/tis-swtpm/test", &ts, tpm_tis_swtpm_test);
+ qtest_add_data_func("/tpm/tis-swtpm-migration/test", &ts,
+ tpm_tis_swtpm_migration_test);
+ ret = g_test_run();
+
+ g_rmdir(ts.dst_tpm_path);
+ g_free(ts.dst_tpm_path);
+ g_rmdir(ts.src_tpm_path);
+ g_free(ts.src_tpm_path);
+ g_free(ts.uri);
+
+ return ret;
+}
diff --git a/tests/qtest/tpm-tis-test.c b/tests/qtest/tpm-tis-test.c
new file mode 100644
index 0000000000..dcf30e05b7
--- /dev/null
+++ b/tests/qtest/tpm-tis-test.c
@@ -0,0 +1,488 @@
+/*
+ * QTest testcase for TPM TIS
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ * Copyright (c) 2018 IBM Corporation
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "hw/acpi/tpm.h"
+#include "io/channel-socket.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "tpm-emu.h"
+
+#define TIS_REG(LOCTY, REG) \
+ (TPM_TIS_ADDR_BASE + ((LOCTY) << 12) + REG)
+
+#define DEBUG_TIS_TEST 0
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_TIS_TEST) { \
+ printf(fmt, ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define DPRINTF_ACCESS \
+ DPRINTF("%s: %d: locty=%d l=%d access=0x%02x pending_request_flag=0x%x\n", \
+ __func__, __LINE__, locty, l, access, pending_request_flag)
+
+#define DPRINTF_STS \
+ DPRINTF("%s: %d: sts = 0x%08x\n", __func__, __LINE__, sts)
+
+static const uint8_t TPM_CMD[12] =
+ "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00";
+
+static void tpm_tis_test_check_localities(const void *data)
+{
+ uint8_t locty;
+ uint8_t access;
+ uint32_t ifaceid;
+ uint32_t capability;
+ uint32_t didvid;
+ uint32_t rid;
+
+ for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES; locty++) {
+ access = readb(TIS_REG(0, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ capability = readl(TIS_REG(locty, TPM_TIS_REG_INTF_CAPABILITY));
+ g_assert_cmpint(capability, ==, TPM_TIS_CAPABILITIES_SUPPORTED2_0);
+
+ ifaceid = readl(TIS_REG(locty, TPM_TIS_REG_INTERFACE_ID));
+ g_assert_cmpint(ifaceid, ==, TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0);
+
+ didvid = readl(TIS_REG(locty, TPM_TIS_REG_DID_VID));
+ g_assert_cmpint(didvid, !=, 0);
+ g_assert_cmpint(didvid, !=, 0xffffffff);
+
+ rid = readl(TIS_REG(locty, TPM_TIS_REG_RID));
+ g_assert_cmpint(rid, !=, 0);
+ g_assert_cmpint(rid, !=, 0xffffffff);
+ }
+}
+
+static void tpm_tis_test_check_access_reg(const void *data)
+{
+ uint8_t locty;
+ uint8_t access;
+
+ /* do not test locality 4 (hw only) */
+ for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES - 1; locty++) {
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of locality */
+ writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* release access */
+ writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+}
+
+/*
+ * Test case for seizing access by a higher number locality
+ */
+static void tpm_tis_test_check_access_reg_seize(const void *data)
+{
+ int locty, l;
+ uint8_t access;
+ uint8_t pending_request_flag;
+
+ /* do not test locality 4 (hw only) */
+ for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES - 1; locty++) {
+ pending_request_flag = 0;
+
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of locality */
+ writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* lower localities cannot seize access */
+ for (l = 0; l < locty; l++) {
+ /* lower locality is not active */
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* try to request use from 'l' */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+
+ /* requesting use from 'l' was not possible;
+ we must see REQUEST_USE and possibly PENDING_REQUEST */
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* locality 'locty' must be unchanged;
+ we must see PENDING_REQUEST */
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_PENDING_REQUEST |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* try to seize from 'l' */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_SEIZE);
+ /* seize from 'l' was not possible */
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* locality 'locty' must be unchanged */
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_PENDING_REQUEST |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* on the next loop we will have a PENDING_REQUEST flag
+ set for locality 'l' */
+ pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+
+ /* higher localities can 'seize' access but not 'request use';
+ note: this will activate first l+1, then l+2 etc. */
+ for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
+ /* try to 'request use' from 'l' */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+
+ /* requesting use from 'l' was not possible; we should see
+ REQUEST_USE and may see PENDING_REQUEST */
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* locality 'l-1' must be unchanged; we should always
+ see PENDING_REQUEST from 'l' requesting access */
+ access = readb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_PENDING_REQUEST |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* try to seize from 'l' */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_SEIZE);
+
+ /* seize from 'l' was possible */
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* l - 1 should show that it has BEEN_SEIZED */
+ access = readb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_BEEN_SEIZED |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* clear the BEEN_SEIZED flag and make sure it's gone */
+ writeb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_BEEN_SEIZED);
+
+ access = readb(TIS_REG(l - 1, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+
+ /* PENDING_REQUEST will not be set if locty = 0 since all localities
+ were active; in case of locty = 1, locality 0 will be active
+ but no PENDING_REQUEST anywhere */
+ if (locty <= 1) {
+ pending_request_flag = 0;
+ }
+
+ /* release access from l - 1; this activates locty - 1 */
+ l--;
+
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+
+ DPRINTF("%s: %d: relinquishing control on l = %d\n",
+ __func__, __LINE__, l);
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ for (l = locty - 1; l >= 0; l--) {
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* release this locality */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+
+ if (l == 1) {
+ pending_request_flag = 0;
+ }
+ }
+
+ /* no locality may be active now */
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+ }
+}
+
+/*
+ * Test case for getting access when higher number locality relinquishes access
+ */
+static void tpm_tis_test_check_access_reg_release(const void *data)
+{
+ int locty, l;
+ uint8_t access;
+ uint8_t pending_request_flag;
+
+ /* do not test locality 4 (hw only) */
+ for (locty = TPM_TIS_NUM_LOCALITIES - 2; locty >= 0; locty--) {
+ pending_request_flag = 0;
+
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of locality */
+ writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+ access = readb(TIS_REG(locty, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of all other localities */
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
+ if (l == locty) {
+ continue;
+ }
+ /* request use of locality 'l' -- we MUST see REQUEST USE and
+ may see PENDING_REQUEST */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+ /* release locality 'locty' */
+ writeb(TIS_REG(locty, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ /* highest locality should now be active; release it and make sure the
+ next higest locality is active afterwards */
+ for (l = TPM_TIS_NUM_LOCALITIES - 2; l >= 0; l--) {
+ if (l == locty) {
+ continue;
+ }
+ /* 'l' should be active now */
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ /* 'l' relinquishes access */
+ writeb(TIS_REG(l, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ access = readb(TIS_REG(l, TPM_TIS_REG_ACCESS));
+ DPRINTF_ACCESS;
+ if (l == 1 || (locty <= 1 && l == 2)) {
+ pending_request_flag = 0;
+ }
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+ }
+}
+
+/*
+ * Test case for transmitting packets
+ */
+static void tpm_tis_test_check_transmit(const void *data)
+{
+ const TestState *s = data;
+ uint8_t access;
+ uint32_t sts;
+ uint16_t bcount;
+ size_t i;
+
+ /* request use of locality 0 */
+ writeb(TIS_REG(0, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+ access = readb(TIS_REG(0, TPM_TIS_REG_ACCESS));
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ sts = readl(TIS_REG(0, TPM_TIS_REG_STS));
+ DPRINTF_STS;
+
+ g_assert_cmpint(sts & 0xff, ==, 0);
+ g_assert_cmpint(sts & TPM_TIS_STS_TPM_FAMILY_MASK, ==,
+ TPM_TIS_STS_TPM_FAMILY2_0);
+
+ bcount = (sts >> 8) & 0xffff;
+ g_assert_cmpint(bcount, >=, 128);
+
+ writel(TIS_REG(0, TPM_TIS_REG_STS), TPM_TIS_STS_COMMAND_READY);
+ sts = readl(TIS_REG(0, TPM_TIS_REG_STS));
+ DPRINTF_STS;
+ g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_COMMAND_READY);
+
+ /* transmit command */
+ for (i = 0; i < sizeof(TPM_CMD); i++) {
+ writeb(TIS_REG(0, TPM_TIS_REG_DATA_FIFO), TPM_CMD[i]);
+ sts = readl(TIS_REG(0, TPM_TIS_REG_STS));
+ DPRINTF_STS;
+ if (i < sizeof(TPM_CMD) - 1) {
+ g_assert_cmpint(sts & 0xff, ==,
+ TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
+ } else {
+ g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_VALID);
+ }
+ g_assert_cmpint((sts >> 8) & 0xffff, ==, --bcount);
+ }
+ /* start processing */
+ writeb(TIS_REG(0, TPM_TIS_REG_STS), TPM_TIS_STS_TPM_GO);
+
+ uint64_t end_time = g_get_monotonic_time() + 50 * G_TIME_SPAN_SECOND;
+ do {
+ sts = readl(TIS_REG(0, TPM_TIS_REG_STS));
+ if ((sts & TPM_TIS_STS_DATA_AVAILABLE) != 0) {
+ break;
+ }
+ } while (g_get_monotonic_time() < end_time);
+
+ sts = readl(TIS_REG(0, TPM_TIS_REG_STS));
+ DPRINTF_STS;
+ g_assert_cmpint(sts & 0xff, == ,
+ TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
+ bcount = (sts >> 8) & 0xffff;
+
+ /* read response */
+ uint8_t tpm_msg[sizeof(struct tpm_hdr)];
+ g_assert_cmpint(sizeof(tpm_msg), ==, bcount);
+
+ for (i = 0; i < sizeof(tpm_msg); i++) {
+ tpm_msg[i] = readb(TIS_REG(0, TPM_TIS_REG_DATA_FIFO));
+ sts = readl(TIS_REG(0, TPM_TIS_REG_STS));
+ DPRINTF_STS;
+ if (sts & TPM_TIS_STS_DATA_AVAILABLE) {
+ g_assert_cmpint((sts >> 8) & 0xffff, ==, --bcount);
+ }
+ }
+ g_assert_cmpmem(tpm_msg, sizeof(tpm_msg), s->tpm_msg, sizeof(*s->tpm_msg));
+
+ /* relinquish use of locality 0 */
+ writeb(TIS_REG(0, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ access = readb(TIS_REG(0, TPM_TIS_REG_ACCESS));
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ char *args, *tmp_path = g_dir_make_tmp("qemu-tpm-tis-test.XXXXXX", NULL);
+ GThread *thread;
+ TestState test;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ test.addr = g_new0(SocketAddress, 1);
+ test.addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL);
+ g_mutex_init(&test.data_mutex);
+ g_cond_init(&test.data_cond);
+ test.data_cond_signal = false;
+
+ thread = g_thread_new(NULL, tpm_emu_ctrl_thread, &test);
+ tpm_emu_test_wait_cond(&test);
+
+ args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device tpm-tis,tpmdev=dev",
+ test.addr->u.q_unix.path);
+ qtest_start(args);
+
+ qtest_add_data_func("/tpm-tis/test_check_localities", &test,
+ tpm_tis_test_check_localities);
+
+ qtest_add_data_func("/tpm-tis/test_check_access_reg", &test,
+ tpm_tis_test_check_access_reg);
+
+ qtest_add_data_func("/tpm-tis/test_check_access_reg_seize", &test,
+ tpm_tis_test_check_access_reg_seize);
+
+ qtest_add_data_func("/tpm-tis/test_check_access_reg_release", &test,
+ tpm_tis_test_check_access_reg_release);
+
+ qtest_add_data_func("/tpm-tis/test_check_transmit", &test,
+ tpm_tis_test_check_transmit);
+
+ ret = g_test_run();
+
+ qtest_end();
+
+ g_thread_join(thread);
+ g_unlink(test.addr->u.q_unix.path);
+ qapi_free_SocketAddress(test.addr);
+ g_rmdir(tmp_path);
+ g_free(tmp_path);
+ g_free(args);
+ return ret;
+}
diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
new file mode 100644
index 0000000000..e08b137651
--- /dev/null
+++ b/tests/qtest/tpm-util.c
@@ -0,0 +1,285 @@
+/*
+ * QTest TPM utilities
+ *
+ * Copyright (c) 2018 IBM Corporation
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/acpi/tpm.h"
+#include "libqtest.h"
+#include "tpm-util.h"
+#include "qapi/qmp/qdict.h"
+
+#define TIS_REG(LOCTY, REG) \
+ (TPM_TIS_ADDR_BASE + ((LOCTY) << 12) + REG)
+
+void tpm_util_crb_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size)
+{
+ uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
+ uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
+
+ qtest_writeb(s, TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 1);
+
+ qtest_memwrite(s, caddr, req, req_size);
+
+ uint32_t sts, start = 1;
+ uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ qtest_writel(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
+ while (true) {
+ start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ if ((start & 1) == 0) {
+ break;
+ }
+ if (g_get_monotonic_time() >= end_time) {
+ break;
+ }
+ };
+ start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ g_assert_cmpint(start & 1, ==, 0);
+ sts = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+ g_assert_cmpint(sts & 1, ==, 0);
+
+ qtest_memread(s, raddr, rsp, rsp_size);
+}
+
+void tpm_util_tis_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size)
+{
+ uint32_t sts;
+ uint16_t bcount;
+ size_t i;
+
+ /* request use of locality 0 */
+ qtest_writeb(s, TIS_REG(0, TPM_TIS_REG_ACCESS), TPM_TIS_ACCESS_REQUEST_USE);
+ qtest_writel(s, TIS_REG(0, TPM_TIS_REG_STS), TPM_TIS_STS_COMMAND_READY);
+
+ sts = qtest_readl(s, TIS_REG(0, TPM_TIS_REG_STS));
+ bcount = (sts >> 8) & 0xffff;
+ g_assert_cmpint(bcount, >=, req_size);
+
+ /* transmit command */
+ for (i = 0; i < req_size; i++) {
+ qtest_writeb(s, TIS_REG(0, TPM_TIS_REG_DATA_FIFO), req[i]);
+ }
+
+ /* start processing */
+ qtest_writeb(s, TIS_REG(0, TPM_TIS_REG_STS), TPM_TIS_STS_TPM_GO);
+
+ uint64_t end_time = g_get_monotonic_time() + 50 * G_TIME_SPAN_SECOND;
+ do {
+ sts = qtest_readl(s, TIS_REG(0, TPM_TIS_REG_STS));
+ if ((sts & TPM_TIS_STS_DATA_AVAILABLE) != 0) {
+ break;
+ }
+ } while (g_get_monotonic_time() < end_time);
+
+ sts = qtest_readl(s, TIS_REG(0, TPM_TIS_REG_STS));
+ bcount = (sts >> 8) & 0xffff;
+
+ memset(rsp, 0, rsp_size);
+ for (i = 0; i < bcount; i++) {
+ rsp[i] = qtest_readb(s, TIS_REG(0, TPM_TIS_REG_DATA_FIFO));
+ }
+
+ /* relinquish use of locality 0 */
+ qtest_writeb(s, TIS_REG(0, TPM_TIS_REG_ACCESS),
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+}
+
+void tpm_util_startup(QTestState *s, tx_func *tx)
+{
+ unsigned char buffer[1024];
+ unsigned char tpm_startup[] =
+ "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00";
+ unsigned char tpm_startup_resp[] =
+ "\x80\x01\x00\x00\x00\x0a\x00\x00\x00\x00";
+
+ tx(s, tpm_startup, sizeof(tpm_startup), buffer, sizeof(buffer));
+
+ g_assert_cmpmem(buffer, sizeof(tpm_startup_resp),
+ tpm_startup_resp, sizeof(tpm_startup_resp));
+}
+
+void tpm_util_pcrextend(QTestState *s, tx_func *tx)
+{
+ unsigned char buffer[1024];
+ unsigned char tpm_pcrextend[] =
+ "\x80\x02\x00\x00\x00\x41\x00\x00\x01\x82\x00\x00\x00\x0a\x00\x00"
+ "\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"
+ "\x0b\x74\x65\x73\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00";
+
+ unsigned char tpm_pcrextend_resp[] =
+ "\x80\x02\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x01\x00\x00";
+
+ tx(s, tpm_pcrextend, sizeof(tpm_pcrextend), buffer, sizeof(buffer));
+
+ g_assert_cmpmem(buffer, sizeof(tpm_pcrextend_resp),
+ tpm_pcrextend_resp, sizeof(tpm_pcrextend_resp));
+}
+
+void tpm_util_pcrread(QTestState *s, tx_func *tx,
+ const unsigned char *exp_resp, size_t exp_resp_size)
+{
+ unsigned char buffer[1024];
+ unsigned char tpm_pcrread[] =
+ "\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b"
+ "\x03\x00\x04\x00";
+
+ tx(s, tpm_pcrread, sizeof(tpm_pcrread), buffer, sizeof(buffer));
+
+ g_assert_cmpmem(buffer, exp_resp_size, exp_resp, exp_resp_size);
+}
+
+bool tpm_util_swtpm_has_tpm2(void)
+{
+ bool has_tpm2 = false;
+ char *out = NULL;
+ static const char *argv[] = {
+ "swtpm", "socket", "--help", NULL
+ };
+
+ if (!g_spawn_sync(NULL /* working_dir */,
+ (char **)argv,
+ NULL /* envp */,
+ G_SPAWN_SEARCH_PATH,
+ NULL /* child_setup */,
+ NULL /* user_data */,
+ &out,
+ NULL /* err */,
+ NULL /* exit_status */,
+ NULL)) {
+ return false;
+ }
+
+ if (strstr(out, "--tpm2")) {
+ has_tpm2 = true;
+ }
+
+ g_free(out);
+ return has_tpm2;
+}
+
+gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
+ SocketAddress **addr, GError **error)
+{
+ char *swtpm_argv_tpmstate = g_strdup_printf("dir=%s", path);
+ char *swtpm_argv_ctrl = g_strdup_printf("type=unixio,path=%s/sock",
+ path);
+ gchar *swtpm_argv[] = {
+ g_strdup("swtpm"), g_strdup("socket"),
+ g_strdup("--tpmstate"), swtpm_argv_tpmstate,
+ g_strdup("--ctrl"), swtpm_argv_ctrl,
+ g_strdup("--tpm2"),
+ NULL
+ };
+ gboolean succ;
+ unsigned i;
+
+ *addr = g_new0(SocketAddress, 1);
+ (*addr)->type = SOCKET_ADDRESS_TYPE_UNIX;
+ (*addr)->u.q_unix.path = g_build_filename(path, "sock", NULL);
+
+ succ = g_spawn_async(NULL, swtpm_argv, NULL, G_SPAWN_SEARCH_PATH,
+ NULL, NULL, pid, error);
+
+ for (i = 0; swtpm_argv[i]; i++) {
+ g_free(swtpm_argv[i]);
+ }
+
+ return succ;
+}
+
+void tpm_util_swtpm_kill(GPid pid)
+{
+ int n;
+
+ if (!pid) {
+ return;
+ }
+
+ g_spawn_close_pid(pid);
+
+ n = kill(pid, 0);
+ if (n < 0) {
+ return;
+ }
+
+ kill(pid, SIGKILL);
+}
+
+void tpm_util_migrate(QTestState *who, const char *uri)
+{
+ QDict *rsp;
+
+ rsp = qtest_qmp(who,
+ "{ 'execute': 'migrate', 'arguments': { 'uri': %s } }",
+ uri);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+}
+
+void tpm_util_wait_for_migration_complete(QTestState *who)
+{
+ while (true) {
+ QDict *rsp_return;
+ bool completed;
+ const char *status;
+
+ qtest_qmp_send(who, "{ 'execute': 'query-migrate' }");
+ rsp_return = qtest_qmp_receive_success(who, NULL, NULL);
+ status = qdict_get_str(rsp_return, "status");
+ completed = strcmp(status, "completed") == 0;
+ g_assert_cmpstr(status, !=, "failed");
+ qobject_unref(rsp_return);
+ if (completed) {
+ return;
+ }
+ usleep(1000);
+ }
+}
+
+void tpm_util_migration_start_qemu(QTestState **src_qemu,
+ QTestState **dst_qemu,
+ SocketAddress *src_tpm_addr,
+ SocketAddress *dst_tpm_addr,
+ const char *miguri,
+ const char *ifmodel)
+{
+ char *src_qemu_args, *dst_qemu_args;
+
+ src_qemu_args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device %s,tpmdev=dev ",
+ src_tpm_addr->u.q_unix.path, ifmodel);
+
+ *src_qemu = qtest_init(src_qemu_args);
+
+ dst_qemu_args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device %s,tpmdev=dev "
+ "-incoming %s",
+ dst_tpm_addr->u.q_unix.path,
+ ifmodel, miguri);
+
+ *dst_qemu = qtest_init(dst_qemu_args);
+
+ free(src_qemu_args);
+ free(dst_qemu_args);
+}
diff --git a/tests/qtest/tpm-util.h b/tests/qtest/tpm-util.h
new file mode 100644
index 0000000000..5755698ad2
--- /dev/null
+++ b/tests/qtest/tpm-util.h
@@ -0,0 +1,51 @@
+/*
+ * QTest TPM utilities
+ *
+ * Copyright (c) 2018 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef TESTS_TPM_UTIL_H
+#define TESTS_TPM_UTIL_H
+
+#include "io/channel-socket.h"
+
+typedef void (tx_func)(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size);
+
+void tpm_util_crb_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size);
+void tpm_util_tis_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size);
+
+void tpm_util_startup(QTestState *s, tx_func *tx);
+void tpm_util_pcrextend(QTestState *s, tx_func *tx);
+void tpm_util_pcrread(QTestState *s, tx_func *tx,
+ const unsigned char *exp_resp, size_t exp_resp_size);
+
+bool tpm_util_swtpm_has_tpm2(void);
+
+gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
+ SocketAddress **addr, GError **error);
+void tpm_util_swtpm_kill(GPid pid);
+
+void tpm_util_migrate(QTestState *who, const char *uri);
+
+void tpm_util_migration_start_qemu(QTestState **src_qemu,
+ QTestState **dst_qemu,
+ SocketAddress *src_tpm_addr,
+ SocketAddress *dst_tpm_addr,
+ const char *miguri,
+ const char *ifmodel);
+
+void tpm_util_wait_for_migration_complete(QTestState *who);
+
+#endif /* TESTS_TPM_UTIL_H */
diff --git a/tests/qtest/usb-hcd-ehci-test.c b/tests/qtest/usb-hcd-ehci-test.c
new file mode 100644
index 0000000000..5251d539e9
--- /dev/null
+++ b/tests/qtest/usb-hcd-ehci-test.c
@@ -0,0 +1,178 @@
+/*
+ * QTest testcase for USB EHCI
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/pci-pc.h"
+#include "hw/usb/uhci-regs.h"
+#include "hw/usb/ehci-regs.h"
+#include "libqos/usb.h"
+
+static QPCIBus *pcibus;
+static struct qhc uhci1;
+static struct qhc uhci2;
+static struct qhc uhci3;
+static struct qhc ehci1;
+
+/* helpers */
+
+#if 0
+static void uhci_port_update(struct qhc *hc, int port,
+ uint16_t set, uint16_t clear)
+{
+ void *addr = hc->base + 0x10 + 2 * port;
+ uint16_t value;
+
+ value = qpci_io_readw(hc->dev, addr);
+ value |= set;
+ value &= ~clear;
+ qpci_io_writew(hc->dev, addr, value);
+}
+#endif
+
+static void ehci_port_test(struct qhc *hc, int port, uint32_t expect)
+{
+ uint32_t value = qpci_io_readl(hc->dev, hc->bar, 0x64 + 4 * port);
+ uint16_t mask = ~(PORTSC_CSC | PORTSC_PEDC | PORTSC_OCC);
+
+#if 0
+ fprintf(stderr, "%s: %d, have 0x%08x, want 0x%08x\n",
+ __func__, port, value & mask, expect & mask);
+#endif
+ g_assert((value & mask) == (expect & mask));
+}
+
+/* tests */
+
+static void test_init(void)
+{
+ pcibus = qpci_new_pc(global_qtest, NULL);
+ g_assert(pcibus != NULL);
+
+ qusb_pci_init_one(pcibus, &uhci1, QPCI_DEVFN(0x1d, 0), 4);
+ qusb_pci_init_one(pcibus, &uhci2, QPCI_DEVFN(0x1d, 1), 4);
+ qusb_pci_init_one(pcibus, &uhci3, QPCI_DEVFN(0x1d, 2), 4);
+ qusb_pci_init_one(pcibus, &ehci1, QPCI_DEVFN(0x1d, 7), 0);
+}
+
+static void test_deinit(void)
+{
+ uhci_deinit(&uhci1);
+ uhci_deinit(&uhci2);
+ uhci_deinit(&uhci3);
+ uhci_deinit(&ehci1);
+ qpci_free_pc(pcibus);
+}
+
+static void pci_uhci_port_1(void)
+{
+ g_assert(pcibus != NULL);
+
+ uhci_port_test(&uhci1, 0, UHCI_PORT_CCS); /* usb-tablet */
+ uhci_port_test(&uhci1, 1, UHCI_PORT_CCS); /* usb-storage */
+ uhci_port_test(&uhci2, 0, 0);
+ uhci_port_test(&uhci2, 1, 0);
+ uhci_port_test(&uhci3, 0, 0);
+ uhci_port_test(&uhci3, 1, 0);
+}
+
+static void pci_ehci_port_1(void)
+{
+ int i;
+
+ g_assert(pcibus != NULL);
+
+ for (i = 0; i < 6; i++) {
+ ehci_port_test(&ehci1, i, PORTSC_POWNER | PORTSC_PPOWER);
+ }
+}
+
+static void pci_ehci_config(void)
+{
+ /* hands over all ports from companion uhci to ehci */
+ qpci_io_writew(ehci1.dev, ehci1.bar, 0x60, 1);
+}
+
+static void pci_uhci_port_2(void)
+{
+ g_assert(pcibus != NULL);
+
+ uhci_port_test(&uhci1, 0, 0); /* usb-tablet, @ehci */
+ uhci_port_test(&uhci1, 1, 0); /* usb-storage, @ehci */
+ uhci_port_test(&uhci2, 0, 0);
+ uhci_port_test(&uhci2, 1, 0);
+ uhci_port_test(&uhci3, 0, 0);
+ uhci_port_test(&uhci3, 1, 0);
+}
+
+static void pci_ehci_port_2(void)
+{
+ static uint32_t expect[] = {
+ PORTSC_PPOWER | PORTSC_CONNECT, /* usb-tablet */
+ PORTSC_PPOWER | PORTSC_CONNECT, /* usb-storage */
+ PORTSC_PPOWER,
+ PORTSC_PPOWER,
+ PORTSC_PPOWER,
+ PORTSC_PPOWER,
+ };
+ int i;
+
+ g_assert(pcibus != NULL);
+
+ for (i = 0; i < 6; i++) {
+ ehci_port_test(&ehci1, i, expect[i]);
+ }
+}
+
+static void pci_ehci_port_3_hotplug(void)
+{
+ /* check for presence of hotplugged usb-tablet */
+ g_assert(pcibus != NULL);
+ ehci_port_test(&ehci1, 2, PORTSC_PPOWER | PORTSC_CONNECT);
+}
+
+static void pci_ehci_port_hotplug(void)
+{
+ usb_test_hotplug(global_qtest, "ich9-ehci-1", "3", pci_ehci_port_3_hotplug);
+}
+
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/ehci/pci/uhci-port-1", pci_uhci_port_1);
+ qtest_add_func("/ehci/pci/ehci-port-1", pci_ehci_port_1);
+ qtest_add_func("/ehci/pci/ehci-config", pci_ehci_config);
+ qtest_add_func("/ehci/pci/uhci-port-2", pci_uhci_port_2);
+ qtest_add_func("/ehci/pci/ehci-port-2", pci_ehci_port_2);
+ qtest_add_func("/ehci/pci/ehci-port-3-hotplug", pci_ehci_port_hotplug);
+
+ qtest_start("-machine q35 -device ich9-usb-ehci1,bus=pcie.0,addr=1d.7,"
+ "multifunction=on,id=ich9-ehci-1 "
+ "-device ich9-usb-uhci1,bus=pcie.0,addr=1d.0,"
+ "multifunction=on,masterbus=ich9-ehci-1.0,firstport=0 "
+ "-device ich9-usb-uhci2,bus=pcie.0,addr=1d.1,"
+ "multifunction=on,masterbus=ich9-ehci-1.0,firstport=2 "
+ "-device ich9-usb-uhci3,bus=pcie.0,addr=1d.2,"
+ "multifunction=on,masterbus=ich9-ehci-1.0,firstport=4 "
+ "-drive if=none,id=usbcdrom,media=cdrom "
+ "-device usb-tablet,bus=ich9-ehci-1.0,port=1,usb_version=1 "
+ "-device usb-storage,bus=ich9-ehci-1.0,port=2,drive=usbcdrom ");
+
+ test_init();
+ ret = g_test_run();
+ test_deinit();
+
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/usb-hcd-ohci-test.c b/tests/qtest/usb-hcd-ohci-test.c
new file mode 100644
index 0000000000..19d760f3fb
--- /dev/null
+++ b/tests/qtest/usb-hcd-ohci-test.c
@@ -0,0 +1,68 @@
+/*
+ * QTest testcase for USB OHCI controller
+ *
+ * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO., LTD.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "libqos/usb.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QOHCI_PCI QOHCI_PCI;
+
+struct QOHCI_PCI {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void test_ohci_hotplug(void *obj, void *data, QGuestAllocator *alloc)
+{
+ usb_test_hotplug(global_qtest, "ohci", "1", NULL);
+}
+
+static void *ohci_pci_get_driver(void *obj, const char *interface)
+{
+ QOHCI_PCI *ohci_pci = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &ohci_pci->dev;
+ }
+
+ fprintf(stderr, "%s not present in pci-ohci\n", interface);
+ g_assert_not_reached();
+}
+
+static void *ohci_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QOHCI_PCI *ohci_pci = g_new0(QOHCI_PCI, 1);
+ ohci_pci->obj.get_driver = ohci_pci_get_driver;
+
+ return &ohci_pci->obj;
+}
+
+static void ohci_pci_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0,id=ohci",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("pci-ohci", ohci_pci_create);
+ qos_node_consumes("pci-ohci", "pci-bus", &opts);
+ qos_node_produces("pci-ohci", "pci-device");
+}
+
+libqos_init(ohci_pci_register_nodes);
+
+static void register_ohci_pci_test(void)
+{
+ qos_add_test("ohci_pci-test-hotplug", "pci-ohci", test_ohci_hotplug, NULL);
+}
+
+libqos_init(register_ohci_pci_test);
diff --git a/tests/qtest/usb-hcd-uhci-test.c b/tests/qtest/usb-hcd-uhci-test.c
new file mode 100644
index 0000000000..7a117b64d9
--- /dev/null
+++ b/tests/qtest/usb-hcd-uhci-test.c
@@ -0,0 +1,88 @@
+/*
+ * QTest testcase for USB UHCI controller
+ *
+ * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO., LTD.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/libqos.h"
+#include "libqos/usb.h"
+#include "libqos/libqos-pc.h"
+#include "libqos/libqos-spapr.h"
+#include "hw/usb/uhci-regs.h"
+
+static QOSState *qs;
+
+static void test_uhci_init(void)
+{
+}
+
+static void test_port(int port)
+{
+ struct qhc uhci;
+
+ g_assert(port > 0);
+ qusb_pci_init_one(qs->pcibus, &uhci, QPCI_DEVFN(0x1d, 0), 4);
+ uhci_port_test(&uhci, port - 1, UHCI_PORT_CCS);
+ uhci_deinit(&uhci);
+}
+
+static void test_port_1(void)
+{
+ test_port(1);
+}
+
+static void test_port_2(void)
+{
+ test_port(2);
+}
+
+static void test_uhci_hotplug(void)
+{
+ usb_test_hotplug(global_qtest, "uhci", "2", test_port_2);
+}
+
+static void test_usb_storage_hotplug(void)
+{
+ QTestState *qts = global_qtest;
+
+ qtest_qmp_device_add(qts, "usb-storage", "usbdev0", "{'drive': 'drive0'}");
+
+ qtest_qmp_device_del(qts, "usbdev0");
+}
+
+int main(int argc, char **argv)
+{
+ const char *arch = qtest_get_arch();
+ const char *cmd = "-device piix3-usb-uhci,id=uhci,addr=1d.0"
+ " -drive id=drive0,if=none,file=null-co://,"
+ "file.read-zeroes=on,format=raw"
+ " -device usb-tablet,bus=uhci.0,port=1";
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/uhci/pci/init", test_uhci_init);
+ qtest_add_func("/uhci/pci/port1", test_port_1);
+ qtest_add_func("/uhci/pci/hotplug", test_uhci_hotplug);
+ qtest_add_func("/uhci/pci/hotplug/usb-storage", test_usb_storage_hotplug);
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ qs = qtest_pc_boot(cmd);
+ } else if (strcmp(arch, "ppc64") == 0) {
+ qs = qtest_spapr_boot(cmd);
+ } else {
+ g_printerr("usb-hcd-uhci-test tests are only "
+ "available on x86 or ppc64\n");
+ exit(EXIT_FAILURE);
+ }
+ global_qtest = qs->qts;
+ ret = g_test_run();
+ qtest_shutdown(qs);
+
+ return ret;
+}
diff --git a/tests/qtest/usb-hcd-xhci-test.c b/tests/qtest/usb-hcd-xhci-test.c
new file mode 100644
index 0000000000..10ef9d2a91
--- /dev/null
+++ b/tests/qtest/usb-hcd-xhci-test.c
@@ -0,0 +1,69 @@
+/*
+ * QTest testcase for USB xHCI controller
+ *
+ * Copyright (c) 2014 HUAWEI TECHNOLOGIES CO., LTD.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/usb.h"
+
+
+static void test_xhci_init(void)
+{
+}
+
+static void test_xhci_hotplug(void)
+{
+ usb_test_hotplug(global_qtest, "xhci", "1", NULL);
+}
+
+static void test_usb_uas_hotplug(void)
+{
+ QTestState *qts = global_qtest;
+
+ qtest_qmp_device_add(qts, "usb-uas", "uas", "{}");
+ qtest_qmp_device_add(qts, "scsi-hd", "scsihd", "{'drive': 'drive0'}");
+
+ /* TODO:
+ UAS HBA driver in libqos, to check that
+ added disk is visible after BUS rescan
+ */
+
+ qtest_qmp_device_del(qts, "scsihd");
+ qtest_qmp_device_del(qts, "uas");
+}
+
+static void test_usb_ccid_hotplug(void)
+{
+ QTestState *qts = global_qtest;
+
+ qtest_qmp_device_add(qts, "usb-ccid", "ccid", "{}");
+ qtest_qmp_device_del(qts, "ccid");
+ /* check the device can be added again */
+ qtest_qmp_device_add(qts, "usb-ccid", "ccid", "{}");
+ qtest_qmp_device_del(qts, "ccid");
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/xhci/pci/init", test_xhci_init);
+ qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug);
+ qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug);
+ qtest_add_func("/xhci/pci/hotplug/usb-ccid", test_usb_ccid_hotplug);
+
+ qtest_start("-device nec-usb-xhci,id=xhci"
+ " -drive id=drive0,if=none,file=null-co://,"
+ "file.read-zeroes=on,format=raw");
+ ret = g_test_run();
+ qtest_end();
+
+ return ret;
+}
diff --git a/tests/qtest/vhost-user-test.c b/tests/qtest/vhost-user-test.c
new file mode 100644
index 0000000000..91ea373ba5
--- /dev/null
+++ b/tests/qtest/vhost-user-test.c
@@ -0,0 +1,967 @@
+/*
+ * QTest testcase for the vhost-user
+ *
+ * Copyright (c) 2014 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "libqtest-single.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+#include "qemu/range.h"
+#include "qemu/sockets.h"
+#include "chardev/char-fe.h"
+#include "qemu/memfd.h"
+#include "qemu/module.h"
+#include "sysemu/sysemu.h"
+#include "libqos/libqos.h"
+#include "libqos/pci-pc.h"
+#include "libqos/virtio-pci.h"
+
+#include "libqos/malloc-pc.h"
+#include "hw/virtio/virtio-net.h"
+
+#include "standard-headers/linux/vhost_types.h"
+#include "standard-headers/linux/virtio_ids.h"
+#include "standard-headers/linux/virtio_net.h"
+
+#ifdef CONFIG_LINUX
+#include <sys/vfs.h>
+#endif
+
+
+#define QEMU_CMD_MEM " -m %d -object memory-backend-file,id=mem,size=%dM," \
+ "mem-path=%s,share=on -numa node,memdev=mem"
+#define QEMU_CMD_MEMFD " -m %d -object memory-backend-memfd,id=mem,size=%dM," \
+ " -numa node,memdev=mem"
+#define QEMU_CMD_CHR " -chardev socket,id=%s,path=%s%s"
+#define QEMU_CMD_NETDEV " -netdev vhost-user,id=hs0,chardev=%s,vhostforce"
+
+#define HUGETLBFS_MAGIC 0x958458f6
+
+/*********** FROM hw/virtio/vhost-user.c *************************************/
+
+#define VHOST_MEMORY_MAX_NREGIONS 8
+#define VHOST_MAX_VIRTQUEUES 0x100
+
+#define VHOST_USER_F_PROTOCOL_FEATURES 30
+#define VHOST_USER_PROTOCOL_F_MQ 0
+#define VHOST_USER_PROTOCOL_F_LOG_SHMFD 1
+#define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN 6
+
+#define VHOST_LOG_PAGE 0x1000
+
+typedef enum VhostUserRequest {
+ VHOST_USER_NONE = 0,
+ VHOST_USER_GET_FEATURES = 1,
+ VHOST_USER_SET_FEATURES = 2,
+ VHOST_USER_SET_OWNER = 3,
+ VHOST_USER_RESET_OWNER = 4,
+ VHOST_USER_SET_MEM_TABLE = 5,
+ VHOST_USER_SET_LOG_BASE = 6,
+ VHOST_USER_SET_LOG_FD = 7,
+ VHOST_USER_SET_VRING_NUM = 8,
+ VHOST_USER_SET_VRING_ADDR = 9,
+ VHOST_USER_SET_VRING_BASE = 10,
+ VHOST_USER_GET_VRING_BASE = 11,
+ VHOST_USER_SET_VRING_KICK = 12,
+ VHOST_USER_SET_VRING_CALL = 13,
+ VHOST_USER_SET_VRING_ERR = 14,
+ VHOST_USER_GET_PROTOCOL_FEATURES = 15,
+ VHOST_USER_SET_PROTOCOL_FEATURES = 16,
+ VHOST_USER_GET_QUEUE_NUM = 17,
+ VHOST_USER_SET_VRING_ENABLE = 18,
+ VHOST_USER_MAX
+} VhostUserRequest;
+
+typedef struct VhostUserMemoryRegion {
+ uint64_t guest_phys_addr;
+ uint64_t memory_size;
+ uint64_t userspace_addr;
+ uint64_t mmap_offset;
+} VhostUserMemoryRegion;
+
+typedef struct VhostUserMemory {
+ uint32_t nregions;
+ uint32_t padding;
+ VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS];
+} VhostUserMemory;
+
+typedef struct VhostUserLog {
+ uint64_t mmap_size;
+ uint64_t mmap_offset;
+} VhostUserLog;
+
+typedef struct VhostUserMsg {
+ VhostUserRequest request;
+
+#define VHOST_USER_VERSION_MASK (0x3)
+#define VHOST_USER_REPLY_MASK (0x1<<2)
+ uint32_t flags;
+ uint32_t size; /* the following payload size */
+ union {
+#define VHOST_USER_VRING_IDX_MASK (0xff)
+#define VHOST_USER_VRING_NOFD_MASK (0x1<<8)
+ uint64_t u64;
+ struct vhost_vring_state state;
+ struct vhost_vring_addr addr;
+ VhostUserMemory memory;
+ VhostUserLog log;
+ } payload;
+} QEMU_PACKED VhostUserMsg;
+
+static VhostUserMsg m __attribute__ ((unused));
+#define VHOST_USER_HDR_SIZE (sizeof(m.request) \
+ + sizeof(m.flags) \
+ + sizeof(m.size))
+
+#define VHOST_USER_PAYLOAD_SIZE (sizeof(m) - VHOST_USER_HDR_SIZE)
+
+/* The version of the protocol we support */
+#define VHOST_USER_VERSION (0x1)
+/*****************************************************************************/
+
+enum {
+ TEST_FLAGS_OK,
+ TEST_FLAGS_DISCONNECT,
+ TEST_FLAGS_BAD,
+ TEST_FLAGS_END,
+};
+
+typedef struct TestServer {
+ gchar *socket_path;
+ gchar *mig_path;
+ gchar *chr_name;
+ gchar *tmpfs;
+ CharBackend chr;
+ int fds_num;
+ int fds[VHOST_MEMORY_MAX_NREGIONS];
+ VhostUserMemory memory;
+ GMainContext *context;
+ GMainLoop *loop;
+ GThread *thread;
+ GMutex data_mutex;
+ GCond data_cond;
+ int log_fd;
+ uint64_t rings;
+ bool test_fail;
+ int test_flags;
+ int queues;
+} TestServer;
+
+static const char *init_hugepagefs(void);
+static TestServer *test_server_new(const gchar *name);
+static void test_server_free(TestServer *server);
+static void test_server_listen(TestServer *server);
+
+enum test_memfd {
+ TEST_MEMFD_AUTO,
+ TEST_MEMFD_YES,
+ TEST_MEMFD_NO,
+};
+
+static void append_vhost_opts(TestServer *s, GString *cmd_line,
+ const char *chr_opts)
+{
+ g_string_append_printf(cmd_line, QEMU_CMD_CHR QEMU_CMD_NETDEV,
+ s->chr_name, s->socket_path,
+ chr_opts, s->chr_name);
+}
+
+static void append_mem_opts(TestServer *server, GString *cmd_line,
+ int size, enum test_memfd memfd)
+{
+ if (memfd == TEST_MEMFD_AUTO) {
+ memfd = qemu_memfd_check(MFD_ALLOW_SEALING) ? TEST_MEMFD_YES
+ : TEST_MEMFD_NO;
+ }
+
+ if (memfd == TEST_MEMFD_YES) {
+ g_string_append_printf(cmd_line, QEMU_CMD_MEMFD, size, size);
+ } else {
+ const char *root = init_hugepagefs() ? : server->tmpfs;
+
+ g_string_append_printf(cmd_line, QEMU_CMD_MEM, size, size, root);
+ }
+}
+
+static bool wait_for_fds(TestServer *s)
+{
+ gint64 end_time;
+ bool got_region;
+ int i;
+
+ g_mutex_lock(&s->data_mutex);
+
+ end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ while (!s->fds_num) {
+ if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+ /* timeout has passed */
+ g_assert(s->fds_num);
+ break;
+ }
+ }
+
+ /* check for sanity */
+ g_assert_cmpint(s->fds_num, >, 0);
+ g_assert_cmpint(s->fds_num, ==, s->memory.nregions);
+
+ g_mutex_unlock(&s->data_mutex);
+
+ got_region = false;
+ for (i = 0; i < s->memory.nregions; ++i) {
+ VhostUserMemoryRegion *reg = &s->memory.regions[i];
+ if (reg->guest_phys_addr == 0) {
+ got_region = true;
+ break;
+ }
+ }
+ if (!got_region) {
+ g_test_skip("No memory at address 0x0");
+ }
+ return got_region;
+}
+
+static void read_guest_mem_server(QTestState *qts, TestServer *s)
+{
+ uint8_t *guest_mem;
+ int i, j;
+ size_t size;
+
+ g_mutex_lock(&s->data_mutex);
+
+ /* iterate all regions */
+ for (i = 0; i < s->fds_num; i++) {
+
+ /* We'll check only the region statring at 0x0*/
+ if (s->memory.regions[i].guest_phys_addr != 0x0) {
+ continue;
+ }
+
+ g_assert_cmpint(s->memory.regions[i].memory_size, >, 1024);
+
+ size = s->memory.regions[i].memory_size +
+ s->memory.regions[i].mmap_offset;
+
+ guest_mem = mmap(0, size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, s->fds[i], 0);
+
+ g_assert(guest_mem != MAP_FAILED);
+ guest_mem += (s->memory.regions[i].mmap_offset / sizeof(*guest_mem));
+
+ for (j = 0; j < 1024; j++) {
+ uint32_t a = qtest_readb(qts, s->memory.regions[i].guest_phys_addr + j);
+ uint32_t b = guest_mem[j];
+
+ g_assert_cmpint(a, ==, b);
+ }
+
+ munmap(guest_mem, s->memory.regions[i].memory_size);
+ }
+
+ g_mutex_unlock(&s->data_mutex);
+}
+
+static void *thread_function(void *data)
+{
+ GMainLoop *loop = data;
+ g_main_loop_run(loop);
+ return NULL;
+}
+
+static int chr_can_read(void *opaque)
+{
+ return VHOST_USER_HDR_SIZE;
+}
+
+static void chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ TestServer *s = opaque;
+ CharBackend *chr = &s->chr;
+ VhostUserMsg msg;
+ uint8_t *p = (uint8_t *) &msg;
+ int fd = -1;
+
+ if (s->test_fail) {
+ qemu_chr_fe_disconnect(chr);
+ /* now switch to non-failure */
+ s->test_fail = false;
+ }
+
+ if (size != VHOST_USER_HDR_SIZE) {
+ g_test_message("Wrong message size received %d", size);
+ return;
+ }
+
+ g_mutex_lock(&s->data_mutex);
+ memcpy(p, buf, VHOST_USER_HDR_SIZE);
+
+ if (msg.size) {
+ p += VHOST_USER_HDR_SIZE;
+ size = qemu_chr_fe_read_all(chr, p, msg.size);
+ if (size != msg.size) {
+ g_test_message("Wrong message size received %d != %d",
+ size, msg.size);
+ return;
+ }
+ }
+
+ switch (msg.request) {
+ case VHOST_USER_GET_FEATURES:
+ /* send back features to qemu */
+ msg.flags |= VHOST_USER_REPLY_MASK;
+ msg.size = sizeof(m.payload.u64);
+ msg.payload.u64 = 0x1ULL << VHOST_F_LOG_ALL |
+ 0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES;
+ if (s->queues > 1) {
+ msg.payload.u64 |= 0x1ULL << VIRTIO_NET_F_MQ;
+ }
+ if (s->test_flags >= TEST_FLAGS_BAD) {
+ msg.payload.u64 = 0;
+ s->test_flags = TEST_FLAGS_END;
+ }
+ p = (uint8_t *) &msg;
+ qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
+ break;
+
+ case VHOST_USER_SET_FEATURES:
+ g_assert_cmpint(msg.payload.u64 & (0x1ULL << VHOST_USER_F_PROTOCOL_FEATURES),
+ !=, 0ULL);
+ if (s->test_flags == TEST_FLAGS_DISCONNECT) {
+ qemu_chr_fe_disconnect(chr);
+ s->test_flags = TEST_FLAGS_BAD;
+ }
+ break;
+
+ case VHOST_USER_GET_PROTOCOL_FEATURES:
+ /* send back features to qemu */
+ msg.flags |= VHOST_USER_REPLY_MASK;
+ msg.size = sizeof(m.payload.u64);
+ msg.payload.u64 = 1 << VHOST_USER_PROTOCOL_F_LOG_SHMFD;
+ msg.payload.u64 |= 1 << VHOST_USER_PROTOCOL_F_CROSS_ENDIAN;
+ if (s->queues > 1) {
+ msg.payload.u64 |= 1 << VHOST_USER_PROTOCOL_F_MQ;
+ }
+ p = (uint8_t *) &msg;
+ qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
+ break;
+
+ case VHOST_USER_GET_VRING_BASE:
+ /* send back vring base to qemu */
+ msg.flags |= VHOST_USER_REPLY_MASK;
+ msg.size = sizeof(m.payload.state);
+ msg.payload.state.num = 0;
+ p = (uint8_t *) &msg;
+ qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
+
+ assert(msg.payload.state.index < s->queues * 2);
+ s->rings &= ~(0x1ULL << msg.payload.state.index);
+ g_cond_broadcast(&s->data_cond);
+ break;
+
+ case VHOST_USER_SET_MEM_TABLE:
+ /* received the mem table */
+ memcpy(&s->memory, &msg.payload.memory, sizeof(msg.payload.memory));
+ s->fds_num = qemu_chr_fe_get_msgfds(chr, s->fds,
+ G_N_ELEMENTS(s->fds));
+
+ /* signal the test that it can continue */
+ g_cond_broadcast(&s->data_cond);
+ break;
+
+ case VHOST_USER_SET_VRING_KICK:
+ case VHOST_USER_SET_VRING_CALL:
+ /* consume the fd */
+ qemu_chr_fe_get_msgfds(chr, &fd, 1);
+ /*
+ * This is a non-blocking eventfd.
+ * The receive function forces it to be blocking,
+ * so revert it back to non-blocking.
+ */
+ qemu_set_nonblock(fd);
+ break;
+
+ case VHOST_USER_SET_LOG_BASE:
+ if (s->log_fd != -1) {
+ close(s->log_fd);
+ s->log_fd = -1;
+ }
+ qemu_chr_fe_get_msgfds(chr, &s->log_fd, 1);
+ msg.flags |= VHOST_USER_REPLY_MASK;
+ msg.size = 0;
+ p = (uint8_t *) &msg;
+ qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE);
+
+ g_cond_broadcast(&s->data_cond);
+ break;
+
+ case VHOST_USER_SET_VRING_BASE:
+ assert(msg.payload.state.index < s->queues * 2);
+ s->rings |= 0x1ULL << msg.payload.state.index;
+ g_cond_broadcast(&s->data_cond);
+ break;
+
+ case VHOST_USER_GET_QUEUE_NUM:
+ msg.flags |= VHOST_USER_REPLY_MASK;
+ msg.size = sizeof(m.payload.u64);
+ msg.payload.u64 = s->queues;
+ p = (uint8_t *) &msg;
+ qemu_chr_fe_write_all(chr, p, VHOST_USER_HDR_SIZE + msg.size);
+ break;
+
+ default:
+ break;
+ }
+
+ g_mutex_unlock(&s->data_mutex);
+}
+
+static const char *init_hugepagefs(void)
+{
+#ifdef CONFIG_LINUX
+ static const char *hugepagefs;
+ const char *path = getenv("QTEST_HUGETLBFS_PATH");
+ struct statfs fs;
+ int ret;
+
+ if (hugepagefs) {
+ return hugepagefs;
+ }
+ if (!path) {
+ return NULL;
+ }
+
+ if (access(path, R_OK | W_OK | X_OK)) {
+ g_test_message("access on path (%s): %s", path, strerror(errno));
+ g_test_fail();
+ return NULL;
+ }
+
+ do {
+ ret = statfs(path, &fs);
+ } while (ret != 0 && errno == EINTR);
+
+ if (ret != 0) {
+ g_test_message("statfs on path (%s): %s", path, strerror(errno));
+ g_test_fail();
+ return NULL;
+ }
+
+ if (fs.f_type != HUGETLBFS_MAGIC) {
+ g_test_message("Warning: path not on HugeTLBFS: %s", path);
+ g_test_fail();
+ return NULL;
+ }
+
+ hugepagefs = path;
+ return hugepagefs;
+#else
+ return NULL;
+#endif
+}
+
+static TestServer *test_server_new(const gchar *name)
+{
+ TestServer *server = g_new0(TestServer, 1);
+ char template[] = "/tmp/vhost-test-XXXXXX";
+ const char *tmpfs;
+
+ server->context = g_main_context_new();
+ server->loop = g_main_loop_new(server->context, FALSE);
+
+ /* run the main loop thread so the chardev may operate */
+ server->thread = g_thread_new(NULL, thread_function, server->loop);
+
+ tmpfs = mkdtemp(template);
+ if (!tmpfs) {
+ g_test_message("mkdtemp on path (%s): %s", template, strerror(errno));
+ }
+ g_assert(tmpfs);
+
+ server->tmpfs = g_strdup(tmpfs);
+ server->socket_path = g_strdup_printf("%s/%s.sock", tmpfs, name);
+ server->mig_path = g_strdup_printf("%s/%s.mig", tmpfs, name);
+ server->chr_name = g_strdup_printf("chr-%s", name);
+
+ g_mutex_init(&server->data_mutex);
+ g_cond_init(&server->data_cond);
+
+ server->log_fd = -1;
+ server->queues = 1;
+
+ return server;
+}
+
+static void chr_event(void *opaque, int event)
+{
+ TestServer *s = opaque;
+
+ if (s->test_flags == TEST_FLAGS_END &&
+ event == CHR_EVENT_CLOSED) {
+ s->test_flags = TEST_FLAGS_OK;
+ }
+}
+
+static void test_server_create_chr(TestServer *server, const gchar *opt)
+{
+ gchar *chr_path;
+ Chardev *chr;
+
+ chr_path = g_strdup_printf("unix:%s%s", server->socket_path, opt);
+ chr = qemu_chr_new(server->chr_name, chr_path, server->context);
+ g_free(chr_path);
+
+ g_assert_nonnull(chr);
+ qemu_chr_fe_init(&server->chr, chr, &error_abort);
+ qemu_chr_fe_set_handlers(&server->chr, chr_can_read, chr_read,
+ chr_event, NULL, server, server->context, true);
+}
+
+static void test_server_listen(TestServer *server)
+{
+ test_server_create_chr(server, ",server,nowait");
+}
+
+static void test_server_free(TestServer *server)
+{
+ int i, ret;
+
+ /* finish the helper thread and dispatch pending sources */
+ g_main_loop_quit(server->loop);
+ g_thread_join(server->thread);
+ while (g_main_context_pending(NULL)) {
+ g_main_context_iteration(NULL, TRUE);
+ }
+
+ unlink(server->socket_path);
+ g_free(server->socket_path);
+
+ unlink(server->mig_path);
+ g_free(server->mig_path);
+
+ ret = rmdir(server->tmpfs);
+ if (ret != 0) {
+ g_test_message("unable to rmdir: path (%s): %s",
+ server->tmpfs, strerror(errno));
+ }
+ g_free(server->tmpfs);
+
+ qemu_chr_fe_deinit(&server->chr, true);
+
+ for (i = 0; i < server->fds_num; i++) {
+ close(server->fds[i]);
+ }
+
+ if (server->log_fd != -1) {
+ close(server->log_fd);
+ }
+
+ g_free(server->chr_name);
+
+ g_main_loop_unref(server->loop);
+ g_main_context_unref(server->context);
+ g_cond_clear(&server->data_cond);
+ g_mutex_clear(&server->data_mutex);
+ g_free(server);
+}
+
+static void wait_for_log_fd(TestServer *s)
+{
+ gint64 end_time;
+
+ g_mutex_lock(&s->data_mutex);
+ end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ while (s->log_fd == -1) {
+ if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+ /* timeout has passed */
+ g_assert(s->log_fd != -1);
+ break;
+ }
+ }
+
+ g_mutex_unlock(&s->data_mutex);
+}
+
+static void write_guest_mem(TestServer *s, uint32_t seed)
+{
+ uint32_t *guest_mem;
+ int i, j;
+ size_t size;
+
+ /* iterate all regions */
+ for (i = 0; i < s->fds_num; i++) {
+
+ /* We'll write only the region statring at 0x0 */
+ if (s->memory.regions[i].guest_phys_addr != 0x0) {
+ continue;
+ }
+
+ g_assert_cmpint(s->memory.regions[i].memory_size, >, 1024);
+
+ size = s->memory.regions[i].memory_size +
+ s->memory.regions[i].mmap_offset;
+
+ guest_mem = mmap(0, size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, s->fds[i], 0);
+
+ g_assert(guest_mem != MAP_FAILED);
+ guest_mem += (s->memory.regions[i].mmap_offset / sizeof(*guest_mem));
+
+ for (j = 0; j < 256; j++) {
+ guest_mem[j] = seed + j;
+ }
+
+ munmap(guest_mem, s->memory.regions[i].memory_size);
+ break;
+ }
+}
+
+static guint64 get_log_size(TestServer *s)
+{
+ guint64 log_size = 0;
+ int i;
+
+ for (i = 0; i < s->memory.nregions; ++i) {
+ VhostUserMemoryRegion *reg = &s->memory.regions[i];
+ guint64 last = range_get_last(reg->guest_phys_addr,
+ reg->memory_size);
+ log_size = MAX(log_size, last / (8 * VHOST_LOG_PAGE) + 1);
+ }
+
+ return log_size;
+}
+
+typedef struct TestMigrateSource {
+ GSource source;
+ TestServer *src;
+ TestServer *dest;
+} TestMigrateSource;
+
+static gboolean
+test_migrate_source_check(GSource *source)
+{
+ TestMigrateSource *t = (TestMigrateSource *)source;
+ gboolean overlap = t->src->rings && t->dest->rings;
+
+ g_assert(!overlap);
+
+ return FALSE;
+}
+
+GSourceFuncs test_migrate_source_funcs = {
+ .check = test_migrate_source_check,
+};
+
+static void vhost_user_test_cleanup(void *s)
+{
+ TestServer *server = s;
+
+ qos_invalidate_command_line();
+ test_server_free(server);
+}
+
+static void *vhost_user_test_setup(GString *cmd_line, void *arg)
+{
+ TestServer *server = test_server_new("vhost-user-test");
+ test_server_listen(server);
+
+ append_mem_opts(server, cmd_line, 256, TEST_MEMFD_AUTO);
+ append_vhost_opts(server, cmd_line, "");
+
+ g_test_queue_destroy(vhost_user_test_cleanup, server);
+
+ return server;
+}
+
+static void *vhost_user_test_setup_memfd(GString *cmd_line, void *arg)
+{
+ TestServer *server = test_server_new("vhost-user-test");
+ test_server_listen(server);
+
+ append_mem_opts(server, cmd_line, 256, TEST_MEMFD_YES);
+ append_vhost_opts(server, cmd_line, "");
+
+ g_test_queue_destroy(vhost_user_test_cleanup, server);
+
+ return server;
+}
+
+static void test_read_guest_mem(void *obj, void *arg, QGuestAllocator *alloc)
+{
+ TestServer *server = arg;
+
+ if (!wait_for_fds(server)) {
+ return;
+ }
+
+ read_guest_mem_server(global_qtest, server);
+}
+
+static void test_migrate(void *obj, void *arg, QGuestAllocator *alloc)
+{
+ TestServer *s = arg;
+ TestServer *dest = test_server_new("dest");
+ GString *dest_cmdline = g_string_new(qos_get_current_command_line());
+ char *uri = g_strdup_printf("%s%s", "unix:", dest->mig_path);
+ QTestState *to;
+ GSource *source;
+ QDict *rsp;
+ guint8 *log;
+ guint64 size;
+
+ if (!wait_for_fds(s)) {
+ return;
+ }
+
+ size = get_log_size(s);
+ g_assert_cmpint(size, ==, (256 * 1024 * 1024) / (VHOST_LOG_PAGE * 8));
+
+ test_server_listen(dest);
+ g_string_append_printf(dest_cmdline, " -incoming %s", uri);
+ append_mem_opts(dest, dest_cmdline, 256, TEST_MEMFD_AUTO);
+ append_vhost_opts(dest, dest_cmdline, "");
+ to = qtest_init(dest_cmdline->str);
+
+ /* This would be where you call qos_allocate_objects(to, NULL), if you want
+ * to talk to the QVirtioNet object on the destination.
+ */
+
+ source = g_source_new(&test_migrate_source_funcs,
+ sizeof(TestMigrateSource));
+ ((TestMigrateSource *)source)->src = s;
+ ((TestMigrateSource *)source)->dest = dest;
+ g_source_attach(source, s->context);
+
+ /* slow down migration to have time to fiddle with log */
+ /* TODO: qtest could learn to break on some places */
+ rsp = qmp("{ 'execute': 'migrate_set_speed',"
+ "'arguments': { 'value': 10 } }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+
+ rsp = qmp("{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", uri);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+
+ wait_for_log_fd(s);
+
+ log = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, s->log_fd, 0);
+ g_assert(log != MAP_FAILED);
+
+ /* modify first page */
+ write_guest_mem(s, 0x42);
+ log[0] = 1;
+ munmap(log, size);
+
+ /* speed things up */
+ rsp = qmp("{ 'execute': 'migrate_set_speed',"
+ "'arguments': { 'value': 0 } }");
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+
+ qmp_eventwait("STOP");
+ qtest_qmp_eventwait(to, "RESUME");
+
+ g_assert(wait_for_fds(dest));
+ read_guest_mem_server(to, dest);
+
+ g_source_destroy(source);
+ g_source_unref(source);
+
+ qtest_quit(to);
+ test_server_free(dest);
+ g_free(uri);
+}
+
+static void wait_for_rings_started(TestServer *s, size_t count)
+{
+ gint64 end_time;
+
+ g_mutex_lock(&s->data_mutex);
+ end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ while (ctpop64(s->rings) != count) {
+ if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+ /* timeout has passed */
+ g_assert_cmpint(ctpop64(s->rings), ==, count);
+ break;
+ }
+ }
+
+ g_mutex_unlock(&s->data_mutex);
+}
+
+static inline void test_server_connect(TestServer *server)
+{
+ test_server_create_chr(server, ",reconnect=1");
+}
+
+static gboolean
+reconnect_cb(gpointer user_data)
+{
+ TestServer *s = user_data;
+
+ qemu_chr_fe_disconnect(&s->chr);
+
+ return FALSE;
+}
+
+static gpointer
+connect_thread(gpointer data)
+{
+ TestServer *s = data;
+
+ /* wait for qemu to start before first try, to avoid extra warnings */
+ g_usleep(G_USEC_PER_SEC);
+ test_server_connect(s);
+
+ return NULL;
+}
+
+static void *vhost_user_test_setup_reconnect(GString *cmd_line, void *arg)
+{
+ TestServer *s = test_server_new("reconnect");
+
+ g_thread_new("connect", connect_thread, s);
+ append_mem_opts(s, cmd_line, 256, TEST_MEMFD_AUTO);
+ append_vhost_opts(s, cmd_line, ",server");
+
+ g_test_queue_destroy(vhost_user_test_cleanup, s);
+
+ return s;
+}
+
+static void test_reconnect(void *obj, void *arg, QGuestAllocator *alloc)
+{
+ TestServer *s = arg;
+ GSource *src;
+
+ if (!wait_for_fds(s)) {
+ return;
+ }
+
+ wait_for_rings_started(s, 2);
+
+ /* reconnect */
+ s->fds_num = 0;
+ s->rings = 0;
+ src = g_idle_source_new();
+ g_source_set_callback(src, reconnect_cb, s, NULL);
+ g_source_attach(src, s->context);
+ g_source_unref(src);
+ g_assert(wait_for_fds(s));
+ wait_for_rings_started(s, 2);
+}
+
+static void *vhost_user_test_setup_connect_fail(GString *cmd_line, void *arg)
+{
+ TestServer *s = test_server_new("connect-fail");
+
+ s->test_fail = true;
+
+ g_thread_new("connect", connect_thread, s);
+ append_mem_opts(s, cmd_line, 256, TEST_MEMFD_AUTO);
+ append_vhost_opts(s, cmd_line, ",server");
+
+ g_test_queue_destroy(vhost_user_test_cleanup, s);
+
+ return s;
+}
+
+static void *vhost_user_test_setup_flags_mismatch(GString *cmd_line, void *arg)
+{
+ TestServer *s = test_server_new("flags-mismatch");
+
+ s->test_flags = TEST_FLAGS_DISCONNECT;
+
+ g_thread_new("connect", connect_thread, s);
+ append_mem_opts(s, cmd_line, 256, TEST_MEMFD_AUTO);
+ append_vhost_opts(s, cmd_line, ",server");
+
+ g_test_queue_destroy(vhost_user_test_cleanup, s);
+
+ return s;
+}
+
+static void test_vhost_user_started(void *obj, void *arg, QGuestAllocator *alloc)
+{
+ TestServer *s = arg;
+
+ if (!wait_for_fds(s)) {
+ return;
+ }
+ wait_for_rings_started(s, 2);
+}
+
+static void *vhost_user_test_setup_multiqueue(GString *cmd_line, void *arg)
+{
+ TestServer *s = vhost_user_test_setup(cmd_line, arg);
+
+ s->queues = 2;
+ g_string_append_printf(cmd_line,
+ " -set netdev.hs0.queues=%d"
+ " -global virtio-net-pci.vectors=%d",
+ s->queues, s->queues * 2 + 2);
+
+ return s;
+}
+
+static void test_multiqueue(void *obj, void *arg, QGuestAllocator *alloc)
+{
+ TestServer *s = arg;
+
+ wait_for_rings_started(s, s->queues * 2);
+}
+
+static void register_vhost_user_test(void)
+{
+ QOSGraphTestOptions opts = {
+ .before = vhost_user_test_setup,
+ .subprocess = true,
+ };
+
+ qemu_add_opts(&qemu_chardev_opts);
+
+ qos_add_test("vhost-user/read-guest-mem/memfile",
+ "virtio-net",
+ test_read_guest_mem, &opts);
+
+ if (qemu_memfd_check(MFD_ALLOW_SEALING)) {
+ opts.before = vhost_user_test_setup_memfd;
+ qos_add_test("vhost-user/read-guest-mem/memfd",
+ "virtio-net",
+ test_read_guest_mem, &opts);
+ }
+
+ qos_add_test("vhost-user/migrate",
+ "virtio-net",
+ test_migrate, &opts);
+
+ /* keeps failing on build-system since Aug 15 2017 */
+ if (getenv("QTEST_VHOST_USER_FIXME")) {
+ opts.before = vhost_user_test_setup_reconnect;
+ qos_add_test("vhost-user/reconnect", "virtio-net",
+ test_reconnect, &opts);
+
+ opts.before = vhost_user_test_setup_connect_fail;
+ qos_add_test("vhost-user/connect-fail", "virtio-net",
+ test_vhost_user_started, &opts);
+
+ opts.before = vhost_user_test_setup_flags_mismatch;
+ qos_add_test("vhost-user/flags-mismatch", "virtio-net",
+ test_vhost_user_started, &opts);
+ }
+
+ opts.before = vhost_user_test_setup_multiqueue;
+ opts.edge.extra_device_opts = "mq=on";
+ qos_add_test("vhost-user/multiqueue",
+ "virtio-net",
+ test_multiqueue, &opts);
+}
+libqos_init(register_vhost_user_test);
diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
new file mode 100644
index 0000000000..e7b58e3a0c
--- /dev/null
+++ b/tests/qtest/virtio-9p-test.c
@@ -0,0 +1,662 @@
+/*
+ * QTest testcase for VirtIO 9P
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "hw/9pfs/9p.h"
+#include "hw/9pfs/9p-synth.h"
+#include "libqos/virtio-9p.h"
+#include "libqos/qgraph.h"
+
+#define QVIRTIO_9P_TIMEOUT_US (10 * 1000 * 1000)
+static QGuestAllocator *alloc;
+
+static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ size_t tag_len = qvirtio_config_readw(v9p->vdev, 0);
+ char *tag;
+ int i;
+
+ g_assert_cmpint(tag_len, ==, strlen(MOUNT_TAG));
+
+ tag = g_malloc(tag_len);
+ for (i = 0; i < tag_len; i++) {
+ tag[i] = qvirtio_config_readb(v9p->vdev, i + 2);
+ }
+ g_assert_cmpmem(tag, tag_len, MOUNT_TAG, tag_len);
+ g_free(tag);
+}
+
+#define P9_MAX_SIZE 4096 /* Max size of a T-message or R-message */
+
+typedef struct {
+ QTestState *qts;
+ QVirtio9P *v9p;
+ uint16_t tag;
+ uint64_t t_msg;
+ uint32_t t_size;
+ uint64_t r_msg;
+ /* No r_size, it is hardcoded to P9_MAX_SIZE */
+ size_t t_off;
+ size_t r_off;
+ uint32_t free_head;
+} P9Req;
+
+static void v9fs_memwrite(P9Req *req, const void *addr, size_t len)
+{
+ qtest_memwrite(req->qts, req->t_msg + req->t_off, addr, len);
+ req->t_off += len;
+}
+
+static void v9fs_memskip(P9Req *req, size_t len)
+{
+ req->r_off += len;
+}
+
+static void v9fs_memread(P9Req *req, void *addr, size_t len)
+{
+ qtest_memread(req->qts, req->r_msg + req->r_off, addr, len);
+ req->r_off += len;
+}
+
+static void v9fs_uint16_write(P9Req *req, uint16_t val)
+{
+ uint16_t le_val = cpu_to_le16(val);
+
+ v9fs_memwrite(req, &le_val, 2);
+}
+
+static void v9fs_uint16_read(P9Req *req, uint16_t *val)
+{
+ v9fs_memread(req, val, 2);
+ le16_to_cpus(val);
+}
+
+static void v9fs_uint32_write(P9Req *req, uint32_t val)
+{
+ uint32_t le_val = cpu_to_le32(val);
+
+ v9fs_memwrite(req, &le_val, 4);
+}
+
+static void v9fs_uint64_write(P9Req *req, uint64_t val)
+{
+ uint64_t le_val = cpu_to_le64(val);
+
+ v9fs_memwrite(req, &le_val, 8);
+}
+
+static void v9fs_uint32_read(P9Req *req, uint32_t *val)
+{
+ v9fs_memread(req, val, 4);
+ le32_to_cpus(val);
+}
+
+/* len[2] string[len] */
+static uint16_t v9fs_string_size(const char *string)
+{
+ size_t len = strlen(string);
+
+ g_assert_cmpint(len, <=, UINT16_MAX - 2);
+
+ return 2 + len;
+}
+
+static void v9fs_string_write(P9Req *req, const char *string)
+{
+ int len = strlen(string);
+
+ g_assert_cmpint(len, <=, UINT16_MAX);
+
+ v9fs_uint16_write(req, (uint16_t) len);
+ v9fs_memwrite(req, string, len);
+}
+
+static void v9fs_string_read(P9Req *req, uint16_t *len, char **string)
+{
+ uint16_t local_len;
+
+ v9fs_uint16_read(req, &local_len);
+ if (len) {
+ *len = local_len;
+ }
+ if (string) {
+ *string = g_malloc(local_len);
+ v9fs_memread(req, *string, local_len);
+ } else {
+ v9fs_memskip(req, local_len);
+ }
+}
+
+ typedef struct {
+ uint32_t size;
+ uint8_t id;
+ uint16_t tag;
+} QEMU_PACKED P9Hdr;
+
+static P9Req *v9fs_req_init(QVirtio9P *v9p, uint32_t size, uint8_t id,
+ uint16_t tag)
+{
+ P9Req *req = g_new0(P9Req, 1);
+ uint32_t total_size = 7; /* 9P header has well-known size of 7 bytes */
+ P9Hdr hdr = {
+ .id = id,
+ .tag = cpu_to_le16(tag)
+ };
+
+ g_assert_cmpint(total_size, <=, UINT32_MAX - size);
+ total_size += size;
+ hdr.size = cpu_to_le32(total_size);
+
+ g_assert_cmpint(total_size, <=, P9_MAX_SIZE);
+
+ req->qts = global_qtest;
+ req->v9p = v9p;
+ req->t_size = total_size;
+ req->t_msg = guest_alloc(alloc, req->t_size);
+ v9fs_memwrite(req, &hdr, 7);
+ req->tag = tag;
+ return req;
+}
+
+static void v9fs_req_send(P9Req *req)
+{
+ QVirtio9P *v9p = req->v9p;
+
+ req->r_msg = guest_alloc(alloc, P9_MAX_SIZE);
+ req->free_head = qvirtqueue_add(req->qts, v9p->vq, req->t_msg, req->t_size,
+ false, true);
+ qvirtqueue_add(req->qts, v9p->vq, req->r_msg, P9_MAX_SIZE, true, false);
+ qvirtqueue_kick(req->qts, v9p->vdev, v9p->vq, req->free_head);
+ req->t_off = 0;
+}
+
+static const char *rmessage_name(uint8_t id)
+{
+ return
+ id == P9_RLERROR ? "RLERROR" :
+ id == P9_RVERSION ? "RVERSION" :
+ id == P9_RATTACH ? "RATTACH" :
+ id == P9_RWALK ? "RWALK" :
+ id == P9_RLOPEN ? "RLOPEN" :
+ id == P9_RWRITE ? "RWRITE" :
+ id == P9_RFLUSH ? "RFLUSH" :
+ "<unknown>";
+}
+
+static void v9fs_req_wait_for_reply(P9Req *req, uint32_t *len)
+{
+ QVirtio9P *v9p = req->v9p;
+
+ qvirtio_wait_used_elem(req->qts, v9p->vdev, v9p->vq, req->free_head, len,
+ QVIRTIO_9P_TIMEOUT_US);
+}
+
+static void v9fs_req_recv(P9Req *req, uint8_t id)
+{
+ P9Hdr hdr;
+
+ v9fs_memread(req, &hdr, 7);
+ hdr.size = ldl_le_p(&hdr.size);
+ hdr.tag = lduw_le_p(&hdr.tag);
+
+ g_assert_cmpint(hdr.size, >=, 7);
+ g_assert_cmpint(hdr.size, <=, P9_MAX_SIZE);
+ g_assert_cmpint(hdr.tag, ==, req->tag);
+
+ if (hdr.id != id) {
+ g_printerr("Received response %d (%s) instead of %d (%s)\n",
+ hdr.id, rmessage_name(hdr.id), id, rmessage_name(id));
+
+ if (hdr.id == P9_RLERROR) {
+ uint32_t err;
+ v9fs_uint32_read(req, &err);
+ g_printerr("Rlerror has errno %d (%s)\n", err, strerror(err));
+ }
+ }
+ g_assert_cmpint(hdr.id, ==, id);
+}
+
+static void v9fs_req_free(P9Req *req)
+{
+ guest_free(alloc, req->t_msg);
+ guest_free(alloc, req->r_msg);
+ g_free(req);
+}
+
+/* size[4] Rlerror tag[2] ecode[4] */
+static void v9fs_rlerror(P9Req *req, uint32_t *err)
+{
+ v9fs_req_recv(req, P9_RLERROR);
+ v9fs_uint32_read(req, err);
+ v9fs_req_free(req);
+}
+
+/* size[4] Tversion tag[2] msize[4] version[s] */
+static P9Req *v9fs_tversion(QVirtio9P *v9p, uint32_t msize, const char *version,
+ uint16_t tag)
+{
+ P9Req *req;
+ uint32_t body_size = 4;
+ uint16_t string_size = v9fs_string_size(version);
+
+ g_assert_cmpint(body_size, <=, UINT32_MAX - string_size);
+ body_size += string_size;
+ req = v9fs_req_init(v9p, body_size, P9_TVERSION, tag);
+
+ v9fs_uint32_write(req, msize);
+ v9fs_string_write(req, version);
+ v9fs_req_send(req);
+ return req;
+}
+
+/* size[4] Rversion tag[2] msize[4] version[s] */
+static void v9fs_rversion(P9Req *req, uint16_t *len, char **version)
+{
+ uint32_t msize;
+
+ v9fs_req_recv(req, P9_RVERSION);
+ v9fs_uint32_read(req, &msize);
+
+ g_assert_cmpint(msize, ==, P9_MAX_SIZE);
+
+ if (len || version) {
+ v9fs_string_read(req, len, version);
+ }
+
+ v9fs_req_free(req);
+}
+
+/* size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s] n_uname[4] */
+static P9Req *v9fs_tattach(QVirtio9P *v9p, uint32_t fid, uint32_t n_uname,
+ uint16_t tag)
+{
+ const char *uname = ""; /* ignored by QEMU */
+ const char *aname = ""; /* ignored by QEMU */
+ P9Req *req = v9fs_req_init(v9p, 4 + 4 + 2 + 2 + 4, P9_TATTACH, tag);
+
+ v9fs_uint32_write(req, fid);
+ v9fs_uint32_write(req, P9_NOFID);
+ v9fs_string_write(req, uname);
+ v9fs_string_write(req, aname);
+ v9fs_uint32_write(req, n_uname);
+ v9fs_req_send(req);
+ return req;
+}
+
+typedef char v9fs_qid[13];
+
+/* size[4] Rattach tag[2] qid[13] */
+static void v9fs_rattach(P9Req *req, v9fs_qid *qid)
+{
+ v9fs_req_recv(req, P9_RATTACH);
+ if (qid) {
+ v9fs_memread(req, qid, 13);
+ }
+ v9fs_req_free(req);
+}
+
+/* size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s]) */
+static P9Req *v9fs_twalk(QVirtio9P *v9p, uint32_t fid, uint32_t newfid,
+ uint16_t nwname, char *const wnames[], uint16_t tag)
+{
+ P9Req *req;
+ int i;
+ uint32_t body_size = 4 + 4 + 2;
+
+ for (i = 0; i < nwname; i++) {
+ uint16_t wname_size = v9fs_string_size(wnames[i]);
+
+ g_assert_cmpint(body_size, <=, UINT32_MAX - wname_size);
+ body_size += wname_size;
+ }
+ req = v9fs_req_init(v9p, body_size, P9_TWALK, tag);
+ v9fs_uint32_write(req, fid);
+ v9fs_uint32_write(req, newfid);
+ v9fs_uint16_write(req, nwname);
+ for (i = 0; i < nwname; i++) {
+ v9fs_string_write(req, wnames[i]);
+ }
+ v9fs_req_send(req);
+ return req;
+}
+
+/* size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13]) */
+static void v9fs_rwalk(P9Req *req, uint16_t *nwqid, v9fs_qid **wqid)
+{
+ uint16_t local_nwqid;
+
+ v9fs_req_recv(req, P9_RWALK);
+ v9fs_uint16_read(req, &local_nwqid);
+ if (nwqid) {
+ *nwqid = local_nwqid;
+ }
+ if (wqid) {
+ *wqid = g_malloc(local_nwqid * 13);
+ v9fs_memread(req, *wqid, local_nwqid * 13);
+ }
+ v9fs_req_free(req);
+}
+
+/* size[4] Tlopen tag[2] fid[4] flags[4] */
+static P9Req *v9fs_tlopen(QVirtio9P *v9p, uint32_t fid, uint32_t flags,
+ uint16_t tag)
+{
+ P9Req *req;
+
+ req = v9fs_req_init(v9p, 4 + 4, P9_TLOPEN, tag);
+ v9fs_uint32_write(req, fid);
+ v9fs_uint32_write(req, flags);
+ v9fs_req_send(req);
+ return req;
+}
+
+/* size[4] Rlopen tag[2] qid[13] iounit[4] */
+static void v9fs_rlopen(P9Req *req, v9fs_qid *qid, uint32_t *iounit)
+{
+ v9fs_req_recv(req, P9_RLOPEN);
+ if (qid) {
+ v9fs_memread(req, qid, 13);
+ } else {
+ v9fs_memskip(req, 13);
+ }
+ if (iounit) {
+ v9fs_uint32_read(req, iounit);
+ }
+ v9fs_req_free(req);
+}
+
+/* size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count] */
+static P9Req *v9fs_twrite(QVirtio9P *v9p, uint32_t fid, uint64_t offset,
+ uint32_t count, const void *data, uint16_t tag)
+{
+ P9Req *req;
+ uint32_t body_size = 4 + 8 + 4;
+
+ g_assert_cmpint(body_size, <=, UINT32_MAX - count);
+ body_size += count;
+ req = v9fs_req_init(v9p, body_size, P9_TWRITE, tag);
+ v9fs_uint32_write(req, fid);
+ v9fs_uint64_write(req, offset);
+ v9fs_uint32_write(req, count);
+ v9fs_memwrite(req, data, count);
+ v9fs_req_send(req);
+ return req;
+}
+
+/* size[4] Rwrite tag[2] count[4] */
+static void v9fs_rwrite(P9Req *req, uint32_t *count)
+{
+ v9fs_req_recv(req, P9_RWRITE);
+ if (count) {
+ v9fs_uint32_read(req, count);
+ }
+ v9fs_req_free(req);
+}
+
+/* size[4] Tflush tag[2] oldtag[2] */
+static P9Req *v9fs_tflush(QVirtio9P *v9p, uint16_t oldtag, uint16_t tag)
+{
+ P9Req *req;
+
+ req = v9fs_req_init(v9p, 2, P9_TFLUSH, tag);
+ v9fs_uint32_write(req, oldtag);
+ v9fs_req_send(req);
+ return req;
+}
+
+/* size[4] Rflush tag[2] */
+static void v9fs_rflush(P9Req *req)
+{
+ v9fs_req_recv(req, P9_RFLUSH);
+ v9fs_req_free(req);
+}
+
+static void fs_version(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ const char *version = "9P2000.L";
+ uint16_t server_len;
+ char *server_version;
+ P9Req *req;
+
+ req = v9fs_tversion(v9p, P9_MAX_SIZE, version, P9_NOTAG);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rversion(req, &server_len, &server_version);
+
+ g_assert_cmpmem(server_version, server_len, version, strlen(version));
+
+ g_free(server_version);
+}
+
+static void fs_attach(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ P9Req *req;
+
+ fs_version(v9p, NULL, t_alloc);
+ req = v9fs_tattach(v9p, 0, getuid(), 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rattach(req, NULL);
+}
+
+static void fs_walk(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ char *wnames[P9_MAXWELEM];
+ uint16_t nwqid;
+ v9fs_qid *wqid;
+ int i;
+ P9Req *req;
+
+ for (i = 0; i < P9_MAXWELEM; i++) {
+ wnames[i] = g_strdup_printf(QTEST_V9FS_SYNTH_WALK_FILE, i);
+ }
+
+ fs_attach(v9p, NULL, t_alloc);
+ req = v9fs_twalk(v9p, 0, 1, P9_MAXWELEM, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwalk(req, &nwqid, &wqid);
+
+ g_assert_cmpint(nwqid, ==, P9_MAXWELEM);
+
+ for (i = 0; i < P9_MAXWELEM; i++) {
+ g_free(wnames[i]);
+ }
+
+ g_free(wqid);
+}
+
+static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ char *const wnames[] = { g_strdup(" /") };
+ P9Req *req;
+ uint32_t err;
+
+ fs_attach(v9p, NULL, t_alloc);
+ req = v9fs_twalk(v9p, 0, 1, 1, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rlerror(req, &err);
+
+ g_assert_cmpint(err, ==, ENOENT);
+
+ g_free(wnames[0]);
+}
+
+static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ char *const wnames[] = { g_strdup("..") };
+ v9fs_qid root_qid, *wqid;
+ P9Req *req;
+
+ fs_version(v9p, NULL, t_alloc);
+ req = v9fs_tattach(v9p, 0, getuid(), 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rattach(req, &root_qid);
+
+ req = v9fs_twalk(v9p, 0, 1, 1, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwalk(req, NULL, &wqid); /* We now we'll get one qid */
+
+ g_assert_cmpmem(&root_qid, 13, wqid[0], 13);
+
+ g_free(wqid);
+ g_free(wnames[0]);
+}
+
+static void fs_lopen(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ char *const wnames[] = { g_strdup(QTEST_V9FS_SYNTH_LOPEN_FILE) };
+ P9Req *req;
+
+ fs_attach(v9p, NULL, t_alloc);
+ req = v9fs_twalk(v9p, 0, 1, 1, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwalk(req, NULL, NULL);
+
+ req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rlopen(req, NULL, NULL);
+
+ g_free(wnames[0]);
+}
+
+static void fs_write(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ static const uint32_t write_count = P9_MAX_SIZE / 2;
+ char *const wnames[] = { g_strdup(QTEST_V9FS_SYNTH_WRITE_FILE) };
+ char *buf = g_malloc0(write_count);
+ uint32_t count;
+ P9Req *req;
+
+ fs_attach(v9p, NULL, t_alloc);
+ req = v9fs_twalk(v9p, 0, 1, 1, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwalk(req, NULL, NULL);
+
+ req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rlopen(req, NULL, NULL);
+
+ req = v9fs_twrite(v9p, 1, 0, write_count, buf, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwrite(req, &count);
+ g_assert_cmpint(count, ==, write_count);
+
+ g_free(buf);
+ g_free(wnames[0]);
+}
+
+static void fs_flush_success(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ char *const wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
+ P9Req *req, *flush_req;
+ uint32_t reply_len;
+ uint8_t should_block;
+
+ fs_attach(v9p, NULL, t_alloc);
+ req = v9fs_twalk(v9p, 0, 1, 1, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwalk(req, NULL, NULL);
+
+ req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rlopen(req, NULL, NULL);
+
+ /* This will cause the 9p server to try to write data to the backend,
+ * until the write request gets cancelled.
+ */
+ should_block = 1;
+ req = v9fs_twrite(v9p, 1, 0, sizeof(should_block), &should_block, 0);
+
+ flush_req = v9fs_tflush(v9p, req->tag, 1);
+
+ /* The write request is supposed to be flushed: the server should just
+ * mark the write request as used and reply to the flush request.
+ */
+ v9fs_req_wait_for_reply(req, &reply_len);
+ g_assert_cmpint(reply_len, ==, 0);
+ v9fs_req_free(req);
+ v9fs_rflush(flush_req);
+
+ g_free(wnames[0]);
+}
+
+static void fs_flush_ignored(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtio9P *v9p = obj;
+ alloc = t_alloc;
+ char *const wnames[] = { g_strdup(QTEST_V9FS_SYNTH_FLUSH_FILE) };
+ P9Req *req, *flush_req;
+ uint32_t count;
+ uint8_t should_block;
+
+ fs_attach(v9p, NULL, t_alloc);
+ req = v9fs_twalk(v9p, 0, 1, 1, wnames, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwalk(req, NULL, NULL);
+
+ req = v9fs_tlopen(v9p, 1, O_WRONLY, 0);
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rlopen(req, NULL, NULL);
+
+ /* This will cause the write request to complete right away, before it
+ * could be actually cancelled.
+ */
+ should_block = 0;
+ req = v9fs_twrite(v9p, 1, 0, sizeof(should_block), &should_block, 0);
+
+ flush_req = v9fs_tflush(v9p, req->tag, 1);
+
+ /* The write request is supposed to complete. The server should
+ * reply to the write request and the flush request.
+ */
+ v9fs_req_wait_for_reply(req, NULL);
+ v9fs_rwrite(req, &count);
+ g_assert_cmpint(count, ==, sizeof(should_block));
+ v9fs_rflush(flush_req);
+
+ g_free(wnames[0]);
+}
+
+static void register_virtio_9p_test(void)
+{
+ qos_add_test("config", "virtio-9p", pci_config, NULL);
+ qos_add_test("fs/version/basic", "virtio-9p", fs_version, NULL);
+ qos_add_test("fs/attach/basic", "virtio-9p", fs_attach, NULL);
+ qos_add_test("fs/walk/basic", "virtio-9p", fs_walk, NULL);
+ qos_add_test("fs/walk/no_slash", "virtio-9p", fs_walk_no_slash,
+ NULL);
+ qos_add_test("fs/walk/dotdot_from_root", "virtio-9p",
+ fs_walk_dotdot, NULL);
+ qos_add_test("fs/lopen/basic", "virtio-9p", fs_lopen, NULL);
+ qos_add_test("fs/write/basic", "virtio-9p", fs_write, NULL);
+ qos_add_test("fs/flush/success", "virtio-9p", fs_flush_success,
+ NULL);
+ qos_add_test("fs/flush/ignored", "virtio-9p", fs_flush_ignored,
+ NULL);
+}
+
+libqos_init(register_virtio_9p_test);
diff --git a/tests/qtest/virtio-blk-test.c b/tests/qtest/virtio-blk-test.c
new file mode 100644
index 0000000000..2a23698211
--- /dev/null
+++ b/tests/qtest/virtio-blk-test.c
@@ -0,0 +1,802 @@
+/*
+ * QTest testcase for VirtIO Block Device
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ * Copyright (c) 2014 Marc Marí
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/bswap.h"
+#include "qemu/module.h"
+#include "standard-headers/linux/virtio_blk.h"
+#include "standard-headers/linux/virtio_pci.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-blk.h"
+
+/* TODO actually test the results and get rid of this */
+#define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__))
+
+#define TEST_IMAGE_SIZE (64 * 1024 * 1024)
+#define QVIRTIO_BLK_TIMEOUT_US (30 * 1000 * 1000)
+#define PCI_SLOT_HP 0x06
+
+typedef struct QVirtioBlkReq {
+ uint32_t type;
+ uint32_t ioprio;
+ uint64_t sector;
+ char *data;
+ uint8_t status;
+} QVirtioBlkReq;
+
+
+#ifdef HOST_WORDS_BIGENDIAN
+const bool host_is_big_endian = true;
+#else
+const bool host_is_big_endian; /* false */
+#endif
+
+static void drive_destroy(void *path)
+{
+ unlink(path);
+ g_free(path);
+ qos_invalidate_command_line();
+}
+
+static char *drive_create(void)
+{
+ int fd, ret;
+ char *t_path = g_strdup("/tmp/qtest.XXXXXX");
+
+ /* Create a temporary raw image */
+ fd = mkstemp(t_path);
+ g_assert_cmpint(fd, >=, 0);
+ ret = ftruncate(fd, TEST_IMAGE_SIZE);
+ g_assert_cmpint(ret, ==, 0);
+ close(fd);
+
+ g_test_queue_destroy(drive_destroy, t_path);
+ return t_path;
+}
+
+static inline void virtio_blk_fix_request(QVirtioDevice *d, QVirtioBlkReq *req)
+{
+ if (qvirtio_is_big_endian(d) != host_is_big_endian) {
+ req->type = bswap32(req->type);
+ req->ioprio = bswap32(req->ioprio);
+ req->sector = bswap64(req->sector);
+ }
+}
+
+
+static inline void virtio_blk_fix_dwz_hdr(QVirtioDevice *d,
+ struct virtio_blk_discard_write_zeroes *dwz_hdr)
+{
+ if (qvirtio_is_big_endian(d) != host_is_big_endian) {
+ dwz_hdr->sector = bswap64(dwz_hdr->sector);
+ dwz_hdr->num_sectors = bswap32(dwz_hdr->num_sectors);
+ dwz_hdr->flags = bswap32(dwz_hdr->flags);
+ }
+}
+
+static uint64_t virtio_blk_request(QGuestAllocator *alloc, QVirtioDevice *d,
+ QVirtioBlkReq *req, uint64_t data_size)
+{
+ uint64_t addr;
+ uint8_t status = 0xFF;
+
+ switch (req->type) {
+ case VIRTIO_BLK_T_IN:
+ case VIRTIO_BLK_T_OUT:
+ g_assert_cmpuint(data_size % 512, ==, 0);
+ break;
+ case VIRTIO_BLK_T_DISCARD:
+ case VIRTIO_BLK_T_WRITE_ZEROES:
+ g_assert_cmpuint(data_size %
+ sizeof(struct virtio_blk_discard_write_zeroes), ==, 0);
+ break;
+ default:
+ g_assert_cmpuint(data_size, ==, 0);
+ }
+
+ addr = guest_alloc(alloc, sizeof(*req) + data_size);
+
+ virtio_blk_fix_request(d, req);
+
+ memwrite(addr, req, 16);
+ memwrite(addr + 16, req->data, data_size);
+ memwrite(addr + 16 + data_size, &status, sizeof(status));
+
+ return addr;
+}
+
+/* Returns the request virtqueue so the caller can perform further tests */
+static QVirtQueue *test_basic(QVirtioDevice *dev, QGuestAllocator *alloc)
+{
+ QVirtioBlkReq req;
+ uint64_t req_addr;
+ uint64_t capacity;
+ uint64_t features;
+ uint32_t free_head;
+ uint8_t status;
+ char *data;
+ QTestState *qts = global_qtest;
+ QVirtQueue *vq;
+
+ features = qvirtio_get_features(dev);
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1u << VIRTIO_RING_F_EVENT_IDX) |
+ (1u << VIRTIO_BLK_F_SCSI));
+ qvirtio_set_features(dev, features);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
+
+ vq = qvirtqueue_setup(dev, alloc, 0);
+
+ qvirtio_set_driver_ok(dev);
+
+ /* Write and read with 3 descriptor layout */
+ /* Write request */
+ req.type = VIRTIO_BLK_T_OUT;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ guest_free(alloc, req_addr);
+
+ /* Read request */
+ req.type = VIRTIO_BLK_T_IN;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ data = g_malloc0(512);
+ memread(req_addr + 16, data, 512);
+ g_assert_cmpstr(data, ==, "TEST");
+ g_free(data);
+
+ guest_free(alloc, req_addr);
+
+ if (features & (1u << VIRTIO_BLK_F_WRITE_ZEROES)) {
+ struct virtio_blk_discard_write_zeroes dwz_hdr;
+ void *expected;
+
+ /*
+ * WRITE_ZEROES request on the same sector of previous test where
+ * we wrote "TEST".
+ */
+ req.type = VIRTIO_BLK_T_WRITE_ZEROES;
+ req.data = (char *) &dwz_hdr;
+ dwz_hdr.sector = 0;
+ dwz_hdr.num_sectors = 1;
+ dwz_hdr.flags = 0;
+
+ virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
+
+ req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true,
+ false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 16 + sizeof(dwz_hdr));
+ g_assert_cmpint(status, ==, 0);
+
+ guest_free(alloc, req_addr);
+
+ /* Read request to check if the sector contains all zeroes */
+ req.type = VIRTIO_BLK_T_IN;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ data = g_malloc(512);
+ expected = g_malloc0(512);
+ memread(req_addr + 16, data, 512);
+ g_assert_cmpmem(data, 512, expected, 512);
+ g_free(expected);
+ g_free(data);
+
+ guest_free(alloc, req_addr);
+ }
+
+ if (features & (1u << VIRTIO_BLK_F_DISCARD)) {
+ struct virtio_blk_discard_write_zeroes dwz_hdr;
+
+ req.type = VIRTIO_BLK_T_DISCARD;
+ req.data = (char *) &dwz_hdr;
+ dwz_hdr.sector = 0;
+ dwz_hdr.num_sectors = 1;
+ dwz_hdr.flags = 0;
+
+ virtio_blk_fix_dwz_hdr(dev, &dwz_hdr);
+
+ req_addr = virtio_blk_request(alloc, dev, &req, sizeof(dwz_hdr));
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, sizeof(dwz_hdr), false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16 + sizeof(dwz_hdr), 1, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 16 + sizeof(dwz_hdr));
+ g_assert_cmpint(status, ==, 0);
+
+ guest_free(alloc, req_addr);
+ }
+
+ if (features & (1u << VIRTIO_F_ANY_LAYOUT)) {
+ /* Write and read with 2 descriptor layout */
+ /* Write request */
+ req.type = VIRTIO_BLK_T_OUT;
+ req.ioprio = 1;
+ req.sector = 1;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 528, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ guest_free(alloc, req_addr);
+
+ /* Read request */
+ req.type = VIRTIO_BLK_T_IN;
+ req.ioprio = 1;
+ req.sector = 1;
+ req.data = g_malloc0(512);
+
+ req_addr = virtio_blk_request(alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 513, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ data = g_malloc0(512);
+ memread(req_addr + 16, data, 512);
+ g_assert_cmpstr(data, ==, "TEST");
+ g_free(data);
+
+ guest_free(alloc, req_addr);
+ }
+
+ return vq;
+}
+
+static void basic(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioBlk *blk_if = obj;
+ QVirtQueue *vq;
+
+ vq = test_basic(blk_if->vdev, t_alloc);
+ qvirtqueue_cleanup(blk_if->vdev->bus, vq, t_alloc);
+
+}
+
+static void indirect(void *obj, void *u_data, QGuestAllocator *t_alloc)
+{
+ QVirtQueue *vq;
+ QVirtioBlk *blk_if = obj;
+ QVirtioDevice *dev = blk_if->vdev;
+ QVirtioBlkReq req;
+ QVRingIndirectDesc *indirect;
+ uint64_t req_addr;
+ uint64_t capacity;
+ uint64_t features;
+ uint32_t free_head;
+ uint8_t status;
+ char *data;
+ QTestState *qts = global_qtest;
+
+ features = qvirtio_get_features(dev);
+ g_assert_cmphex(features & (1u << VIRTIO_RING_F_INDIRECT_DESC), !=, 0);
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
+ (1u << VIRTIO_RING_F_EVENT_IDX) |
+ (1u << VIRTIO_BLK_F_SCSI));
+ qvirtio_set_features(dev, features);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
+
+ vq = qvirtqueue_setup(dev, t_alloc, 0);
+ qvirtio_set_driver_ok(dev);
+
+ /* Write request */
+ req.type = VIRTIO_BLK_T_OUT;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ indirect = qvring_indirect_desc_setup(qts, dev, t_alloc, 2);
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr, 528, false);
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr + 528, 1, true);
+ free_head = qvirtqueue_add_indirect(qts, vq, indirect);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ g_free(indirect);
+ guest_free(t_alloc, req_addr);
+
+ /* Read request */
+ req.type = VIRTIO_BLK_T_IN;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ indirect = qvring_indirect_desc_setup(qts, dev, t_alloc, 2);
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr, 16, false);
+ qvring_indirect_desc_add(dev, qts, indirect, req_addr + 16, 513, true);
+ free_head = qvirtqueue_add_indirect(qts, vq, indirect);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ data = g_malloc0(512);
+ memread(req_addr + 16, data, 512);
+ g_assert_cmpstr(data, ==, "TEST");
+ g_free(data);
+
+ g_free(indirect);
+ guest_free(t_alloc, req_addr);
+ qvirtqueue_cleanup(dev->bus, vq, t_alloc);
+}
+
+static void config(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioBlk *blk_if = obj;
+ QVirtioDevice *dev = blk_if->vdev;
+ int n_size = TEST_IMAGE_SIZE / 2;
+ uint64_t features;
+ uint64_t capacity;
+
+ features = qvirtio_get_features(dev);
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1u << VIRTIO_RING_F_EVENT_IDX) |
+ (1u << VIRTIO_BLK_F_SCSI));
+ qvirtio_set_features(dev, features);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
+
+ qvirtio_set_driver_ok(dev);
+
+ qmp_discard_response("{ 'execute': 'block_resize', "
+ " 'arguments': { 'device': 'drive0', "
+ " 'size': %d } }", n_size);
+ qvirtio_wait_config_isr(dev, QVIRTIO_BLK_TIMEOUT_US);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, n_size / 512);
+}
+
+static void msix(void *obj, void *u_data, QGuestAllocator *t_alloc)
+{
+ QVirtQueue *vq;
+ QVirtioBlkPCI *blk = obj;
+ QVirtioPCIDevice *pdev = &blk->pci_vdev;
+ QVirtioDevice *dev = &pdev->vdev;
+ QVirtioBlkReq req;
+ int n_size = TEST_IMAGE_SIZE / 2;
+ uint64_t req_addr;
+ uint64_t capacity;
+ uint64_t features;
+ uint32_t free_head;
+ uint8_t status;
+ char *data;
+ QOSGraphObject *blk_object = obj;
+ QPCIDevice *pci_dev = blk_object->get_driver(blk_object, "pci-device");
+ QTestState *qts = global_qtest;
+
+ if (qpci_check_buggy_msi(pci_dev)) {
+ return;
+ }
+
+ qpci_msix_enable(pdev->pdev);
+ qvirtio_pci_set_msix_configuration_vector(pdev, t_alloc, 0);
+
+ features = qvirtio_get_features(dev);
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1u << VIRTIO_RING_F_EVENT_IDX) |
+ (1u << VIRTIO_BLK_F_SCSI));
+ qvirtio_set_features(dev, features);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
+
+ vq = qvirtqueue_setup(dev, t_alloc, 0);
+ qvirtqueue_pci_msix_setup(pdev, (QVirtQueuePCI *)vq, t_alloc, 1);
+
+ qvirtio_set_driver_ok(dev);
+
+ qmp_discard_response("{ 'execute': 'block_resize', "
+ " 'arguments': { 'device': 'drive0', "
+ " 'size': %d } }", n_size);
+
+ qvirtio_wait_config_isr(dev, QVIRTIO_BLK_TIMEOUT_US);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, n_size / 512);
+
+ /* Write request */
+ req.type = VIRTIO_BLK_T_OUT;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ guest_free(t_alloc, req_addr);
+
+ /* Read request */
+ req.type = VIRTIO_BLK_T_IN;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ data = g_malloc0(512);
+ memread(req_addr + 16, data, 512);
+ g_assert_cmpstr(data, ==, "TEST");
+ g_free(data);
+
+ guest_free(t_alloc, req_addr);
+
+ /* End test */
+ qpci_msix_disable(pdev->pdev);
+ qvirtqueue_cleanup(dev->bus, vq, t_alloc);
+}
+
+static void idx(void *obj, void *u_data, QGuestAllocator *t_alloc)
+{
+ QVirtQueue *vq;
+ QVirtioBlkPCI *blk = obj;
+ QVirtioPCIDevice *pdev = &blk->pci_vdev;
+ QVirtioDevice *dev = &pdev->vdev;
+ QVirtioBlkReq req;
+ uint64_t req_addr;
+ uint64_t capacity;
+ uint64_t features;
+ uint32_t free_head;
+ uint32_t write_head;
+ uint32_t desc_idx;
+ uint8_t status;
+ char *data;
+ QOSGraphObject *blk_object = obj;
+ QPCIDevice *pci_dev = blk_object->get_driver(blk_object, "pci-device");
+ QTestState *qts = global_qtest;
+
+ if (qpci_check_buggy_msi(pci_dev)) {
+ return;
+ }
+
+ qpci_msix_enable(pdev->pdev);
+ qvirtio_pci_set_msix_configuration_vector(pdev, t_alloc, 0);
+
+ features = qvirtio_get_features(dev);
+ features = features & ~(QVIRTIO_F_BAD_FEATURE |
+ (1u << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1u << VIRTIO_F_NOTIFY_ON_EMPTY) |
+ (1u << VIRTIO_BLK_F_SCSI));
+ qvirtio_set_features(dev, features);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512);
+
+ vq = qvirtqueue_setup(dev, t_alloc, 0);
+ qvirtqueue_pci_msix_setup(pdev, (QVirtQueuePCI *)vq, t_alloc, 1);
+
+ qvirtio_set_driver_ok(dev);
+
+ /* Write request */
+ req.type = VIRTIO_BLK_T_OUT;
+ req.ioprio = 1;
+ req.sector = 0;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+
+ /* Write request */
+ req.type = VIRTIO_BLK_T_OUT;
+ req.ioprio = 1;
+ req.sector = 1;
+ req.data = g_malloc0(512);
+ strcpy(req.data, "TEST");
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ /* Notify after processing the third request */
+ qvirtqueue_set_used_event(qts, vq, 2);
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+ write_head = free_head;
+
+ /* No notification expected */
+ status = qvirtio_wait_status_byte_no_isr(qts, dev,
+ vq, req_addr + 528,
+ QVIRTIO_BLK_TIMEOUT_US);
+ g_assert_cmpint(status, ==, 0);
+
+ guest_free(t_alloc, req_addr);
+
+ /* Read request */
+ req.type = VIRTIO_BLK_T_IN;
+ req.ioprio = 1;
+ req.sector = 1;
+ req.data = g_malloc0(512);
+
+ req_addr = virtio_blk_request(t_alloc, dev, &req, 512);
+
+ g_free(req.data);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 16, false, true);
+ qvirtqueue_add(qts, vq, req_addr + 16, 512, true, true);
+ qvirtqueue_add(qts, vq, req_addr + 528, 1, true, false);
+
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ /* We get just one notification for both requests */
+ qvirtio_wait_used_elem(qts, dev, vq, write_head, NULL,
+ QVIRTIO_BLK_TIMEOUT_US);
+ g_assert(qvirtqueue_get_buf(qts, vq, &desc_idx, NULL));
+ g_assert_cmpint(desc_idx, ==, free_head);
+
+ status = readb(req_addr + 528);
+ g_assert_cmpint(status, ==, 0);
+
+ data = g_malloc0(512);
+ memread(req_addr + 16, data, 512);
+ g_assert_cmpstr(data, ==, "TEST");
+ g_free(data);
+
+ guest_free(t_alloc, req_addr);
+
+ /* End test */
+ qpci_msix_disable(pdev->pdev);
+
+ qvirtqueue_cleanup(dev->bus, vq, t_alloc);
+}
+
+static void pci_hotplug(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioPCIDevice *dev1 = obj;
+ QVirtioPCIDevice *dev;
+ QTestState *qts = dev1->pdev->bus->qts;
+
+ /* plug secondary disk */
+ qtest_qmp_device_add(qts, "virtio-blk-pci", "drv1",
+ "{'addr': %s, 'drive': 'drive1'}",
+ stringify(PCI_SLOT_HP) ".0");
+
+ dev = virtio_pci_new(dev1->pdev->bus,
+ &(QPCIAddress) { .devfn = QPCI_DEVFN(PCI_SLOT_HP, 0) });
+ g_assert_nonnull(dev);
+ g_assert_cmpint(dev->vdev.device_type, ==, VIRTIO_ID_BLOCK);
+ qvirtio_pci_device_disable(dev);
+ qos_object_destroy((QOSGraphObject *)dev);
+
+ /* unplug secondary disk */
+ qpci_unplug_acpi_device_test(qts, "drv1", PCI_SLOT_HP);
+}
+
+/*
+ * Check that setting the vring addr on a non-existent virtqueue does
+ * not crash.
+ */
+static void test_nonexistent_virtqueue(void *obj, void *data,
+ QGuestAllocator *t_alloc)
+{
+ QVirtioBlkPCI *blk = obj;
+ QVirtioPCIDevice *pdev = &blk->pci_vdev;
+ QPCIBar bar0;
+ QPCIDevice *dev;
+
+ dev = qpci_device_find(pdev->pdev->bus, QPCI_DEVFN(4, 0));
+ g_assert(dev != NULL);
+ qpci_device_enable(dev);
+
+ bar0 = qpci_iomap(dev, 0, NULL);
+
+ qpci_io_writeb(dev, bar0, VIRTIO_PCI_QUEUE_SEL, 2);
+ qpci_io_writel(dev, bar0, VIRTIO_PCI_QUEUE_PFN, 1);
+
+
+ g_free(dev);
+}
+
+static void resize(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioBlk *blk_if = obj;
+ QVirtioDevice *dev = blk_if->vdev;
+ int n_size = TEST_IMAGE_SIZE / 2;
+ uint64_t capacity;
+ QVirtQueue *vq;
+ QTestState *qts = global_qtest;
+
+ vq = test_basic(dev, t_alloc);
+
+ qmp_discard_response("{ 'execute': 'block_resize', "
+ " 'arguments': { 'device': 'drive0', "
+ " 'size': %d } }", n_size);
+
+ qvirtio_wait_queue_isr(qts, dev, vq, QVIRTIO_BLK_TIMEOUT_US);
+
+ capacity = qvirtio_config_readq(dev, 0);
+ g_assert_cmpint(capacity, ==, n_size / 512);
+
+ qvirtqueue_cleanup(dev->bus, vq, t_alloc);
+
+}
+
+static void *virtio_blk_test_setup(GString *cmd_line, void *arg)
+{
+ char *tmp_path = drive_create();
+
+ g_string_append_printf(cmd_line,
+ " -drive if=none,id=drive0,file=%s,"
+ "format=raw,auto-read-only=off "
+ "-drive if=none,id=drive1,file=null-co://,"
+ "file.read-zeroes=on,format=raw ",
+ tmp_path);
+
+ return arg;
+}
+
+static void register_virtio_blk_test(void)
+{
+ QOSGraphTestOptions opts = {
+ .before = virtio_blk_test_setup,
+ };
+
+ qos_add_test("indirect", "virtio-blk", indirect, &opts);
+ qos_add_test("config", "virtio-blk", config, &opts);
+ qos_add_test("basic", "virtio-blk", basic, &opts);
+ qos_add_test("resize", "virtio-blk", resize, &opts);
+
+ /* tests just for virtio-blk-pci */
+ qos_add_test("msix", "virtio-blk-pci", msix, &opts);
+ qos_add_test("idx", "virtio-blk-pci", idx, &opts);
+ qos_add_test("nxvirtq", "virtio-blk-pci",
+ test_nonexistent_virtqueue, &opts);
+ qos_add_test("hotplug", "virtio-blk-pci", pci_hotplug, &opts);
+}
+
+libqos_init(register_virtio_blk_test);
diff --git a/tests/qtest/virtio-ccw-test.c b/tests/qtest/virtio-ccw-test.c
new file mode 100644
index 0000000000..d05236407b
--- /dev/null
+++ b/tests/qtest/virtio-ccw-test.c
@@ -0,0 +1,115 @@
+/*
+ * QTest testcase for VirtIO CCW
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/* Until we have a full libqos implementation of virtio-ccw (which requires
+ * also to add support for I/O channels to qtest), we can only do simple
+ * tests that initialize the devices.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "libqos/virtio.h"
+
+static void virtio_balloon_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-balloon-ccw");
+ qtest_end();
+}
+
+static void virtconsole_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-serial-ccw,id=vser0 "
+ "-device virtconsole,bus=vser0.0");
+ qtest_end();
+}
+
+static void virtserialport_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-serial-ccw,id=vser0 "
+ "-device virtserialport,bus=vser0.0");
+ qtest_end();
+}
+
+static void virtio_serial_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-serial-ccw");
+ qtest_end();
+}
+
+static void virtio_serial_hotplug(void)
+{
+ QTestState *qts = qtest_initf("-device virtio-serial-ccw");
+
+ qtest_qmp_device_add(qts, "virtserialport", "hp-port", "{}");
+ qtest_qmp_device_del(qts, "hp-port");
+
+ qtest_quit(qts);
+}
+
+static void virtio_blk_nop(void)
+{
+ global_qtest = qtest_initf("-drive if=none,id=drv0,file=null-co://,"
+ "file.read-zeroes=on,format=raw "
+ "-device virtio-blk-ccw,drive=drv0");
+ qtest_end();
+}
+
+static void virtio_net_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-net-ccw");
+ qtest_end();
+}
+
+static void virtio_rng_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-rng-ccw");
+ qtest_end();
+}
+
+static void virtio_scsi_nop(void)
+{
+ global_qtest = qtest_initf("-device virtio-scsi-ccw");
+ qtest_end();
+}
+
+static void virtio_scsi_hotplug(void)
+{
+ QTestState *s = qtest_initf("-drive if=none,id=drv0,file=null-co://,"
+ "file.read-zeroes=on,format=raw "
+ "-drive if=none,id=drv1,file=null-co://,"
+ "file.read-zeroes=on,format=raw "
+ "-device virtio-scsi-ccw "
+ "-device scsi-hd,drive=drv0");
+ qtest_qmp_device_add(s, "scsi-hd", "scsihd", "{'drive': 'drv1'}");
+ qtest_qmp_device_del(s, "scsihd");
+
+ qtest_quit(s);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/virtio/balloon/nop", virtio_balloon_nop);
+ qtest_add_func("/virtio/console/nop", virtconsole_nop);
+ qtest_add_func("/virtio/serialport/nop", virtserialport_nop);
+ qtest_add_func("/virtio/serial/nop", virtio_serial_nop);
+ qtest_add_func("/virtio/serial/hotplug", virtio_serial_hotplug);
+ qtest_add_func("/virtio/block/nop", virtio_blk_nop);
+ qtest_add_func("/virtio/net/nop", virtio_net_nop);
+ qtest_add_func("/virtio/rng/nop", virtio_rng_nop);
+ qtest_add_func("/virtio/scsi/nop", virtio_scsi_nop);
+ qtest_add_func("/virtio/scsi/hotplug", virtio_scsi_hotplug);
+
+ ret = g_test_run();
+
+ return ret;
+}
diff --git a/tests/qtest/virtio-net-test.c b/tests/qtest/virtio-net-test.c
new file mode 100644
index 0000000000..a08e2ffe12
--- /dev/null
+++ b/tests/qtest/virtio-net-test.c
@@ -0,0 +1,337 @@
+/*
+ * QTest testcase for VirtIO NIC
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "libqtest-single.h"
+#include "qemu/iov.h"
+#include "qemu/module.h"
+#include "qapi/qmp/qdict.h"
+#include "hw/virtio/virtio-net.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-net.h"
+
+#ifndef ETH_P_RARP
+#define ETH_P_RARP 0x8035
+#endif
+
+#define PCI_SLOT_HP 0x06
+#define PCI_SLOT 0x04
+
+#define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000)
+#define VNET_HDR_SIZE sizeof(struct virtio_net_hdr_mrg_rxbuf)
+
+#ifndef _WIN32
+
+static void rx_test(QVirtioDevice *dev,
+ QGuestAllocator *alloc, QVirtQueue *vq,
+ int socket)
+{
+ QTestState *qts = global_qtest;
+ uint64_t req_addr;
+ uint32_t free_head;
+ char test[] = "TEST";
+ char buffer[64];
+ int len = htonl(sizeof(test));
+ struct iovec iov[] = {
+ {
+ .iov_base = &len,
+ .iov_len = sizeof(len),
+ }, {
+ .iov_base = test,
+ .iov_len = sizeof(test),
+ },
+ };
+ int ret;
+
+ req_addr = guest_alloc(alloc, 64);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 64, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test));
+ g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len));
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_NET_TIMEOUT_US);
+ memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test));
+ g_assert_cmpstr(buffer, ==, "TEST");
+
+ guest_free(alloc, req_addr);
+}
+
+static void tx_test(QVirtioDevice *dev,
+ QGuestAllocator *alloc, QVirtQueue *vq,
+ int socket)
+{
+ QTestState *qts = global_qtest;
+ uint64_t req_addr;
+ uint32_t free_head;
+ uint32_t len;
+ char buffer[64];
+ int ret;
+
+ req_addr = guest_alloc(alloc, 64);
+ memwrite(req_addr + VNET_HDR_SIZE, "TEST", 4);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 64, false, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_NET_TIMEOUT_US);
+ guest_free(alloc, req_addr);
+
+ ret = qemu_recv(socket, &len, sizeof(len), 0);
+ g_assert_cmpint(ret, ==, sizeof(len));
+ len = ntohl(len);
+
+ ret = qemu_recv(socket, buffer, len, 0);
+ g_assert_cmpstr(buffer, ==, "TEST");
+}
+
+static void rx_stop_cont_test(QVirtioDevice *dev,
+ QGuestAllocator *alloc, QVirtQueue *vq,
+ int socket)
+{
+ QTestState *qts = global_qtest;
+ uint64_t req_addr;
+ uint32_t free_head;
+ char test[] = "TEST";
+ char buffer[64];
+ int len = htonl(sizeof(test));
+ QDict *rsp;
+ struct iovec iov[] = {
+ {
+ .iov_base = &len,
+ .iov_len = sizeof(len),
+ }, {
+ .iov_base = test,
+ .iov_len = sizeof(test),
+ },
+ };
+ int ret;
+
+ req_addr = guest_alloc(alloc, 64);
+
+ free_head = qvirtqueue_add(qts, vq, req_addr, 64, true, false);
+ qvirtqueue_kick(qts, dev, vq, free_head);
+
+ rsp = qmp("{ 'execute' : 'stop'}");
+ qobject_unref(rsp);
+
+ ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test));
+ g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len));
+
+ /* We could check the status, but this command is more importantly to
+ * ensure the packet data gets queued in QEMU, before we do 'cont'.
+ */
+ rsp = qmp("{ 'execute' : 'query-status'}");
+ qobject_unref(rsp);
+ rsp = qmp("{ 'execute' : 'cont'}");
+ qobject_unref(rsp);
+
+ qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
+ QVIRTIO_NET_TIMEOUT_US);
+ memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test));
+ g_assert_cmpstr(buffer, ==, "TEST");
+
+ guest_free(alloc, req_addr);
+}
+
+static void send_recv_test(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioNet *net_if = obj;
+ QVirtioDevice *dev = net_if->vdev;
+ QVirtQueue *rx = net_if->queues[0];
+ QVirtQueue *tx = net_if->queues[1];
+ int *sv = data;
+
+ rx_test(dev, t_alloc, rx, sv[0]);
+ tx_test(dev, t_alloc, tx, sv[0]);
+}
+
+static void stop_cont_test(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioNet *net_if = obj;
+ QVirtioDevice *dev = net_if->vdev;
+ QVirtQueue *rx = net_if->queues[0];
+ int *sv = data;
+
+ rx_stop_cont_test(dev, t_alloc, rx, sv[0]);
+}
+
+#endif
+
+static void hotplug(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioPCIDevice *dev = obj;
+ QTestState *qts = dev->pdev->bus->qts;
+ const char *arch = qtest_get_arch();
+
+ qtest_qmp_device_add(qts, "virtio-net-pci", "net1",
+ "{'addr': %s}", stringify(PCI_SLOT_HP));
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ qpci_unplug_acpi_device_test(qts, "net1", PCI_SLOT_HP);
+ }
+}
+
+static void announce_self(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ int *sv = data;
+ char buffer[60];
+ int len;
+ QDict *rsp;
+ int ret;
+ uint16_t *proto = (uint16_t *)&buffer[12];
+ size_t total_received = 0;
+ uint64_t start, now, last_rxt, deadline;
+
+ /* Send a set of packets over a few second period */
+ rsp = qmp("{ 'execute' : 'announce-self', "
+ " 'arguments': {"
+ " 'initial': 20, 'max': 100,"
+ " 'rounds': 300, 'step': 10, 'id': 'bob' } }");
+ assert(!qdict_haskey(rsp, "error"));
+ qobject_unref(rsp);
+
+ /* Catch the first packet and make sure it's a RARP */
+ ret = qemu_recv(sv[0], &len, sizeof(len), 0);
+ g_assert_cmpint(ret, ==, sizeof(len));
+ len = ntohl(len);
+
+ ret = qemu_recv(sv[0], buffer, len, 0);
+ g_assert_cmpint(*proto, ==, htons(ETH_P_RARP));
+
+ /*
+ * Stop the announcment by settings rounds to 0 on the
+ * existing timer.
+ */
+ rsp = qmp("{ 'execute' : 'announce-self', "
+ " 'arguments': {"
+ " 'initial': 20, 'max': 100,"
+ " 'rounds': 0, 'step': 10, 'id': 'bob' } }");
+ assert(!qdict_haskey(rsp, "error"));
+ qobject_unref(rsp);
+
+ /* Now make sure the packets stop */
+
+ /* Times are in us */
+ start = g_get_monotonic_time();
+ /* 30 packets, max gap 100ms, * 4 for wiggle */
+ deadline = start + 1000 * (100 * 30 * 4);
+ last_rxt = start;
+
+ while (true) {
+ int saved_err;
+ ret = qemu_recv(sv[0], buffer, 60, MSG_DONTWAIT);
+ saved_err = errno;
+ now = g_get_monotonic_time();
+ g_assert_cmpint(now, <, deadline);
+
+ if (ret >= 0) {
+ if (ret) {
+ last_rxt = now;
+ }
+ total_received += ret;
+
+ /* Check it's not spewing loads */
+ g_assert_cmpint(total_received, <, 60 * 30 * 2);
+ } else {
+ g_assert_cmpint(saved_err, ==, EAGAIN);
+
+ /* 400ms, i.e. 4 worst case gaps */
+ if ((now - last_rxt) > (1000 * 100 * 4)) {
+ /* Nothings arrived for a while - must have stopped */
+ break;
+ };
+
+ /* 100ms */
+ g_usleep(1000 * 100);
+ }
+ };
+}
+
+static void virtio_net_test_cleanup(void *sockets)
+{
+ int *sv = sockets;
+
+ close(sv[0]);
+ qos_invalidate_command_line();
+ close(sv[1]);
+ g_free(sv);
+}
+
+static void *virtio_net_test_setup(GString *cmd_line, void *arg)
+{
+ int ret;
+ int *sv = g_new(int, 2);
+
+ ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv);
+ g_assert_cmpint(ret, !=, -1);
+
+ g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ", sv[1]);
+
+ g_test_queue_destroy(virtio_net_test_cleanup, sv);
+ return sv;
+}
+
+static void large_tx(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+ QVirtioNet *dev = obj;
+ QVirtQueue *vq = dev->queues[1];
+ uint64_t req_addr;
+ uint32_t free_head;
+ size_t alloc_size = (size_t)data / 64;
+ QTestState *qts = global_qtest;
+ int i;
+
+ /* Bypass the limitation by pointing several descriptors to a single
+ * smaller area */
+ req_addr = guest_alloc(t_alloc, alloc_size);
+ free_head = qvirtqueue_add(qts, vq, req_addr, alloc_size, false, true);
+
+ for (i = 0; i < 64; i++) {
+ qvirtqueue_add(qts, vq, req_addr, alloc_size, false, i != 63);
+ }
+ qvirtqueue_kick(qts, dev->vdev, vq, free_head);
+
+ qvirtio_wait_used_elem(qts, dev->vdev, vq, free_head, NULL,
+ QVIRTIO_NET_TIMEOUT_US);
+ guest_free(t_alloc, req_addr);
+}
+
+static void *virtio_net_test_setup_nosocket(GString *cmd_line, void *arg)
+{
+ g_string_append(cmd_line, " -netdev hubport,hubid=0,id=hs0 ");
+ return arg;
+}
+
+static void register_virtio_net_test(void)
+{
+ QOSGraphTestOptions opts = {
+ .before = virtio_net_test_setup,
+ };
+
+ qos_add_test("hotplug", "virtio-pci", hotplug, &opts);
+#ifndef _WIN32
+ qos_add_test("basic", "virtio-net", send_recv_test, &opts);
+ qos_add_test("rx_stop_cont", "virtio-net", stop_cont_test, &opts);
+#endif
+ qos_add_test("announce-self", "virtio-net", announce_self, &opts);
+
+ /* These tests do not need a loopback backend. */
+ opts.before = virtio_net_test_setup_nosocket;
+ opts.arg = (gpointer)UINT_MAX;
+ qos_add_test("large_tx/uint_max", "virtio-net", large_tx, &opts);
+ opts.arg = (gpointer)NET_BUFSIZE;
+ qos_add_test("large_tx/net_bufsize", "virtio-net", large_tx, &opts);
+}
+
+libqos_init(register_virtio_net_test);
diff --git a/tests/qtest/virtio-rng-test.c b/tests/qtest/virtio-rng-test.c
new file mode 100644
index 0000000000..092ba13068
--- /dev/null
+++ b/tests/qtest/virtio-rng-test.c
@@ -0,0 +1,38 @@
+/*
+ * QTest testcase for VirtIO RNG
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-rng.h"
+
+#define PCI_SLOT_HP 0x06
+
+static void rng_hotplug(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QVirtioPCIDevice *dev = obj;
+ QTestState *qts = dev->pdev->bus->qts;
+
+ const char *arch = qtest_get_arch();
+
+ qtest_qmp_device_add(qts, "virtio-rng-pci", "rng1",
+ "{'addr': %s}", stringify(PCI_SLOT_HP));
+
+ if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) {
+ qpci_unplug_acpi_device_test(qts, "rng1", PCI_SLOT_HP);
+ }
+}
+
+static void register_virtio_rng_test(void)
+{
+ qos_add_test("hotplug", "virtio-rng-pci", rng_hotplug, NULL);
+}
+
+libqos_init(register_virtio_rng_test);
diff --git a/tests/qtest/virtio-scsi-test.c b/tests/qtest/virtio-scsi-test.c
new file mode 100644
index 0000000000..0415e75876
--- /dev/null
+++ b/tests/qtest/virtio-scsi-test.c
@@ -0,0 +1,298 @@
+/*
+ * QTest testcase for VirtIO SCSI
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ * Copyright (c) 2015 Red Hat Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "scsi/constants.h"
+#include "libqos/libqos-pc.h"
+#include "libqos/libqos-spapr.h"
+#include "libqos/virtio.h"
+#include "libqos/virtio-pci.h"
+#include "standard-headers/linux/virtio_ids.h"
+#include "standard-headers/linux/virtio_pci.h"
+#include "standard-headers/linux/virtio_scsi.h"
+#include "libqos/virtio-scsi.h"
+#include "libqos/qgraph.h"
+
+#define PCI_SLOT 0x02
+#define PCI_FN 0x00
+#define QVIRTIO_SCSI_TIMEOUT_US (1 * 1000 * 1000)
+
+#define MAX_NUM_QUEUES 64
+
+typedef struct {
+ QVirtioDevice *dev;
+ int num_queues;
+ QVirtQueue *vq[MAX_NUM_QUEUES + 2];
+} QVirtioSCSIQueues;
+
+static QGuestAllocator *alloc;
+
+static void qvirtio_scsi_pci_free(QVirtioSCSIQueues *vs)
+{
+ int i;
+
+ for (i = 0; i < vs->num_queues + 2; i++) {
+ qvirtqueue_cleanup(vs->dev->bus, vs->vq[i], alloc);
+ }
+ g_free(vs);
+}
+
+static uint64_t qvirtio_scsi_alloc(QVirtioSCSIQueues *vs, size_t alloc_size,
+ const void *data)
+{
+ uint64_t addr;
+
+ addr = guest_alloc(alloc, alloc_size);
+ if (data) {
+ memwrite(addr, data, alloc_size);
+ }
+
+ return addr;
+}
+
+static uint8_t virtio_scsi_do_command(QVirtioSCSIQueues *vs,
+ const uint8_t *cdb,
+ const uint8_t *data_in,
+ size_t data_in_len,
+ uint8_t *data_out, size_t data_out_len,
+ struct virtio_scsi_cmd_resp *resp_out)
+{
+ QVirtQueue *vq;
+ struct virtio_scsi_cmd_req req = { { 0 } };
+ struct virtio_scsi_cmd_resp resp = { .response = 0xff, .status = 0xff };
+ uint64_t req_addr, resp_addr, data_in_addr = 0, data_out_addr = 0;
+ uint8_t response;
+ uint32_t free_head;
+ QTestState *qts = global_qtest;
+
+ vq = vs->vq[2];
+
+ req.lun[0] = 1; /* Select LUN */
+ req.lun[1] = 1; /* Select target 1 */
+ memcpy(req.cdb, cdb, VIRTIO_SCSI_CDB_SIZE);
+
+ /* XXX: Fix endian if any multi-byte field in req/resp is used */
+
+ /* Add request header */
+ req_addr = qvirtio_scsi_alloc(vs, sizeof(req), &req);
+ free_head = qvirtqueue_add(qts, vq, req_addr, sizeof(req), false, true);
+
+ if (data_out_len) {
+ data_out_addr = qvirtio_scsi_alloc(vs, data_out_len, data_out);
+ qvirtqueue_add(qts, vq, data_out_addr, data_out_len, false, true);
+ }
+
+ /* Add response header */
+ resp_addr = qvirtio_scsi_alloc(vs, sizeof(resp), &resp);
+ qvirtqueue_add(qts, vq, resp_addr, sizeof(resp), true, !!data_in_len);
+
+ if (data_in_len) {
+ data_in_addr = qvirtio_scsi_alloc(vs, data_in_len, data_in);
+ qvirtqueue_add(qts, vq, data_in_addr, data_in_len, true, false);
+ }
+
+ qvirtqueue_kick(qts, vs->dev, vq, free_head);
+ qvirtio_wait_used_elem(qts, vs->dev, vq, free_head, NULL,
+ QVIRTIO_SCSI_TIMEOUT_US);
+
+ response = readb(resp_addr +
+ offsetof(struct virtio_scsi_cmd_resp, response));
+
+ if (resp_out) {
+ memread(resp_addr, resp_out, sizeof(*resp_out));
+ }
+
+ guest_free(alloc, req_addr);
+ guest_free(alloc, resp_addr);
+ guest_free(alloc, data_in_addr);
+ guest_free(alloc, data_out_addr);
+ return response;
+}
+
+static QVirtioSCSIQueues *qvirtio_scsi_init(QVirtioDevice *dev)
+{
+ QVirtioSCSIQueues *vs;
+ const uint8_t test_unit_ready_cdb[VIRTIO_SCSI_CDB_SIZE] = {};
+ struct virtio_scsi_cmd_resp resp;
+ uint64_t features;
+ int i;
+
+ vs = g_new0(QVirtioSCSIQueues, 1);
+ vs->dev = dev;
+
+ features = qvirtio_get_features(dev);
+ features &= ~(QVIRTIO_F_BAD_FEATURE | (1ull << VIRTIO_RING_F_EVENT_IDX));
+ qvirtio_set_features(dev, features);
+
+ vs->num_queues = qvirtio_config_readl(dev, 0);
+
+ g_assert_cmpint(vs->num_queues, <, MAX_NUM_QUEUES);
+
+ for (i = 0; i < vs->num_queues + 2; i++) {
+ vs->vq[i] = qvirtqueue_setup(dev, alloc, i);
+ }
+
+ qvirtio_set_driver_ok(dev);
+
+ /* Clear the POWER ON OCCURRED unit attention */
+ g_assert_cmpint(virtio_scsi_do_command(vs, test_unit_ready_cdb,
+ NULL, 0, NULL, 0, &resp),
+ ==, 0);
+ g_assert_cmpint(resp.status, ==, CHECK_CONDITION);
+ g_assert_cmpint(resp.sense[0], ==, 0x70); /* Fixed format sense buffer */
+ g_assert_cmpint(resp.sense[2], ==, UNIT_ATTENTION);
+ g_assert_cmpint(resp.sense[12], ==, 0x29); /* POWER ON */
+ g_assert_cmpint(resp.sense[13], ==, 0x00);
+
+ return vs;
+}
+
+static void hotplug(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QTestState *qts = global_qtest;
+
+ qtest_qmp_device_add(qts, "scsi-hd", "scsihd", "{'drive': 'drv1'}");
+ qtest_qmp_device_del(qts, "scsihd");
+}
+
+/* Test WRITE SAME with the lba not aligned */
+static void test_unaligned_write_same(void *obj, void *data,
+ QGuestAllocator *t_alloc)
+{
+ QVirtioSCSI *scsi = obj;
+ QVirtioSCSIQueues *vs;
+ uint8_t buf1[512] = { 0 };
+ uint8_t buf2[512] = { 1 };
+ const uint8_t write_same_cdb_1[VIRTIO_SCSI_CDB_SIZE] = {
+ 0x41, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00
+ };
+ const uint8_t write_same_cdb_2[VIRTIO_SCSI_CDB_SIZE] = {
+ 0x41, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00
+ };
+ const uint8_t write_same_cdb_ndob[VIRTIO_SCSI_CDB_SIZE] = {
+ 0x41, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00
+ };
+
+ alloc = t_alloc;
+ vs = qvirtio_scsi_init(scsi->vdev);
+
+ g_assert_cmphex(0, ==,
+ virtio_scsi_do_command(vs, write_same_cdb_1, NULL, 0, buf1, 512,
+ NULL));
+
+ g_assert_cmphex(0, ==,
+ virtio_scsi_do_command(vs, write_same_cdb_2, NULL, 0, buf2, 512,
+ NULL));
+
+ g_assert_cmphex(0, ==,
+ virtio_scsi_do_command(vs, write_same_cdb_ndob, NULL, 0, NULL, 0,
+ NULL));
+
+ qvirtio_scsi_pci_free(vs);
+}
+
+static void test_iothread_attach_node(void *obj, void *data,
+ QGuestAllocator *t_alloc)
+{
+ QVirtioSCSIPCI *scsi_pci = obj;
+ QVirtioSCSI *scsi = &scsi_pci->scsi;
+ QVirtioSCSIQueues *vs;
+ char tmp_path[] = "/tmp/qtest.XXXXXX";
+ int fd;
+ int ret;
+
+ uint8_t buf[512] = { 0 };
+ const uint8_t write_cdb[VIRTIO_SCSI_CDB_SIZE] = {
+ /* WRITE(10) to LBA 0, transfer length 1 */
+ 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00
+ };
+
+ alloc = t_alloc;
+ vs = qvirtio_scsi_init(scsi->vdev);
+
+ /* Create a temporary qcow2 overlay*/
+ fd = mkstemp(tmp_path);
+ g_assert(fd >= 0);
+ close(fd);
+
+ if (!have_qemu_img()) {
+ g_test_message("QTEST_QEMU_IMG not set or qemu-img missing; "
+ "skipping snapshot test");
+ goto fail;
+ }
+
+ mkqcow2(tmp_path, 64);
+
+ /* Attach the overlay to the null0 node */
+ qtest_qmp_assert_success(scsi_pci->pci_vdev.pdev->bus->qts,
+ "{'execute': 'blockdev-add', 'arguments': {"
+ " 'driver': 'qcow2', 'node-name': 'overlay',"
+ " 'backing': 'null0', 'file': {"
+ " 'driver': 'file', 'filename': %s}}}",
+ tmp_path);
+
+ /* Send a request to see if the AioContext is still right */
+ ret = virtio_scsi_do_command(vs, write_cdb, NULL, 0, buf, 512, NULL);
+ g_assert_cmphex(ret, ==, 0);
+
+fail:
+ qvirtio_scsi_pci_free(vs);
+ unlink(tmp_path);
+}
+
+static void *virtio_scsi_hotplug_setup(GString *cmd_line, void *arg)
+{
+ g_string_append(cmd_line,
+ " -drive id=drv1,if=none,file=null-co://,"
+ "file.read-zeroes=on,format=raw");
+ return arg;
+}
+
+static void *virtio_scsi_setup(GString *cmd_line, void *arg)
+{
+ g_string_append(cmd_line,
+ " -drive file=blkdebug::null-co://,"
+ "file.image.read-zeroes=on,"
+ "if=none,id=dr1,format=raw,file.align=4k "
+ "-device scsi-hd,drive=dr1,lun=0,scsi-id=1");
+ return arg;
+}
+
+static void *virtio_scsi_setup_iothread(GString *cmd_line, void *arg)
+{
+ g_string_append(cmd_line,
+ " -object iothread,id=thread0"
+ " -blockdev driver=null-co,read-zeroes=on,node-name=null0"
+ " -device scsi-hd,drive=null0");
+ return arg;
+}
+
+static void register_virtio_scsi_test(void)
+{
+ QOSGraphTestOptions opts = { };
+
+ opts.before = virtio_scsi_hotplug_setup;
+ qos_add_test("hotplug", "virtio-scsi", hotplug, &opts);
+
+ opts.before = virtio_scsi_setup;
+ qos_add_test("unaligned-write-same", "virtio-scsi",
+ test_unaligned_write_same, &opts);
+
+ opts.before = virtio_scsi_setup_iothread;
+ opts.edge = (QOSGraphEdgeOptions) {
+ .extra_device_opts = "iothread=thread0",
+ };
+ qos_add_test("iothread-attach-node", "virtio-scsi-pci",
+ test_iothread_attach_node, &opts);
+}
+
+libqos_init(register_virtio_scsi_test);
diff --git a/tests/qtest/virtio-serial-test.c b/tests/qtest/virtio-serial-test.c
new file mode 100644
index 0000000000..2541034822
--- /dev/null
+++ b/tests/qtest/virtio-serial-test.c
@@ -0,0 +1,39 @@
+/*
+ * QTest testcase for VirtIO Serial
+ *
+ * Copyright (c) 2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/module.h"
+#include "libqos/virtio-serial.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void virtio_serial_nop(void *obj, void *data, QGuestAllocator *alloc)
+{
+ /* no operation */
+}
+
+static void serial_hotplug(void *obj, void *data, QGuestAllocator *alloc)
+{
+ qtest_qmp_device_add(global_qtest, "virtserialport", "hp-port", "{}");
+ qtest_qmp_device_del(global_qtest, "hp-port");
+}
+
+static void register_virtio_serial_test(void)
+{
+ QOSGraphTestOptions opts = { };
+
+ opts.edge.before_cmd_line = "-device virtconsole,bus=vser0.0";
+ qos_add_test("console-nop", "virtio-serial", virtio_serial_nop, &opts);
+
+ opts.edge.before_cmd_line = "-device virtserialport,bus=vser0.0";
+ qos_add_test("serialport-nop", "virtio-serial", virtio_serial_nop, &opts);
+
+ qos_add_test("hotplug", "virtio-serial", serial_hotplug, NULL);
+}
+libqos_init(register_virtio_serial_test);
diff --git a/tests/qtest/virtio-test.c b/tests/qtest/virtio-test.c
new file mode 100644
index 0000000000..f7c6afdcf1
--- /dev/null
+++ b/tests/qtest/virtio-test.c
@@ -0,0 +1,26 @@
+/*
+ * QTest testcase for virtio
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+/* Tests only initialization so far. TODO: Replace with functional tests */
+static void nop(void *obj, void *data, QGuestAllocator *alloc)
+{
+}
+
+static void register_virtio_test(void)
+{
+ qos_add_test("nop", "virtio", nop, NULL);
+}
+
+libqos_init(register_virtio_test);
diff --git a/tests/qtest/vmgenid-test.c b/tests/qtest/vmgenid-test.c
new file mode 100644
index 0000000000..efba76e716
--- /dev/null
+++ b/tests/qtest/vmgenid-test.c
@@ -0,0 +1,185 @@
+/*
+ * QTest testcase for VM Generation ID
+ *
+ * Copyright (c) 2016 Red Hat, Inc.
+ * Copyright (c) 2017 Skyport Systems
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitmap.h"
+#include "qemu/uuid.h"
+#include "hw/acpi/acpi-defs.h"
+#include "boot-sector.h"
+#include "acpi-utils.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+
+#define VGID_GUID "324e6eaf-d1d1-4bf6-bf41-b9bb6c91fb87"
+#define VMGENID_GUID_OFFSET 40 /* allow space for
+ * OVMF SDT Header Probe Supressor
+ */
+#define RSDP_ADDR_INVALID 0x100000 /* RSDP must be below this address */
+
+static uint32_t acpi_find_vgia(QTestState *qts)
+{
+ uint32_t rsdp_offset;
+ uint32_t guid_offset = 0;
+ uint8_t rsdp_table[36 /* ACPI 2.0+ RSDP size */];
+ uint32_t rsdt_len, table_length;
+ uint8_t *rsdt, *ent;
+
+ /* Wait for guest firmware to finish and start the payload. */
+ boot_sector_test(qts);
+
+ /* Tables should be initialized now. */
+ rsdp_offset = acpi_find_rsdp_address(qts);
+
+ g_assert_cmphex(rsdp_offset, <, RSDP_ADDR_INVALID);
+
+
+ acpi_fetch_rsdp_table(qts, rsdp_offset, rsdp_table);
+ acpi_fetch_table(qts, &rsdt, &rsdt_len, &rsdp_table[16 /* RsdtAddress */],
+ 4, "RSDT", true);
+
+ ACPI_FOREACH_RSDT_ENTRY(rsdt, rsdt_len, ent, 4 /* Entry size */) {
+ uint8_t *table_aml;
+
+ acpi_fetch_table(qts, &table_aml, &table_length, ent, 4, NULL, true);
+ if (!memcmp(table_aml + 16 /* OEM Table ID */, "VMGENID", 7)) {
+ uint32_t vgia_val;
+ uint8_t *aml = &table_aml[36 /* AML byte-code start */];
+ /* the first entry in the table should be VGIA
+ * That's all we need
+ */
+ g_assert(aml[0 /* name_op*/] == 0x08);
+ g_assert(memcmp(&aml[1 /* name */], "VGIA", 4) == 0);
+ g_assert(aml[5 /* value op */] == 0x0C /* dword */);
+ memcpy(&vgia_val, &aml[6 /* value */], 4);
+
+ /* The GUID is written at a fixed offset into the fw_cfg file
+ * in order to implement the "OVMF SDT Header probe suppressor"
+ * see docs/specs/vmgenid.txt for more details
+ */
+ guid_offset = le32_to_cpu(vgia_val) + VMGENID_GUID_OFFSET;
+ g_free(table_aml);
+ break;
+ }
+ g_free(table_aml);
+ }
+ g_free(rsdt);
+ return guid_offset;
+}
+
+static void read_guid_from_memory(QTestState *qts, QemuUUID *guid)
+{
+ uint32_t vmgenid_addr;
+ int i;
+
+ vmgenid_addr = acpi_find_vgia(qts);
+ g_assert(vmgenid_addr);
+
+ /* Read the GUID directly from guest memory */
+ for (i = 0; i < 16; i++) {
+ guid->data[i] = qtest_readb(qts, vmgenid_addr + i);
+ }
+ /* The GUID is in little-endian format in the guest, while QEMU
+ * uses big-endian. Swap after reading.
+ */
+ *guid = qemu_uuid_bswap(*guid);
+}
+
+static void read_guid_from_monitor(QTestState *qts, QemuUUID *guid)
+{
+ QDict *rsp, *rsp_ret;
+ const char *guid_str;
+
+ rsp = qtest_qmp(qts, "{ 'execute': 'query-vm-generation-id' }");
+ if (qdict_haskey(rsp, "return")) {
+ rsp_ret = qdict_get_qdict(rsp, "return");
+ g_assert(qdict_haskey(rsp_ret, "guid"));
+ guid_str = qdict_get_str(rsp_ret, "guid");
+ g_assert(qemu_uuid_parse(guid_str, guid) == 0);
+ }
+ qobject_unref(rsp);
+}
+
+static char disk[] = "tests/vmgenid-test-disk-XXXXXX";
+
+#define GUID_CMD(guid) \
+ "-accel kvm -accel tcg " \
+ "-device vmgenid,id=testvgid,guid=%s " \
+ "-drive id=hd0,if=none,file=%s,format=raw " \
+ "-device ide-hd,drive=hd0 ", guid, disk
+
+static void vmgenid_set_guid_test(void)
+{
+ QemuUUID expected, measured;
+ QTestState *qts;
+
+ g_assert(qemu_uuid_parse(VGID_GUID, &expected) == 0);
+
+ qts = qtest_initf(GUID_CMD(VGID_GUID));
+
+ /* Read the GUID from accessing guest memory */
+ read_guid_from_memory(qts, &measured);
+ g_assert(memcmp(measured.data, expected.data, sizeof(measured.data)) == 0);
+
+ qtest_quit(qts);
+}
+
+static void vmgenid_set_guid_auto_test(void)
+{
+ QemuUUID measured;
+ QTestState *qts;
+
+ qts = qtest_initf(GUID_CMD("auto"));
+
+ read_guid_from_memory(qts, &measured);
+
+ /* Just check that the GUID is non-null */
+ g_assert(!qemu_uuid_is_null(&measured));
+
+ qtest_quit(qts);
+}
+
+static void vmgenid_query_monitor_test(void)
+{
+ QemuUUID expected, measured;
+ QTestState *qts;
+
+ g_assert(qemu_uuid_parse(VGID_GUID, &expected) == 0);
+
+ qts = qtest_initf(GUID_CMD(VGID_GUID));
+
+ /* Read the GUID via the monitor */
+ read_guid_from_monitor(qts, &measured);
+ g_assert(memcmp(measured.data, expected.data, sizeof(measured.data)) == 0);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+
+ ret = boot_sector_init(disk);
+ if (ret) {
+ return ret;
+ }
+
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/vmgenid/vmgenid/set-guid",
+ vmgenid_set_guid_test);
+ qtest_add_func("/vmgenid/vmgenid/set-guid-auto",
+ vmgenid_set_guid_auto_test);
+ qtest_add_func("/vmgenid/vmgenid/query-monitor",
+ vmgenid_query_monitor_test);
+ ret = g_test_run();
+ boot_sector_cleanup(disk);
+
+ return ret;
+}
diff --git a/tests/qtest/vmxnet3-test.c b/tests/qtest/vmxnet3-test.c
new file mode 100644
index 0000000000..a81025252c
--- /dev/null
+++ b/tests/qtest/vmxnet3-test.c
@@ -0,0 +1,58 @@
+/*
+ * QTest testcase for vmxnet3 NIC
+ *
+ * Copyright (c) 2013-2014 SUSE LINUX Products GmbH
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/module.h"
+#include "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QVmxnet3 QVmxnet3;
+
+struct QVmxnet3 {
+ QOSGraphObject obj;
+ QPCIDevice dev;
+};
+
+static void *vmxnet3_get_driver(void *obj, const char *interface)
+{
+ QVmxnet3 *vmxnet3 = obj;
+
+ if (!g_strcmp0(interface, "pci-device")) {
+ return &vmxnet3->dev;
+ }
+
+ fprintf(stderr, "%s not present in vmxnet3\n", interface);
+ g_assert_not_reached();
+}
+
+static void *vmxnet3_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+ QVmxnet3 *vmxnet3 = g_new0(QVmxnet3, 1);
+ QPCIBus *bus = pci_bus;
+
+ qpci_device_init(&vmxnet3->dev, bus, addr);
+ vmxnet3->obj.get_driver = vmxnet3_get_driver;
+
+ return &vmxnet3->obj;
+}
+
+static void vmxnet3_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "addr=04.0",
+ };
+ add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+ qos_node_create_driver("vmxnet3", vmxnet3_create);
+ qos_node_consumes("vmxnet3", "pci-bus", &opts);
+ qos_node_produces("vmxnet3", "pci-device");
+}
+
+libqos_init(vmxnet3_register_nodes);
diff --git a/tests/qtest/wdt_ib700-test.c b/tests/qtest/wdt_ib700-test.c
new file mode 100644
index 0000000000..797288d939
--- /dev/null
+++ b/tests/qtest/wdt_ib700-test.c
@@ -0,0 +1,118 @@
+/*
+ * QTest testcase for the IB700 watchdog
+ *
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/timer.h"
+
+static void qmp_check_no_event(QTestState *s)
+{
+ QDict *resp = qtest_qmp(s, "{'execute':'query-status'}");
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static QDict *ib700_program_and_wait(QTestState *s)
+{
+ QDict *event, *data;
+
+ qtest_clock_step(s, NANOSECONDS_PER_SECOND * 40);
+ qmp_check_no_event(s);
+
+ /* 2 second limit */
+ qtest_outb(s, 0x443, 14);
+
+ /* Ping */
+ qtest_clock_step(s, NANOSECONDS_PER_SECOND);
+ qmp_check_no_event(s);
+ qtest_outb(s, 0x443, 14);
+
+ /* Disable */
+ qtest_clock_step(s, NANOSECONDS_PER_SECOND);
+ qmp_check_no_event(s);
+ qtest_outb(s, 0x441, 1);
+ qtest_clock_step(s, 3 * NANOSECONDS_PER_SECOND);
+ qmp_check_no_event(s);
+
+ /* Enable and let it fire */
+ qtest_outb(s, 0x443, 13);
+ qtest_clock_step(s, 3 * NANOSECONDS_PER_SECOND);
+ qmp_check_no_event(s);
+ qtest_clock_step(s, 2 * NANOSECONDS_PER_SECOND);
+ event = qtest_qmp_eventwait_ref(s, "WATCHDOG");
+ data = qdict_get_qdict(event, "data");
+ qobject_ref(data);
+ qobject_unref(event);
+ return data;
+}
+
+
+static void ib700_pause(void)
+{
+ QDict *d;
+ QTestState *s = qtest_init("-watchdog-action pause -device ib700");
+
+ qtest_irq_intercept_in(s, "ioapic");
+ d = ib700_program_and_wait(s);
+ g_assert(!strcmp(qdict_get_str(d, "action"), "pause"));
+ qobject_unref(d);
+ qtest_qmp_eventwait(s, "STOP");
+ qtest_quit(s);
+}
+
+static void ib700_reset(void)
+{
+ QDict *d;
+ QTestState *s = qtest_init("-watchdog-action reset -device ib700");
+
+ qtest_irq_intercept_in(s, "ioapic");
+ d = ib700_program_and_wait(s);
+ g_assert(!strcmp(qdict_get_str(d, "action"), "reset"));
+ qobject_unref(d);
+ qtest_qmp_eventwait(s, "RESET");
+ qtest_quit(s);
+}
+
+static void ib700_shutdown(void)
+{
+ QDict *d;
+ QTestState *s;
+
+ s = qtest_init("-watchdog-action reset -no-reboot -device ib700");
+ qtest_irq_intercept_in(s, "ioapic");
+ d = ib700_program_and_wait(s);
+ g_assert(!strcmp(qdict_get_str(d, "action"), "reset"));
+ qobject_unref(d);
+ qtest_qmp_eventwait(s, "SHUTDOWN");
+ qtest_quit(s);
+}
+
+static void ib700_none(void)
+{
+ QDict *d;
+ QTestState *s = qtest_init("-watchdog-action none -device ib700");
+
+ qtest_irq_intercept_in(s, "ioapic");
+ d = ib700_program_and_wait(s);
+ g_assert(!strcmp(qdict_get_str(d, "action"), "none"));
+ qobject_unref(d);
+ qtest_quit(s);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/wdt_ib700/pause", ib700_pause);
+ qtest_add_func("/wdt_ib700/reset", ib700_reset);
+ qtest_add_func("/wdt_ib700/shutdown", ib700_shutdown);
+ qtest_add_func("/wdt_ib700/none", ib700_none);
+
+ return g_test_run();
+}