diff options
author | Thomas Huth <thuth@redhat.com> | 2019-09-09 12:04:01 +0200 |
---|---|---|
committer | Thomas Huth <thuth@redhat.com> | 2020-01-12 11:42:41 +0100 |
commit | 1e8a1fae7464ef79c9e50aa0f807d2c511be3c8e (patch) | |
tree | 80d1a4f0454b9a75c09461e69f969213350540ea /tests/qtest | |
parent | 10ae5b303a0de07f0659a2c90d9c1266b3908b97 (diff) | |
download | qemu-1e8a1fae7464ef79c9e50aa0f807d2c511be3c8e.zip qemu-1e8a1fae7464ef79c9e50aa0f807d2c511be3c8e.tar.gz qemu-1e8a1fae7464ef79c9e50aa0f807d2c511be3c8e.tar.bz2 |
test: Move qtests to a separate directory
The tests directory itself is pretty overcrowded, and it's hard to
see which test belongs to which test subsystem (unit, qtest, ...).
Let's move the qtests to a separate folder for more clarity.
Message-Id: <20191218103059.11729-6-thuth@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
Diffstat (limited to 'tests/qtest')
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 0000000..b084e31 --- /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 0000000..d2a202e --- /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 0000000..0c86780 --- /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 0000000..c8d42ce --- /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 0000000..bef3ed2 --- /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 0000000..dfb8523 --- /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 0000000..f1ac2d7 --- /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 0000000..a725bce --- /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 0000000..9e66c6d --- /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 0000000..6ee6bb4 --- /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 0000000..05c7f44 --- /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 0000000..67635e3 --- /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 0000000..e8ffbbc --- /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 0000000..2e5e47d --- /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 0000000..cc8563b --- /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 0000000..04f2290 --- /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 0000000..318e422 --- /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 0000000..ace3bb2 --- /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 0000000..5f8839b --- /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 0000000..f6ade9a --- /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 0000000..c387984 --- /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 0000000..1a232a6 --- /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 0000000..8dbffff --- /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 0000000..5852795 --- /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 0000000..adccdac --- /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 0000000..26b69f7 --- /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 0000000..5dc807b --- /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 0000000..a2498005 --- /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 0000000..8b7aa2d --- /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 0000000..1f57d96 --- /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 0000000..4345da3 --- /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 0000000..0277e7d --- /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 0000000..fc25ccc --- /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 0000000..f6ca43c --- /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 0000000..a42207d --- /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 0000000..693a6aa --- /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 0000000..53a8c9b --- /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 0000000..ecda256 --- /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 0000000..6f1bb13 --- /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 0000000..76c9f8e --- /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 0000000..c9e21e0 --- /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 0000000..50c6b79 --- /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 0000000..b94a123 --- /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 0000000..5953d31 --- /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 0000000..d6796b9 --- /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 0000000..04e199e --- /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 0000000..516093b --- /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 0000000..a11808b --- /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 0000000..53afec4 --- /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 0000000..8821768 --- /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 0000000..3fc0e55 --- /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 0000000..17dd807 --- /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 0000000..ff04421 --- /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 0000000..4b800d3 --- /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, ®, 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 0000000..4b2092b --- /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 0000000..900944f --- /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 0000000..17aa669 --- /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 0000000..2c46d5c --- /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 0000000..9be52c7 --- /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 0000000..ff9176a --- /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 0000000..f68d0aa --- /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 0000000..a68183d --- /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 0000000..9f5228c --- /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 0000000..1b0eb69 --- /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 0000000..4f94cc6 --- /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 0000000..fd70d73 --- /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 0000000..167b42d --- /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 0000000..c7af34f --- /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 0000000..4506049 --- /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 0000000..6275e76 --- /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 0000000..093dc22 --- /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 0000000..254f735 --- /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 0000000..7a56d56 --- /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 0000000..1e3ced8 --- /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 0000000..e4d5322 --- /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 0000000..5029c4d --- /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 0000000..22927ee --- /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 0000000..772287b --- /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 0000000..f930a96 --- /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 0000000..2c4fb8a --- /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 0000000..632fb7f --- /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 0000000..c43ac4a --- /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 0000000..a4f1d64 --- /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 0000000..6e45a0b --- /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 0000000..b97688f --- /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 0000000..9f58a3a --- /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 0000000..dcf30e0 --- /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 0000000..e08b137 --- /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 0000000..5755698 --- /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 0000000..5251d53 --- /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 0000000..19d760f --- /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 0000000..7a117b6 --- /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 0000000..10ef9d2 --- /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 0000000..91ea373 --- /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 0000000..e7b58e3 --- /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 0000000..2a23698 --- /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 0000000..d052364 --- /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 0000000..a08e2ff --- /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 0000000..092ba13 --- /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 0000000..0415e75 --- /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 0000000..2541034 --- /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 0000000..f7c6afd --- /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 0000000..efba76e --- /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 0000000..a810252 --- /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 0000000..797288d --- /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(); +} |