From: Thomas Huth Date: Tue, 10 Sep 2019 14:41:20 +0000 (+0200) Subject: tests/libqos: Move the libqos files under tests/qtest/ X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=1cf4323ecd03235984e43a416a42f10c975cf785;p=qemu.git tests/libqos: Move the libqos files under tests/qtest/ The qos stuff belongs to qtest, so move it into that directory, too. Message-Id: <20191218103059.11729-8-thuth@redhat.com> Reviewed-by: Paolo Bonzini Signed-off-by: Thomas Huth --- diff --git a/MAINTAINERS b/MAINTAINERS index 02eab66b02..ceb5c45c0f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1102,9 +1102,9 @@ F: pc-bios/slof.bin F: docs/specs/ppc-spapr-hcalls.txt F: docs/specs/ppc-spapr-hotplug.txt F: tests/qtest/spapr* -F: tests/libqos/*spapr* +F: tests/qtest/libqos/*spapr* F: tests/qtest/rtas* -F: tests/libqos/rtas* +F: tests/qtest/libqos/rtas* PowerNV (Non-Virtualized) M: Cédric Le Goater @@ -1363,7 +1363,7 @@ F: hw/block/hd-geometry.c F: tests/qtest/ide-test.c F: tests/qtest/ahci-test.c F: tests/qtest/cdrom-test.c -F: tests/libqos/ahci* +F: tests/qtest/libqos/ahci* T: git https://github.com/jnsnow/qemu.git ide IPMI @@ -1776,7 +1776,7 @@ F: hw/nvram/fw_cfg.c F: stubs/fw_cfg.c F: include/hw/nvram/fw_cfg.h F: include/standard-headers/linux/qemu_fw_cfg.h -F: tests/libqos/fw_cfg.c +F: tests/qtest/libqos/fw_cfg.c F: tests/qtest/fw_cfg-test.c T: git https://github.com/philmd/qemu.git fw_cfg-next @@ -2137,7 +2137,6 @@ R: Paolo Bonzini S: Maintained F: qtest.c F: accel/qtest.c -F: tests/libqos/ F: tests/qtest/ Register API diff --git a/configure b/configure index 1b8796fc21..08c3a1c1f0 100755 --- a/configure +++ b/configure @@ -7963,8 +7963,8 @@ fi # so the build tree will be missing the link back to the new file, and # tests might fail. Prefer to keep the relevant files in their own # directory and symlink the directory instead. -DIRS="tests tests/tcg tests/tcg/lm32 tests/libqos tests/qapi-schema tests/qtest" -DIRS="$DIRS tests/qemu-iotests tests/vm tests/fp tests/qgraph" +DIRS="tests tests/tcg tests/tcg/lm32 tests/qapi-schema tests/qtest/libqos" +DIRS="$DIRS tests/qtest tests/qemu-iotests tests/vm tests/fp tests/qgraph" DIRS="$DIRS docs docs/interop fsdev scsi" DIRS="$DIRS pc-bios/optionrom pc-bios/s390-ccw" DIRS="$DIRS roms/seabios roms/vgabios" diff --git a/tests/Makefile.include b/tests/Makefile.include index 065fd09964..1ae14a8b15 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -912,6 +912,6 @@ all: $(QEMU_IOTESTS_HELPERS-y) -include $(wildcard tests/*.d) -include $(wildcard tests/qtest/*.d) --include $(wildcard tests/libqos/*.d) +-include $(wildcard tests/qtest/qos/*.d) endif diff --git a/tests/libqos/aarch64-xlnx-zcu102-machine.c b/tests/libqos/aarch64-xlnx-zcu102-machine.c deleted file mode 100644 index 1d5de5af00..0000000000 --- a/tests/libqos/aarch64-xlnx-zcu102-machine.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "sdhci.h" - -typedef struct QXlnxZCU102Machine QXlnxZCU102Machine; - -struct QXlnxZCU102Machine { - QOSGraphObject obj; - QGuestAllocator alloc; - QSDHCI_MemoryMapped sdhci; -}; - -#define ARM_PAGE_SIZE 4096 -#define XLNX_ZCU102_RAM_ADDR 0 -#define XLNX_ZCU102_RAM_SIZE 0x20000000 - -static void *xlnx_zcu102_get_driver(void *object, const char *interface) -{ - QXlnxZCU102Machine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in aarch64/xlnx-zcu102\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *xlnx_zcu102_get_device(void *obj, const char *device) -{ - QXlnxZCU102Machine *machine = obj; - if (!g_strcmp0(device, "generic-sdhci")) { - return &machine->sdhci.obj; - } - - fprintf(stderr, "%s not present in aarch64/xlnx-zcu102\n", device); - g_assert_not_reached(); -} - -static void xlnx_zcu102_destructor(QOSGraphObject *obj) -{ - QXlnxZCU102Machine *machine = (QXlnxZCU102Machine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_aarch64_xlnx_zcu102(QTestState *qts) -{ - QXlnxZCU102Machine *machine = g_new0(QXlnxZCU102Machine, 1); - - alloc_init(&machine->alloc, 0, - XLNX_ZCU102_RAM_ADDR + (1 << 20), - XLNX_ZCU102_RAM_ADDR + XLNX_ZCU102_RAM_SIZE, - ARM_PAGE_SIZE); - - machine->obj.get_device = xlnx_zcu102_get_device; - machine->obj.get_driver = xlnx_zcu102_get_driver; - machine->obj.destructor = xlnx_zcu102_destructor; - /* Datasheet: UG1085 (v1.7) */ - qos_init_sdhci_mm(&machine->sdhci, qts, 0xff160000, &(QSDHCIProperties) { - .version = 3, - .baseclock = 0, - .capab.sdma = true, - .capab.reg = 0x280737ec6481 - }); - return &machine->obj; -} - -static void xlnx_zcu102_register_nodes(void) -{ - qos_node_create_machine("aarch64/xlnx-zcu102", - qos_create_machine_aarch64_xlnx_zcu102); - qos_node_contains("aarch64/xlnx-zcu102", "generic-sdhci", NULL); -} - -libqos_init(xlnx_zcu102_register_nodes); diff --git a/tests/libqos/ahci.c b/tests/libqos/ahci.c deleted file mode 100644 index cc1b08eabe..0000000000 --- a/tests/libqos/ahci.c +++ /dev/null @@ -1,1242 +0,0 @@ -/* - * libqos AHCI functions - * - * Copyright (c) 2014 John Snow - * - * 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/ahci.h" -#include "libqos/pci-pc.h" - -#include "qemu-common.h" -#include "qemu/host-utils.h" - -#include "hw/pci/pci_ids.h" -#include "hw/pci/pci_regs.h" - -typedef struct AHCICommandProp { - uint8_t cmd; /* Command Code */ - bool data; /* Data transfer command? */ - bool pio; - bool dma; - bool lba28; - bool lba48; - bool read; - bool write; - bool atapi; - bool ncq; - uint64_t size; /* Static transfer size, for commands like IDENTIFY. */ - uint32_t interrupts; /* Expected interrupts for this command. */ -} AHCICommandProp; - -AHCICommandProp ahci_command_properties[] = { - { .cmd = CMD_READ_PIO, .data = true, .pio = true, - .lba28 = true, .read = true }, - { .cmd = CMD_WRITE_PIO, .data = true, .pio = true, - .lba28 = true, .write = true }, - { .cmd = CMD_READ_PIO_EXT, .data = true, .pio = true, - .lba48 = true, .read = true }, - { .cmd = CMD_WRITE_PIO_EXT, .data = true, .pio = true, - .lba48 = true, .write = true }, - { .cmd = CMD_READ_DMA, .data = true, .dma = true, - .lba28 = true, .read = true }, - { .cmd = CMD_WRITE_DMA, .data = true, .dma = true, - .lba28 = true, .write = true }, - { .cmd = CMD_READ_DMA_EXT, .data = true, .dma = true, - .lba48 = true, .read = true }, - { .cmd = CMD_WRITE_DMA_EXT, .data = true, .dma = true, - .lba48 = true, .write = true }, - { .cmd = CMD_IDENTIFY, .data = true, .pio = true, - .size = 512, .read = true }, - { .cmd = READ_FPDMA_QUEUED, .data = true, .dma = true, - .lba48 = true, .read = true, .ncq = true }, - { .cmd = WRITE_FPDMA_QUEUED, .data = true, .dma = true, - .lba48 = true, .write = true, .ncq = true }, - { .cmd = CMD_READ_MAX, .lba28 = true }, - { .cmd = CMD_READ_MAX_EXT, .lba48 = true }, - { .cmd = CMD_FLUSH_CACHE, .data = false }, - { .cmd = CMD_PACKET, .data = true, .size = 16, - .atapi = true, .pio = true }, - { .cmd = CMD_PACKET_ID, .data = true, .pio = true, - .size = 512, .read = true } -}; - -struct AHCICommand { - /* Test Management Data */ - uint8_t name; - uint8_t port; - uint8_t slot; - uint8_t errors; - uint32_t interrupts; - uint64_t xbytes; - uint32_t prd_size; - uint32_t sector_size; - uint64_t buffer; - AHCICommandProp *props; - /* Data to be transferred to the guest */ - AHCICommandHeader header; - RegH2DFIS fis; - unsigned char *atapi_cmd; -}; - -/** - * Allocate space in the guest using information in the AHCIQState object. - */ -uint64_t ahci_alloc(AHCIQState *ahci, size_t bytes) -{ - g_assert(ahci); - g_assert(ahci->parent); - return qmalloc(ahci->parent, bytes); -} - -void ahci_free(AHCIQState *ahci, uint64_t addr) -{ - g_assert(ahci); - g_assert(ahci->parent); - qfree(ahci->parent, addr); -} - -bool is_atapi(AHCIQState *ahci, uint8_t port) -{ - return ahci_px_rreg(ahci, port, AHCI_PX_SIG) == AHCI_SIGNATURE_CDROM; -} - -/** - * Locate, verify, and return a handle to the AHCI device. - */ -QPCIDevice *get_ahci_device(QTestState *qts, uint32_t *fingerprint) -{ - QPCIDevice *ahci; - uint32_t ahci_fingerprint; - QPCIBus *pcibus; - - pcibus = qpci_new_pc(qts, NULL); - - /* Find the AHCI PCI device and verify it's the right one. */ - ahci = qpci_device_find(pcibus, QPCI_DEVFN(0x1F, 0x02)); - g_assert(ahci != NULL); - - ahci_fingerprint = qpci_config_readl(ahci, PCI_VENDOR_ID); - - switch (ahci_fingerprint) { - case AHCI_INTEL_ICH9: - break; - default: - /* Unknown device. */ - g_assert_not_reached(); - } - - if (fingerprint) { - *fingerprint = ahci_fingerprint; - } - return ahci; -} - -void free_ahci_device(QPCIDevice *dev) -{ - QPCIBus *pcibus = dev ? dev->bus : NULL; - - /* libqos doesn't have a function for this, so free it manually */ - g_free(dev); - qpci_free_pc(pcibus); -} - -/* Free all memory in-use by the AHCI device. */ -void ahci_clean_mem(AHCIQState *ahci) -{ - uint8_t port, slot; - - for (port = 0; port < 32; ++port) { - if (ahci->port[port].fb) { - ahci_free(ahci, ahci->port[port].fb); - ahci->port[port].fb = 0; - } - if (ahci->port[port].clb) { - for (slot = 0; slot < 32; slot++) { - ahci_destroy_command(ahci, port, slot); - } - ahci_free(ahci, ahci->port[port].clb); - ahci->port[port].clb = 0; - } - } -} - -/*** Logical Device Initialization ***/ - -/** - * Start the PCI device and sanity-check default operation. - */ -void ahci_pci_enable(AHCIQState *ahci) -{ - uint8_t reg; - - start_ahci_device(ahci); - - switch (ahci->fingerprint) { - case AHCI_INTEL_ICH9: - /* ICH9 has a register at PCI 0x92 that - * acts as a master port enabler mask. */ - reg = qpci_config_readb(ahci->dev, 0x92); - reg |= 0x3F; - qpci_config_writeb(ahci->dev, 0x92, reg); - /* 0...0111111b -- bit significant, ports 0-5 enabled. */ - ASSERT_BIT_SET(qpci_config_readb(ahci->dev, 0x92), 0x3F); - break; - } - -} - -/** - * Map BAR5/ABAR, and engage the PCI device. - */ -void start_ahci_device(AHCIQState *ahci) -{ - /* Map AHCI's ABAR (BAR5) */ - ahci->hba_bar = qpci_iomap(ahci->dev, 5, &ahci->barsize); - - /* turns on pci.cmd.iose, pci.cmd.mse and pci.cmd.bme */ - qpci_device_enable(ahci->dev); -} - -/** - * Test and initialize the AHCI's HBA memory areas. - * Initialize and start any ports with devices attached. - * Bring the HBA into the idle state. - */ -void ahci_hba_enable(AHCIQState *ahci) -{ - /* Bits of interest in this section: - * GHC.AE Global Host Control / AHCI Enable - * PxCMD.ST Port Command: Start - * PxCMD.SUD "Spin Up Device" - * PxCMD.POD "Power On Device" - * PxCMD.FRE "FIS Receive Enable" - * PxCMD.FR "FIS Receive Running" - * PxCMD.CR "Command List Running" - */ - uint32_t reg, ports_impl; - uint16_t i; - uint8_t num_cmd_slots; - - g_assert(ahci != NULL); - - /* Set GHC.AE to 1 */ - ahci_set(ahci, AHCI_GHC, AHCI_GHC_AE); - reg = ahci_rreg(ahci, AHCI_GHC); - ASSERT_BIT_SET(reg, AHCI_GHC_AE); - - /* Cache CAP and CAP2. */ - ahci->cap = ahci_rreg(ahci, AHCI_CAP); - ahci->cap2 = ahci_rreg(ahci, AHCI_CAP2); - - /* Read CAP.NCS, how many command slots do we have? */ - num_cmd_slots = ((ahci->cap & AHCI_CAP_NCS) >> ctzl(AHCI_CAP_NCS)) + 1; - g_test_message("Number of Command Slots: %u", num_cmd_slots); - - /* Determine which ports are implemented. */ - ports_impl = ahci_rreg(ahci, AHCI_PI); - - for (i = 0; ports_impl; ports_impl >>= 1, ++i) { - if (!(ports_impl & 0x01)) { - continue; - } - - g_test_message("Initializing port %u", i); - - reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); - if (BITCLR(reg, AHCI_PX_CMD_ST | AHCI_PX_CMD_CR | - AHCI_PX_CMD_FRE | AHCI_PX_CMD_FR)) { - g_test_message("port is idle"); - } else { - g_test_message("port needs to be idled"); - ahci_px_clr(ahci, i, AHCI_PX_CMD, - (AHCI_PX_CMD_ST | AHCI_PX_CMD_FRE)); - /* The port has 500ms to disengage. */ - usleep(500000); - reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); - ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR); - ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FR); - g_test_message("port is now idle"); - /* The spec does allow for possibly needing a PORT RESET - * or HBA reset if we fail to idle the port. */ - } - - /* Allocate Memory for the Command List Buffer & FIS Buffer */ - /* PxCLB space ... 0x20 per command, as in 4.2.2 p 36 */ - ahci->port[i].clb = ahci_alloc(ahci, num_cmd_slots * 0x20); - qtest_memset(ahci->parent->qts, ahci->port[i].clb, 0x00, - num_cmd_slots * 0x20); - g_test_message("CLB: 0x%08" PRIx64, ahci->port[i].clb); - ahci_px_wreg(ahci, i, AHCI_PX_CLB, ahci->port[i].clb); - g_assert_cmphex(ahci->port[i].clb, ==, - ahci_px_rreg(ahci, i, AHCI_PX_CLB)); - - /* PxFB space ... 0x100, as in 4.2.1 p 35 */ - ahci->port[i].fb = ahci_alloc(ahci, 0x100); - qtest_memset(ahci->parent->qts, ahci->port[i].fb, 0x00, 0x100); - g_test_message("FB: 0x%08" PRIx64, ahci->port[i].fb); - ahci_px_wreg(ahci, i, AHCI_PX_FB, ahci->port[i].fb); - g_assert_cmphex(ahci->port[i].fb, ==, - ahci_px_rreg(ahci, i, AHCI_PX_FB)); - - /* Clear PxSERR, PxIS, then IS.IPS[x] by writing '1's. */ - ahci_px_wreg(ahci, i, AHCI_PX_SERR, 0xFFFFFFFF); - ahci_px_wreg(ahci, i, AHCI_PX_IS, 0xFFFFFFFF); - ahci_wreg(ahci, AHCI_IS, (1 << i)); - - /* Verify Interrupts Cleared */ - reg = ahci_px_rreg(ahci, i, AHCI_PX_SERR); - g_assert_cmphex(reg, ==, 0); - - reg = ahci_px_rreg(ahci, i, AHCI_PX_IS); - g_assert_cmphex(reg, ==, 0); - - reg = ahci_rreg(ahci, AHCI_IS); - ASSERT_BIT_CLEAR(reg, (1 << i)); - - /* Enable All Interrupts: */ - ahci_px_wreg(ahci, i, AHCI_PX_IE, 0xFFFFFFFF); - reg = ahci_px_rreg(ahci, i, AHCI_PX_IE); - g_assert_cmphex(reg, ==, ~((uint32_t)AHCI_PX_IE_RESERVED)); - - /* Enable the FIS Receive Engine. */ - ahci_px_set(ahci, i, AHCI_PX_CMD, AHCI_PX_CMD_FRE); - reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); - ASSERT_BIT_SET(reg, AHCI_PX_CMD_FR); - - /* AHCI 1.3 spec: if !STS.BSY, !STS.DRQ and PxSSTS.DET indicates - * physical presence, a device is present and may be started. However, - * PxSERR.DIAG.X /may/ need to be cleared a priori. */ - reg = ahci_px_rreg(ahci, i, AHCI_PX_SERR); - if (BITSET(reg, AHCI_PX_SERR_DIAG_X)) { - ahci_px_set(ahci, i, AHCI_PX_SERR, AHCI_PX_SERR_DIAG_X); - } - - reg = ahci_px_rreg(ahci, i, AHCI_PX_TFD); - if (BITCLR(reg, AHCI_PX_TFD_STS_BSY | AHCI_PX_TFD_STS_DRQ)) { - reg = ahci_px_rreg(ahci, i, AHCI_PX_SSTS); - if ((reg & AHCI_PX_SSTS_DET) == SSTS_DET_ESTABLISHED) { - /* Device Found: set PxCMD.ST := 1 */ - ahci_px_set(ahci, i, AHCI_PX_CMD, AHCI_PX_CMD_ST); - ASSERT_BIT_SET(ahci_px_rreg(ahci, i, AHCI_PX_CMD), - AHCI_PX_CMD_CR); - g_test_message("Started Device %u", i); - } else if ((reg & AHCI_PX_SSTS_DET)) { - /* Device present, but in some unknown state. */ - g_assert_not_reached(); - } - } - } - - /* Enable GHC.IE */ - ahci_set(ahci, AHCI_GHC, AHCI_GHC_IE); - reg = ahci_rreg(ahci, AHCI_GHC); - ASSERT_BIT_SET(reg, AHCI_GHC_IE); - - ahci->enabled = true; - /* TODO: The device should now be idling and waiting for commands. - * In the future, a small test-case to inspect the Register D2H FIS - * and clear the initial interrupts might be good. */ -} - -/** - * Pick the first implemented and running port - */ -unsigned ahci_port_select(AHCIQState *ahci) -{ - uint32_t ports, reg; - unsigned i; - - ports = ahci_rreg(ahci, AHCI_PI); - for (i = 0; i < 32; ports >>= 1, ++i) { - if (ports == 0) { - i = 32; - } - - if (!(ports & 0x01)) { - continue; - } - - reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); - if (BITSET(reg, AHCI_PX_CMD_ST)) { - break; - } - } - g_assert(i < 32); - return i; -} - -/** - * Clear a port's interrupts and status information prior to a test. - */ -void ahci_port_clear(AHCIQState *ahci, uint8_t port) -{ - uint32_t reg; - - /* Clear out this port's interrupts (ignore the init register d2h fis) */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); - ahci_px_wreg(ahci, port, AHCI_PX_IS, reg); - g_assert_cmphex(ahci_px_rreg(ahci, port, AHCI_PX_IS), ==, 0); - - /* Wipe the FIS-Receive Buffer */ - qtest_memset(ahci->parent->qts, ahci->port[port].fb, 0x00, 0x100); -} - -/** - * Check a port for errors. - */ -void ahci_port_check_error(AHCIQState *ahci, uint8_t port, - uint32_t imask, uint8_t emask) -{ - uint32_t reg; - - /* The upper 9 bits of the IS register all indicate errors. */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); - reg &= ~imask; - reg >>= 23; - g_assert_cmphex(reg, ==, 0); - - /* The Sata Error Register should be empty. */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_SERR); - g_assert_cmphex(reg, ==, 0); - - /* The TFD also has two error sections. */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); - if (!emask) { - ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_ERR); - } else { - ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_ERR); - } - ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR & (~emask << 8)); - ASSERT_BIT_SET(reg, AHCI_PX_TFD_ERR & (emask << 8)); -} - -void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port, - uint32_t intr_mask) -{ - uint32_t reg; - - /* Check for expected interrupts */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); - ASSERT_BIT_SET(reg, intr_mask); - - /* Clear expected interrupts and assert all interrupts now cleared. */ - ahci_px_wreg(ahci, port, AHCI_PX_IS, intr_mask); - g_assert_cmphex(ahci_px_rreg(ahci, port, AHCI_PX_IS), ==, 0); -} - -void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot) -{ - uint32_t reg; - - /* Assert that the command slot is no longer busy (NCQ) */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_SACT); - ASSERT_BIT_CLEAR(reg, (1 << slot)); - - /* Non-NCQ */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_CI); - ASSERT_BIT_CLEAR(reg, (1 << slot)); - - /* And assert that we are generally not busy. */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); - ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_BSY); - ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_DRQ); -} - -void ahci_port_check_d2h_sanity(AHCIQState *ahci, uint8_t port, uint8_t slot) -{ - RegD2HFIS *d2h = g_malloc0(0x20); - uint32_t reg; - - qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x40, d2h, 0x20); - g_assert_cmphex(d2h->fis_type, ==, 0x34); - - reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); - g_assert_cmphex((reg & AHCI_PX_TFD_ERR) >> 8, ==, d2h->error); - g_assert_cmphex((reg & AHCI_PX_TFD_STS), ==, d2h->status); - - g_free(d2h); -} - -void ahci_port_check_pio_sanity(AHCIQState *ahci, AHCICommand *cmd) -{ - PIOSetupFIS *pio = g_malloc0(0x20); - uint8_t port = cmd->port; - - /* We cannot check the Status or E_Status registers, because - * the status may have again changed between the PIO Setup FIS - * and the conclusion of the command with the D2H Register FIS. */ - qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x20, pio, 0x20); - g_assert_cmphex(pio->fis_type, ==, 0x5f); - - /* Data transferred by PIO will either be: - * (1) 12 or 16 bytes for an ATAPI command packet (QEMU always uses 12), or - * (2) Actual data from the drive. - * If we do both, (2) winds up erasing any evidence of (1). - */ - if (cmd->props->atapi && (cmd->xbytes == 0 || cmd->props->dma)) { - g_assert(le16_to_cpu(pio->tx_count) == 12 || - le16_to_cpu(pio->tx_count) == 16); - } else { - /* The AHCI test suite here does not test any PIO command that specifies - * a DRQ block larger than one sector (like 0xC4), so this should always - * be one sector or less. */ - size_t pio_len = ((cmd->xbytes % cmd->sector_size) ? - (cmd->xbytes % cmd->sector_size) : cmd->sector_size); - g_assert_cmphex(le16_to_cpu(pio->tx_count), ==, pio_len); - } - g_free(pio); -} - -void ahci_port_check_cmd_sanity(AHCIQState *ahci, AHCICommand *cmd) -{ - AHCICommandHeader cmdh; - - ahci_get_command_header(ahci, cmd->port, cmd->slot, &cmdh); - /* Physical Region Descriptor Byte Count is not required to work for NCQ. */ - if (!cmd->props->ncq) { - g_assert_cmphex(cmd->xbytes, ==, cmdh.prdbc); - } -} - -/* Get the command in #slot of port #port. */ -void ahci_get_command_header(AHCIQState *ahci, uint8_t port, - uint8_t slot, AHCICommandHeader *cmd) -{ - uint64_t ba = ahci->port[port].clb; - ba += slot * sizeof(AHCICommandHeader); - qtest_memread(ahci->parent->qts, ba, cmd, sizeof(AHCICommandHeader)); - - cmd->flags = le16_to_cpu(cmd->flags); - cmd->prdtl = le16_to_cpu(cmd->prdtl); - cmd->prdbc = le32_to_cpu(cmd->prdbc); - cmd->ctba = le64_to_cpu(cmd->ctba); -} - -/* Set the command in #slot of port #port. */ -void ahci_set_command_header(AHCIQState *ahci, uint8_t port, - uint8_t slot, AHCICommandHeader *cmd) -{ - AHCICommandHeader tmp = { .flags = 0 }; - uint64_t ba = ahci->port[port].clb; - ba += slot * sizeof(AHCICommandHeader); - - tmp.flags = cpu_to_le16(cmd->flags); - tmp.prdtl = cpu_to_le16(cmd->prdtl); - tmp.prdbc = cpu_to_le32(cmd->prdbc); - tmp.ctba = cpu_to_le64(cmd->ctba); - - qtest_memwrite(ahci->parent->qts, ba, &tmp, sizeof(AHCICommandHeader)); -} - -void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot) -{ - AHCICommandHeader cmd; - - /* Obtain the Nth Command Header */ - ahci_get_command_header(ahci, port, slot, &cmd); - if (cmd.ctba == 0) { - /* No address in it, so just return -- it's empty. */ - goto tidy; - } - - /* Free the Table */ - ahci_free(ahci, cmd.ctba); - - tidy: - /* NULL the header. */ - memset(&cmd, 0x00, sizeof(cmd)); - ahci_set_command_header(ahci, port, slot, &cmd); - ahci->port[port].ctba[slot] = 0; - ahci->port[port].prdtl[slot] = 0; -} - -void ahci_write_fis(AHCIQState *ahci, AHCICommand *cmd) -{ - RegH2DFIS tmp = cmd->fis; - uint64_t addr = cmd->header.ctba; - - /* NCQ commands use exclusively 8 bit fields and needs no adjustment. - * Only the count field needs to be adjusted for non-NCQ commands. - * The auxiliary FIS fields are defined per-command and are not currently - * implemented in libqos/ahci.o, but may or may not need to be flipped. */ - if (!cmd->props->ncq) { - tmp.count = cpu_to_le16(tmp.count); - } - - qtest_memwrite(ahci->parent->qts, addr, &tmp, sizeof(tmp)); -} - -unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port) -{ - unsigned i; - unsigned j; - uint32_t reg; - - reg = ahci_px_rreg(ahci, port, AHCI_PX_CI); - - /* Pick the least recently used command slot that's available */ - for (i = 0; i < 32; ++i) { - j = ((ahci->port[port].next + i) % 32); - if (reg & (1 << j)) { - continue; - } - ahci_destroy_command(ahci, port, j); - ahci->port[port].next = (j + 1) % 32; - return j; - } - - g_test_message("All command slots were busy."); - g_assert_not_reached(); -} - -inline unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd) -{ - /* Each PRD can describe up to 4MiB */ - g_assert_cmphex(bytes_per_prd, <=, 4096 * 1024); - g_assert_cmphex(bytes_per_prd & 0x01, ==, 0x00); - return (bytes + bytes_per_prd - 1) / bytes_per_prd; -} - -const AHCIOpts default_opts = { .size = 0 }; - -/** - * ahci_exec: execute a given command on a specific - * AHCI port. - * - * @ahci: The device to send the command to - * @port: The port number of the SATA device we wish - * to have execute this command - * @op: The S/ATA command to execute, or if opts.atapi - * is true, the SCSI command code. - * @opts: Optional arguments to modify execution behavior. - */ -void ahci_exec(AHCIQState *ahci, uint8_t port, - uint8_t op, const AHCIOpts *opts_in) -{ - AHCICommand *cmd; - int rc; - AHCIOpts *opts; - - opts = g_memdup((opts_in == NULL ? &default_opts : opts_in), - sizeof(AHCIOpts)); - - /* No guest buffer provided, create one. */ - if (opts->size && !opts->buffer) { - opts->buffer = ahci_alloc(ahci, opts->size); - g_assert(opts->buffer); - qtest_memset(ahci->parent->qts, opts->buffer, 0x00, opts->size); - } - - /* Command creation */ - if (opts->atapi) { - uint16_t bcl = opts->set_bcl ? opts->bcl : ATAPI_SECTOR_SIZE; - cmd = ahci_atapi_command_create(op, bcl, opts->atapi_dma); - } else { - cmd = ahci_command_create(op); - } - ahci_command_adjust(cmd, opts->lba, opts->buffer, - opts->size, opts->prd_size); - - if (opts->pre_cb) { - rc = opts->pre_cb(ahci, cmd, opts); - g_assert_cmpint(rc, ==, 0); - } - - /* Write command to memory and issue it */ - ahci_command_commit(ahci, cmd, port); - ahci_command_issue_async(ahci, cmd); - if (opts->error) { - qtest_qmp_eventwait(ahci->parent->qts, "STOP"); - } - if (opts->mid_cb) { - rc = opts->mid_cb(ahci, cmd, opts); - g_assert_cmpint(rc, ==, 0); - } - if (opts->error) { - qtest_qmp_send(ahci->parent->qts, "{'execute':'cont' }"); - qtest_qmp_eventwait(ahci->parent->qts, "RESUME"); - } - - /* Wait for command to complete and verify sanity */ - ahci_command_wait(ahci, cmd); - ahci_command_verify(ahci, cmd); - if (opts->post_cb) { - rc = opts->post_cb(ahci, cmd, opts); - g_assert_cmpint(rc, ==, 0); - } - ahci_command_free(cmd); - if (opts->buffer != opts_in->buffer) { - ahci_free(ahci, opts->buffer); - } - g_free(opts); -} - -/* Issue a command, expecting it to fail and STOP the VM */ -AHCICommand *ahci_guest_io_halt(AHCIQState *ahci, uint8_t port, - uint8_t ide_cmd, uint64_t buffer, - size_t bufsize, uint64_t sector) -{ - AHCICommand *cmd; - - cmd = ahci_command_create(ide_cmd); - ahci_command_adjust(cmd, sector, buffer, bufsize, 0); - ahci_command_commit(ahci, cmd, port); - ahci_command_issue_async(ahci, cmd); - qtest_qmp_eventwait(ahci->parent->qts, "STOP"); - - return cmd; -} - -/* Resume a previously failed command and verify/finalize */ -void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd) -{ - /* Complete the command */ - qtest_qmp_send(ahci->parent->qts, "{'execute':'cont' }"); - qtest_qmp_eventwait(ahci->parent->qts, "RESUME"); - ahci_command_wait(ahci, cmd); - ahci_command_verify(ahci, cmd); - ahci_command_free(cmd); -} - -/* Given a guest buffer address, perform an IO operation */ -void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - uint64_t buffer, size_t bufsize, uint64_t sector) -{ - AHCICommand *cmd; - cmd = ahci_command_create(ide_cmd); - ahci_command_set_buffer(cmd, buffer); - ahci_command_set_size(cmd, bufsize); - if (sector) { - ahci_command_set_offset(cmd, sector); - } - ahci_command_commit(ahci, cmd, port); - ahci_command_issue(ahci, cmd); - ahci_command_verify(ahci, cmd); - ahci_command_free(cmd); -} - -static AHCICommandProp *ahci_command_find(uint8_t command_name) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(ahci_command_properties); i++) { - if (ahci_command_properties[i].cmd == command_name) { - return &ahci_command_properties[i]; - } - } - - return NULL; -} - -/* Given a HOST buffer, create a buffer address and perform an IO operation. */ -void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - void *buffer, size_t bufsize, uint64_t sector) -{ - uint64_t ptr; - AHCICommandProp *props; - - props = ahci_command_find(ide_cmd); - g_assert(props); - ptr = ahci_alloc(ahci, bufsize); - g_assert(!bufsize || ptr); - qtest_memset(ahci->parent->qts, ptr, 0x00, bufsize); - - if (bufsize && props->write) { - qtest_bufwrite(ahci->parent->qts, ptr, buffer, bufsize); - } - - ahci_guest_io(ahci, port, ide_cmd, ptr, bufsize, sector); - - if (bufsize && props->read) { - qtest_bufread(ahci->parent->qts, ptr, buffer, bufsize); - } - - ahci_free(ahci, ptr); -} - -/** - * Initializes a basic command header in memory. - * We assume that this is for an ATA command using RegH2DFIS. - */ -static void command_header_init(AHCICommand *cmd) -{ - AHCICommandHeader *hdr = &cmd->header; - AHCICommandProp *props = cmd->props; - - hdr->flags = 5; /* RegH2DFIS is 5 DW long. Must be < 32 */ - hdr->flags |= CMDH_CLR_BSY; /* Clear the BSY bit when done */ - if (props->write) { - hdr->flags |= CMDH_WRITE; - } - if (props->atapi) { - hdr->flags |= CMDH_ATAPI; - } - /* Other flags: PREFETCH, RESET, and BIST */ - hdr->prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size); - hdr->prdbc = 0; - hdr->ctba = 0; -} - -static void command_table_init(AHCICommand *cmd) -{ - RegH2DFIS *fis = &(cmd->fis); - uint16_t sect_count = (cmd->xbytes / cmd->sector_size); - - fis->fis_type = REG_H2D_FIS; - fis->flags = REG_H2D_FIS_CMD; /* "Command" bit */ - fis->command = cmd->name; - - if (cmd->props->ncq) { - NCQFIS *ncqfis = (NCQFIS *)fis; - /* NCQ is weird and re-uses FIS frames for unrelated data. - * See SATA 3.2, 13.6.4.1 READ FPDMA QUEUED for an example. */ - ncqfis->sector_low = sect_count & 0xFF; - ncqfis->sector_hi = (sect_count >> 8) & 0xFF; - ncqfis->device = NCQ_DEVICE_MAGIC; - /* Force Unit Access is bit 7 in the device register */ - ncqfis->tag = 0; /* bits 3-7 are the NCQ tag */ - ncqfis->prio = 0; /* bits 6,7 are a prio tag */ - /* RARC bit is bit 0 of TAG field */ - } else { - fis->feature_low = 0x00; - fis->feature_high = 0x00; - if (cmd->props->lba28 || cmd->props->lba48) { - fis->device = ATA_DEVICE_LBA; - } - fis->count = (cmd->xbytes / cmd->sector_size); - } - fis->icc = 0x00; - fis->control = 0x00; - memset(fis->aux, 0x00, ARRAY_SIZE(fis->aux)); -} - -void ahci_command_enable_atapi_dma(AHCICommand *cmd) -{ - RegH2DFIS *fis = &(cmd->fis); - g_assert(cmd->props->atapi); - fis->feature_low |= 0x01; - /* PIO is still used to transfer the ATAPI command */ - g_assert(cmd->props->pio); - cmd->props->dma = true; - /* BUG: We expect the DMA Setup interrupt for DMA commands */ - /* cmd->interrupts |= AHCI_PX_IS_DSS; */ -} - -AHCICommand *ahci_command_create(uint8_t command_name) -{ - AHCICommandProp *props = ahci_command_find(command_name); - AHCICommand *cmd; - - g_assert(props); - cmd = g_new0(AHCICommand, 1); - g_assert(!(props->dma && props->pio) || props->atapi); - g_assert(!(props->lba28 && props->lba48)); - g_assert(!(props->read && props->write)); - g_assert(!props->size || props->data); - g_assert(!props->ncq || props->lba48); - - /* Defaults and book-keeping */ - cmd->props = g_memdup(props, sizeof(AHCICommandProp)); - cmd->name = command_name; - cmd->xbytes = props->size; - cmd->prd_size = 4096; - cmd->buffer = 0xabad1dea; - cmd->sector_size = props->atapi ? ATAPI_SECTOR_SIZE : AHCI_SECTOR_SIZE; - - if (!cmd->props->ncq) { - cmd->interrupts = AHCI_PX_IS_DHRS; - } - /* BUG: We expect the DPS interrupt for data commands */ - /* cmd->interrupts |= props->data ? AHCI_PX_IS_DPS : 0; */ - /* BUG: We expect the DMA Setup interrupt for DMA commands */ - /* cmd->interrupts |= props->dma ? AHCI_PX_IS_DSS : 0; */ - cmd->interrupts |= props->ncq ? AHCI_PX_IS_SDBS : 0; - - command_header_init(cmd); - command_table_init(cmd); - - return cmd; -} - -AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl, bool dma) -{ - AHCICommand *cmd = ahci_command_create(CMD_PACKET); - cmd->atapi_cmd = g_malloc0(16); - cmd->atapi_cmd[0] = scsi_cmd; - stw_le_p(&cmd->fis.lba_lo[1], bcl); - if (dma) { - ahci_command_enable_atapi_dma(cmd); - } else { - cmd->interrupts |= bcl ? AHCI_PX_IS_PSS : 0; - } - return cmd; -} - -void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port, - bool ready, uint8_t expected_sense) -{ - AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_TEST_UNIT_READY, 0, false); - ahci_command_set_size(cmd, 0); - if (!ready) { - cmd->interrupts |= AHCI_PX_IS_TFES; - cmd->errors |= expected_sense << 4; - } - ahci_command_commit(ahci, cmd, port); - ahci_command_issue(ahci, cmd); - ahci_command_verify(ahci, cmd); - ahci_command_free(cmd); -} - -static int copy_buffer(AHCIQState *ahci, AHCICommand *cmd, - const AHCIOpts *opts) -{ - unsigned char *rx = opts->opaque; - qtest_bufread(ahci->parent->qts, opts->buffer, rx, opts->size); - return 0; -} - -void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port, - uint8_t *sense, uint8_t *asc) -{ - unsigned char *rx; - AHCIOpts opts = { - .size = 18, - .atapi = true, - .post_cb = copy_buffer, - }; - rx = g_malloc(18); - opts.opaque = rx; - - ahci_exec(ahci, port, CMD_ATAPI_REQUEST_SENSE, &opts); - - *sense = rx[2]; - *asc = rx[12]; - - g_free(rx); -} - -void ahci_atapi_eject(AHCIQState *ahci, uint8_t port) -{ - AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0, false); - ahci_command_set_size(cmd, 0); - - cmd->atapi_cmd[4] = 0x02; /* loej = true */ - ahci_command_commit(ahci, cmd, port); - ahci_command_issue(ahci, cmd); - ahci_command_verify(ahci, cmd); - ahci_command_free(cmd); -} - -void ahci_atapi_load(AHCIQState *ahci, uint8_t port) -{ - AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0, false); - ahci_command_set_size(cmd, 0); - - cmd->atapi_cmd[4] = 0x03; /* loej,start = true */ - ahci_command_commit(ahci, cmd, port); - ahci_command_issue(ahci, cmd); - ahci_command_verify(ahci, cmd); - ahci_command_free(cmd); -} - -void ahci_command_free(AHCICommand *cmd) -{ - g_free(cmd->atapi_cmd); - g_free(cmd->props); - g_free(cmd); -} - -void ahci_command_set_flags(AHCICommand *cmd, uint16_t cmdh_flags) -{ - cmd->header.flags |= cmdh_flags; -} - -void ahci_command_clr_flags(AHCICommand *cmd, uint16_t cmdh_flags) -{ - cmd->header.flags &= ~cmdh_flags; -} - -static void ahci_atapi_command_set_offset(AHCICommand *cmd, uint64_t lba) -{ - unsigned char *cbd = cmd->atapi_cmd; - g_assert(cbd); - - switch (cbd[0]) { - case CMD_ATAPI_READ_10: - case CMD_ATAPI_READ_CD: - g_assert_cmpuint(lba, <=, UINT32_MAX); - stl_be_p(&cbd[2], lba); - break; - case CMD_ATAPI_REQUEST_SENSE: - case CMD_ATAPI_TEST_UNIT_READY: - case CMD_ATAPI_START_STOP_UNIT: - g_assert_cmpuint(lba, ==, 0x00); - break; - default: - /* SCSI doesn't have uniform packet formats, - * so you have to add support for it manually. Sorry! */ - fprintf(stderr, "The Libqos AHCI driver does not support the " - "set_offset operation for ATAPI command 0x%02x, " - "please add support.\n", - cbd[0]); - g_assert_not_reached(); - } -} - -void ahci_command_set_offset(AHCICommand *cmd, uint64_t lba_sect) -{ - RegH2DFIS *fis = &(cmd->fis); - - if (cmd->props->atapi) { - ahci_atapi_command_set_offset(cmd, lba_sect); - return; - } else if (!cmd->props->data && !lba_sect) { - /* Not meaningful, ignore. */ - return; - } else if (cmd->props->lba28) { - g_assert_cmphex(lba_sect, <=, 0xFFFFFFF); - } else if (cmd->props->lba48 || cmd->props->ncq) { - g_assert_cmphex(lba_sect, <=, 0xFFFFFFFFFFFF); - } else { - /* Can't set offset if we don't know the format. */ - g_assert_not_reached(); - } - - /* LBA28 uses the low nibble of the device/control register for LBA24:27 */ - fis->lba_lo[0] = (lba_sect & 0xFF); - fis->lba_lo[1] = (lba_sect >> 8) & 0xFF; - fis->lba_lo[2] = (lba_sect >> 16) & 0xFF; - if (cmd->props->lba28) { - fis->device = (fis->device & 0xF0) | ((lba_sect >> 24) & 0x0F); - } - fis->lba_hi[0] = (lba_sect >> 24) & 0xFF; - fis->lba_hi[1] = (lba_sect >> 32) & 0xFF; - fis->lba_hi[2] = (lba_sect >> 40) & 0xFF; -} - -void ahci_command_set_buffer(AHCICommand *cmd, uint64_t buffer) -{ - cmd->buffer = buffer; -} - -static void ahci_atapi_set_size(AHCICommand *cmd, uint64_t xbytes) -{ - unsigned char *cbd = cmd->atapi_cmd; - uint64_t nsectors = xbytes / ATAPI_SECTOR_SIZE; - uint32_t tmp; - g_assert(cbd); - - switch (cbd[0]) { - case CMD_ATAPI_READ_10: - g_assert_cmpuint(nsectors, <=, UINT16_MAX); - stw_be_p(&cbd[7], nsectors); - break; - case CMD_ATAPI_READ_CD: - /* 24bit BE store */ - g_assert_cmpuint(nsectors, <, 1ULL << 24); - tmp = nsectors; - cbd[6] = (tmp & 0xFF0000) >> 16; - cbd[7] = (tmp & 0xFF00) >> 8; - cbd[8] = (tmp & 0xFF); - break; - case CMD_ATAPI_REQUEST_SENSE: - g_assert_cmpuint(xbytes, <=, UINT8_MAX); - cbd[4] = (uint8_t)xbytes; - break; - case CMD_ATAPI_TEST_UNIT_READY: - case CMD_ATAPI_START_STOP_UNIT: - g_assert_cmpuint(xbytes, ==, 0); - break; - default: - /* SCSI doesn't have uniform packet formats, - * so you have to add support for it manually. Sorry! */ - fprintf(stderr, "The Libqos AHCI driver does not support the set_size " - "operation for ATAPI command 0x%02x, please add support.\n", - cbd[0]); - g_assert_not_reached(); - } -} - -void ahci_command_set_sizes(AHCICommand *cmd, uint64_t xbytes, - unsigned prd_size) -{ - uint16_t sect_count; - - /* Each PRD can describe up to 4MiB, and must not be odd. */ - g_assert_cmphex(prd_size, <=, 4096 * 1024); - g_assert_cmphex(prd_size & 0x01, ==, 0x00); - if (prd_size) { - cmd->prd_size = prd_size; - } - cmd->xbytes = xbytes; - sect_count = (cmd->xbytes / cmd->sector_size); - - if (cmd->props->ncq) { - NCQFIS *nfis = (NCQFIS *)&(cmd->fis); - nfis->sector_low = sect_count & 0xFF; - nfis->sector_hi = (sect_count >> 8) & 0xFF; - } else if (cmd->props->atapi) { - ahci_atapi_set_size(cmd, xbytes); - } else { - /* For writes, the PIO Setup FIS interrupt only comes from DRQs - * after the first. - */ - if (cmd->props->pio && sect_count > (cmd->props->read ? 0 : 1)) { - cmd->interrupts |= AHCI_PX_IS_PSS; - } - cmd->fis.count = sect_count; - } - cmd->header.prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size); -} - -void ahci_command_set_size(AHCICommand *cmd, uint64_t xbytes) -{ - ahci_command_set_sizes(cmd, xbytes, cmd->prd_size); -} - -void ahci_command_set_prd_size(AHCICommand *cmd, unsigned prd_size) -{ - ahci_command_set_sizes(cmd, cmd->xbytes, prd_size); -} - -void ahci_command_adjust(AHCICommand *cmd, uint64_t offset, uint64_t buffer, - uint64_t xbytes, unsigned prd_size) -{ - ahci_command_set_sizes(cmd, xbytes, prd_size); - ahci_command_set_buffer(cmd, buffer); - ahci_command_set_offset(cmd, offset); -} - -void ahci_command_commit(AHCIQState *ahci, AHCICommand *cmd, uint8_t port) -{ - uint16_t i, prdtl; - uint64_t table_size, table_ptr, remaining; - PRD prd; - - /* This command is now tied to this port/command slot */ - cmd->port = port; - cmd->slot = ahci_pick_cmd(ahci, port); - - if (cmd->props->ncq) { - NCQFIS *nfis = (NCQFIS *)&cmd->fis; - nfis->tag = (cmd->slot << 3) & 0xFC; - } - - /* Create a buffer for the command table */ - prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size); - table_size = CMD_TBL_SIZ(prdtl); - table_ptr = ahci_alloc(ahci, table_size); - g_assert(table_ptr); - /* AHCI 1.3: Must be aligned to 0x80 */ - g_assert((table_ptr & 0x7F) == 0x00); - cmd->header.ctba = table_ptr; - - /* Commit the command header (part of the Command List Buffer) */ - ahci_set_command_header(ahci, port, cmd->slot, &(cmd->header)); - /* Now, write the command table (FIS, ACMD, and PRDT) -- FIS first, */ - ahci_write_fis(ahci, cmd); - /* Then ATAPI CMD, if needed */ - if (cmd->props->atapi) { - qtest_memwrite(ahci->parent->qts, table_ptr + 0x40, cmd->atapi_cmd, 16); - } - - /* Construct and write the PRDs to the command table */ - g_assert_cmphex(prdtl, ==, cmd->header.prdtl); - remaining = cmd->xbytes; - for (i = 0; i < prdtl; ++i) { - prd.dba = cpu_to_le64(cmd->buffer + (cmd->prd_size * i)); - prd.res = 0; - if (remaining > cmd->prd_size) { - /* Note that byte count is 0-based. */ - prd.dbc = cpu_to_le32(cmd->prd_size - 1); - remaining -= cmd->prd_size; - } else { - /* Again, dbc is 0-based. */ - prd.dbc = cpu_to_le32(remaining - 1); - remaining = 0; - } - prd.dbc |= cpu_to_le32(0x80000000); /* Request DPS Interrupt */ - - /* Commit the PRD entry to the Command Table */ - qtest_memwrite(ahci->parent->qts, table_ptr + 0x80 + (i * sizeof(PRD)), - &prd, sizeof(PRD)); - } - - /* Bookmark the PRDTL and CTBA values */ - ahci->port[port].ctba[cmd->slot] = table_ptr; - ahci->port[port].prdtl[cmd->slot] = prdtl; -} - -void ahci_command_issue_async(AHCIQState *ahci, AHCICommand *cmd) -{ - if (cmd->props->ncq) { - ahci_px_wreg(ahci, cmd->port, AHCI_PX_SACT, (1 << cmd->slot)); - } - - ahci_px_wreg(ahci, cmd->port, AHCI_PX_CI, (1 << cmd->slot)); -} - -void ahci_command_wait(AHCIQState *ahci, AHCICommand *cmd) -{ - /* We can't rely on STS_BSY until the command has started processing. - * Therefore, we also use the Command Issue bit as indication of - * a command in-flight. */ - -#define RSET(REG, MASK) (BITSET(ahci_px_rreg(ahci, cmd->port, (REG)), (MASK))) - - while (RSET(AHCI_PX_TFD, AHCI_PX_TFD_STS_BSY) || - RSET(AHCI_PX_CI, 1 << cmd->slot) || - (cmd->props->ncq && RSET(AHCI_PX_SACT, 1 << cmd->slot))) { - usleep(50); - } - -} - -void ahci_command_issue(AHCIQState *ahci, AHCICommand *cmd) -{ - ahci_command_issue_async(ahci, cmd); - ahci_command_wait(ahci, cmd); -} - -void ahci_command_verify(AHCIQState *ahci, AHCICommand *cmd) -{ - uint8_t slot = cmd->slot; - uint8_t port = cmd->port; - - ahci_port_check_error(ahci, port, cmd->interrupts, cmd->errors); - ahci_port_check_interrupts(ahci, port, cmd->interrupts); - ahci_port_check_nonbusy(ahci, port, slot); - ahci_port_check_cmd_sanity(ahci, cmd); - if (cmd->interrupts & AHCI_PX_IS_DHRS) { - ahci_port_check_d2h_sanity(ahci, port, slot); - } - if (cmd->props->pio) { - ahci_port_check_pio_sanity(ahci, cmd); - } -} - -uint8_t ahci_command_slot(AHCICommand *cmd) -{ - return cmd->slot; -} diff --git a/tests/libqos/ahci.h b/tests/libqos/ahci.h deleted file mode 100644 index f05b3e5fce..0000000000 --- a/tests/libqos/ahci.h +++ /dev/null @@ -1,651 +0,0 @@ -#ifndef LIBQOS_AHCI_H -#define LIBQOS_AHCI_H - -/* - * AHCI qtest library functions and definitions - * - * Copyright (c) 2014 John Snow - * - * 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 "libqos/libqos.h" -#include "libqos/pci.h" -#include "libqos/malloc-pc.h" - -/*** Supplementary PCI Config Space IDs & Masks ***/ -#define PCI_DEVICE_ID_INTEL_Q35_AHCI (0x2922) -#define PCI_MSI_FLAGS_RESERVED (0xFF00) -#define PCI_PM_CTRL_RESERVED (0xFC) -#define PCI_BCC(REG32) ((REG32) >> 24) -#define PCI_PI(REG32) (((REG32) >> 8) & 0xFF) -#define PCI_SCC(REG32) (((REG32) >> 16) & 0xFF) - -/*** Recognized AHCI Device Types ***/ -#define AHCI_INTEL_ICH9 (PCI_DEVICE_ID_INTEL_Q35_AHCI << 16 | \ - PCI_VENDOR_ID_INTEL) - -/*** AHCI/HBA Register Offsets and Bitmasks ***/ -#define AHCI_CAP (0) -#define AHCI_CAP_NP (0x1F) -#define AHCI_CAP_SXS (0x20) -#define AHCI_CAP_EMS (0x40) -#define AHCI_CAP_CCCS (0x80) -#define AHCI_CAP_NCS (0x1F00) -#define AHCI_CAP_PSC (0x2000) -#define AHCI_CAP_SSC (0x4000) -#define AHCI_CAP_PMD (0x8000) -#define AHCI_CAP_FBSS (0x10000) -#define AHCI_CAP_SPM (0x20000) -#define AHCI_CAP_SAM (0x40000) -#define AHCI_CAP_RESERVED (0x80000) -#define AHCI_CAP_ISS (0xF00000) -#define AHCI_CAP_SCLO (0x1000000) -#define AHCI_CAP_SAL (0x2000000) -#define AHCI_CAP_SALP (0x4000000) -#define AHCI_CAP_SSS (0x8000000) -#define AHCI_CAP_SMPS (0x10000000) -#define AHCI_CAP_SSNTF (0x20000000) -#define AHCI_CAP_SNCQ (0x40000000) -#define AHCI_CAP_S64A (0x80000000) - -#define AHCI_GHC (1) -#define AHCI_GHC_HR (0x01) -#define AHCI_GHC_IE (0x02) -#define AHCI_GHC_MRSM (0x04) -#define AHCI_GHC_RESERVED (0x7FFFFFF8) -#define AHCI_GHC_AE (0x80000000) - -#define AHCI_IS (2) -#define AHCI_PI (3) -#define AHCI_VS (4) - -#define AHCI_CCCCTL (5) -#define AHCI_CCCCTL_EN (0x01) -#define AHCI_CCCCTL_RESERVED (0x06) -#define AHCI_CCCCTL_CC (0xFF00) -#define AHCI_CCCCTL_TV (0xFFFF0000) - -#define AHCI_CCCPORTS (6) -#define AHCI_EMLOC (7) - -#define AHCI_EMCTL (8) -#define AHCI_EMCTL_STSMR (0x01) -#define AHCI_EMCTL_CTLTM (0x100) -#define AHCI_EMCTL_CTLRST (0x200) -#define AHCI_EMCTL_RESERVED (0xF0F0FCFE) - -#define AHCI_CAP2 (9) -#define AHCI_CAP2_BOH (0x01) -#define AHCI_CAP2_NVMP (0x02) -#define AHCI_CAP2_APST (0x04) -#define AHCI_CAP2_RESERVED (0xFFFFFFF8) - -#define AHCI_BOHC (10) -#define AHCI_RESERVED (11) -#define AHCI_NVMHCI (24) -#define AHCI_VENDOR (40) -#define AHCI_PORTS (64) - -/*** Port Memory Offsets & Bitmasks ***/ -#define AHCI_PX_CLB (0) -#define AHCI_PX_CLB_RESERVED (0x1FF) - -#define AHCI_PX_CLBU (1) - -#define AHCI_PX_FB (2) -#define AHCI_PX_FB_RESERVED (0xFF) - -#define AHCI_PX_FBU (3) - -#define AHCI_PX_IS (4) -#define AHCI_PX_IS_DHRS (0x1) -#define AHCI_PX_IS_PSS (0x2) -#define AHCI_PX_IS_DSS (0x4) -#define AHCI_PX_IS_SDBS (0x8) -#define AHCI_PX_IS_UFS (0x10) -#define AHCI_PX_IS_DPS (0x20) -#define AHCI_PX_IS_PCS (0x40) -#define AHCI_PX_IS_DMPS (0x80) -#define AHCI_PX_IS_RESERVED (0x23FFF00) -#define AHCI_PX_IS_PRCS (0x400000) -#define AHCI_PX_IS_IPMS (0x800000) -#define AHCI_PX_IS_OFS (0x1000000) -#define AHCI_PX_IS_INFS (0x4000000) -#define AHCI_PX_IS_IFS (0x8000000) -#define AHCI_PX_IS_HBDS (0x10000000) -#define AHCI_PX_IS_HBFS (0x20000000) -#define AHCI_PX_IS_TFES (0x40000000) -#define AHCI_PX_IS_CPDS (0x80000000) - -#define AHCI_PX_IE (5) -#define AHCI_PX_IE_DHRE (0x1) -#define AHCI_PX_IE_PSE (0x2) -#define AHCI_PX_IE_DSE (0x4) -#define AHCI_PX_IE_SDBE (0x8) -#define AHCI_PX_IE_UFE (0x10) -#define AHCI_PX_IE_DPE (0x20) -#define AHCI_PX_IE_PCE (0x40) -#define AHCI_PX_IE_DMPE (0x80) -#define AHCI_PX_IE_RESERVED (0x23FFF00) -#define AHCI_PX_IE_PRCE (0x400000) -#define AHCI_PX_IE_IPME (0x800000) -#define AHCI_PX_IE_OFE (0x1000000) -#define AHCI_PX_IE_INFE (0x4000000) -#define AHCI_PX_IE_IFE (0x8000000) -#define AHCI_PX_IE_HBDE (0x10000000) -#define AHCI_PX_IE_HBFE (0x20000000) -#define AHCI_PX_IE_TFEE (0x40000000) -#define AHCI_PX_IE_CPDE (0x80000000) - -#define AHCI_PX_CMD (6) -#define AHCI_PX_CMD_ST (0x1) -#define AHCI_PX_CMD_SUD (0x2) -#define AHCI_PX_CMD_POD (0x4) -#define AHCI_PX_CMD_CLO (0x8) -#define AHCI_PX_CMD_FRE (0x10) -#define AHCI_PX_CMD_RESERVED (0xE0) -#define AHCI_PX_CMD_CCS (0x1F00) -#define AHCI_PX_CMD_MPSS (0x2000) -#define AHCI_PX_CMD_FR (0x4000) -#define AHCI_PX_CMD_CR (0x8000) -#define AHCI_PX_CMD_CPS (0x10000) -#define AHCI_PX_CMD_PMA (0x20000) -#define AHCI_PX_CMD_HPCP (0x40000) -#define AHCI_PX_CMD_MPSP (0x80000) -#define AHCI_PX_CMD_CPD (0x100000) -#define AHCI_PX_CMD_ESP (0x200000) -#define AHCI_PX_CMD_FBSCP (0x400000) -#define AHCI_PX_CMD_APSTE (0x800000) -#define AHCI_PX_CMD_ATAPI (0x1000000) -#define AHCI_PX_CMD_DLAE (0x2000000) -#define AHCI_PX_CMD_ALPE (0x4000000) -#define AHCI_PX_CMD_ASP (0x8000000) -#define AHCI_PX_CMD_ICC (0xF0000000) - -#define AHCI_PX_RES1 (7) - -#define AHCI_PX_TFD (8) -#define AHCI_PX_TFD_STS (0xFF) -#define AHCI_PX_TFD_STS_ERR (0x01) -#define AHCI_PX_TFD_STS_CS1 (0x06) -#define AHCI_PX_TFD_STS_DRQ (0x08) -#define AHCI_PX_TFD_STS_CS2 (0x70) -#define AHCI_PX_TFD_STS_BSY (0x80) -#define AHCI_PX_TFD_ERR (0xFF00) -#define AHCI_PX_TFD_RESERVED (0xFFFF0000) - -#define AHCI_PX_SIG (9) -#define AHCI_PX_SIG_SECTOR_COUNT (0xFF) -#define AHCI_PX_SIG_LBA_LOW (0xFF00) -#define AHCI_PX_SIG_LBA_MID (0xFF0000) -#define AHCI_PX_SIG_LBA_HIGH (0xFF000000) - -#define AHCI_PX_SSTS (10) -#define AHCI_PX_SSTS_DET (0x0F) -#define AHCI_PX_SSTS_SPD (0xF0) -#define AHCI_PX_SSTS_IPM (0xF00) -#define AHCI_PX_SSTS_RESERVED (0xFFFFF000) -#define SSTS_DET_NO_DEVICE (0x00) -#define SSTS_DET_PRESENT (0x01) -#define SSTS_DET_ESTABLISHED (0x03) -#define SSTS_DET_OFFLINE (0x04) - -#define AHCI_PX_SCTL (11) - -#define AHCI_PX_SERR (12) -#define AHCI_PX_SERR_ERR (0xFFFF) -#define AHCI_PX_SERR_DIAG (0xFFFF0000) -#define AHCI_PX_SERR_DIAG_X (0x04000000) - -#define AHCI_PX_SACT (13) -#define AHCI_PX_CI (14) -#define AHCI_PX_SNTF (15) - -#define AHCI_PX_FBS (16) -#define AHCI_PX_FBS_EN (0x1) -#define AHCI_PX_FBS_DEC (0x2) -#define AHCI_PX_FBS_SDE (0x4) -#define AHCI_PX_FBS_DEV (0xF00) -#define AHCI_PX_FBS_ADO (0xF000) -#define AHCI_PX_FBS_DWE (0xF0000) -#define AHCI_PX_FBS_RESERVED (0xFFF000F8) - -#define AHCI_PX_RES2 (17) -#define AHCI_PX_VS (28) - -#define HBA_DATA_REGION_SIZE (256) -#define HBA_PORT_DATA_SIZE (128) -#define HBA_PORT_NUM_REG (HBA_PORT_DATA_SIZE/4) - -#define AHCI_VERSION_0_95 (0x00000905) -#define AHCI_VERSION_1_0 (0x00010000) -#define AHCI_VERSION_1_1 (0x00010100) -#define AHCI_VERSION_1_2 (0x00010200) -#define AHCI_VERSION_1_3 (0x00010300) - -#define AHCI_SECTOR_SIZE (512) -#define ATAPI_SECTOR_SIZE (2048) - -#define AHCI_SIGNATURE_CDROM (0xeb140101) -#define AHCI_SIGNATURE_DISK (0x00000101) - -/* FIS types */ -enum { - REG_H2D_FIS = 0x27, - REG_D2H_FIS = 0x34, - DMA_ACTIVATE_FIS = 0x39, - DMA_SETUP_FIS = 0x41, - DATA_FIS = 0x46, - BIST_ACTIVATE_FIS = 0x58, - PIO_SETUP_FIS = 0x5F, - SDB_FIS = 0xA1 -}; - -/* FIS flags */ -#define REG_H2D_FIS_CMD 0x80 - -/* ATA Commands */ -enum { - /* DMA */ - CMD_READ_DMA = 0xC8, - CMD_READ_DMA_EXT = 0x25, - CMD_WRITE_DMA = 0xCA, - CMD_WRITE_DMA_EXT = 0x35, - /* PIO */ - CMD_READ_PIO = 0x20, - CMD_READ_PIO_EXT = 0x24, - CMD_WRITE_PIO = 0x30, - CMD_WRITE_PIO_EXT = 0x34, - /* Misc */ - CMD_READ_MAX = 0xF8, - CMD_READ_MAX_EXT = 0x27, - CMD_FLUSH_CACHE = 0xE7, - CMD_IDENTIFY = 0xEC, - CMD_PACKET = 0xA0, - CMD_PACKET_ID = 0xA1, - /* NCQ */ - READ_FPDMA_QUEUED = 0x60, - WRITE_FPDMA_QUEUED = 0x61, -}; - -/* ATAPI Commands */ -enum { - CMD_ATAPI_TEST_UNIT_READY = 0x00, - CMD_ATAPI_REQUEST_SENSE = 0x03, - CMD_ATAPI_START_STOP_UNIT = 0x1b, - CMD_ATAPI_READ_10 = 0x28, - CMD_ATAPI_READ_CD = 0xbe, -}; - -enum { - SENSE_NO_SENSE = 0x00, - SENSE_NOT_READY = 0x02, - SENSE_UNIT_ATTENTION = 0x06, -}; - -enum { - ASC_MEDIUM_MAY_HAVE_CHANGED = 0x28, - ASC_MEDIUM_NOT_PRESENT = 0x3a, -}; - -/* AHCI Command Header Flags & Masks*/ -#define CMDH_CFL (0x1F) -#define CMDH_ATAPI (0x20) -#define CMDH_WRITE (0x40) -#define CMDH_PREFETCH (0x80) -#define CMDH_RESET (0x100) -#define CMDH_BIST (0x200) -#define CMDH_CLR_BSY (0x400) -#define CMDH_RES (0x800) -#define CMDH_PMP (0xF000) - -/* ATA device register masks */ -#define ATA_DEVICE_MAGIC 0xA0 /* used in ata1-3 */ -#define ATA_DEVICE_LBA 0x40 -#define NCQ_DEVICE_MAGIC 0x40 /* for ncq device registers */ -#define ATA_DEVICE_DRIVE 0x10 -#define ATA_DEVICE_HEAD 0x0F - -/*** Structures ***/ - -typedef struct AHCIPortQState { - uint64_t fb; - uint64_t clb; - uint64_t ctba[32]; - uint16_t prdtl[32]; - uint8_t next; /** Next Command Slot to Use **/ -} AHCIPortQState; - -typedef struct AHCIQState { - QOSState *parent; - QPCIDevice *dev; - QPCIBar hba_bar; - uint64_t barsize; - uint32_t fingerprint; - uint32_t cap; - uint32_t cap2; - AHCIPortQState port[32]; - bool enabled; -} AHCIQState; - -/** - * Generic FIS structure. - */ -typedef struct FIS { - uint8_t fis_type; - uint8_t flags; - char data[0]; -} __attribute__((__packed__)) FIS; - -/** - * Register device-to-host FIS structure. - */ -typedef struct RegD2HFIS { - /* DW0 */ - uint8_t fis_type; - uint8_t flags; - uint8_t status; - uint8_t error; - /* DW1 */ - uint8_t lba_lo[3]; - uint8_t device; - /* DW2 */ - uint8_t lba_hi[3]; - uint8_t res0; - /* DW3 */ - uint16_t count; - uint16_t res1; - /* DW4 */ - uint32_t res2; -} __attribute__((__packed__)) RegD2HFIS; - -/** - * Register device-to-host FIS structure; - * PIO Setup variety. - */ -typedef struct PIOSetupFIS { - /* DW0 */ - uint8_t fis_type; - uint8_t flags; - uint8_t status; - uint8_t error; - /* DW1 */ - uint8_t lba_lo[3]; - uint8_t device; - /* DW2 */ - uint8_t lba_hi[3]; - uint8_t res0; - /* DW3 */ - uint16_t count; - uint8_t res1; - uint8_t e_status; - /* DW4 */ - uint16_t tx_count; - uint16_t res2; -} __attribute__((__packed__)) PIOSetupFIS; - -/** - * Register host-to-device FIS structure. - */ -typedef struct RegH2DFIS { - /* DW0 */ - uint8_t fis_type; - uint8_t flags; - uint8_t command; - uint8_t feature_low; - /* DW1 */ - uint8_t lba_lo[3]; - uint8_t device; - /* DW2 */ - uint8_t lba_hi[3]; - uint8_t feature_high; - /* DW3 */ - uint16_t count; - uint8_t icc; - uint8_t control; - /* DW4 */ - uint8_t aux[4]; -} __attribute__((__packed__)) RegH2DFIS; - -/** - * Register host-to-device FIS structure, for NCQ commands. - * Actually just a RegH2DFIS, but with fields repurposed. - * Repurposed fields are annotated below. - */ -typedef struct NCQFIS { - /* DW0 */ - uint8_t fis_type; - uint8_t flags; - uint8_t command; - uint8_t sector_low; /* H2D: Feature 7:0 */ - /* DW1 */ - uint8_t lba_lo[3]; - uint8_t device; - /* DW2 */ - uint8_t lba_hi[3]; - uint8_t sector_hi; /* H2D: Feature 15:8 */ - /* DW3 */ - uint8_t tag; /* H2D: Count 0:7 */ - uint8_t prio; /* H2D: Count 15:8 */ - uint8_t icc; - uint8_t control; - /* DW4 */ - uint8_t aux[4]; -} __attribute__((__packed__)) NCQFIS; - -/** - * Command List entry structure. - * The command list contains between 1-32 of these structures. - */ -typedef struct AHCICommandHeader { - uint16_t flags; /* Cmd-Fis-Len, PMP#, and flags. */ - uint16_t prdtl; /* Phys Region Desc. Table Length */ - uint32_t prdbc; /* Phys Region Desc. Byte Count */ - uint64_t ctba; /* Command Table Descriptor Base Address */ - uint32_t res[4]; -} __attribute__((__packed__)) AHCICommandHeader; - -/** - * Physical Region Descriptor; pointed to by the Command List Header, - * struct ahci_command. - */ -typedef struct PRD { - uint64_t dba; /* Data Base Address */ - uint32_t res; /* Reserved */ - uint32_t dbc; /* Data Byte Count (0-indexed) & Interrupt Flag (bit 2^31) */ -} __attribute__((__packed__)) PRD; - -/* Opaque, defined within ahci.c */ -typedef struct AHCICommand AHCICommand; - -/* Options to ahci_exec */ -typedef struct AHCIOpts { - size_t size; /* Size of transfer */ - unsigned prd_size; /* Size per-each PRD */ - bool set_bcl; /* Override the default BCL of ATAPI_SECTOR_SIZE */ - unsigned bcl; /* Byte Count Limit, for ATAPI PIO */ - uint64_t lba; /* Starting LBA offset */ - uint64_t buffer; /* Pointer to source or destination guest buffer */ - bool atapi; /* ATAPI command? */ - bool atapi_dma; /* Use DMA for ATAPI? */ - bool error; - int (*pre_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *); - int (*mid_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *); - int (*post_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *); - void *opaque; -} AHCIOpts; - -/*** Macro Utilities ***/ -#define BITANY(data, mask) (((data) & (mask)) != 0) -#define BITSET(data, mask) (((data) & (mask)) == (mask)) -#define BITCLR(data, mask) (((data) & (mask)) == 0) -#define ASSERT_BIT_SET(data, mask) g_assert_cmphex((data) & (mask), ==, (mask)) -#define ASSERT_BIT_CLEAR(data, mask) g_assert_cmphex((data) & (mask), ==, 0) - -/* For calculating how big the PRD table needs to be: */ -#define CMD_TBL_SIZ(n) ((0x80 + ((n) * sizeof(PRD)) + 0x7F) & ~0x7F) - -/* Helpers for reading/writing AHCI HBA register values */ - -static inline uint32_t ahci_mread(AHCIQState *ahci, size_t offset) -{ - return qpci_io_readl(ahci->dev, ahci->hba_bar, offset); -} - -static inline void ahci_mwrite(AHCIQState *ahci, size_t offset, uint32_t value) -{ - qpci_io_writel(ahci->dev, ahci->hba_bar, offset, value); -} - -static inline uint32_t ahci_rreg(AHCIQState *ahci, uint32_t reg_num) -{ - return ahci_mread(ahci, 4 * reg_num); -} - -static inline void ahci_wreg(AHCIQState *ahci, uint32_t reg_num, uint32_t value) -{ - ahci_mwrite(ahci, 4 * reg_num, value); -} - -static inline void ahci_set(AHCIQState *ahci, uint32_t reg_num, uint32_t mask) -{ - ahci_wreg(ahci, reg_num, ahci_rreg(ahci, reg_num) | mask); -} - -static inline void ahci_clr(AHCIQState *ahci, uint32_t reg_num, uint32_t mask) -{ - ahci_wreg(ahci, reg_num, ahci_rreg(ahci, reg_num) & ~mask); -} - -static inline size_t ahci_px_offset(uint8_t port, uint32_t reg_num) -{ - return AHCI_PORTS + (HBA_PORT_NUM_REG * port) + reg_num; -} - -static inline uint32_t ahci_px_rreg(AHCIQState *ahci, uint8_t port, - uint32_t reg_num) -{ - return ahci_rreg(ahci, ahci_px_offset(port, reg_num)); -} - -static inline void ahci_px_wreg(AHCIQState *ahci, uint8_t port, - uint32_t reg_num, uint32_t value) -{ - ahci_wreg(ahci, ahci_px_offset(port, reg_num), value); -} - -static inline void ahci_px_set(AHCIQState *ahci, uint8_t port, - uint32_t reg_num, uint32_t mask) -{ - ahci_px_wreg(ahci, port, reg_num, - ahci_px_rreg(ahci, port, reg_num) | mask); -} - -static inline void ahci_px_clr(AHCIQState *ahci, uint8_t port, - uint32_t reg_num, uint32_t mask) -{ - ahci_px_wreg(ahci, port, reg_num, - ahci_px_rreg(ahci, port, reg_num) & ~mask); -} - -/*** Prototypes ***/ -uint64_t ahci_alloc(AHCIQState *ahci, size_t bytes); -void ahci_free(AHCIQState *ahci, uint64_t addr); -void ahci_clean_mem(AHCIQState *ahci); - -/* Device management */ -QPCIDevice *get_ahci_device(QTestState *qts, uint32_t *fingerprint); -void free_ahci_device(QPCIDevice *dev); -void ahci_pci_enable(AHCIQState *ahci); -void start_ahci_device(AHCIQState *ahci); -void ahci_hba_enable(AHCIQState *ahci); - -/* Port Management */ -unsigned ahci_port_select(AHCIQState *ahci); -void ahci_port_clear(AHCIQState *ahci, uint8_t port); - -/* Command header / table management */ -unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port); -void ahci_get_command_header(AHCIQState *ahci, uint8_t port, - uint8_t slot, AHCICommandHeader *cmd); -void ahci_set_command_header(AHCIQState *ahci, uint8_t port, - uint8_t slot, AHCICommandHeader *cmd); -void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot); - -/* AHCI sanity check routines */ -void ahci_port_check_error(AHCIQState *ahci, uint8_t port, - uint32_t imask, uint8_t emask); -void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port, - uint32_t intr_mask); -void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot); -void ahci_port_check_d2h_sanity(AHCIQState *ahci, uint8_t port, uint8_t slot); -void ahci_port_check_pio_sanity(AHCIQState *ahci, AHCICommand *cmd); -void ahci_port_check_cmd_sanity(AHCIQState *ahci, AHCICommand *cmd); - -/* Misc */ -bool is_atapi(AHCIQState *ahci, uint8_t port); -unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd); - -/* Command: Macro level execution */ -void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - uint64_t gbuffer, size_t size, uint64_t sector); -AHCICommand *ahci_guest_io_halt(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - uint64_t gbuffer, size_t size, uint64_t sector); -void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd); -void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, - void *buffer, size_t bufsize, uint64_t sector); -void ahci_exec(AHCIQState *ahci, uint8_t port, - uint8_t op, const AHCIOpts *opts); -void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port, bool ready, - uint8_t expected_sense); -void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port, - uint8_t *sense, uint8_t *asc); -void ahci_atapi_eject(AHCIQState *ahci, uint8_t port); -void ahci_atapi_load(AHCIQState *ahci, uint8_t port); - -/* Command: Fine-grained lifecycle */ -AHCICommand *ahci_command_create(uint8_t command_name); -AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl, bool dma); -void ahci_command_commit(AHCIQState *ahci, AHCICommand *cmd, uint8_t port); -void ahci_command_issue(AHCIQState *ahci, AHCICommand *cmd); -void ahci_command_issue_async(AHCIQState *ahci, AHCICommand *cmd); -void ahci_command_wait(AHCIQState *ahci, AHCICommand *cmd); -void ahci_command_verify(AHCIQState *ahci, AHCICommand *cmd); -void ahci_command_free(AHCICommand *cmd); - -/* Command: adjustments */ -void ahci_command_set_flags(AHCICommand *cmd, uint16_t cmdh_flags); -void ahci_command_clr_flags(AHCICommand *cmd, uint16_t cmdh_flags); -void ahci_command_set_offset(AHCICommand *cmd, uint64_t lba_sect); -void ahci_command_set_buffer(AHCICommand *cmd, uint64_t buffer); -void ahci_command_set_size(AHCICommand *cmd, uint64_t xbytes); -void ahci_command_set_prd_size(AHCICommand *cmd, unsigned prd_size); -void ahci_command_set_sizes(AHCICommand *cmd, uint64_t xbytes, - unsigned prd_size); -void ahci_command_set_acmd(AHCICommand *cmd, void *acmd); -void ahci_command_enable_atapi_dma(AHCICommand *cmd); -void ahci_command_adjust(AHCICommand *cmd, uint64_t lba_sect, uint64_t gbuffer, - uint64_t xbytes, unsigned prd_size); - -/* Command: Misc */ -uint8_t ahci_command_slot(AHCICommand *cmd); -void ahci_write_fis(AHCIQState *ahci, AHCICommand *cmd); - -#endif diff --git a/tests/libqos/arm-imx25-pdk-machine.c b/tests/libqos/arm-imx25-pdk-machine.c deleted file mode 100644 index 25066fb8a9..0000000000 --- a/tests/libqos/arm-imx25-pdk-machine.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2019 Red Hat, Inc. - * - * Author: Paolo Bonzini - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "libqos/i2c.h" - -#define ARM_PAGE_SIZE 4096 -#define IMX25_PDK_RAM_START 0x80000000 -#define IMX25_PDK_RAM_END 0x88000000 - -typedef struct QIMX25PDKMachine QIMX25PDKMachine; - -struct QIMX25PDKMachine { - QOSGraphObject obj; - QGuestAllocator alloc; - IMXI2C i2c_1; -}; - -static void *imx25_pdk_get_driver(void *object, const char *interface) -{ - QIMX25PDKMachine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/imx25_pdk\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *imx25_pdk_get_device(void *obj, const char *device) -{ - QIMX25PDKMachine *machine = obj; - if (!g_strcmp0(device, "imx.i2c")) { - return &machine->i2c_1.obj; - } - - fprintf(stderr, "%s not present in arm/imx25_pdk\n", device); - g_assert_not_reached(); -} - -static void imx25_pdk_destructor(QOSGraphObject *obj) -{ - QIMX25PDKMachine *machine = (QIMX25PDKMachine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_arm_imx25_pdk(QTestState *qts) -{ - QIMX25PDKMachine *machine = g_new0(QIMX25PDKMachine, 1); - - alloc_init(&machine->alloc, 0, - IMX25_PDK_RAM_START, - IMX25_PDK_RAM_END, - ARM_PAGE_SIZE); - machine->obj.get_device = imx25_pdk_get_device; - machine->obj.get_driver = imx25_pdk_get_driver; - machine->obj.destructor = imx25_pdk_destructor; - - imx_i2c_init(&machine->i2c_1, qts, 0x43f80000); - return &machine->obj; -} - -static void imx25_pdk_register_nodes(void) -{ - QOSGraphEdgeOptions edge = { - .extra_device_opts = "bus=i2c-bus.0" - }; - qos_node_create_machine("arm/imx25-pdk", qos_create_machine_arm_imx25_pdk); - qos_node_contains("arm/imx25-pdk", "imx.i2c", &edge, NULL); -} - -libqos_init(imx25_pdk_register_nodes); diff --git a/tests/libqos/arm-n800-machine.c b/tests/libqos/arm-n800-machine.c deleted file mode 100644 index 87279bdb26..0000000000 --- a/tests/libqos/arm-n800-machine.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2019 Red Hat, Inc. - * - * Author: Paolo Bonzini - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "libqos/i2c.h" - -#define ARM_PAGE_SIZE 4096 -#define N800_RAM_START 0x80000000 -#define N800_RAM_END 0x88000000 - -typedef struct QN800Machine QN800Machine; - -struct QN800Machine { - QOSGraphObject obj; - QGuestAllocator alloc; - OMAPI2C i2c_1; -}; - -static void *n800_get_driver(void *object, const char *interface) -{ - QN800Machine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/n800\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *n800_get_device(void *obj, const char *device) -{ - QN800Machine *machine = obj; - if (!g_strcmp0(device, "omap_i2c")) { - return &machine->i2c_1.obj; - } - - fprintf(stderr, "%s not present in arm/n800\n", device); - g_assert_not_reached(); -} - -static void n800_destructor(QOSGraphObject *obj) -{ - QN800Machine *machine = (QN800Machine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_arm_n800(QTestState *qts) -{ - QN800Machine *machine = g_new0(QN800Machine, 1); - - alloc_init(&machine->alloc, 0, - N800_RAM_START, - N800_RAM_END, - ARM_PAGE_SIZE); - machine->obj.get_device = n800_get_device; - machine->obj.get_driver = n800_get_driver; - machine->obj.destructor = n800_destructor; - - omap_i2c_init(&machine->i2c_1, qts, 0x48070000); - return &machine->obj; -} - -static void n800_register_nodes(void) -{ - QOSGraphEdgeOptions edge = { - .extra_device_opts = "bus=i2c-bus.0" - }; - qos_node_create_machine("arm/n800", qos_create_machine_arm_n800); - qos_node_contains("arm/n800", "omap_i2c", &edge, NULL); -} - -libqos_init(n800_register_nodes); diff --git a/tests/libqos/arm-raspi2-machine.c b/tests/libqos/arm-raspi2-machine.c deleted file mode 100644 index 12a7cb7e4b..0000000000 --- a/tests/libqos/arm-raspi2-machine.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "sdhci.h" - -#define ARM_PAGE_SIZE 4096 -#define RASPI2_RAM_ADDR 0 -#define RASPI2_RAM_SIZE 0x20000000 - -typedef struct QRaspi2Machine QRaspi2Machine; - -struct QRaspi2Machine { - QOSGraphObject obj; - QGuestAllocator alloc; - QSDHCI_MemoryMapped sdhci; -}; - -static void *raspi2_get_driver(void *object, const char *interface) -{ - QRaspi2Machine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/raspi2\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *raspi2_get_device(void *obj, const char *device) -{ - QRaspi2Machine *machine = obj; - if (!g_strcmp0(device, "generic-sdhci")) { - return &machine->sdhci.obj; - } - - fprintf(stderr, "%s not present in arm/raspi2\n", device); - g_assert_not_reached(); -} - -static void raspi2_destructor(QOSGraphObject *obj) -{ - QRaspi2Machine *machine = (QRaspi2Machine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_arm_raspi2(QTestState *qts) -{ - QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1); - - alloc_init(&machine->alloc, 0, - RASPI2_RAM_ADDR + (1 << 20), - RASPI2_RAM_ADDR + RASPI2_RAM_SIZE, - ARM_PAGE_SIZE); - machine->obj.get_device = raspi2_get_device; - machine->obj.get_driver = raspi2_get_driver; - machine->obj.destructor = raspi2_destructor; - qos_init_sdhci_mm(&machine->sdhci, qts, 0x3f300000, &(QSDHCIProperties) { - .version = 3, - .baseclock = 52, - .capab.sdma = false, - .capab.reg = 0x052134b4 - }); - return &machine->obj; -} - -static void raspi2_register_nodes(void) -{ - qos_node_create_machine("arm/raspi2", qos_create_machine_arm_raspi2); - qos_node_contains("arm/raspi2", "generic-sdhci", NULL); -} - -libqos_init(raspi2_register_nodes); diff --git a/tests/libqos/arm-sabrelite-machine.c b/tests/libqos/arm-sabrelite-machine.c deleted file mode 100644 index e6df43795a..0000000000 --- a/tests/libqos/arm-sabrelite-machine.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "sdhci.h" - -#define ARM_PAGE_SIZE 4096 -#define SABRELITE_RAM_START 0x10000000 -#define SABRELITE_RAM_END 0x30000000 - -typedef struct QSabreliteMachine QSabreliteMachine; - -struct QSabreliteMachine { - QOSGraphObject obj; - QGuestAllocator alloc; - QSDHCI_MemoryMapped sdhci; -}; - -static void *sabrelite_get_driver(void *object, const char *interface) -{ - QSabreliteMachine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/sabrelite\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *sabrelite_get_device(void *obj, const char *device) -{ - QSabreliteMachine *machine = obj; - if (!g_strcmp0(device, "generic-sdhci")) { - return &machine->sdhci.obj; - } - - fprintf(stderr, "%s not present in arm/sabrelite\n", device); - g_assert_not_reached(); -} - -static void sabrelite_destructor(QOSGraphObject *obj) -{ - QSabreliteMachine *machine = (QSabreliteMachine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_arm_sabrelite(QTestState *qts) -{ - QSabreliteMachine *machine = g_new0(QSabreliteMachine, 1); - - alloc_init(&machine->alloc, 0, - SABRELITE_RAM_START, - SABRELITE_RAM_END, - ARM_PAGE_SIZE); - machine->obj.get_device = sabrelite_get_device; - machine->obj.get_driver = sabrelite_get_driver; - machine->obj.destructor = sabrelite_destructor; - qos_init_sdhci_mm(&machine->sdhci, qts, 0x02190000, &(QSDHCIProperties) { - .version = 3, - .baseclock = 0, - .capab.sdma = true, - .capab.reg = 0x057834b4, - }); - return &machine->obj; -} - -static void sabrelite_register_nodes(void) -{ - qos_node_create_machine("arm/sabrelite", qos_create_machine_arm_sabrelite); - qos_node_contains("arm/sabrelite", "generic-sdhci", NULL); -} - -libqos_init(sabrelite_register_nodes); diff --git a/tests/libqos/arm-smdkc210-machine.c b/tests/libqos/arm-smdkc210-machine.c deleted file mode 100644 index 215b628a7d..0000000000 --- a/tests/libqos/arm-smdkc210-machine.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "sdhci.h" - -#define ARM_PAGE_SIZE 4096 -#define SMDKC210_RAM_ADDR 0x40000000ull -#define SMDKC210_RAM_SIZE 0x40000000ull - -typedef struct QSmdkc210Machine QSmdkc210Machine; - -struct QSmdkc210Machine { - QOSGraphObject obj; - QGuestAllocator alloc; - QSDHCI_MemoryMapped sdhci; -}; - -static void *smdkc210_get_driver(void *object, const char *interface) -{ - QSmdkc210Machine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/smdkc210\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *smdkc210_get_device(void *obj, const char *device) -{ - QSmdkc210Machine *machine = obj; - if (!g_strcmp0(device, "generic-sdhci")) { - return &machine->sdhci.obj; - } - - fprintf(stderr, "%s not present in arm/smdkc210\n", device); - g_assert_not_reached(); -} - -static void smdkc210_destructor(QOSGraphObject *obj) -{ - QSmdkc210Machine *machine = (QSmdkc210Machine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_arm_smdkc210(QTestState *qts) -{ - QSmdkc210Machine *machine = g_new0(QSmdkc210Machine, 1); - - alloc_init(&machine->alloc, 0, - SMDKC210_RAM_ADDR, - SMDKC210_RAM_ADDR + SMDKC210_RAM_SIZE, - ARM_PAGE_SIZE); - machine->obj.get_device = smdkc210_get_device; - machine->obj.get_driver = smdkc210_get_driver; - machine->obj.destructor = smdkc210_destructor; - qos_init_sdhci_mm(&machine->sdhci, qts, 0x12510000, &(QSDHCIProperties) { - .version = 2, - .baseclock = 0, - .capab.sdma = true, - .capab.reg = 0x5e80080, - }); - return &machine->obj; -} - -static void smdkc210_register_nodes(void) -{ - qos_node_create_machine("arm/smdkc210", qos_create_machine_arm_smdkc210); - qos_node_contains("arm/smdkc210", "generic-sdhci", NULL); -} - -libqos_init(smdkc210_register_nodes); diff --git a/tests/libqos/arm-virt-machine.c b/tests/libqos/arm-virt-machine.c deleted file mode 100644 index 96ffe3ee5c..0000000000 --- a/tests/libqos/arm-virt-machine.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-mmio.h" - -#define ARM_PAGE_SIZE 4096 -#define VIRTIO_MMIO_BASE_ADDR 0x0A003E00 -#define ARM_VIRT_RAM_ADDR 0x40000000 -#define ARM_VIRT_RAM_SIZE 0x20000000 -#define VIRTIO_MMIO_SIZE 0x00000200 - -typedef struct QVirtMachine QVirtMachine; - -struct QVirtMachine { - QOSGraphObject obj; - QGuestAllocator alloc; - QVirtioMMIODevice virtio_mmio; -}; - -static void virt_destructor(QOSGraphObject *obj) -{ - QVirtMachine *machine = (QVirtMachine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *virt_get_driver(void *object, const char *interface) -{ - QVirtMachine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/virtio\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *virt_get_device(void *obj, const char *device) -{ - QVirtMachine *machine = obj; - if (!g_strcmp0(device, "virtio-mmio")) { - return &machine->virtio_mmio.obj; - } - - fprintf(stderr, "%s not present in arm/virtio\n", device); - g_assert_not_reached(); -} - -static void *qos_create_machine_arm_virt(QTestState *qts) -{ - QVirtMachine *machine = g_new0(QVirtMachine, 1); - - alloc_init(&machine->alloc, 0, - ARM_VIRT_RAM_ADDR, - ARM_VIRT_RAM_ADDR + ARM_VIRT_RAM_SIZE, - ARM_PAGE_SIZE); - qvirtio_mmio_init_device(&machine->virtio_mmio, qts, VIRTIO_MMIO_BASE_ADDR, - VIRTIO_MMIO_SIZE); - - machine->obj.get_device = virt_get_device; - machine->obj.get_driver = virt_get_driver; - machine->obj.destructor = virt_destructor; - return machine; -} - -static void virtio_mmio_register_nodes(void) -{ - qos_node_create_machine("arm/virt", qos_create_machine_arm_virt); - qos_node_contains("arm/virt", "virtio-mmio", NULL); -} - -libqos_init(virtio_mmio_register_nodes); diff --git a/tests/libqos/arm-xilinx-zynq-a9-machine.c b/tests/libqos/arm-xilinx-zynq-a9-machine.c deleted file mode 100644 index 5bc95f2e73..0000000000 --- a/tests/libqos/arm-xilinx-zynq-a9-machine.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "sdhci.h" - -typedef struct QXilinxZynqA9Machine QXilinxZynqA9Machine; - -struct QXilinxZynqA9Machine { - QOSGraphObject obj; - QGuestAllocator alloc; - QSDHCI_MemoryMapped sdhci; -}; - -#define ARM_PAGE_SIZE 4096 -#define XILINX_ZYNQ_A9_RAM_ADDR 0 -#define XILINX_ZYNQ_A9_RAM_SIZE 0x20000000 - -static void *xilinx_zynq_a9_get_driver(void *object, const char *interface) -{ - QXilinxZynqA9Machine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in arm/xilinx-zynq-a9\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *xilinx_zynq_a9_get_device(void *obj, const char *device) -{ - QXilinxZynqA9Machine *machine = obj; - if (!g_strcmp0(device, "generic-sdhci")) { - return &machine->sdhci.obj; - } - - fprintf(stderr, "%s not present in arm/xilinx-zynq-a9\n", device); - g_assert_not_reached(); -} - -static void xilinx_zynq_a9_destructor(QOSGraphObject *obj) -{ - QXilinxZynqA9Machine *machine = (QXilinxZynqA9Machine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *qos_create_machine_arm_xilinx_zynq_a9(QTestState *qts) -{ - QXilinxZynqA9Machine *machine = g_new0(QXilinxZynqA9Machine, 1); - - alloc_init(&machine->alloc, 0, - XILINX_ZYNQ_A9_RAM_ADDR + (1 << 20), - XILINX_ZYNQ_A9_RAM_ADDR + XILINX_ZYNQ_A9_RAM_SIZE, - ARM_PAGE_SIZE); - - machine->obj.get_device = xilinx_zynq_a9_get_device; - machine->obj.get_driver = xilinx_zynq_a9_get_driver; - machine->obj.destructor = xilinx_zynq_a9_destructor; - /* Datasheet: UG585 (v1.12.1) */ - qos_init_sdhci_mm(&machine->sdhci, qts, 0xe0100000, &(QSDHCIProperties) { - .version = 2, - .baseclock = 0, - .capab.sdma = true, - .capab.reg = 0x69ec0080, - }); - return &machine->obj; -} - -static void xilinx_zynq_a9_register_nodes(void) -{ - qos_node_create_machine("arm/xilinx-zynq-a9", - qos_create_machine_arm_xilinx_zynq_a9); - qos_node_contains("arm/xilinx-zynq-a9", "generic-sdhci", NULL); -} - -libqos_init(xilinx_zynq_a9_register_nodes); diff --git a/tests/libqos/e1000e.c b/tests/libqos/e1000e.c deleted file mode 100644 index 560e7a2bb2..0000000000 --- a/tests/libqos/e1000e.c +++ /dev/null @@ -1,266 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.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/qgraph.h" -#include "e1000e.h" - -#define E1000E_IMS (0x00d0) - -#define E1000E_STATUS (0x0008) -#define E1000E_STATUS_LU BIT(1) -#define E1000E_STATUS_ASDV1000 BIT(9) - -#define E1000E_CTRL (0x0000) -#define E1000E_CTRL_RESET BIT(26) - -#define E1000E_RCTL (0x0100) -#define E1000E_RCTL_EN BIT(1) -#define E1000E_RCTL_UPE BIT(3) -#define E1000E_RCTL_MPE BIT(4) - -#define E1000E_RFCTL (0x5008) -#define E1000E_RFCTL_EXTEN BIT(15) - -#define E1000E_TCTL (0x0400) -#define E1000E_TCTL_EN BIT(1) - -#define E1000E_CTRL_EXT (0x0018) -#define E1000E_CTRL_EXT_DRV_LOAD BIT(28) -#define E1000E_CTRL_EXT_TXLSFLOW BIT(22) - -#define E1000E_IVAR (0x00E4) -#define E1000E_IVAR_TEST_CFG ((E1000E_RX0_MSG_ID << 0) | BIT(3) | \ - (E1000E_TX0_MSG_ID << 8) | BIT(11) | \ - (E1000E_OTHER_MSG_ID << 16) | BIT(19) | \ - BIT(31)) - -#define E1000E_RING_LEN (0x1000) - -#define E1000E_TDBAL (0x3800) - -#define E1000E_TDBAH (0x3804) -#define E1000E_TDH (0x3810) - -#define E1000E_RDBAL (0x2800) -#define E1000E_RDBAH (0x2804) -#define E1000E_RDH (0x2810) - -#define E1000E_TXD_LEN (16) -#define E1000E_RXD_LEN (16) - -static void e1000e_macreg_write(QE1000E *d, uint32_t reg, uint32_t val) -{ - QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); - qpci_io_writel(&d_pci->pci_dev, d_pci->mac_regs, reg, val); -} - -static uint32_t e1000e_macreg_read(QE1000E *d, uint32_t reg) -{ - QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); - return qpci_io_readl(&d_pci->pci_dev, d_pci->mac_regs, reg); -} - -void e1000e_tx_ring_push(QE1000E *d, void *descr) -{ - QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); - uint32_t tail = e1000e_macreg_read(d, E1000E_TDT); - uint32_t len = e1000e_macreg_read(d, E1000E_TDLEN) / E1000E_TXD_LEN; - - qtest_memwrite(d_pci->pci_dev.bus->qts, d->tx_ring + tail * E1000E_TXD_LEN, - descr, E1000E_TXD_LEN); - e1000e_macreg_write(d, E1000E_TDT, (tail + 1) % len); - - /* Read WB data for the packet transmitted */ - qtest_memread(d_pci->pci_dev.bus->qts, d->tx_ring + tail * E1000E_TXD_LEN, - descr, E1000E_TXD_LEN); -} - -void e1000e_rx_ring_push(QE1000E *d, void *descr) -{ - QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); - uint32_t tail = e1000e_macreg_read(d, E1000E_RDT); - uint32_t len = e1000e_macreg_read(d, E1000E_RDLEN) / E1000E_RXD_LEN; - - qtest_memwrite(d_pci->pci_dev.bus->qts, d->rx_ring + tail * E1000E_RXD_LEN, - descr, E1000E_RXD_LEN); - e1000e_macreg_write(d, E1000E_RDT, (tail + 1) % len); - - /* Read WB data for the packet received */ - qtest_memread(d_pci->pci_dev.bus->qts, d->rx_ring + tail * E1000E_RXD_LEN, - descr, E1000E_RXD_LEN); -} - -static void e1000e_foreach_callback(QPCIDevice *dev, int devfn, void *data) -{ - QPCIDevice *res = data; - memcpy(res, dev, sizeof(QPCIDevice)); - g_free(dev); -} - -void e1000e_wait_isr(QE1000E *d, uint16_t msg_id) -{ - QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); - guint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; - - do { - if (qpci_msix_pending(&d_pci->pci_dev, msg_id)) { - return; - } - qtest_clock_step(d_pci->pci_dev.bus->qts, 10000); - } while (g_get_monotonic_time() < end_time); - - g_error("Timeout expired"); -} - -static void e1000e_pci_destructor(QOSGraphObject *obj) -{ - QE1000E_PCI *epci = (QE1000E_PCI *) obj; - qpci_iounmap(&epci->pci_dev, epci->mac_regs); - qpci_msix_disable(&epci->pci_dev); -} - -static void e1000e_pci_start_hw(QOSGraphObject *obj) -{ - QE1000E_PCI *d = (QE1000E_PCI *) obj; - uint32_t val; - - /* Enable the device */ - qpci_device_enable(&d->pci_dev); - - /* Reset the device */ - val = e1000e_macreg_read(&d->e1000e, E1000E_CTRL); - e1000e_macreg_write(&d->e1000e, E1000E_CTRL, val | E1000E_CTRL_RESET); - - /* Enable and configure MSI-X */ - qpci_msix_enable(&d->pci_dev); - e1000e_macreg_write(&d->e1000e, E1000E_IVAR, E1000E_IVAR_TEST_CFG); - - /* Check the device status - link and speed */ - val = e1000e_macreg_read(&d->e1000e, E1000E_STATUS); - g_assert_cmphex(val & (E1000E_STATUS_LU | E1000E_STATUS_ASDV1000), - ==, E1000E_STATUS_LU | E1000E_STATUS_ASDV1000); - - /* Initialize TX/RX logic */ - e1000e_macreg_write(&d->e1000e, E1000E_RCTL, 0); - e1000e_macreg_write(&d->e1000e, E1000E_TCTL, 0); - - /* Notify the device that the driver is ready */ - val = e1000e_macreg_read(&d->e1000e, E1000E_CTRL_EXT); - e1000e_macreg_write(&d->e1000e, E1000E_CTRL_EXT, - val | E1000E_CTRL_EXT_DRV_LOAD | E1000E_CTRL_EXT_TXLSFLOW); - - e1000e_macreg_write(&d->e1000e, E1000E_TDBAL, - (uint32_t) d->e1000e.tx_ring); - e1000e_macreg_write(&d->e1000e, E1000E_TDBAH, - (uint32_t) (d->e1000e.tx_ring >> 32)); - e1000e_macreg_write(&d->e1000e, E1000E_TDLEN, E1000E_RING_LEN); - e1000e_macreg_write(&d->e1000e, E1000E_TDT, 0); - e1000e_macreg_write(&d->e1000e, E1000E_TDH, 0); - - /* Enable transmit */ - e1000e_macreg_write(&d->e1000e, E1000E_TCTL, E1000E_TCTL_EN); - e1000e_macreg_write(&d->e1000e, E1000E_RDBAL, - (uint32_t)d->e1000e.rx_ring); - e1000e_macreg_write(&d->e1000e, E1000E_RDBAH, - (uint32_t)(d->e1000e.rx_ring >> 32)); - e1000e_macreg_write(&d->e1000e, E1000E_RDLEN, E1000E_RING_LEN); - e1000e_macreg_write(&d->e1000e, E1000E_RDT, 0); - e1000e_macreg_write(&d->e1000e, E1000E_RDH, 0); - - /* Enable receive */ - e1000e_macreg_write(&d->e1000e, E1000E_RFCTL, E1000E_RFCTL_EXTEN); - e1000e_macreg_write(&d->e1000e, E1000E_RCTL, E1000E_RCTL_EN | - E1000E_RCTL_UPE | - E1000E_RCTL_MPE); - - /* Enable all interrupts */ - e1000e_macreg_write(&d->e1000e, E1000E_IMS, 0xFFFFFFFF); - -} - -static void *e1000e_pci_get_driver(void *obj, const char *interface) -{ - QE1000E_PCI *epci = obj; - if (!g_strcmp0(interface, "e1000e-if")) { - return &epci->e1000e; - } - - /* implicit contains */ - if (!g_strcmp0(interface, "pci-device")) { - return &epci->pci_dev; - } - - fprintf(stderr, "%s not present in e1000e\n", interface); - g_assert_not_reached(); -} - -static void *e1000e_pci_create(void *pci_bus, QGuestAllocator *alloc, - void *addr) -{ - QE1000E_PCI *d = g_new0(QE1000E_PCI, 1); - QPCIBus *bus = pci_bus; - QPCIAddress *address = addr; - - qpci_device_foreach(bus, address->vendor_id, address->device_id, - e1000e_foreach_callback, &d->pci_dev); - - /* Map BAR0 (mac registers) */ - d->mac_regs = qpci_iomap(&d->pci_dev, 0, NULL); - - /* Allocate and setup TX ring */ - d->e1000e.tx_ring = guest_alloc(alloc, E1000E_RING_LEN); - g_assert(d->e1000e.tx_ring != 0); - - /* Allocate and setup RX ring */ - d->e1000e.rx_ring = guest_alloc(alloc, E1000E_RING_LEN); - g_assert(d->e1000e.rx_ring != 0); - - d->obj.get_driver = e1000e_pci_get_driver; - d->obj.start_hw = e1000e_pci_start_hw; - d->obj.destructor = e1000e_pci_destructor; - - return &d->obj; -} - -static void e1000e_register_nodes(void) -{ - QPCIAddress addr = { - .vendor_id = 0x8086, - .device_id = 0x10D3, - }; - - /* FIXME: every test using this node needs to setup a -netdev socket,id=hs0 - * otherwise QEMU is not going to start */ - QOSGraphEdgeOptions opts = { - .extra_device_opts = "netdev=hs0", - }; - add_qpci_address(&opts, &addr); - - qos_node_create_driver("e1000e", e1000e_pci_create); - qos_node_consumes("e1000e", "pci-bus", &opts); -} - -libqos_init(e1000e_register_nodes); diff --git a/tests/libqos/e1000e.h b/tests/libqos/e1000e.h deleted file mode 100644 index dc4ab10f58..0000000000 --- a/tests/libqos/e1000e.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef QGRAPH_E1000E_H -#define QGRAPH_E1000E_H - -#include "libqos/qgraph.h" -#include "pci.h" - -#define E1000E_RX0_MSG_ID (0) -#define E1000E_TX0_MSG_ID (1) -#define E1000E_OTHER_MSG_ID (2) - -#define E1000E_TDLEN (0x3808) -#define E1000E_TDT (0x3818) -#define E1000E_RDLEN (0x2808) -#define E1000E_RDT (0x2818) - -typedef struct QE1000E QE1000E; -typedef struct QE1000E_PCI QE1000E_PCI; - -struct QE1000E { - uint64_t tx_ring; - uint64_t rx_ring; -}; - -struct QE1000E_PCI { - QOSGraphObject obj; - QPCIDevice pci_dev; - QPCIBar mac_regs; - QE1000E e1000e; -}; - -void e1000e_wait_isr(QE1000E *d, uint16_t msg_id); -void e1000e_tx_ring_push(QE1000E *d, void *descr); -void e1000e_rx_ring_push(QE1000E *d, void *descr); - -#endif diff --git a/tests/libqos/fw_cfg.c b/tests/libqos/fw_cfg.c deleted file mode 100644 index 1f46258f96..0000000000 --- a/tests/libqos/fw_cfg.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * libqos fw_cfg support - * - * Copyright IBM, Corp. 2012-2013 - * Copyright (C) 2013 Red Hat Inc. - * - * Authors: - * Anthony Liguori - * Markus Armbruster - * - * 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 "qemu/bswap.h" -#include "hw/nvram/fw_cfg.h" - -void qfw_cfg_select(QFWCFG *fw_cfg, uint16_t key) -{ - fw_cfg->select(fw_cfg, key); -} - -void qfw_cfg_read_data(QFWCFG *fw_cfg, void *data, size_t len) -{ - fw_cfg->read(fw_cfg, data, len); -} - -void qfw_cfg_get(QFWCFG *fw_cfg, uint16_t key, void *data, size_t len) -{ - qfw_cfg_select(fw_cfg, key); - qfw_cfg_read_data(fw_cfg, data, len); -} - -uint16_t qfw_cfg_get_u16(QFWCFG *fw_cfg, uint16_t key) -{ - uint16_t value; - qfw_cfg_get(fw_cfg, key, &value, sizeof(value)); - return le16_to_cpu(value); -} - -uint32_t qfw_cfg_get_u32(QFWCFG *fw_cfg, uint16_t key) -{ - uint32_t value; - qfw_cfg_get(fw_cfg, key, &value, sizeof(value)); - return le32_to_cpu(value); -} - -uint64_t qfw_cfg_get_u64(QFWCFG *fw_cfg, uint16_t key) -{ - uint64_t value; - qfw_cfg_get(fw_cfg, key, &value, sizeof(value)); - return le64_to_cpu(value); -} - -static void mm_fw_cfg_select(QFWCFG *fw_cfg, uint16_t key) -{ - qtest_writew(fw_cfg->qts, fw_cfg->base, key); -} - -/* - * The caller need check the return value. When the return value is - * nonzero, it means that some bytes have been transferred. - * - * If the fw_cfg file in question is smaller than the allocated & passed-in - * buffer, then the buffer has been populated only in part. - * - * If the fw_cfg file in question is larger than the passed-in - * buffer, then the return value explains how much room would have been - * necessary in total. And, while the caller's buffer has been fully - * populated, it has received only a starting slice of the fw_cfg file. - */ -size_t qfw_cfg_get_file(QFWCFG *fw_cfg, const char *filename, - void *data, size_t buflen) -{ - uint32_t count; - uint32_t i; - unsigned char *filesbuf = NULL; - size_t dsize; - FWCfgFile *pdir_entry; - size_t filesize = 0; - - qfw_cfg_get(fw_cfg, FW_CFG_FILE_DIR, &count, sizeof(count)); - count = be32_to_cpu(count); - dsize = sizeof(uint32_t) + count * sizeof(struct fw_cfg_file); - filesbuf = g_malloc(dsize); - qfw_cfg_get(fw_cfg, FW_CFG_FILE_DIR, filesbuf, dsize); - pdir_entry = (FWCfgFile *)(filesbuf + sizeof(uint32_t)); - for (i = 0; i < count; ++i, ++pdir_entry) { - if (!strcmp(pdir_entry->name, filename)) { - uint32_t len = be32_to_cpu(pdir_entry->size); - uint16_t sel = be16_to_cpu(pdir_entry->select); - filesize = len; - if (len > buflen) { - len = buflen; - } - qfw_cfg_get(fw_cfg, sel, data, len); - break; - } - } - g_free(filesbuf); - return filesize; -} - -static void mm_fw_cfg_read(QFWCFG *fw_cfg, void *data, size_t len) -{ - uint8_t *ptr = data; - int i; - - for (i = 0; i < len; i++) { - ptr[i] = qtest_readb(fw_cfg->qts, fw_cfg->base + 2); - } -} - -QFWCFG *mm_fw_cfg_init(QTestState *qts, uint64_t base) -{ - QFWCFG *fw_cfg = g_malloc0(sizeof(*fw_cfg)); - - fw_cfg->base = base; - fw_cfg->qts = qts; - fw_cfg->select = mm_fw_cfg_select; - fw_cfg->read = mm_fw_cfg_read; - - return fw_cfg; -} - -void mm_fw_cfg_uninit(QFWCFG *fw_cfg) -{ - g_free(fw_cfg); -} - -static void io_fw_cfg_select(QFWCFG *fw_cfg, uint16_t key) -{ - qtest_outw(fw_cfg->qts, fw_cfg->base, key); -} - -static void io_fw_cfg_read(QFWCFG *fw_cfg, void *data, size_t len) -{ - uint8_t *ptr = data; - int i; - - for (i = 0; i < len; i++) { - ptr[i] = qtest_inb(fw_cfg->qts, fw_cfg->base + 1); - } -} - -QFWCFG *io_fw_cfg_init(QTestState *qts, uint16_t base) -{ - QFWCFG *fw_cfg = g_malloc0(sizeof(*fw_cfg)); - - fw_cfg->base = base; - fw_cfg->qts = qts; - fw_cfg->select = io_fw_cfg_select; - fw_cfg->read = io_fw_cfg_read; - - return fw_cfg; -} - -void io_fw_cfg_uninit(QFWCFG *fw_cfg) -{ - g_free(fw_cfg); -} diff --git a/tests/libqos/fw_cfg.h b/tests/libqos/fw_cfg.h deleted file mode 100644 index 13325cc4ff..0000000000 --- a/tests/libqos/fw_cfg.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * libqos fw_cfg support - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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 LIBQOS_FW_CFG_H -#define LIBQOS_FW_CFG_H - -#include "libqtest.h" - -typedef struct QFWCFG QFWCFG; - -struct QFWCFG -{ - uint64_t base; - QTestState *qts; - void (*select)(QFWCFG *fw_cfg, uint16_t key); - void (*read)(QFWCFG *fw_cfg, void *data, size_t len); -}; - -void qfw_cfg_select(QFWCFG *fw_cfg, uint16_t key); -void qfw_cfg_read_data(QFWCFG *fw_cfg, void *data, size_t len); -void qfw_cfg_get(QFWCFG *fw_cfg, uint16_t key, void *data, size_t len); -uint16_t qfw_cfg_get_u16(QFWCFG *fw_cfg, uint16_t key); -uint32_t qfw_cfg_get_u32(QFWCFG *fw_cfg, uint16_t key); -uint64_t qfw_cfg_get_u64(QFWCFG *fw_cfg, uint16_t key); -size_t qfw_cfg_get_file(QFWCFG *fw_cfg, const char *filename, - void *data, size_t buflen); - -QFWCFG *mm_fw_cfg_init(QTestState *qts, uint64_t base); -void mm_fw_cfg_uninit(QFWCFG *fw_cfg); -QFWCFG *io_fw_cfg_init(QTestState *qts, uint16_t base); -void io_fw_cfg_uninit(QFWCFG *fw_cfg); - -static inline QFWCFG *pc_fw_cfg_init(QTestState *qts) -{ - return io_fw_cfg_init(qts, 0x510); -} - -static inline void pc_fw_cfg_uninit(QFWCFG *fw_cfg) -{ - io_fw_cfg_uninit(fw_cfg); -} - -#endif diff --git a/tests/libqos/i2c-imx.c b/tests/libqos/i2c-imx.c deleted file mode 100644 index f33ece55a3..0000000000 --- a/tests/libqos/i2c-imx.c +++ /dev/null @@ -1,216 +0,0 @@ -/* - * QTest i.MX I2C driver - * - * 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 . - */ - -#include "qemu/osdep.h" -#include "libqos/i2c.h" - - -#include "libqtest.h" - -#include "hw/i2c/imx_i2c.h" - -enum IMXI2CDirection { - IMX_I2C_READ, - IMX_I2C_WRITE, -}; - -static void imx_i2c_set_slave_addr(IMXI2C *s, uint8_t addr, - enum IMXI2CDirection direction) -{ - qtest_writeb(s->parent.qts, s->addr + I2DR_ADDR, - (addr << 1) | (direction == IMX_I2C_READ ? 1 : 0)); -} - -static void imx_i2c_send(I2CAdapter *i2c, uint8_t addr, - const uint8_t *buf, uint16_t len) -{ - IMXI2C *s = container_of(i2c, IMXI2C, parent); - uint8_t data; - uint8_t status; - uint16_t size = 0; - - if (!len) { - return; - } - - /* set the bus for write */ - data = I2CR_IEN | - I2CR_IIEN | - I2CR_MSTA | - I2CR_MTX | - I2CR_TXAK; - - qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) != 0); - - /* set the slave address */ - imx_i2c_set_slave_addr(s, addr, IMX_I2C_WRITE); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) != 0); - g_assert((status & I2SR_RXAK) == 0); - - /* ack the interrupt */ - qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) == 0); - - while (size < len) { - /* check we are still busy */ - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) != 0); - - /* write the data */ - qtest_writeb(i2c->qts, s->addr + I2DR_ADDR, buf[size]); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) != 0); - g_assert((status & I2SR_RXAK) == 0); - - /* ack the interrupt */ - qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) == 0); - - size++; - } - - /* release the bus */ - data &= ~(I2CR_MSTA | I2CR_MTX); - qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) == 0); -} - -static void imx_i2c_recv(I2CAdapter *i2c, uint8_t addr, - uint8_t *buf, uint16_t len) -{ - IMXI2C *s = container_of(i2c, IMXI2C, parent); - uint8_t data; - uint8_t status; - uint16_t size = 0; - - if (!len) { - return; - } - - /* set the bus for write */ - data = I2CR_IEN | - I2CR_IIEN | - I2CR_MSTA | - I2CR_MTX | - I2CR_TXAK; - - qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) != 0); - - /* set the slave address */ - imx_i2c_set_slave_addr(s, addr, IMX_I2C_READ); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) != 0); - g_assert((status & I2SR_RXAK) == 0); - - /* ack the interrupt */ - qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) == 0); - - /* set the bus for read */ - data &= ~I2CR_MTX; - /* if only one byte don't ack */ - if (len != 1) { - data &= ~I2CR_TXAK; - } - qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) != 0); - - /* dummy read */ - qtest_readb(i2c->qts, s->addr + I2DR_ADDR); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) != 0); - - /* ack the interrupt */ - qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) == 0); - - while (size < len) { - /* check we are still busy */ - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) != 0); - - if (size == (len - 1)) { - /* stop the read transaction */ - data &= ~(I2CR_MSTA | I2CR_MTX); - } else { - /* ack the data read */ - data |= I2CR_TXAK; - } - qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); - - /* read the data */ - buf[size] = qtest_readb(i2c->qts, s->addr + I2DR_ADDR); - - if (size != (len - 1)) { - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) != 0); - - /* ack the interrupt */ - qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); - } - - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IIF) == 0); - - size++; - } - - status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); - g_assert((status & I2SR_IBB) == 0); -} - -static void *imx_i2c_get_driver(void *obj, const char *interface) -{ - IMXI2C *s = obj; - if (!g_strcmp0(interface, "i2c-bus")) { - return &s->parent; - } - fprintf(stderr, "%s not present in imx-i2c\n", interface); - g_assert_not_reached(); -} - -void imx_i2c_init(IMXI2C *s, QTestState *qts, uint64_t addr) -{ - s->addr = addr; - - s->obj.get_driver = imx_i2c_get_driver; - - s->parent.send = imx_i2c_send; - s->parent.recv = imx_i2c_recv; - s->parent.qts = qts; -} - -static void imx_i2c_register_nodes(void) -{ - qos_node_create_driver("imx.i2c", NULL); - qos_node_produces("imx.i2c", "i2c-bus"); -} - -libqos_init(imx_i2c_register_nodes); diff --git a/tests/libqos/i2c-omap.c b/tests/libqos/i2c-omap.c deleted file mode 100644 index 9ae8214fa8..0000000000 --- a/tests/libqos/i2c-omap.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - * QTest I2C driver - * - * 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 "libqos/i2c.h" - - -#include "qemu/bswap.h" -#include "libqtest.h" - -enum OMAPI2CRegisters { - OMAP_I2C_REV = 0x00, - OMAP_I2C_STAT = 0x08, - OMAP_I2C_CNT = 0x18, - OMAP_I2C_DATA = 0x1c, - OMAP_I2C_CON = 0x24, - OMAP_I2C_SA = 0x2c, -}; - -enum OMAPI2CSTATBits { - OMAP_I2C_STAT_NACK = 1 << 1, - OMAP_I2C_STAT_ARDY = 1 << 2, - OMAP_I2C_STAT_RRDY = 1 << 3, - OMAP_I2C_STAT_XRDY = 1 << 4, - OMAP_I2C_STAT_ROVR = 1 << 11, - OMAP_I2C_STAT_SBD = 1 << 15, -}; - -enum OMAPI2CCONBits { - OMAP_I2C_CON_STT = 1 << 0, - OMAP_I2C_CON_STP = 1 << 1, - OMAP_I2C_CON_TRX = 1 << 9, - OMAP_I2C_CON_MST = 1 << 10, - OMAP_I2C_CON_BE = 1 << 14, - OMAP_I2C_CON_I2C_EN = 1 << 15, -}; - - -static void omap_i2c_set_slave_addr(OMAPI2C *s, uint8_t addr) -{ - uint16_t data = addr; - - qtest_writew(s->parent.qts, s->addr + OMAP_I2C_SA, data); - data = qtest_readw(s->parent.qts, s->addr + OMAP_I2C_SA); - g_assert_cmphex(data, ==, addr); -} - -static void omap_i2c_send(I2CAdapter *i2c, uint8_t addr, - const uint8_t *buf, uint16_t len) -{ - OMAPI2C *s = container_of(i2c, OMAPI2C, parent); - uint16_t data; - - omap_i2c_set_slave_addr(s, addr); - - data = len; - qtest_writew(i2c->qts, s->addr + OMAP_I2C_CNT, data); - - data = OMAP_I2C_CON_I2C_EN | - OMAP_I2C_CON_TRX | - OMAP_I2C_CON_MST | - OMAP_I2C_CON_STT | - OMAP_I2C_CON_STP; - qtest_writew(i2c->qts, s->addr + OMAP_I2C_CON, data); - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); - g_assert((data & OMAP_I2C_CON_STP) != 0); - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); - g_assert((data & OMAP_I2C_STAT_NACK) == 0); - - while (len > 1) { - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); - g_assert((data & OMAP_I2C_STAT_XRDY) != 0); - - data = buf[0] | ((uint16_t)buf[1] << 8); - qtest_writew(i2c->qts, s->addr + OMAP_I2C_DATA, data); - buf = (uint8_t *)buf + 2; - len -= 2; - } - if (len == 1) { - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); - g_assert((data & OMAP_I2C_STAT_XRDY) != 0); - - data = buf[0]; - qtest_writew(i2c->qts, s->addr + OMAP_I2C_DATA, data); - } - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); - g_assert((data & OMAP_I2C_CON_STP) == 0); -} - -static void omap_i2c_recv(I2CAdapter *i2c, uint8_t addr, - uint8_t *buf, uint16_t len) -{ - OMAPI2C *s = container_of(i2c, OMAPI2C, parent); - uint16_t data, stat; - uint16_t orig_len = len; - - omap_i2c_set_slave_addr(s, addr); - - data = len; - qtest_writew(i2c->qts, s->addr + OMAP_I2C_CNT, data); - - data = OMAP_I2C_CON_I2C_EN | - OMAP_I2C_CON_MST | - OMAP_I2C_CON_STT | - OMAP_I2C_CON_STP; - qtest_writew(i2c->qts, s->addr + OMAP_I2C_CON, data); - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); - g_assert((data & OMAP_I2C_STAT_NACK) == 0); - - while (len > 0) { - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); - if (len <= 4) { - g_assert((data & OMAP_I2C_CON_STP) == 0); - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CNT); - g_assert_cmpuint(data, ==, orig_len); - } else { - g_assert((data & OMAP_I2C_CON_STP) != 0); - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CNT); - g_assert_cmpuint(data, ==, len - 4); - } - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); - g_assert((data & OMAP_I2C_STAT_RRDY) != 0); - g_assert((data & OMAP_I2C_STAT_ROVR) == 0); - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_DATA); - - stat = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); - - if (unlikely(len == 1)) { - g_assert((stat & OMAP_I2C_STAT_SBD) != 0); - - buf[0] = data & 0xff; - buf++; - len--; - } else { - buf[0] = data & 0xff; - buf[1] = data >> 8; - buf += 2; - len -= 2; - } - } - - data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); - g_assert((data & OMAP_I2C_CON_STP) == 0); -} - -static void *omap_i2c_get_driver(void *obj, const char *interface) -{ - OMAPI2C *s = obj; - if (!g_strcmp0(interface, "i2c-bus")) { - return &s->parent; - } - fprintf(stderr, "%s not present in omap_i2c\n", interface); - g_assert_not_reached(); -} - -static void omap_i2c_start_hw(QOSGraphObject *object) -{ - OMAPI2C *s = (OMAPI2C *) object; - uint16_t data; - - /* verify the mmio address by looking for a known signature */ - data = qtest_readw(s->parent.qts, s->addr + OMAP_I2C_REV); - g_assert_cmphex(data, ==, 0x34); -} - -void omap_i2c_init(OMAPI2C *s, QTestState *qts, uint64_t addr) -{ - s->addr = addr; - - s->obj.get_driver = omap_i2c_get_driver; - s->obj.start_hw = omap_i2c_start_hw; - - s->parent.send = omap_i2c_send; - s->parent.recv = omap_i2c_recv; - s->parent.qts = qts; -} - -static void omap_i2c_register_nodes(void) -{ - qos_node_create_driver("omap_i2c", NULL); - qos_node_produces("omap_i2c", "i2c-bus"); -} - -libqos_init(omap_i2c_register_nodes); diff --git a/tests/libqos/i2c.c b/tests/libqos/i2c.c deleted file mode 100644 index 156114e745..0000000000 --- a/tests/libqos/i2c.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * QTest I2C driver - * - * 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 "libqos/i2c.h" -#include "libqtest.h" - -void i2c_send(QI2CDevice *i2cdev, const uint8_t *buf, uint16_t len) -{ - i2cdev->bus->send(i2cdev->bus, i2cdev->addr, buf, len); -} - -void i2c_recv(QI2CDevice *i2cdev, uint8_t *buf, uint16_t len) -{ - i2cdev->bus->recv(i2cdev->bus, i2cdev->addr, buf, len); -} - -void i2c_read_block(QI2CDevice *i2cdev, uint8_t reg, - uint8_t *buf, uint16_t len) -{ - i2c_send(i2cdev, ®, 1); - i2c_recv(i2cdev, buf, len); -} - -void i2c_write_block(QI2CDevice *i2cdev, uint8_t reg, - const uint8_t *buf, uint16_t len) -{ - uint8_t *cmd = g_malloc(len + 1); - cmd[0] = reg; - memcpy(&cmd[1], buf, len); - i2c_send(i2cdev, cmd, len + 1); - g_free(cmd); -} - -uint8_t i2c_get8(QI2CDevice *i2cdev, uint8_t reg) -{ - uint8_t resp[1]; - i2c_read_block(i2cdev, reg, resp, sizeof(resp)); - return resp[0]; -} - -uint16_t i2c_get16(QI2CDevice *i2cdev, uint8_t reg) -{ - uint8_t resp[2]; - i2c_read_block(i2cdev, reg, resp, sizeof(resp)); - return (resp[0] << 8) | resp[1]; -} - -void i2c_set8(QI2CDevice *i2cdev, uint8_t reg, uint8_t value) -{ - i2c_write_block(i2cdev, reg, &value, 1); -} - -void i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value) -{ - uint8_t data[2]; - - data[0] = value >> 8; - data[1] = value & 255; - i2c_write_block(i2cdev, reg, data, sizeof(data)); -} - -void *i2c_device_create(void *i2c_bus, QGuestAllocator *alloc, void *addr) -{ - QI2CDevice *i2cdev = g_new0(QI2CDevice, 1); - - i2cdev->bus = i2c_bus; - if (addr) { - i2cdev->addr = ((QI2CAddress *)addr)->addr; - } - return &i2cdev->obj; -} - -void add_qi2c_address(QOSGraphEdgeOptions *opts, QI2CAddress *addr) -{ - g_assert(addr); - - opts->arg = addr; - opts->size_arg = sizeof(QI2CAddress); -} diff --git a/tests/libqos/i2c.h b/tests/libqos/i2c.h deleted file mode 100644 index 945b65b34c..0000000000 --- a/tests/libqos/i2c.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * I2C libqos - * - * 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. - */ -#ifndef LIBQOS_I2C_H -#define LIBQOS_I2C_H - -#include "libqtest.h" -#include "libqos/qgraph.h" - -typedef struct I2CAdapter I2CAdapter; -struct I2CAdapter { - void (*send)(I2CAdapter *adapter, uint8_t addr, - const uint8_t *buf, uint16_t len); - void (*recv)(I2CAdapter *adapter, uint8_t addr, - uint8_t *buf, uint16_t len); - - QTestState *qts; -}; - -typedef struct QI2CAddress QI2CAddress; -struct QI2CAddress { - uint8_t addr; -}; - -typedef struct QI2CDevice QI2CDevice; -struct QI2CDevice { - /* - * For now, all devices are simple enough that there is no need for - * them to define their own constructor and get_driver functions. - * Therefore, QOSGraphObject is included directly in QI2CDevice; - * the tests expect to get a QI2CDevice rather than doing something - * like obj->get_driver("i2c-device"). - * - * In fact there is no i2c-device interface even, because there are - * no generic I2C tests). - */ - QOSGraphObject obj; - I2CAdapter *bus; - uint8_t addr; -}; - -void *i2c_device_create(void *i2c_bus, QGuestAllocator *alloc, void *addr); -void add_qi2c_address(QOSGraphEdgeOptions *opts, QI2CAddress *addr); - -void i2c_send(QI2CDevice *dev, const uint8_t *buf, uint16_t len); -void i2c_recv(QI2CDevice *dev, uint8_t *buf, uint16_t len); - -void i2c_read_block(QI2CDevice *dev, uint8_t reg, - uint8_t *buf, uint16_t len); -void i2c_write_block(QI2CDevice *dev, uint8_t reg, - const uint8_t *buf, uint16_t len); -uint8_t i2c_get8(QI2CDevice *dev, uint8_t reg); -uint16_t i2c_get16(QI2CDevice *dev, uint8_t reg); -void i2c_set8(QI2CDevice *dev, uint8_t reg, uint8_t value); -void i2c_set16(QI2CDevice *dev, uint8_t reg, uint16_t value); - -/* i2c-omap.c */ -typedef struct OMAPI2C { - QOSGraphObject obj; - I2CAdapter parent; - - uint64_t addr; -} OMAPI2C; - -void omap_i2c_init(OMAPI2C *s, QTestState *qts, uint64_t addr); - -/* i2c-imx.c */ -typedef struct IMXI2C { - QOSGraphObject obj; - I2CAdapter parent; - - uint64_t addr; -} IMXI2C; - -void imx_i2c_init(IMXI2C *s, QTestState *qts, uint64_t addr); - -#endif diff --git a/tests/libqos/libqos-pc.c b/tests/libqos/libqos-pc.c deleted file mode 100644 index d04abc548b..0000000000 --- a/tests/libqos/libqos-pc.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "qemu/osdep.h" -#include "libqos/libqos-pc.h" -#include "libqos/malloc-pc.h" -#include "libqos/pci-pc.h" - -static QOSOps qos_ops = { - .alloc_init = pc_alloc_init, - .qpci_new = qpci_new_pc, - .qpci_free = qpci_free_pc, - .shutdown = qtest_pc_shutdown, -}; - -QOSState *qtest_pc_vboot(const char *cmdline_fmt, va_list ap) -{ - return qtest_vboot(&qos_ops, cmdline_fmt, ap); -} - -QOSState *qtest_pc_boot(const char *cmdline_fmt, ...) -{ - QOSState *qs; - va_list ap; - - va_start(ap, cmdline_fmt); - qs = qtest_vboot(&qos_ops, cmdline_fmt, ap); - va_end(ap); - - qtest_irq_intercept_in(qs->qts, "ioapic"); - - return qs; -} - -void qtest_pc_shutdown(QOSState *qs) -{ - return qtest_common_shutdown(qs); -} diff --git a/tests/libqos/libqos-pc.h b/tests/libqos/libqos-pc.h deleted file mode 100644 index a0e4c45516..0000000000 --- a/tests/libqos/libqos-pc.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef LIBQOS_PC_H -#define LIBQOS_PC_H - -#include "libqos/libqos.h" - -QOSState *qtest_pc_vboot(const char *cmdline_fmt, va_list ap); -QOSState *qtest_pc_boot(const char *cmdline_fmt, ...); -void qtest_pc_shutdown(QOSState *qs); - -#endif diff --git a/tests/libqos/libqos-spapr.c b/tests/libqos/libqos-spapr.c deleted file mode 100644 index 8766d543ce..0000000000 --- a/tests/libqos/libqos-spapr.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "qemu/osdep.h" -#include "libqos/libqos-spapr.h" -#include "libqos/malloc-spapr.h" -#include "libqos/pci-spapr.h" - -static QOSOps qos_ops = { - .alloc_init = spapr_alloc_init, - .qpci_new = qpci_new_spapr, - .qpci_free = qpci_free_spapr, - .shutdown = qtest_spapr_shutdown, -}; - -QOSState *qtest_spapr_vboot(const char *cmdline_fmt, va_list ap) -{ - return qtest_vboot(&qos_ops, cmdline_fmt, ap); -} - -QOSState *qtest_spapr_boot(const char *cmdline_fmt, ...) -{ - QOSState *qs; - va_list ap; - - va_start(ap, cmdline_fmt); - qs = qtest_vboot(&qos_ops, cmdline_fmt, ap); - va_end(ap); - - return qs; -} - -void qtest_spapr_shutdown(QOSState *qs) -{ - return qtest_common_shutdown(qs); -} diff --git a/tests/libqos/libqos-spapr.h b/tests/libqos/libqos-spapr.h deleted file mode 100644 index dcb5c43ad3..0000000000 --- a/tests/libqos/libqos-spapr.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef LIBQOS_SPAPR_H -#define LIBQOS_SPAPR_H - -#include "libqos/libqos.h" - -QOSState *qtest_spapr_vboot(const char *cmdline_fmt, va_list ap); -QOSState *qtest_spapr_boot(const char *cmdline_fmt, ...); -void qtest_spapr_shutdown(QOSState *qs); - -#endif diff --git a/tests/libqos/libqos.c b/tests/libqos/libqos.c deleted file mode 100644 index f229eb2cb8..0000000000 --- a/tests/libqos/libqos.c +++ /dev/null @@ -1,240 +0,0 @@ -#include "qemu/osdep.h" -#include - -#include "libqtest.h" -#include "libqos/libqos.h" -#include "libqos/pci.h" -#include "qapi/qmp/qdict.h" - -/*** Test Setup & Teardown ***/ - -/** - * Launch QEMU with the given command line, - * and then set up interrupts and our guest malloc interface. - * Never returns NULL: - * Terminates the application in case an error is encountered. - */ -QOSState *qtest_vboot(QOSOps *ops, const char *cmdline_fmt, va_list ap) -{ - char *cmdline; - - QOSState *qs = g_new0(QOSState, 1); - - cmdline = g_strdup_vprintf(cmdline_fmt, ap); - qs->qts = qtest_init(cmdline); - qs->ops = ops; - if (ops) { - ops->alloc_init(&qs->alloc, qs->qts, ALLOC_NO_FLAGS); - qs->pcibus = ops->qpci_new(qs->qts, &qs->alloc); - } - - g_free(cmdline); - return qs; -} - -/** - * Launch QEMU with the given command line, - * and then set up interrupts and our guest malloc interface. - */ -QOSState *qtest_boot(QOSOps *ops, const char *cmdline_fmt, ...) -{ - QOSState *qs; - va_list ap; - - va_start(ap, cmdline_fmt); - qs = qtest_vboot(ops, cmdline_fmt, ap); - va_end(ap); - - return qs; -} - -/** - * Tear down the QEMU instance. - */ -void qtest_common_shutdown(QOSState *qs) -{ - if (qs->ops) { - if (qs->pcibus && qs->ops->qpci_free) { - qs->ops->qpci_free(qs->pcibus); - qs->pcibus = NULL; - } - } - alloc_destroy(&qs->alloc); - qtest_quit(qs->qts); - g_free(qs); -} - -void qtest_shutdown(QOSState *qs) -{ - if (qs->ops && qs->ops->shutdown) { - qs->ops->shutdown(qs); - } else { - qtest_common_shutdown(qs); - } -} - -static QDict *qmp_execute(QTestState *qts, const char *command) -{ - return qtest_qmp(qts, "{ 'execute': %s }", command); -} - -void migrate(QOSState *from, QOSState *to, const char *uri) -{ - const char *st; - QDict *rsp, *sub; - bool running; - - /* Is the machine currently running? */ - rsp = qmp_execute(from->qts, "query-status"); - g_assert(qdict_haskey(rsp, "return")); - sub = qdict_get_qdict(rsp, "return"); - g_assert(qdict_haskey(sub, "running")); - running = qdict_get_bool(sub, "running"); - qobject_unref(rsp); - - /* Issue the migrate command. */ - rsp = qtest_qmp(from->qts, - "{ 'execute': 'migrate', 'arguments': { 'uri': %s }}", - uri); - g_assert(qdict_haskey(rsp, "return")); - qobject_unref(rsp); - - /* Wait for STOP event, but only if we were running: */ - if (running) { - qtest_qmp_eventwait(from->qts, "STOP"); - } - - /* If we were running, we can wait for an event. */ - if (running) { - migrate_allocator(&from->alloc, &to->alloc); - qtest_qmp_eventwait(to->qts, "RESUME"); - return; - } - - /* Otherwise, we need to wait: poll until migration is completed. */ - while (1) { - rsp = qmp_execute(from->qts, "query-migrate"); - g_assert(qdict_haskey(rsp, "return")); - sub = qdict_get_qdict(rsp, "return"); - g_assert(qdict_haskey(sub, "status")); - st = qdict_get_str(sub, "status"); - - /* "setup", "active", "completed", "failed", "cancelled" */ - if (strcmp(st, "completed") == 0) { - qobject_unref(rsp); - break; - } - - if ((strcmp(st, "setup") == 0) || (strcmp(st, "active") == 0) - || (strcmp(st, "wait-unplug") == 0)) { - qobject_unref(rsp); - g_usleep(5000); - continue; - } - - fprintf(stderr, "Migration did not complete, status: %s\n", st); - g_assert_not_reached(); - } - - migrate_allocator(&from->alloc, &to->alloc); -} - -bool have_qemu_img(void) -{ - char *rpath; - const char *path = getenv("QTEST_QEMU_IMG"); - if (!path) { - return false; - } - - rpath = realpath(path, NULL); - if (!rpath) { - return false; - } else { - free(rpath); - return true; - } -} - -void mkimg(const char *file, const char *fmt, unsigned size_mb) -{ - gchar *cli; - bool ret; - int rc; - GError *err = NULL; - char *qemu_img_path; - gchar *out, *out2; - char *qemu_img_abs_path; - - 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); - - cli = g_strdup_printf("%s create -f %s %s %uM", qemu_img_abs_path, - fmt, file, size_mb); - ret = g_spawn_command_line_sync(cli, &out, &out2, &rc, &err); - if (err || !g_spawn_check_exit_status(rc, &err)) { - fprintf(stderr, "%s\n", err->message); - g_error_free(err); - } - g_assert(ret && !err); - - g_free(out); - g_free(out2); - g_free(cli); - free(qemu_img_abs_path); -} - -void mkqcow2(const char *file, unsigned size_mb) -{ - return mkimg(file, "qcow2", size_mb); -} - -void prepare_blkdebug_script(const char *debug_fn, const char *event) -{ - FILE *debug_file = fopen(debug_fn, "w"); - int ret; - - fprintf(debug_file, "[inject-error]\n"); - fprintf(debug_file, "event = \"%s\"\n", event); - fprintf(debug_file, "errno = \"5\"\n"); - fprintf(debug_file, "state = \"1\"\n"); - fprintf(debug_file, "immediately = \"off\"\n"); - fprintf(debug_file, "once = \"on\"\n"); - - fprintf(debug_file, "[set-state]\n"); - fprintf(debug_file, "event = \"%s\"\n", event); - fprintf(debug_file, "new_state = \"2\"\n"); - fflush(debug_file); - g_assert(!ferror(debug_file)); - - ret = fclose(debug_file); - g_assert(ret == 0); -} - -void generate_pattern(void *buffer, size_t len, size_t cycle_len) -{ - int i, j; - unsigned char *tx = (unsigned char *)buffer; - unsigned char p; - size_t *sx; - - /* Write an indicative pattern that varies and is unique per-cycle */ - p = rand() % 256; - for (i = 0; i < len; i++) { - tx[i] = p++ % 256; - if (i % cycle_len == 0) { - p = rand() % 256; - } - } - - /* force uniqueness by writing an id per-cycle */ - for (i = 0; i < len / cycle_len; i++) { - j = i * cycle_len; - if (j + sizeof(*sx) <= len) { - sx = (size_t *)&tx[j]; - *sx = i; - } - } -} diff --git a/tests/libqos/libqos.h b/tests/libqos/libqos.h deleted file mode 100644 index 8e971c25a3..0000000000 --- a/tests/libqos/libqos.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef LIBQOS_H -#define LIBQOS_H - -#include "libqtest.h" -#include "libqos/pci.h" -#include "libqos/malloc.h" - -typedef struct QOSState QOSState; - -typedef struct QOSOps { - void (*alloc_init)(QGuestAllocator *, QTestState *, QAllocOpts); - QPCIBus *(*qpci_new)(QTestState *qts, QGuestAllocator *alloc); - void (*qpci_free)(QPCIBus *bus); - void (*shutdown)(QOSState *); -} QOSOps; - -struct QOSState { - QTestState *qts; - QGuestAllocator alloc; - QPCIBus *pcibus; - QOSOps *ops; -}; - -QOSState *qtest_vboot(QOSOps *ops, const char *cmdline_fmt, va_list ap); -QOSState *qtest_boot(QOSOps *ops, const char *cmdline_fmt, ...); -void qtest_common_shutdown(QOSState *qs); -void qtest_shutdown(QOSState *qs); -bool have_qemu_img(void); -void mkimg(const char *file, const char *fmt, unsigned size_mb); -void mkqcow2(const char *file, unsigned size_mb); -void migrate(QOSState *from, QOSState *to, const char *uri); -void prepare_blkdebug_script(const char *debug_fn, const char *event); -void generate_pattern(void *buffer, size_t len, size_t cycle_len); - -static inline uint64_t qmalloc(QOSState *q, size_t bytes) -{ - return guest_alloc(&q->alloc, bytes); -} - -static inline void qfree(QOSState *q, uint64_t addr) -{ - guest_free(&q->alloc, addr); -} - -#endif diff --git a/tests/libqos/malloc-pc.c b/tests/libqos/malloc-pc.c deleted file mode 100644 index 6f92ce4135..0000000000 --- a/tests/libqos/malloc-pc.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * libqos malloc support for PC - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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/malloc-pc.h" -#include "libqos/fw_cfg.h" - -#include "standard-headers/linux/qemu_fw_cfg.h" - -#include "qemu-common.h" - -#define PAGE_SIZE (4096) - -void pc_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags) -{ - uint64_t ram_size; - QFWCFG *fw_cfg = pc_fw_cfg_init(qts); - - ram_size = qfw_cfg_get_u64(fw_cfg, FW_CFG_RAM_SIZE); - alloc_init(s, flags, 1 << 20, MIN(ram_size, 0xE0000000), PAGE_SIZE); - - /* clean-up */ - pc_fw_cfg_uninit(fw_cfg); -} diff --git a/tests/libqos/malloc-pc.h b/tests/libqos/malloc-pc.h deleted file mode 100644 index 21e75ae004..0000000000 --- a/tests/libqos/malloc-pc.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * libqos malloc support for PC - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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 LIBQOS_MALLOC_PC_H -#define LIBQOS_MALLOC_PC_H - -#include "libqos/malloc.h" - -void pc_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags); - -#endif diff --git a/tests/libqos/malloc-spapr.c b/tests/libqos/malloc-spapr.c deleted file mode 100644 index 2a6b7e3776..0000000000 --- a/tests/libqos/malloc-spapr.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * libqos malloc support for SPAPR - * - * 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/malloc-spapr.h" - -#include "qemu-common.h" - -#define PAGE_SIZE 4096 - -/* Memory must be a multiple of 256 MB, - * so we have at least 256MB - */ -#define SPAPR_MIN_SIZE 0x10000000 - -void spapr_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags) -{ - alloc_init(s, flags, 1 << 20, SPAPR_MIN_SIZE, PAGE_SIZE); -} diff --git a/tests/libqos/malloc-spapr.h b/tests/libqos/malloc-spapr.h deleted file mode 100644 index e5fe9bfc4b..0000000000 --- a/tests/libqos/malloc-spapr.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * libqos malloc support for SPAPR - * - * 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 LIBQOS_MALLOC_SPAPR_H -#define LIBQOS_MALLOC_SPAPR_H - -#include "libqos/malloc.h" - -void spapr_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags); - -#endif diff --git a/tests/libqos/malloc.c b/tests/libqos/malloc.c deleted file mode 100644 index 615422a5c4..0000000000 --- a/tests/libqos/malloc.c +++ /dev/null @@ -1,347 +0,0 @@ -/* - * libqos malloc support - * - * Copyright (c) 2014 - * - * Author: - * John Snow - * - * 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/malloc.h" -#include "qemu-common.h" -#include "qemu/host-utils.h" - -typedef struct MemBlock { - QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME; - uint64_t size; - uint64_t addr; -} MemBlock; - -#define DEFAULT_PAGE_SIZE 4096 - -static void mlist_delete(MemList *list, MemBlock *node) -{ - g_assert(list && node); - QTAILQ_REMOVE(list, node, MLIST_ENTNAME); - g_free(node); -} - -static MemBlock *mlist_find_key(MemList *head, uint64_t addr) -{ - MemBlock *node; - QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { - if (node->addr == addr) { - return node; - } - } - return NULL; -} - -static MemBlock *mlist_find_space(MemList *head, uint64_t size) -{ - MemBlock *node; - - QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { - if (node->size >= size) { - return node; - } - } - return NULL; -} - -static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr) -{ - MemBlock *node; - g_assert(head && insr); - - QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { - if (insr->addr < node->addr) { - QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME); - return insr; - } - } - - QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME); - return insr; -} - -static inline uint64_t mlist_boundary(MemBlock *node) -{ - return node->size + node->addr; -} - -static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right) -{ - g_assert(head && left && right); - - left->size += right->size; - mlist_delete(head, right); - return left; -} - -static void mlist_coalesce(MemList *head, MemBlock *node) -{ - g_assert(node); - MemBlock *left; - MemBlock *right; - char merge; - - do { - merge = 0; - left = QTAILQ_PREV(node, MLIST_ENTNAME); - right = QTAILQ_NEXT(node, MLIST_ENTNAME); - - /* clowns to the left of me */ - if (left && mlist_boundary(left) == node->addr) { - node = mlist_join(head, left, node); - merge = 1; - } - - /* jokers to the right */ - if (right && mlist_boundary(node) == right->addr) { - node = mlist_join(head, node, right); - merge = 1; - } - - } while (merge); -} - -static MemBlock *mlist_new(uint64_t addr, uint64_t size) -{ - MemBlock *block; - - if (!size) { - return NULL; - } - block = g_new0(MemBlock, 1); - - block->addr = addr; - block->size = size; - - return block; -} - -static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode, - uint64_t size) -{ - uint64_t addr; - MemBlock *usednode; - - g_assert(freenode); - g_assert_cmpint(freenode->size, >=, size); - - addr = freenode->addr; - if (freenode->size == size) { - /* re-use this freenode as our used node */ - QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME); - usednode = freenode; - } else { - /* adjust the free node and create a new used node */ - freenode->addr += size; - freenode->size -= size; - usednode = mlist_new(addr, size); - } - - mlist_sort_insert(s->used, usednode); - return addr; -} - -/* To assert the correctness of the list. - * Used only if ALLOC_PARANOID is set. */ -static void mlist_check(QGuestAllocator *s) -{ - MemBlock *node; - uint64_t addr = s->start > 0 ? s->start - 1 : 0; - uint64_t next = s->start; - - QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) { - g_assert_cmpint(node->addr, >, addr); - g_assert_cmpint(node->addr, >=, next); - addr = node->addr; - next = node->addr + node->size; - } - - addr = s->start > 0 ? s->start - 1 : 0; - next = s->start; - QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) { - g_assert_cmpint(node->addr, >, addr); - g_assert_cmpint(node->addr, >=, next); - addr = node->addr; - next = node->addr + node->size; - } -} - -static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size) -{ - MemBlock *node; - - node = mlist_find_space(s->free, size); - if (!node) { - fprintf(stderr, "Out of guest memory.\n"); - g_assert_not_reached(); - } - return mlist_fulfill(s, node, size); -} - -static void mlist_free(QGuestAllocator *s, uint64_t addr) -{ - MemBlock *node; - - if (addr == 0) { - return; - } - - node = mlist_find_key(s->used, addr); - if (!node) { - fprintf(stderr, "Error: no record found for an allocation at " - "0x%016" PRIx64 ".\n", - addr); - g_assert_not_reached(); - } - - /* Rip it out of the used list and re-insert back into the free list. */ - QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME); - mlist_sort_insert(s->free, node); - mlist_coalesce(s->free, node); -} - -/* - * Mostly for valgrind happiness, but it does offer - * a chokepoint for debugging guest memory leaks, too. - */ -void alloc_destroy(QGuestAllocator *allocator) -{ - MemBlock *node; - MemBlock *tmp; - QAllocOpts mask; - - /* Check for guest leaks, and destroy the list. */ - QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) { - if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) { - fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; " - "size 0x%016" PRIx64 ".\n", - node->addr, node->size); - } - if (allocator->opts & (ALLOC_LEAK_ASSERT)) { - g_assert_not_reached(); - } - g_free(node); - } - - /* If we have previously asserted that there are no leaks, then there - * should be only one node here with a specific address and size. */ - mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID; - QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) { - if ((allocator->opts & mask) == mask) { - if ((node->addr != allocator->start) || - (node->size != allocator->end - allocator->start)) { - fprintf(stderr, "Free list is corrupted.\n"); - g_assert_not_reached(); - } - } - - g_free(node); - } - - g_free(allocator->used); - g_free(allocator->free); -} - -uint64_t guest_alloc(QGuestAllocator *allocator, size_t size) -{ - uint64_t rsize = size; - uint64_t naddr; - - if (!size) { - return 0; - } - - rsize += (allocator->page_size - 1); - rsize &= -allocator->page_size; - g_assert_cmpint((allocator->start + rsize), <=, allocator->end); - g_assert_cmpint(rsize, >=, size); - - naddr = mlist_alloc(allocator, rsize); - if (allocator->opts & ALLOC_PARANOID) { - mlist_check(allocator); - } - - return naddr; -} - -void guest_free(QGuestAllocator *allocator, uint64_t addr) -{ - if (!addr) { - return; - } - mlist_free(allocator, addr); - if (allocator->opts & ALLOC_PARANOID) { - mlist_check(allocator); - } -} - -void alloc_init(QGuestAllocator *s, QAllocOpts opts, - uint64_t start, uint64_t end, - size_t page_size) -{ - MemBlock *node; - - s->opts = opts; - s->start = start; - s->end = end; - - s->used = g_new(MemList, 1); - s->free = g_new(MemList, 1); - QTAILQ_INIT(s->used); - QTAILQ_INIT(s->free); - - node = mlist_new(s->start, s->end - s->start); - QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME); - - s->page_size = page_size; -} - -void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts) -{ - allocator->opts |= opts; -} - -void migrate_allocator(QGuestAllocator *src, - QGuestAllocator *dst) -{ - MemBlock *node, *tmp; - MemList *tmpused, *tmpfree; - - /* The general memory layout should be equivalent, - * though opts can differ. */ - g_assert_cmphex(src->start, ==, dst->start); - g_assert_cmphex(src->end, ==, dst->end); - - /* Destroy (silently, regardless of options) the dest-list: */ - QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) { - g_free(node); - } - QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) { - g_free(node); - } - - tmpused = dst->used; - tmpfree = dst->free; - - /* Inherit the lists of the source allocator: */ - dst->used = src->used; - dst->free = src->free; - - /* Source is now re-initialized, the source memory is 'invalid' now: */ - src->used = tmpused; - src->free = tmpfree; - QTAILQ_INIT(src->used); - QTAILQ_INIT(src->free); - node = mlist_new(src->start, src->end - src->start); - QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME); - return; -} diff --git a/tests/libqos/malloc.h b/tests/libqos/malloc.h deleted file mode 100644 index 4d1a2e2bef..0000000000 --- a/tests/libqos/malloc.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * libqos malloc support - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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 LIBQOS_MALLOC_H -#define LIBQOS_MALLOC_H - -#include "qemu/queue.h" -#include "libqtest.h" - -typedef enum { - ALLOC_NO_FLAGS = 0x00, - ALLOC_LEAK_WARN = 0x01, - ALLOC_LEAK_ASSERT = 0x02, - ALLOC_PARANOID = 0x04 -} QAllocOpts; - -typedef QTAILQ_HEAD(MemList, MemBlock) MemList; - -typedef struct QGuestAllocator { - QAllocOpts opts; - uint64_t start; - uint64_t end; - uint32_t page_size; - - MemList *used; - MemList *free; -} QGuestAllocator; - -/* Always returns page aligned values */ -uint64_t guest_alloc(QGuestAllocator *allocator, size_t size); -void guest_free(QGuestAllocator *allocator, uint64_t addr); -void migrate_allocator(QGuestAllocator *src, QGuestAllocator *dst); - -void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts); - -void alloc_init(QGuestAllocator *alloc, QAllocOpts flags, - uint64_t start, uint64_t end, - size_t page_size); -void alloc_destroy(QGuestAllocator *allocator); - -#endif diff --git a/tests/libqos/pci-pc.c b/tests/libqos/pci-pc.c deleted file mode 100644 index 0bc591d1da..0000000000 --- a/tests/libqos/pci-pc.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - * libqos PCI bindings for PC - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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-pc.h" -#include "qapi/qmp/qdict.h" -#include "hw/pci/pci_regs.h" - -#include "qemu/module.h" - -#define ACPI_PCIHP_ADDR 0xae00 -#define PCI_EJ_BASE 0x0008 - -static uint8_t qpci_pc_pio_readb(QPCIBus *bus, uint32_t addr) -{ - return qtest_inb(bus->qts, addr); -} - -static void qpci_pc_pio_writeb(QPCIBus *bus, uint32_t addr, uint8_t val) -{ - qtest_outb(bus->qts, addr, val); -} - -static uint16_t qpci_pc_pio_readw(QPCIBus *bus, uint32_t addr) -{ - return qtest_inw(bus->qts, addr); -} - -static void qpci_pc_pio_writew(QPCIBus *bus, uint32_t addr, uint16_t val) -{ - qtest_outw(bus->qts, addr, val); -} - -static uint32_t qpci_pc_pio_readl(QPCIBus *bus, uint32_t addr) -{ - return qtest_inl(bus->qts, addr); -} - -static void qpci_pc_pio_writel(QPCIBus *bus, uint32_t addr, uint32_t val) -{ - qtest_outl(bus->qts, addr, val); -} - -static uint64_t qpci_pc_pio_readq(QPCIBus *bus, uint32_t addr) -{ - return (uint64_t)qtest_inl(bus->qts, addr) + - ((uint64_t)qtest_inl(bus->qts, addr + 4) << 32); -} - -static void qpci_pc_pio_writeq(QPCIBus *bus, uint32_t addr, uint64_t val) -{ - qtest_outl(bus->qts, addr, val & 0xffffffff); - qtest_outl(bus->qts, addr + 4, val >> 32); -} - -static void qpci_pc_memread(QPCIBus *bus, uint32_t addr, void *buf, size_t len) -{ - qtest_memread(bus->qts, addr, buf, len); -} - -static void qpci_pc_memwrite(QPCIBus *bus, uint32_t addr, - const void *buf, size_t len) -{ - qtest_memwrite(bus->qts, addr, buf, len); -} - -static uint8_t qpci_pc_config_readb(QPCIBus *bus, int devfn, uint8_t offset) -{ - qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); - return qtest_inb(bus->qts, 0xcfc); -} - -static uint16_t qpci_pc_config_readw(QPCIBus *bus, int devfn, uint8_t offset) -{ - qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); - return qtest_inw(bus->qts, 0xcfc); -} - -static uint32_t qpci_pc_config_readl(QPCIBus *bus, int devfn, uint8_t offset) -{ - qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); - return qtest_inl(bus->qts, 0xcfc); -} - -static void qpci_pc_config_writeb(QPCIBus *bus, int devfn, uint8_t offset, uint8_t value) -{ - qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); - qtest_outb(bus->qts, 0xcfc, value); -} - -static void qpci_pc_config_writew(QPCIBus *bus, int devfn, uint8_t offset, uint16_t value) -{ - qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); - qtest_outw(bus->qts, 0xcfc, value); -} - -static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint32_t value) -{ - qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); - qtest_outl(bus->qts, 0xcfc, value); -} - -static void *qpci_pc_get_driver(void *obj, const char *interface) -{ - QPCIBusPC *qpci = obj; - if (!g_strcmp0(interface, "pci-bus")) { - return &qpci->bus; - } - fprintf(stderr, "%s not present in pci-bus-pc\n", interface); - g_assert_not_reached(); -} - -void qpci_init_pc(QPCIBusPC *qpci, QTestState *qts, QGuestAllocator *alloc) -{ - assert(qts); - - /* tests can use pci-bus */ - qpci->bus.has_buggy_msi = false; - - qpci->bus.pio_readb = qpci_pc_pio_readb; - qpci->bus.pio_readw = qpci_pc_pio_readw; - qpci->bus.pio_readl = qpci_pc_pio_readl; - qpci->bus.pio_readq = qpci_pc_pio_readq; - - qpci->bus.pio_writeb = qpci_pc_pio_writeb; - qpci->bus.pio_writew = qpci_pc_pio_writew; - qpci->bus.pio_writel = qpci_pc_pio_writel; - qpci->bus.pio_writeq = qpci_pc_pio_writeq; - - qpci->bus.memread = qpci_pc_memread; - qpci->bus.memwrite = qpci_pc_memwrite; - - qpci->bus.config_readb = qpci_pc_config_readb; - qpci->bus.config_readw = qpci_pc_config_readw; - qpci->bus.config_readl = qpci_pc_config_readl; - - qpci->bus.config_writeb = qpci_pc_config_writeb; - qpci->bus.config_writew = qpci_pc_config_writew; - qpci->bus.config_writel = qpci_pc_config_writel; - - qpci->bus.qts = qts; - qpci->bus.pio_alloc_ptr = 0xc000; - qpci->bus.mmio_alloc_ptr = 0xE0000000; - qpci->bus.mmio_limit = 0x100000000ULL; - - qpci->obj.get_driver = qpci_pc_get_driver; -} - -QPCIBus *qpci_new_pc(QTestState *qts, QGuestAllocator *alloc) -{ - QPCIBusPC *qpci = g_new0(QPCIBusPC, 1); - qpci_init_pc(qpci, qts, alloc); - - return &qpci->bus; -} - -void qpci_free_pc(QPCIBus *bus) -{ - QPCIBusPC *s; - - if (!bus) { - return; - } - s = container_of(bus, QPCIBusPC, bus); - - g_free(s); -} - -void qpci_unplug_acpi_device_test(QTestState *qts, const char *id, uint8_t slot) -{ - QDict *response; - - response = qtest_qmp(qts, "{'execute': 'device_del'," - " 'arguments': {'id': %s}}", id); - g_assert(response); - g_assert(!qdict_haskey(response, "error")); - qobject_unref(response); - - qtest_outb(qts, ACPI_PCIHP_ADDR + PCI_EJ_BASE, 1 << slot); - - qtest_qmp_eventwait(qts, "DEVICE_DELETED"); -} - -static void qpci_pc_register_nodes(void) -{ - qos_node_create_driver("pci-bus-pc", NULL); - qos_node_produces("pci-bus-pc", "pci-bus"); -} - -libqos_init(qpci_pc_register_nodes); diff --git a/tests/libqos/pci-pc.h b/tests/libqos/pci-pc.h deleted file mode 100644 index 4690005232..0000000000 --- a/tests/libqos/pci-pc.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * libqos PCI bindings for PC - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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 LIBQOS_PCI_PC_H -#define LIBQOS_PCI_PC_H - -#include "libqos/pci.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" - -typedef struct QPCIBusPC { - QOSGraphObject obj; - QPCIBus bus; -} QPCIBusPC; - -/* qpci_init_pc(): - * @ret: A valid QPCIBusPC * pointer - * @qts: The %QTestState for this PC machine - * @alloc: A previously initialized @alloc providing memory for @qts - * - * This function initializes an already allocated - * QPCIBusPC object. - */ -void qpci_init_pc(QPCIBusPC *ret, QTestState *qts, QGuestAllocator *alloc); - -/* qpci_pc_new(): - * @qts: The %QTestState for this PC machine - * @alloc: A previously initialized @alloc providing memory for @qts - * - * This function creates a new QPCIBusPC object, - * and properly initialize its fields. - * - * Returns the QPCIBus *bus field of a newly - * allocated QPCIBusPC. - */ -QPCIBus *qpci_new_pc(QTestState *qts, QGuestAllocator *alloc); - -void qpci_free_pc(QPCIBus *bus); - -#endif diff --git a/tests/libqos/pci-spapr.c b/tests/libqos/pci-spapr.c deleted file mode 100644 index d6f8c01cb7..0000000000 --- a/tests/libqos/pci-spapr.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * libqos PCI bindings for SPAPR - * - * 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-spapr.h" -#include "libqos/rtas.h" -#include "libqos/qgraph.h" - -#include "hw/pci/pci_regs.h" - -#include "qemu/host-utils.h" -#include "qemu/module.h" - -/* - * PCI devices are always little-endian - * SPAPR by default is big-endian - * so PCI accessors need to swap data endianness - */ - -static uint8_t qpci_spapr_pio_readb(QPCIBus *bus, uint32_t addr) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - return qtest_readb(bus->qts, s->pio_cpu_base + addr); -} - -static void qpci_spapr_pio_writeb(QPCIBus *bus, uint32_t addr, uint8_t val) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - qtest_writeb(bus->qts, s->pio_cpu_base + addr, val); -} - -static uint16_t qpci_spapr_pio_readw(QPCIBus *bus, uint32_t addr) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - return bswap16(qtest_readw(bus->qts, s->pio_cpu_base + addr)); -} - -static void qpci_spapr_pio_writew(QPCIBus *bus, uint32_t addr, uint16_t val) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - qtest_writew(bus->qts, s->pio_cpu_base + addr, bswap16(val)); -} - -static uint32_t qpci_spapr_pio_readl(QPCIBus *bus, uint32_t addr) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - return bswap32(qtest_readl(bus->qts, s->pio_cpu_base + addr)); -} - -static void qpci_spapr_pio_writel(QPCIBus *bus, uint32_t addr, uint32_t val) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - qtest_writel(bus->qts, s->pio_cpu_base + addr, bswap32(val)); -} - -static uint64_t qpci_spapr_pio_readq(QPCIBus *bus, uint32_t addr) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - return bswap64(qtest_readq(bus->qts, s->pio_cpu_base + addr)); -} - -static void qpci_spapr_pio_writeq(QPCIBus *bus, uint32_t addr, uint64_t val) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - qtest_writeq(bus->qts, s->pio_cpu_base + addr, bswap64(val)); -} - -static void qpci_spapr_memread(QPCIBus *bus, uint32_t addr, - void *buf, size_t len) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - qtest_memread(bus->qts, s->mmio32_cpu_base + addr, buf, len); -} - -static void qpci_spapr_memwrite(QPCIBus *bus, uint32_t addr, - const void *buf, size_t len) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - qtest_memwrite(bus->qts, s->mmio32_cpu_base + addr, buf, len); -} - -static uint8_t qpci_spapr_config_readb(QPCIBus *bus, int devfn, uint8_t offset) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - uint32_t config_addr = (devfn << 8) | offset; - return qrtas_ibm_read_pci_config(bus->qts, s->alloc, s->buid, - config_addr, 1); -} - -static uint16_t qpci_spapr_config_readw(QPCIBus *bus, int devfn, uint8_t offset) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - uint32_t config_addr = (devfn << 8) | offset; - return qrtas_ibm_read_pci_config(bus->qts, s->alloc, s->buid, - config_addr, 2); -} - -static uint32_t qpci_spapr_config_readl(QPCIBus *bus, int devfn, uint8_t offset) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - uint32_t config_addr = (devfn << 8) | offset; - return qrtas_ibm_read_pci_config(bus->qts, s->alloc, s->buid, - config_addr, 4); -} - -static void qpci_spapr_config_writeb(QPCIBus *bus, int devfn, uint8_t offset, - uint8_t value) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - uint32_t config_addr = (devfn << 8) | offset; - qrtas_ibm_write_pci_config(bus->qts, s->alloc, s->buid, - config_addr, 1, value); -} - -static void qpci_spapr_config_writew(QPCIBus *bus, int devfn, uint8_t offset, - uint16_t value) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - uint32_t config_addr = (devfn << 8) | offset; - qrtas_ibm_write_pci_config(bus->qts, s->alloc, s->buid, - config_addr, 2, value); -} - -static void qpci_spapr_config_writel(QPCIBus *bus, int devfn, uint8_t offset, - uint32_t value) -{ - QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); - uint32_t config_addr = (devfn << 8) | offset; - qrtas_ibm_write_pci_config(bus->qts, s->alloc, s->buid, - config_addr, 4, value); -} - -#define SPAPR_PCI_BASE (1ULL << 45) - -#define SPAPR_PCI_MMIO32_WIN_SIZE 0x80000000 /* 2 GiB */ -#define SPAPR_PCI_IO_WIN_SIZE 0x10000 - -static void *qpci_spapr_get_driver(void *obj, const char *interface) -{ - QPCIBusSPAPR *qpci = obj; - if (!g_strcmp0(interface, "pci-bus")) { - return &qpci->bus; - } - fprintf(stderr, "%s not present in pci-bus-spapr", interface); - g_assert_not_reached(); -} - -void qpci_init_spapr(QPCIBusSPAPR *qpci, QTestState *qts, - QGuestAllocator *alloc) -{ - assert(qts); - - /* tests cannot use spapr, needs to be fixed first */ - qpci->bus.has_buggy_msi = true; - - qpci->alloc = alloc; - - qpci->bus.pio_readb = qpci_spapr_pio_readb; - qpci->bus.pio_readw = qpci_spapr_pio_readw; - qpci->bus.pio_readl = qpci_spapr_pio_readl; - qpci->bus.pio_readq = qpci_spapr_pio_readq; - - qpci->bus.pio_writeb = qpci_spapr_pio_writeb; - qpci->bus.pio_writew = qpci_spapr_pio_writew; - qpci->bus.pio_writel = qpci_spapr_pio_writel; - qpci->bus.pio_writeq = qpci_spapr_pio_writeq; - - qpci->bus.memread = qpci_spapr_memread; - qpci->bus.memwrite = qpci_spapr_memwrite; - - qpci->bus.config_readb = qpci_spapr_config_readb; - qpci->bus.config_readw = qpci_spapr_config_readw; - qpci->bus.config_readl = qpci_spapr_config_readl; - - qpci->bus.config_writeb = qpci_spapr_config_writeb; - qpci->bus.config_writew = qpci_spapr_config_writew; - qpci->bus.config_writel = qpci_spapr_config_writel; - - /* FIXME: We assume the default location of the PHB for now. - * Ideally we'd parse the device tree deposited in the guest to - * get the window locations */ - qpci->buid = 0x800000020000000ULL; - - qpci->pio_cpu_base = SPAPR_PCI_BASE; - qpci->pio.pci_base = 0; - qpci->pio.size = SPAPR_PCI_IO_WIN_SIZE; - - /* 32-bit portion of the MMIO window is at PCI address 2..4 GiB */ - qpci->mmio32_cpu_base = SPAPR_PCI_BASE; - qpci->mmio32.pci_base = SPAPR_PCI_MMIO32_WIN_SIZE; - qpci->mmio32.size = SPAPR_PCI_MMIO32_WIN_SIZE; - - qpci->bus.qts = qts; - qpci->bus.pio_alloc_ptr = 0xc000; - qpci->bus.mmio_alloc_ptr = qpci->mmio32.pci_base; - qpci->bus.mmio_limit = qpci->mmio32.pci_base + qpci->mmio32.size; - - qpci->obj.get_driver = qpci_spapr_get_driver; -} - -QPCIBus *qpci_new_spapr(QTestState *qts, QGuestAllocator *alloc) -{ - QPCIBusSPAPR *qpci = g_new0(QPCIBusSPAPR, 1); - qpci_init_spapr(qpci, qts, alloc); - - return &qpci->bus; -} - -void qpci_free_spapr(QPCIBus *bus) -{ - QPCIBusSPAPR *s; - - if (!bus) { - return; - } - s = container_of(bus, QPCIBusSPAPR, bus); - - g_free(s); -} - -static void qpci_spapr_register_nodes(void) -{ - qos_node_create_driver("pci-bus-spapr", NULL); - qos_node_produces("pci-bus-spapr", "pci-bus"); -} - -libqos_init(qpci_spapr_register_nodes); diff --git a/tests/libqos/pci-spapr.h b/tests/libqos/pci-spapr.h deleted file mode 100644 index d9e25631c6..0000000000 --- a/tests/libqos/pci-spapr.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * libqos PCI bindings for SPAPR - * - * 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 LIBQOS_PCI_SPAPR_H -#define LIBQOS_PCI_SPAPR_H - -#include "libqos/malloc.h" -#include "libqos/pci.h" -#include "libqos/qgraph.h" - -/* From include/hw/pci-host/spapr.h */ - -typedef struct QPCIWindow { - uint64_t pci_base; /* window address in PCI space */ - uint64_t size; /* window size */ -} QPCIWindow; - -typedef struct QPCIBusSPAPR { - QOSGraphObject obj; - QPCIBus bus; - QGuestAllocator *alloc; - - uint64_t buid; - - uint64_t pio_cpu_base; - QPCIWindow pio; - - uint64_t mmio32_cpu_base; - QPCIWindow mmio32; -} QPCIBusSPAPR; - -void qpci_init_spapr(QPCIBusSPAPR *ret, QTestState *qts, - QGuestAllocator *alloc); -QPCIBus *qpci_new_spapr(QTestState *qts, QGuestAllocator *alloc); -void qpci_free_spapr(QPCIBus *bus); - -#endif diff --git a/tests/libqos/pci.c b/tests/libqos/pci.c deleted file mode 100644 index 2309a724e4..0000000000 --- a/tests/libqos/pci.c +++ /dev/null @@ -1,457 +0,0 @@ -/* - * libqos PCI bindings - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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/pci.h" - -#include "hw/pci/pci_regs.h" -#include "qemu/host-utils.h" -#include "libqos/qgraph.h" - -void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, - void (*func)(QPCIDevice *dev, int devfn, void *data), - void *data) -{ - int slot; - - for (slot = 0; slot < 32; slot++) { - int fn; - - for (fn = 0; fn < 8; fn++) { - QPCIDevice *dev; - - dev = qpci_device_find(bus, QPCI_DEVFN(slot, fn)); - if (!dev) { - continue; - } - - if (vendor_id != -1 && - qpci_config_readw(dev, PCI_VENDOR_ID) != vendor_id) { - g_free(dev); - continue; - } - - if (device_id != -1 && - qpci_config_readw(dev, PCI_DEVICE_ID) != device_id) { - g_free(dev); - continue; - } - - func(dev, QPCI_DEVFN(slot, fn), data); - } - } -} - -bool qpci_has_buggy_msi(QPCIDevice *dev) -{ - return dev->bus->has_buggy_msi; -} - -bool qpci_check_buggy_msi(QPCIDevice *dev) -{ - if (qpci_has_buggy_msi(dev)) { - g_test_skip("Skipping due to incomplete support for MSI"); - return true; - } - return false; -} - -static void qpci_device_set(QPCIDevice *dev, QPCIBus *bus, int devfn) -{ - g_assert(dev); - - dev->bus = bus; - dev->devfn = devfn; -} - -QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn) -{ - QPCIDevice *dev; - - dev = g_malloc0(sizeof(*dev)); - qpci_device_set(dev, bus, devfn); - - if (qpci_config_readw(dev, PCI_VENDOR_ID) == 0xFFFF) { - g_free(dev); - return NULL; - } - - return dev; -} - -void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr) -{ - uint16_t vendor_id, device_id; - - qpci_device_set(dev, bus, addr->devfn); - vendor_id = qpci_config_readw(dev, PCI_VENDOR_ID); - device_id = qpci_config_readw(dev, PCI_DEVICE_ID); - g_assert(!addr->vendor_id || vendor_id == addr->vendor_id); - g_assert(!addr->device_id || device_id == addr->device_id); -} - -void qpci_device_enable(QPCIDevice *dev) -{ - uint16_t cmd; - - /* FIXME -- does this need to be a bus callout? */ - cmd = qpci_config_readw(dev, PCI_COMMAND); - cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; - qpci_config_writew(dev, PCI_COMMAND, cmd); - - /* Verify the bits are now set. */ - cmd = qpci_config_readw(dev, PCI_COMMAND); - g_assert_cmphex(cmd & PCI_COMMAND_IO, ==, PCI_COMMAND_IO); - g_assert_cmphex(cmd & PCI_COMMAND_MEMORY, ==, PCI_COMMAND_MEMORY); - g_assert_cmphex(cmd & PCI_COMMAND_MASTER, ==, PCI_COMMAND_MASTER); -} - -/** - * qpci_find_capability: - * @dev: the PCI device - * @id: the PCI Capability ID (PCI_CAP_ID_*) - * @start_addr: 0 to begin iteration or the last return value to continue - * iteration - * - * Iterate over the PCI Capabilities List. - * - * Returns: PCI Configuration Space offset of the capabililty structure or - * 0 if no further matching capability is found - */ -uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id, uint8_t start_addr) -{ - uint8_t cap; - uint8_t addr; - - if (start_addr) { - addr = qpci_config_readb(dev, start_addr + PCI_CAP_LIST_NEXT); - } else { - addr = qpci_config_readb(dev, PCI_CAPABILITY_LIST); - } - - do { - cap = qpci_config_readb(dev, addr); - if (cap != id) { - addr = qpci_config_readb(dev, addr + PCI_CAP_LIST_NEXT); - } - } while (cap != id && addr != 0); - - return addr; -} - -void qpci_msix_enable(QPCIDevice *dev) -{ - uint8_t addr; - uint16_t val; - uint32_t table; - uint8_t bir_table; - uint8_t bir_pba; - - addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); - g_assert_cmphex(addr, !=, 0); - - val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); - qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, val | PCI_MSIX_FLAGS_ENABLE); - - table = qpci_config_readl(dev, addr + PCI_MSIX_TABLE); - bir_table = table & PCI_MSIX_FLAGS_BIRMASK; - dev->msix_table_bar = qpci_iomap(dev, bir_table, NULL); - dev->msix_table_off = table & ~PCI_MSIX_FLAGS_BIRMASK; - - table = qpci_config_readl(dev, addr + PCI_MSIX_PBA); - bir_pba = table & PCI_MSIX_FLAGS_BIRMASK; - if (bir_pba != bir_table) { - dev->msix_pba_bar = qpci_iomap(dev, bir_pba, NULL); - } else { - dev->msix_pba_bar = dev->msix_table_bar; - } - dev->msix_pba_off = table & ~PCI_MSIX_FLAGS_BIRMASK; - - dev->msix_enabled = true; -} - -void qpci_msix_disable(QPCIDevice *dev) -{ - uint8_t addr; - uint16_t val; - - g_assert(dev->msix_enabled); - addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); - g_assert_cmphex(addr, !=, 0); - val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); - qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, - val & ~PCI_MSIX_FLAGS_ENABLE); - - if (dev->msix_pba_bar.addr != dev->msix_table_bar.addr) { - qpci_iounmap(dev, dev->msix_pba_bar); - } - qpci_iounmap(dev, dev->msix_table_bar); - - dev->msix_enabled = 0; - dev->msix_table_off = 0; - dev->msix_pba_off = 0; -} - -bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry) -{ - uint32_t pba_entry; - uint8_t bit_n = entry % 32; - uint64_t off = (entry / 32) * PCI_MSIX_ENTRY_SIZE / 4; - - g_assert(dev->msix_enabled); - pba_entry = qpci_io_readl(dev, dev->msix_pba_bar, dev->msix_pba_off + off); - qpci_io_writel(dev, dev->msix_pba_bar, dev->msix_pba_off + off, - pba_entry & ~(1 << bit_n)); - return (pba_entry & (1 << bit_n)) != 0; -} - -bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry) -{ - uint8_t addr; - uint16_t val; - uint64_t vector_off = dev->msix_table_off + entry * PCI_MSIX_ENTRY_SIZE; - - g_assert(dev->msix_enabled); - addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); - g_assert_cmphex(addr, !=, 0); - val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); - - if (val & PCI_MSIX_FLAGS_MASKALL) { - return true; - } else { - return (qpci_io_readl(dev, dev->msix_table_bar, - vector_off + PCI_MSIX_ENTRY_VECTOR_CTRL) - & PCI_MSIX_ENTRY_CTRL_MASKBIT) != 0; - } -} - -uint16_t qpci_msix_table_size(QPCIDevice *dev) -{ - uint8_t addr; - uint16_t control; - - addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); - g_assert_cmphex(addr, !=, 0); - - control = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); - return (control & PCI_MSIX_FLAGS_QSIZE) + 1; -} - -uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset) -{ - return dev->bus->config_readb(dev->bus, dev->devfn, offset); -} - -uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset) -{ - return dev->bus->config_readw(dev->bus, dev->devfn, offset); -} - -uint32_t qpci_config_readl(QPCIDevice *dev, uint8_t offset) -{ - return dev->bus->config_readl(dev->bus, dev->devfn, offset); -} - - -void qpci_config_writeb(QPCIDevice *dev, uint8_t offset, uint8_t value) -{ - dev->bus->config_writeb(dev->bus, dev->devfn, offset, value); -} - -void qpci_config_writew(QPCIDevice *dev, uint8_t offset, uint16_t value) -{ - dev->bus->config_writew(dev->bus, dev->devfn, offset, value); -} - -void qpci_config_writel(QPCIDevice *dev, uint8_t offset, uint32_t value) -{ - dev->bus->config_writel(dev->bus, dev->devfn, offset, value); -} - -uint8_t qpci_io_readb(QPCIDevice *dev, QPCIBar token, uint64_t off) -{ - if (token.addr < QPCI_PIO_LIMIT) { - return dev->bus->pio_readb(dev->bus, token.addr + off); - } else { - uint8_t val; - dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); - return val; - } -} - -uint16_t qpci_io_readw(QPCIDevice *dev, QPCIBar token, uint64_t off) -{ - if (token.addr < QPCI_PIO_LIMIT) { - return dev->bus->pio_readw(dev->bus, token.addr + off); - } else { - uint16_t val; - dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); - return le16_to_cpu(val); - } -} - -uint32_t qpci_io_readl(QPCIDevice *dev, QPCIBar token, uint64_t off) -{ - if (token.addr < QPCI_PIO_LIMIT) { - return dev->bus->pio_readl(dev->bus, token.addr + off); - } else { - uint32_t val; - dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); - return le32_to_cpu(val); - } -} - -uint64_t qpci_io_readq(QPCIDevice *dev, QPCIBar token, uint64_t off) -{ - if (token.addr < QPCI_PIO_LIMIT) { - return dev->bus->pio_readq(dev->bus, token.addr + off); - } else { - uint64_t val; - dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); - return le64_to_cpu(val); - } -} - -void qpci_io_writeb(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint8_t value) -{ - if (token.addr < QPCI_PIO_LIMIT) { - dev->bus->pio_writeb(dev->bus, token.addr + off, value); - } else { - dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); - } -} - -void qpci_io_writew(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint16_t value) -{ - if (token.addr < QPCI_PIO_LIMIT) { - dev->bus->pio_writew(dev->bus, token.addr + off, value); - } else { - value = cpu_to_le16(value); - dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); - } -} - -void qpci_io_writel(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint32_t value) -{ - if (token.addr < QPCI_PIO_LIMIT) { - dev->bus->pio_writel(dev->bus, token.addr + off, value); - } else { - value = cpu_to_le32(value); - dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); - } -} - -void qpci_io_writeq(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint64_t value) -{ - if (token.addr < QPCI_PIO_LIMIT) { - dev->bus->pio_writeq(dev->bus, token.addr + off, value); - } else { - value = cpu_to_le64(value); - dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); - } -} - -void qpci_memread(QPCIDevice *dev, QPCIBar token, uint64_t off, - void *buf, size_t len) -{ - g_assert(token.addr >= QPCI_PIO_LIMIT); - dev->bus->memread(dev->bus, token.addr + off, buf, len); -} - -void qpci_memwrite(QPCIDevice *dev, QPCIBar token, uint64_t off, - const void *buf, size_t len) -{ - g_assert(token.addr >= QPCI_PIO_LIMIT); - dev->bus->memwrite(dev->bus, token.addr + off, buf, len); -} - -QPCIBar qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr) -{ - QPCIBus *bus = dev->bus; - static const int bar_reg_map[] = { - PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2, - PCI_BASE_ADDRESS_3, PCI_BASE_ADDRESS_4, PCI_BASE_ADDRESS_5, - }; - QPCIBar bar; - int bar_reg; - uint32_t addr, size; - uint32_t io_type; - uint64_t loc; - - g_assert(barno >= 0 && barno <= 5); - bar_reg = bar_reg_map[barno]; - - qpci_config_writel(dev, bar_reg, 0xFFFFFFFF); - addr = qpci_config_readl(dev, bar_reg); - - io_type = addr & PCI_BASE_ADDRESS_SPACE; - if (io_type == PCI_BASE_ADDRESS_SPACE_IO) { - addr &= PCI_BASE_ADDRESS_IO_MASK; - } else { - addr &= PCI_BASE_ADDRESS_MEM_MASK; - } - - g_assert(addr); /* Must have *some* size bits */ - - size = 1U << ctz32(addr); - if (sizeptr) { - *sizeptr = size; - } - - if (io_type == PCI_BASE_ADDRESS_SPACE_IO) { - loc = QEMU_ALIGN_UP(bus->pio_alloc_ptr, size); - - g_assert(loc >= bus->pio_alloc_ptr); - g_assert(loc + size <= QPCI_PIO_LIMIT); /* Keep PIO below 64kiB */ - - bus->pio_alloc_ptr = loc + size; - - qpci_config_writel(dev, bar_reg, loc | PCI_BASE_ADDRESS_SPACE_IO); - } else { - loc = QEMU_ALIGN_UP(bus->mmio_alloc_ptr, size); - - /* Check for space */ - g_assert(loc >= bus->mmio_alloc_ptr); - g_assert(loc + size <= bus->mmio_limit); - - bus->mmio_alloc_ptr = loc + size; - - qpci_config_writel(dev, bar_reg, loc); - } - - bar.addr = loc; - return bar; -} - -void qpci_iounmap(QPCIDevice *dev, QPCIBar bar) -{ - /* FIXME */ -} - -QPCIBar qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr) -{ - QPCIBar bar = { .addr = addr }; - return bar; -} - -void add_qpci_address(QOSGraphEdgeOptions *opts, QPCIAddress *addr) -{ - g_assert(addr); - g_assert(opts); - - opts->arg = addr; - opts->size_arg = sizeof(QPCIAddress); -} diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h deleted file mode 100644 index 590c175190..0000000000 --- a/tests/libqos/pci.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * libqos PCI bindings - * - * Copyright IBM, Corp. 2012-2013 - * - * Authors: - * Anthony Liguori - * - * 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 LIBQOS_PCI_H -#define LIBQOS_PCI_H - -#include "libqtest.h" -#include "libqos/qgraph.h" - -#define QPCI_PIO_LIMIT 0x10000 - -#define QPCI_DEVFN(dev, fn) (((dev) << 3) | (fn)) - -typedef struct QPCIDevice QPCIDevice; -typedef struct QPCIBus QPCIBus; -typedef struct QPCIBar QPCIBar; -typedef struct QPCIAddress QPCIAddress; - -struct QPCIBus { - uint8_t (*pio_readb)(QPCIBus *bus, uint32_t addr); - uint16_t (*pio_readw)(QPCIBus *bus, uint32_t addr); - uint32_t (*pio_readl)(QPCIBus *bus, uint32_t addr); - uint64_t (*pio_readq)(QPCIBus *bus, uint32_t addr); - - void (*pio_writeb)(QPCIBus *bus, uint32_t addr, uint8_t value); - void (*pio_writew)(QPCIBus *bus, uint32_t addr, uint16_t value); - void (*pio_writel)(QPCIBus *bus, uint32_t addr, uint32_t value); - void (*pio_writeq)(QPCIBus *bus, uint32_t addr, uint64_t value); - - void (*memread)(QPCIBus *bus, uint32_t addr, void *buf, size_t len); - void (*memwrite)(QPCIBus *bus, uint32_t addr, const void *buf, size_t len); - - uint8_t (*config_readb)(QPCIBus *bus, int devfn, uint8_t offset); - uint16_t (*config_readw)(QPCIBus *bus, int devfn, uint8_t offset); - uint32_t (*config_readl)(QPCIBus *bus, int devfn, uint8_t offset); - - void (*config_writeb)(QPCIBus *bus, int devfn, - uint8_t offset, uint8_t value); - void (*config_writew)(QPCIBus *bus, int devfn, - uint8_t offset, uint16_t value); - void (*config_writel)(QPCIBus *bus, int devfn, - uint8_t offset, uint32_t value); - - QTestState *qts; - uint16_t pio_alloc_ptr; - uint64_t mmio_alloc_ptr, mmio_limit; - bool has_buggy_msi; /* TRUE for spapr, FALSE for pci */ - -}; - -struct QPCIBar { - uint64_t addr; -}; - -struct QPCIDevice -{ - QPCIBus *bus; - int devfn; - bool msix_enabled; - QPCIBar msix_table_bar, msix_pba_bar; - uint64_t msix_table_off, msix_pba_off; -}; - -struct QPCIAddress { - uint32_t devfn; - uint16_t vendor_id; - uint16_t device_id; -}; - -void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, - void (*func)(QPCIDevice *dev, int devfn, void *data), - void *data); -QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn); -void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr); - -bool qpci_has_buggy_msi(QPCIDevice *dev); -bool qpci_check_buggy_msi(QPCIDevice *dev); - -void qpci_device_enable(QPCIDevice *dev); -uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id, uint8_t start_addr); -void qpci_msix_enable(QPCIDevice *dev); -void qpci_msix_disable(QPCIDevice *dev); -bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry); -bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry); -uint16_t qpci_msix_table_size(QPCIDevice *dev); - -uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset); -uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset); -uint32_t qpci_config_readl(QPCIDevice *dev, uint8_t offset); - -void qpci_config_writeb(QPCIDevice *dev, uint8_t offset, uint8_t value); -void qpci_config_writew(QPCIDevice *dev, uint8_t offset, uint16_t value); -void qpci_config_writel(QPCIDevice *dev, uint8_t offset, uint32_t value); - -uint8_t qpci_io_readb(QPCIDevice *dev, QPCIBar token, uint64_t off); -uint16_t qpci_io_readw(QPCIDevice *dev, QPCIBar token, uint64_t off); -uint32_t qpci_io_readl(QPCIDevice *dev, QPCIBar token, uint64_t off); -uint64_t qpci_io_readq(QPCIDevice *dev, QPCIBar token, uint64_t off); - -void qpci_io_writeb(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint8_t value); -void qpci_io_writew(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint16_t value); -void qpci_io_writel(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint32_t value); -void qpci_io_writeq(QPCIDevice *dev, QPCIBar token, uint64_t off, - uint64_t value); - -void qpci_memread(QPCIDevice *bus, QPCIBar token, uint64_t off, - void *buf, size_t len); -void qpci_memwrite(QPCIDevice *bus, QPCIBar token, uint64_t off, - const void *buf, size_t len); -QPCIBar qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr); -void qpci_iounmap(QPCIDevice *dev, QPCIBar addr); -QPCIBar qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr); - -void qpci_unplug_acpi_device_test(QTestState *qs, const char *id, uint8_t slot); - -void add_qpci_address(QOSGraphEdgeOptions *opts, QPCIAddress *addr); -#endif diff --git a/tests/libqos/ppc64_pseries-machine.c b/tests/libqos/ppc64_pseries-machine.c deleted file mode 100644 index 867f27a3c8..0000000000 --- a/tests/libqos/ppc64_pseries-machine.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "libqos/qgraph.h" -#include "pci-spapr.h" -#include "qemu/module.h" -#include "libqos/malloc-spapr.h" - -typedef struct QSPAPR_pci_host QSPAPR_pci_host; -typedef struct Qppc64_pseriesMachine Qppc64_pseriesMachine; - -struct QSPAPR_pci_host { - QOSGraphObject obj; - QPCIBusSPAPR pci; -}; - -struct Qppc64_pseriesMachine { - QOSGraphObject obj; - QGuestAllocator alloc; - QSPAPR_pci_host bridge; -}; - -/* QSPAPR_pci_host */ - -static QOSGraphObject *QSPAPR_host_get_device(void *obj, const char *device) -{ - QSPAPR_pci_host *host = obj; - if (!g_strcmp0(device, "pci-bus-spapr")) { - return &host->pci.obj; - } - fprintf(stderr, "%s not present in QSPAPR_pci_host\n", device); - g_assert_not_reached(); -} - -static void qos_create_QSPAPR_host(QSPAPR_pci_host *host, - QTestState *qts, - QGuestAllocator *alloc) -{ - host->obj.get_device = QSPAPR_host_get_device; - qpci_init_spapr(&host->pci, qts, alloc); -} - -/* ppc64/pseries machine */ - -static void spapr_destructor(QOSGraphObject *obj) -{ - Qppc64_pseriesMachine *machine = (Qppc64_pseriesMachine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *spapr_get_driver(void *object, const char *interface) -{ - Qppc64_pseriesMachine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in ppc64/pseries\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *spapr_get_device(void *obj, const char *device) -{ - Qppc64_pseriesMachine *machine = obj; - if (!g_strcmp0(device, "spapr-pci-host-bridge")) { - return &machine->bridge.obj; - } - - fprintf(stderr, "%s not present in ppc64/pseries\n", device); - g_assert_not_reached(); -} - -static void *qos_create_machine_spapr(QTestState *qts) -{ - Qppc64_pseriesMachine *machine = g_new0(Qppc64_pseriesMachine, 1); - machine->obj.get_device = spapr_get_device; - machine->obj.get_driver = spapr_get_driver; - machine->obj.destructor = spapr_destructor; - spapr_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS); - - qos_create_QSPAPR_host(&machine->bridge, qts, &machine->alloc); - - return &machine->obj; -} - -static void spapr_machine_register_nodes(void) -{ - qos_node_create_machine("ppc64/pseries", qos_create_machine_spapr); - qos_node_create_driver("spapr-pci-host-bridge", NULL); - qos_node_contains("ppc64/pseries", "spapr-pci-host-bridge", NULL); - qos_node_contains("spapr-pci-host-bridge", "pci-bus-spapr", NULL); -} - -libqos_init(spapr_machine_register_nodes); - diff --git a/tests/libqos/qgraph.c b/tests/libqos/qgraph.c deleted file mode 100644 index 7a7ae2a19e..0000000000 --- a/tests/libqos/qgraph.c +++ /dev/null @@ -1,759 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/queue.h" -#include "libqos/qgraph_internal.h" -#include "libqos/qgraph.h" - -#define QGRAPH_PRINT_DEBUG 0 -#define QOS_ROOT "" -typedef struct QOSStackElement QOSStackElement; - -/* Graph Edge.*/ -struct QOSGraphEdge { - QOSEdgeType type; - char *dest; - void *arg; /* just for QEDGE_CONTAINS - * and QEDGE_CONSUMED_BY */ - char *extra_device_opts; /* added to -device option, "," is - * automatically added - */ - char *before_cmd_line; /* added before node cmd_line */ - char *after_cmd_line; /* added after -device options */ - char *edge_name; /* used by QEDGE_CONTAINS */ - QSLIST_ENTRY(QOSGraphEdge) edge_list; -}; - -typedef QSLIST_HEAD(, QOSGraphEdge) QOSGraphEdgeList; - -/** - * Stack used to keep track of the discovered path when using - * the DFS algorithm - */ -struct QOSStackElement { - QOSGraphNode *node; - QOSStackElement *parent; - QOSGraphEdge *parent_edge; - int length; -}; - -/* Each enty in these hash table will consist of pair. */ -static GHashTable *edge_table; -static GHashTable *node_table; - -/* stack used by the DFS algorithm to store the path from machine to test */ -static QOSStackElement qos_node_stack[QOS_PATH_MAX_ELEMENT_SIZE]; -static int qos_node_tos; - -/** - * add_edge(): creates an edge of type @type - * from @source to @dest node, and inserts it in the - * edges hash table - * - * Nodes @source and @dest do not necessarily need to exist. - * Possibility to add also options (see #QOSGraphEdgeOptions) - * edge->edge_name is used as identifier for get_device relationships, - * so by default is equal to @dest. - */ -static void add_edge(const char *source, const char *dest, - QOSEdgeType type, QOSGraphEdgeOptions *opts) -{ - char *key; - QOSGraphEdgeList *list = g_hash_table_lookup(edge_table, source); - QOSGraphEdgeOptions def_opts = { }; - - if (!list) { - list = g_new0(QOSGraphEdgeList, 1); - key = g_strdup(source); - g_hash_table_insert(edge_table, key, list); - } - - if (!opts) { - opts = &def_opts; - } - - QOSGraphEdge *edge = g_new0(QOSGraphEdge, 1); - edge->type = type; - edge->dest = g_strdup(dest); - edge->edge_name = g_strdup(opts->edge_name ?: dest); - edge->arg = g_memdup(opts->arg, opts->size_arg); - - edge->before_cmd_line = - opts->before_cmd_line ? g_strconcat(" ", opts->before_cmd_line, NULL) : NULL; - edge->extra_device_opts = - opts->extra_device_opts ? g_strconcat(",", opts->extra_device_opts, NULL) : NULL; - edge->after_cmd_line = - opts->after_cmd_line ? g_strconcat(" ", opts->after_cmd_line, NULL) : NULL; - - QSLIST_INSERT_HEAD(list, edge, edge_list); -} - -/* destroy_edges(): frees all edges inside a given @list */ -static void destroy_edges(void *list) -{ - QOSGraphEdge *temp; - QOSGraphEdgeList *elist = list; - - while (!QSLIST_EMPTY(elist)) { - temp = QSLIST_FIRST(elist); - QSLIST_REMOVE_HEAD(elist, edge_list); - g_free(temp->dest); - g_free(temp->before_cmd_line); - g_free(temp->after_cmd_line); - g_free(temp->extra_device_opts); - g_free(temp->edge_name); - g_free(temp->arg); - g_free(temp); - } - g_free(elist); -} - -/** - * create_node(): creates a node @name of type @type - * and inserts it to the nodes hash table. - * By default, node is not available. - */ -static QOSGraphNode *create_node(const char *name, QOSNodeType type) -{ - if (g_hash_table_lookup(node_table, name)) { - g_printerr("Node %s already created\n", name); - abort(); - } - - QOSGraphNode *node = g_new0(QOSGraphNode, 1); - node->type = type; - node->available = false; - node->name = g_strdup(name); - g_hash_table_insert(node_table, node->name, node); - return node; -} - -/** - * destroy_node(): frees a node @val from the nodes hash table. - * Note that node->name is not free'd since it will represent the - * hash table key - */ -static void destroy_node(void *val) -{ - QOSGraphNode *node = val; - g_free(node->command_line); - g_free(node); -} - -/** - * destroy_string(): frees @key from the nodes hash table. - * Actually frees the node->name - */ -static void destroy_string(void *key) -{ - g_free(key); -} - -/** - * search_node(): search for a node @key in the nodes hash table - * Returns the QOSGraphNode if found, #NULL otherwise - */ -static QOSGraphNode *search_node(const char *key) -{ - return g_hash_table_lookup(node_table, key); -} - -/** - * get_edgelist(): returns the edge list (value) assigned to - * the @key in the edge hash table. - * This list will contain all edges with source equal to @key - * - * Returns: on success: the %QOSGraphEdgeList - * otherwise: abort() - */ -static QOSGraphEdgeList *get_edgelist(const char *key) -{ - return g_hash_table_lookup(edge_table, key); -} - -/** - * search_list_edges(): search for an edge with destination @dest - * in the given @edgelist. - * - * Returns: on success: the %QOSGraphEdge - * otherwise: #NULL - */ -static QOSGraphEdge *search_list_edges(QOSGraphEdgeList *edgelist, - const char *dest) -{ - QOSGraphEdge *tmp, *next; - if (!edgelist) { - return NULL; - } - QSLIST_FOREACH_SAFE(tmp, edgelist, edge_list, next) { - if (g_strcmp0(tmp->dest, dest) == 0) { - break; - } - } - return tmp; -} - -/** - * search_machine(): search for a machine @name in the node hash - * table. A machine is the child of the root node. - * This function forces the research in the childs of the root, - * to check the node is a proper machine - * - * Returns: on success: the %QOSGraphNode - * otherwise: #NULL - */ -static QOSGraphNode *search_machine(const char *name) -{ - QOSGraphNode *n; - QOSGraphEdgeList *root_list = get_edgelist(QOS_ROOT); - QOSGraphEdge *e = search_list_edges(root_list, name); - if (!e) { - return NULL; - } - n = search_node(e->dest); - if (n->type == QNODE_MACHINE) { - return n; - } - return NULL; -} - -/** - * create_interface(): checks if there is already - * a node @node in the node hash table, if not - * creates a node @node of type #QNODE_INTERFACE - * and inserts it. If there is one, check it's - * a #QNODE_INTERFACE and abort() if it's not. - */ -static void create_interface(const char *node) -{ - QOSGraphNode *interface; - interface = search_node(node); - if (!interface) { - create_node(node, QNODE_INTERFACE); - } else if (interface->type != QNODE_INTERFACE) { - fprintf(stderr, "Error: Node %s is not an interface\n", node); - abort(); - } -} - -/** - * build_machine_cmd_line(): builds the command line for the machine - * @node. The node name must be a valid qemu identifier, since it - * will be used to build the command line. - * - * It is also possible to pass an optional @args that will be - * concatenated to the command line. - * - * For machines, prepend -M to the machine name. ", @rgs" is added - * after the -M command. - */ -static void build_machine_cmd_line(QOSGraphNode *node, const char *args) -{ - char *machine = qos_get_machine_type(node->name); - if (args) { - node->command_line = g_strconcat("-M ", machine, ",", args, NULL); - } else { - node->command_line = g_strconcat("-M ", machine, " ", NULL); - } -} - -/** - * build_driver_cmd_line(): builds the command line for the driver - * @node. The node name must be a valid qemu identifier, since it - * will be used to build the command line. - * - * Driver do not need additional command line, since it will be - * provided by the edge options. - * - * For drivers, prepend -device to the node name. - */ -static void build_driver_cmd_line(QOSGraphNode *node) -{ - node->command_line = g_strconcat(" -device ", node->name, NULL); -} - -/* qos_print_cb(): callback prints all path found by the DFS algorithm. */ -static void qos_print_cb(QOSGraphNode *path, int length) -{ - #if QGRAPH_PRINT_DEBUG - printf("%d elements\n", length); - - if (!path) { - return; - } - - while (path->path_edge) { - printf("%s ", path->name); - switch (path->path_edge->type) { - case QEDGE_PRODUCES: - printf("--PRODUCES--> "); - break; - case QEDGE_CONSUMED_BY: - printf("--CONSUMED_BY--> "); - break; - case QEDGE_CONTAINS: - printf("--CONTAINS--> "); - break; - } - path = search_node(path->path_edge->dest); - } - - printf("%s\n\n", path->name); - #endif -} - -/* qos_push(): push a node @el and edge @e in the qos_node_stack */ -static void qos_push(QOSGraphNode *el, QOSStackElement *parent, - QOSGraphEdge *e) -{ - int len = 0; /* root is not counted */ - if (qos_node_tos == QOS_PATH_MAX_ELEMENT_SIZE) { - g_printerr("QOSStack: full stack, cannot push"); - abort(); - } - - if (parent) { - len = parent->length + 1; - } - qos_node_stack[qos_node_tos++] = (QOSStackElement) { - .node = el, - .parent = parent, - .parent_edge = e, - .length = len, - }; -} - -/* qos_tos(): returns the top of stack, without popping */ -static QOSStackElement *qos_tos(void) -{ - return &qos_node_stack[qos_node_tos - 1]; -} - -/* qos_pop(): pops an element from the tos, setting it unvisited*/ -static QOSStackElement *qos_pop(void) -{ - if (qos_node_tos == 0) { - g_printerr("QOSStack: empty stack, cannot pop"); - abort(); - } - QOSStackElement *e = qos_tos(); - e->node->visited = false; - qos_node_tos--; - return e; -} - -/** - * qos_reverse_path(): reverses the found path, going from - * test-to-machine to machine-to-test - */ -static QOSGraphNode *qos_reverse_path(QOSStackElement *el) -{ - if (!el) { - return NULL; - } - - el->node->path_edge = NULL; - - while (el->parent) { - el->parent->node->path_edge = el->parent_edge; - el = el->parent; - } - - return el->node; -} - -/** - * qos_traverse_graph(): graph-walking algorithm, using Depth First Search it - * starts from the root @machine and walks all possible path until it - * reaches a test node. - * At that point, it reverses the path found and invokes the @callback. - * - * Being Depth First Search, time complexity is O(|V| + |E|), while - * space is O(|V|). In this case, the maximum stack size is set by - * QOS_PATH_MAX_ELEMENT_SIZE. - */ -static void qos_traverse_graph(QOSGraphNode *root, QOSTestCallback callback) -{ - QOSGraphNode *v, *dest_node, *path; - QOSStackElement *s_el; - QOSGraphEdge *e, *next; - QOSGraphEdgeList *list; - - qos_push(root, NULL, NULL); - - while (qos_node_tos > 0) { - s_el = qos_tos(); - v = s_el->node; - if (v->visited) { - qos_pop(); - continue; - } - v->visited = true; - list = get_edgelist(v->name); - if (!list) { - qos_pop(); - if (v->type == QNODE_TEST) { - v->visited = false; - path = qos_reverse_path(s_el); - callback(path, s_el->length); - } - } else { - QSLIST_FOREACH_SAFE(e, list, edge_list, next) { - dest_node = search_node(e->dest); - - if (!dest_node) { - fprintf(stderr, "node %s in %s -> %s does not exist\n", - e->dest, v->name, e->dest); - abort(); - } - - if (!dest_node->visited && dest_node->available) { - qos_push(dest_node, s_el, e); - } - } - } - } -} - -/* QGRAPH API*/ - -QOSGraphNode *qos_graph_get_node(const char *key) -{ - return search_node(key); -} - -bool qos_graph_has_node(const char *node) -{ - QOSGraphNode *n = search_node(node); - return n != NULL; -} - -QOSNodeType qos_graph_get_node_type(const char *node) -{ - QOSGraphNode *n = search_node(node); - if (n) { - return n->type; - } - return -1; -} - -bool qos_graph_get_node_availability(const char *node) -{ - QOSGraphNode *n = search_node(node); - if (n) { - return n->available; - } - return false; -} - -QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest) -{ - QOSGraphEdgeList *list = get_edgelist(node); - return search_list_edges(list, dest); -} - -QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge) -{ - if (!edge) { - return -1; - } - return edge->type;; -} - -char *qos_graph_edge_get_dest(QOSGraphEdge *edge) -{ - if (!edge) { - return NULL; - } - return edge->dest; -} - -void *qos_graph_edge_get_arg(QOSGraphEdge *edge) -{ - if (!edge) { - return NULL; - } - return edge->arg; -} - -char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge) -{ - if (!edge) { - return NULL; - } - return edge->after_cmd_line; -} - -char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge) -{ - if (!edge) { - return NULL; - } - return edge->before_cmd_line; -} - -char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge) -{ - if (!edge) { - return NULL; - } - return edge->extra_device_opts; -} - -char *qos_graph_edge_get_name(QOSGraphEdge *edge) -{ - if (!edge) { - return NULL; - } - return edge->edge_name; -} - -bool qos_graph_has_edge(const char *start, const char *dest) -{ - QOSGraphEdgeList *list = get_edgelist(start); - QOSGraphEdge *e = search_list_edges(list, dest); - return e != NULL; -} - -QOSGraphNode *qos_graph_get_machine(const char *node) -{ - return search_machine(node); -} - -bool qos_graph_has_machine(const char *node) -{ - QOSGraphNode *m = search_machine(node); - return m != NULL; -} - -void qos_print_graph(void) -{ - qos_graph_foreach_test_path(qos_print_cb); -} - -void qos_graph_init(void) -{ - if (!node_table) { - node_table = g_hash_table_new_full(g_str_hash, g_str_equal, - destroy_string, destroy_node); - create_node(QOS_ROOT, QNODE_DRIVER); - } - - if (!edge_table) { - edge_table = g_hash_table_new_full(g_str_hash, g_str_equal, - destroy_string, destroy_edges); - } -} - -void qos_graph_destroy(void) -{ - if (node_table) { - g_hash_table_destroy(node_table); - } - - if (edge_table) { - g_hash_table_destroy(edge_table); - } - - node_table = NULL; - edge_table = NULL; -} - -void qos_node_destroy(void *key) -{ - g_hash_table_remove(node_table, key); -} - -void qos_edge_destroy(void *key) -{ - g_hash_table_remove(edge_table, key); -} - -void qos_add_test(const char *name, const char *interface, - QOSTestFunc test_func, QOSGraphTestOptions *opts) -{ - QOSGraphNode *node; - char *test_name = g_strdup_printf("%s-tests/%s", interface, name);; - QOSGraphTestOptions def_opts = { }; - - if (!opts) { - opts = &def_opts; - } - node = create_node(test_name, QNODE_TEST); - node->u.test.function = test_func; - node->u.test.arg = opts->arg; - assert(!opts->edge.arg); - assert(!opts->edge.size_arg); - - node->u.test.before = opts->before; - node->u.test.subprocess = opts->subprocess; - node->available = true; - add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge); - g_free(test_name); -} - -void qos_node_create_machine(const char *name, QOSCreateMachineFunc function) -{ - qos_node_create_machine_args(name, function, NULL); -} - -void qos_node_create_machine_args(const char *name, - QOSCreateMachineFunc function, - const char *opts) -{ - QOSGraphNode *node = create_node(name, QNODE_MACHINE); - build_machine_cmd_line(node, opts); - node->u.machine.constructor = function; - add_edge(QOS_ROOT, name, QEDGE_CONTAINS, NULL); -} - -void qos_node_create_driver(const char *name, QOSCreateDriverFunc function) -{ - QOSGraphNode *node = create_node(name, QNODE_DRIVER); - build_driver_cmd_line(node); - node->u.driver.constructor = function; -} - -void qos_node_contains(const char *container, const char *contained, - QOSGraphEdgeOptions *opts, ...) -{ - va_list va; - - if (opts == NULL) { - add_edge(container, contained, QEDGE_CONTAINS, NULL); - return; - } - - va_start(va, opts); - do { - add_edge(container, contained, QEDGE_CONTAINS, opts); - opts = va_arg(va, QOSGraphEdgeOptions *); - } while (opts != NULL); - - va_end(va); -} - -void qos_node_produces(const char *producer, const char *interface) -{ - create_interface(interface); - add_edge(producer, interface, QEDGE_PRODUCES, NULL); -} - -void qos_node_consumes(const char *consumer, const char *interface, - QOSGraphEdgeOptions *opts) -{ - create_interface(interface); - add_edge(interface, consumer, QEDGE_CONSUMED_BY, opts); -} - -void qos_graph_node_set_availability(const char *node, bool av) -{ - QOSGraphEdgeList *elist; - QOSGraphNode *n = search_node(node); - QOSGraphEdge *e, *next; - if (!n) { - return; - } - n->available = av; - elist = get_edgelist(node); - if (!elist) { - return; - } - QSLIST_FOREACH_SAFE(e, elist, edge_list, next) { - if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) { - qos_graph_node_set_availability(e->dest, av); - } - } -} - -void qos_graph_foreach_test_path(QOSTestCallback fn) -{ - QOSGraphNode *root = qos_graph_get_node(QOS_ROOT); - qos_traverse_graph(root, fn); -} - -QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts) -{ - QOSGraphObject *obj; - - g_assert(node->type == QNODE_MACHINE); - obj = node->u.machine.constructor(qts); - obj->free = g_free; - return obj; -} - -QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, - QGuestAllocator *alloc, void *arg) -{ - QOSGraphObject *obj; - - g_assert(node->type == QNODE_DRIVER); - obj = node->u.driver.constructor(parent, alloc, arg); - obj->free = g_free; - return obj; -} - -void qos_object_destroy(QOSGraphObject *obj) -{ - if (!obj) { - return; - } - if (obj->destructor) { - obj->destructor(obj); - } - if (obj->free) { - obj->free(obj); - } -} - -void qos_object_queue_destroy(QOSGraphObject *obj) -{ - g_test_queue_destroy((GDestroyNotify) qos_object_destroy, obj); -} - -void qos_object_start_hw(QOSGraphObject *obj) -{ - if (obj->start_hw) { - obj->start_hw(obj); - } -} - -char *qos_get_machine_type(char *name) -{ - while (*name != '\0' && *name != '/') { - name++; - } - - if (!*name || !name[1]) { - fprintf(stderr, "Machine name has to be of the form /\n"); - abort(); - } - - return name + 1; -} - -void qos_delete_cmd_line(const char *name) -{ - QOSGraphNode *node = search_node(name); - if (node) { - g_free(node->command_line); - node->command_line = NULL; - } -} diff --git a/tests/libqos/qgraph.h b/tests/libqos/qgraph.h deleted file mode 100644 index 3a25dda4b2..0000000000 --- a/tests/libqos/qgraph.h +++ /dev/null @@ -1,574 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef QGRAPH_H -#define QGRAPH_H - -#include -#include "qemu/module.h" -#include "malloc.h" - -/* maximum path length */ -#define QOS_PATH_MAX_ELEMENT_SIZE 50 - -typedef struct QOSGraphObject QOSGraphObject; -typedef struct QOSGraphNode QOSGraphNode; -typedef struct QOSGraphEdge QOSGraphEdge; -typedef struct QOSGraphNodeOptions QOSGraphNodeOptions; -typedef struct QOSGraphEdgeOptions QOSGraphEdgeOptions; -typedef struct QOSGraphTestOptions QOSGraphTestOptions; - -/* Constructor for drivers, machines and test */ -typedef void *(*QOSCreateDriverFunc) (void *parent, QGuestAllocator *alloc, - void *addr); -typedef void *(*QOSCreateMachineFunc) (QTestState *qts); -typedef void (*QOSTestFunc) (void *parent, void *arg, QGuestAllocator *alloc); - -/* QOSGraphObject functions */ -typedef void *(*QOSGetDriver) (void *object, const char *interface); -typedef QOSGraphObject *(*QOSGetDevice) (void *object, const char *name); -typedef void (*QOSDestructorFunc) (QOSGraphObject *object); -typedef void (*QOSStartFunct) (QOSGraphObject *object); - -/* Test options functions */ -typedef void *(*QOSBeforeTest) (GString *cmd_line, void *arg); - -/** - * SECTION: qgraph.h - * @title: Qtest Driver Framework - * @short_description: interfaces to organize drivers and tests - * as nodes in a graph - * - * This Qgraph API provides all basic functions to create a graph - * and instantiate nodes representing machines, drivers and tests - * representing their relations with CONSUMES, PRODUCES, and CONTAINS - * edges. - * - * The idea is to have a framework where each test asks for a specific - * driver, and the framework takes care of allocating the proper devices - * required and passing the correct command line arguments to QEMU. - * - * A node can be of four types: - * - QNODE_MACHINE: for example "arm/raspi2" - * - QNODE_DRIVER: for example "generic-sdhci" - * - QNODE_INTERFACE: for example "sdhci" (interface for all "-sdhci" drivers) - * an interface is not explicitly created, it will be auto- - * matically instantiated when a node consumes or produces - * it. - * - QNODE_TEST: for example "sdhci-test", consumes an interface and tests - * the functions provided - * - * Notes for the nodes: - * - QNODE_MACHINE: each machine struct must have a QGuestAllocator and - * implement get_driver to return the allocator passing - * "memory". The function can also return NULL if the - * allocator is not set. - * - QNODE_DRIVER: driver names must be unique, and machines and nodes - * planned to be "consumed" by other nodes must match QEMU - * drivers name, otherwise they won't be discovered - * - * An edge relation between two nodes (drivers or machines) X and Y can be: - * - X CONSUMES Y: Y can be plugged into X - * - X PRODUCES Y: X provides the interface Y - * - X CONTAINS Y: Y is part of X component - * - * Basic framework steps are the following: - * - All nodes and edges are created in their respective - * machine/driver/test files - * - The framework starts QEMU and asks for a list of available devices - * and machines (note that only machines and "consumed" nodes are mapped - * 1:1 with QEMU devices) - * - The framework walks the graph starting from the available machines and - * performs a Depth First Search for tests - * - Once a test is found, the path is walked again and all drivers are - * allocated accordingly and the final interface is passed to the test - * - The test is executed - * - Unused objects are cleaned and the path discovery is continued - * - * Depending on the QEMU binary used, only some drivers/machines will be - * available and only test that are reached by them will be executed. - * - * - * Creating new driver an its interface - * - #include "libqos/qgraph.h" - - struct My_driver { - QOSGraphObject obj; - Node_produced prod; - Node_contained cont; - } - - static void my_destructor(QOSGraphObject *obj) - { - g_free(obj); - } - - static void my_get_driver(void *object, const char *interface) { - My_driver *dev = object; - if (!g_strcmp0(interface, "my_interface")) { - return &dev->prod; - } - abort(); - } - - static void my_get_device(void *object, const char *device) { - My_driver *dev = object; - if (!g_strcmp0(device, "my_driver_contained")) { - return &dev->cont; - } - abort(); - } - - static void *my_driver_constructor(void *node_consumed, - QOSGraphObject *alloc) - { - My_driver dev = g_new(My_driver, 1); - // get the node pointed by the produce edge - dev->obj.get_driver = my_get_driver; - // get the node pointed by the contains - dev->obj.get_device = my_get_device; - // free the object - dev->obj.destructor = my_destructor; - do_something_with_node_consumed(node_consumed); - // set all fields of contained device - init_contained_device(&dev->cont); - return &dev->obj; - } - - static void register_my_driver(void) - { - qos_node_create_driver("my_driver", my_driver_constructor); - // contained drivers don't need a constructor, - // they will be init by the parent. - qos_node_create_driver("my_driver_contained", NULL); - - // For the sake of this example, assume machine x86_64/pc contains - // "other_node". - // This relation, along with the machine and "other_node" creation, - // should be defined in the x86_64_pc-machine.c file. - // "my_driver" will then consume "other_node" - qos_node_contains("my_driver", "my_driver_contained"); - qos_node_produces("my_driver", "my_interface"); - qos_node_consumes("my_driver", "other_node"); - } - * - * - * - * In the above example, all possible types of relations are created: - * node "my_driver" consumes, contains and produces other nodes. - * more specifically: - * x86_64/pc -->contains--> other_node <--consumes-- my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_interface <--produces--+ - * - * or inverting the consumes edge in consumed_by: - * - * x86_64/pc -->contains--> other_node --consumed_by--> my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_interface <--produces--+ - * - * - * Creating new test - * - * #include "libqos/qgraph.h" - * - * static void my_test_function(void *obj, void *data) - * { - * Node_produced *interface_to_test = obj; - * // test interface_to_test - * } - * - * static void register_my_test(void) - * { - * qos_add_test("my_interface", "my_test", my_test_function); - * } - * - * libqos_init(register_my_test); - * - * - * - * - * Here a new test is created, consuming "my_interface" node - * and creating a valid path from a machine to a test. - * Final graph will be like this: - * x86_64/pc -->contains--> other_node <--consumes-- my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_test --consumes--> my_interface <--produces--+ - * - * or inverting the consumes edge in consumed_by: - * - * x86_64/pc -->contains--> other_node --consumed_by--> my_driver - * | - * my_driver_contained <--contains--+ - * | - * my_test <--consumed_by-- my_interface <--produces--+ - * - * Assuming there the binary is - * QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64 - * a valid test path will be: - * "/x86_64/pc/other_node/my_driver/my_interface/my_test". - * - * Additional examples are also in libqos/test-qgraph.c - * - * Command line: - * Command line is built by using node names and optional arguments - * passed by the user when building the edges. - * - * There are three types of command line arguments: - * - in node : created from the node name. For example, machines will - * have "-M " to its command line, while devices - * "-device ". It is automatically done by the - * framework. - * - after node : added as additional argument to the node name. - * This argument is added optionally when creating edges, - * by setting the parameter @after_cmd_line and - * @extra_edge_opts in #QOSGraphEdgeOptions. - * The framework automatically adds - * a comma before @extra_edge_opts, - * because it is going to add attributes - * after the destination node pointed by - * the edge containing these options, and automatically - * adds a space before @after_cmd_line, because it - * adds an additional device, not an attribute. - * - before node : added as additional argument to the node name. - * This argument is added optionally when creating edges, - * by setting the parameter @before_cmd_line in - * #QOSGraphEdgeOptions. This attribute - * is going to add attributes before the destination node - * pointed by the edge containing these options. It is - * helpful to commands that are not node-representable, - * such as "-fdsev" or "-netdev". - * - * While adding command line in edges is always used, not all nodes names are - * used in every path walk: this is because the contained or produced ones - * are already added by QEMU, so only nodes that "consumes" will be used to - * build the command line. Also, nodes that will have { "abstract" : true } - * as QMP attribute will loose their command line, since they are not proper - * devices to be added in QEMU. - * - * Example: - * - QOSGraphEdgeOptions opts = { - .arg = NULL, - .size_arg = 0, - .after_cmd_line = "-device other", - .before_cmd_line = "-netdev something", - .extra_edge_opts = "addr=04.0", - }; - QOSGraphNode * node = qos_node_create_driver("my_node", constructor); - qos_node_consumes_args("my_node", "interface", &opts); - * - * Will produce the following command line: - * "-netdev something -device my_node,addr=04.0 -device other" - */ - -/** - * Edge options to be passed to the contains/consumes *_args function. - */ -struct QOSGraphEdgeOptions { - void *arg; /* - * optional arg that will be used by - * dest edge - */ - uint32_t size_arg; /* - * optional arg size that will be used by - * dest edge - */ - const char *extra_device_opts;/* - *optional additional command line for dest - * edge, used to add additional attributes - * *after* the node command line, the - * framework automatically prepends "," - * to this argument. - */ - const char *before_cmd_line; /* - * optional additional command line for dest - * edge, used to add additional attributes - * *before* the node command line, usually - * other non-node represented commands, - * like "-fdsev synt" - */ - const char *after_cmd_line; /* - * optional extra command line to be added - * after the device command. This option - * is used to add other devices - * command line that depend on current node. - * Automatically prepends " " to this - * argument - */ - const char *edge_name; /* - * optional edge to differentiate multiple - * devices with same node name - */ -}; - -/** - * Test options to be passed to the test functions. - */ -struct QOSGraphTestOptions { - QOSGraphEdgeOptions edge; /* edge arguments that will be used by test. - * Note that test *does not* use edge_name, - * and uses instead arg and size_arg as - * data arg for its test function. - */ - void *arg; /* passed to the .before function, or to the - * test function if there is no .before - * function - */ - QOSBeforeTest before; /* executed before the test. Can add - * additional parameters to the command line - * and modify the argument to the test function. - */ - bool subprocess; /* run the test in a subprocess */ -}; - -/** - * Each driver, test or machine of this framework will have a - * QOSGraphObject as first field. - * - * This set of functions offered by QOSGraphObject are executed - * in different stages of the framework: - * - get_driver / get_device : Once a machine-to-test path has been - * found, the framework traverses it again and allocates all the - * nodes, using the provided constructor. To satisfy their relations, - * i.e. for produces or contains, where a struct constructor needs - * an external parameter represented by the previous node, - * the framework will call get_device (for contains) or - * get_driver (for produces), depending on the edge type, passing - * them the name of the next node to be taken and getting from them - * the corresponding pointer to the actual structure of the next node to - * be used in the path. - * - * - start_hw: This function is executed after all the path objects - * have been allocated, but before the test is run. It starts the hw, setting - * the initial configurations (*_device_enable) and making it ready for the - * test. - * - * - destructor: Opposite to the node constructor, destroys the object. - * This function is called after the test has been executed, and performs - * a complete cleanup of each node allocated field. In case no constructor - * is provided, no destructor will be called. - * - */ -struct QOSGraphObject { - /* for produces edges, returns void * */ - QOSGetDriver get_driver; - /* for contains edges, returns a QOSGraphObject * */ - QOSGetDevice get_device; - /* start the hw, get ready for the test */ - QOSStartFunct start_hw; - /* destroy this QOSGraphObject */ - QOSDestructorFunc destructor; - /* free the memory associated to the QOSGraphObject and its contained - * children */ - GDestroyNotify free; -}; - -/** - * qos_graph_init(): initialize the framework, creates two hash - * tables: one for the nodes and another for the edges. - */ -void qos_graph_init(void); - -/** - * qos_graph_destroy(): deallocates all the hash tables, - * freeing all nodes and edges. - */ -void qos_graph_destroy(void); - -/** - * qos_node_destroy(): removes and frees a node from the, - * nodes hash table. - */ -void qos_node_destroy(void *key); - -/** - * qos_edge_destroy(): removes and frees an edge from the, - * edges hash table. - */ -void qos_edge_destroy(void *key); - -/** - * qos_add_test(): adds a test node @name to the nodes hash table. - * - * The test will consume a @interface node, and once the - * graph walking algorithm has found it, the @test_func will be - * executed. It also has the possibility to - * add an optional @opts (see %QOSGraphNodeOptions). - * - * For tests, opts->edge.arg and size_arg represent the arg to pass - * to @test_func - */ -void qos_add_test(const char *name, const char *interface, - QOSTestFunc test_func, - QOSGraphTestOptions *opts); - -/** - * qos_node_create_machine(): creates the machine @name and - * adds it to the node hash table. - * - * This node will be of type QNODE_MACHINE and have @function - * as constructor - */ -void qos_node_create_machine(const char *name, QOSCreateMachineFunc function); - -/** - * qos_node_create_machine_args(): same as qos_node_create_machine, - * but with the possibility to add an optional ", @opts" after -M machine - * command line. - */ -void qos_node_create_machine_args(const char *name, - QOSCreateMachineFunc function, - const char *opts); - -/** - * qos_node_create_driver(): creates the driver @name and - * adds it to the node hash table. - * - * This node will be of type QNODE_DRIVER and have @function - * as constructor - */ -void qos_node_create_driver(const char *name, QOSCreateDriverFunc function); - -/** - * qos_node_contains(): creates one or more edges of type QEDGE_CONTAINS - * and adds them to the edge list mapped to @container in the - * edge hash table. - * - * The edges will have @container as source and @contained as destination. - * - * If @opts is NULL, a single edge will be added with no options. - * If @opts is non-NULL, the arguments after @contained represent a - * NULL-terminated list of %QOSGraphEdgeOptions structs, and an - * edge will be added for each of them. - * - * This function can be useful when there are multiple devices - * with the same node name contained in a machine/other node - * - * For example, if "arm/raspi2" contains 2 "generic-sdhci" - * devices, the right commands will be: - * qos_node_create_machine("arm/raspi2"); - * qos_node_create_driver("generic-sdhci", constructor); - * //assume rest of the fields are set NULL - * QOSGraphEdgeOptions op1 = { .edge_name = "emmc" }; - * QOSGraphEdgeOptions op2 = { .edge_name = "sdcard" }; - * qos_node_contains("arm/raspi2", "generic-sdhci", &op1, &op2, NULL); - * - * Of course this also requires that the @container's get_device function - * should implement a case for "emmc" and "sdcard". - * - * For contains, op1.arg and op1.size_arg represent the arg to pass - * to @contained constructor to properly initialize it. - */ -void qos_node_contains(const char *container, const char *contained, - QOSGraphEdgeOptions *opts, ...); - -/** - * qos_node_produces(): creates an edge of type QEDGE_PRODUCES and - * adds it to the edge list mapped to @producer in the - * edge hash table. - * - * This edge will have @producer as source and @interface as destination. - */ -void qos_node_produces(const char *producer, const char *interface); - -/** - * qos_node_consumes(): creates an edge of type QEDGE_CONSUMED_BY and - * adds it to the edge list mapped to @interface in the - * edge hash table. - * - * This edge will have @interface as source and @consumer as destination. - * It also has the possibility to add an optional @opts - * (see %QOSGraphEdgeOptions) - */ -void qos_node_consumes(const char *consumer, const char *interface, - QOSGraphEdgeOptions *opts); - -/** - * qos_invalidate_command_line(): invalidates current command line, so that - * qgraph framework cannot try to cache the current command line and - * forces QEMU to restart. - */ -void qos_invalidate_command_line(void); - -/** - * qos_get_current_command_line(): return the command line required by the - * machine and driver objects. This is the same string that was passed to - * the test's "before" callback, if any. - */ -const char *qos_get_current_command_line(void); - -/** - * qos_allocate_objects(): - * @qts: The #QTestState that will be referred to by the machine object. - * @alloc: Where to store the allocator for the machine object, or %NULL. - * - * Allocate driver objects for the current test - * path, but relative to the QTestState @qts. - * - * Returns a test object just like the one that was passed to - * the test function, but relative to @qts. - */ -void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc); - -/** - * qos_object_destroy(): calls the destructor for @obj - */ -void qos_object_destroy(QOSGraphObject *obj); - -/** - * qos_object_queue_destroy(): queue the destructor for @obj so that it is - * called at the end of the test - */ -void qos_object_queue_destroy(QOSGraphObject *obj); - -/** - * qos_object_start_hw(): calls the start_hw function for @obj - */ -void qos_object_start_hw(QOSGraphObject *obj); - -/** - * qos_machine_new(): instantiate a new machine node - * @node: A machine node to be instantiated - * @qts: The #QTestState that will be referred to by the machine object. - * - * Returns a machine object. - */ -QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts); - -/** - * qos_machine_new(): instantiate a new driver node - * @node: A driver node to be instantiated - * @parent: A #QOSGraphObject to be consumed by the new driver node - * @alloc: An allocator to be used by the new driver node. - * @arg: The argument for the consumed-by edge to @node. - * - * Calls the constructor for the driver object. - */ -QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, - QGuestAllocator *alloc, void *arg); - - -#endif diff --git a/tests/libqos/qgraph_internal.h b/tests/libqos/qgraph_internal.h deleted file mode 100644 index f4734c8681..0000000000 --- a/tests/libqos/qgraph_internal.h +++ /dev/null @@ -1,257 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef QGRAPH_INTERNAL_H -#define QGRAPH_INTERNAL_H - -/* This header is declaring additional helper functions defined in - * libqos/qgraph.c - * It should not be included in tests - */ - -#include "libqos/qgraph.h" - -typedef struct QOSGraphMachine QOSGraphMachine; -typedef enum QOSEdgeType QOSEdgeType; -typedef enum QOSNodeType QOSNodeType; - -/* callback called when the walk path algorithm found a - * valid path - */ -typedef void (*QOSTestCallback) (QOSGraphNode *path, int len); - -/* edge types*/ -enum QOSEdgeType { - QEDGE_CONTAINS, - QEDGE_PRODUCES, - QEDGE_CONSUMED_BY -}; - -/* node types*/ -enum QOSNodeType { - QNODE_MACHINE, - QNODE_DRIVER, - QNODE_INTERFACE, - QNODE_TEST -}; - -/* Graph Node */ -struct QOSGraphNode { - QOSNodeType type; - bool available; /* set by QEMU via QMP, used during graph walk */ - bool visited; /* used during graph walk */ - char *name; /* used to identify the node */ - char *command_line; /* used to start QEMU at test execution */ - union { - struct { - QOSCreateDriverFunc constructor; - } driver; - struct { - QOSCreateMachineFunc constructor; - } machine; - struct { - QOSTestFunc function; - void *arg; - QOSBeforeTest before; - bool subprocess; - } test; - } u; - - /** - * only used when traversing the path, never rely on that except in the - * qos_traverse_graph callback function - */ - QOSGraphEdge *path_edge; -}; - -/** - * qos_graph_get_node(): returns the node mapped to that @key. - * It performs an hash map search O(1) - * - * Returns: on success: the %QOSGraphNode - * otherwise: #NULL - */ -QOSGraphNode *qos_graph_get_node(const char *key); - -/** - * qos_graph_has_node(): returns #TRUE if the node - * has map has a node mapped to that @key. - */ -bool qos_graph_has_node(const char *node); - -/** - * qos_graph_get_node_type(): returns the %QOSNodeType - * of the node @node. - * It performs an hash map search O(1) - * Returns: on success: the %QOSNodeType - * otherwise: #-1 - */ -QOSNodeType qos_graph_get_node_type(const char *node); - -/** - * qos_graph_get_node_availability(): returns the availability (boolean) - * of the node @node. - */ -bool qos_graph_get_node_availability(const char *node); - -/** - * qos_graph_get_edge(): returns the edge - * linking of the node @node with @dest. - * - * Returns: on success: the %QOSGraphEdge - * otherwise: #NULL - */ -QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest); - -/** - * qos_graph_edge_get_type(): returns the edge type - * of the edge @edge. - * - * Returns: on success: the %QOSEdgeType - * otherwise: #-1 - */ -QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge); - -/** - * qos_graph_edge_get_dest(): returns the name of the node - * pointed as destination of edge @edge. - * - * Returns: on success: the destination - * otherwise: #NULL - */ -char *qos_graph_edge_get_dest(QOSGraphEdge *edge); - -/** - * qos_graph_has_edge(): returns #TRUE if there - * exists an edge from @start to @dest. - */ -bool qos_graph_has_edge(const char *start, const char *dest); - -/** - * qos_graph_edge_get_arg(): returns the args assigned - * to that @edge. - * - * Returns: on success: the arg - * otherwise: #NULL - */ -void *qos_graph_edge_get_arg(QOSGraphEdge *edge); - -/** - * qos_graph_edge_get_after_cmd_line(): returns the edge - * command line that will be added after all the node arguments - * and all the before_cmd_line arguments. - * - * Returns: on success: the char* arg - * otherwise: #NULL - */ -char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge); - -/** - * qos_graph_edge_get_before_cmd_line(): returns the edge - * command line that will be added before the node command - * line argument. - * - * Returns: on success: the char* arg - * otherwise: #NULL - */ -char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge); - -/** - * qos_graph_edge_get_extra_device_opts(): returns the arg - * command line that will be added to the node command - * line argument. - * - * Returns: on success: the char* arg - * otherwise: #NULL - */ -char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge); - -/** - * qos_graph_edge_get_name(): returns the name - * assigned to the destination node (different only) - * if there are multiple devices with the same node name - * e.g. a node has two "generic-sdhci", "emmc" and "sdcard" - * there will be two edges with edge_name ="emmc" and "sdcard" - * - * Returns always the char* edge_name - */ -char *qos_graph_edge_get_name(QOSGraphEdge *edge); - -/** - * qos_graph_get_machine(): returns the machine assigned - * to that @node name. - * - * It performs a search only trough the list of machines - * (i.e. the QOS_ROOT child). - * - * Returns: on success: the %QOSGraphNode - * otherwise: #NULL - */ -QOSGraphNode *qos_graph_get_machine(const char *node); - -/** - * qos_graph_has_machine(): returns #TRUE if the node - * has map has a node mapped to that @node. - */ -bool qos_graph_has_machine(const char *node); - - -/** - * qos_print_graph(): walks the graph and prints - * all machine-to-test paths. - */ -void qos_print_graph(void); - -/** - * qos_graph_foreach_test_path(): executes the Depth First search - * algorithm and applies @fn to all discovered paths. - * - * See qos_traverse_graph() in qgraph.c for more info on - * how it works. - */ -void qos_graph_foreach_test_path(QOSTestCallback fn); - -/** - * qos_get_machine_type(): return QEMU machine type for a machine node. - * This function requires every machine @name to be in the form - * /, like "arm/raspi2" or "x86_64/pc". - * - * The function will validate the format and return a pointer to - * @machine to . For example, when passed "x86_64/pc" - * it will return "pc". - * - * Note that this function *does not* allocate any new string. - */ -char *qos_get_machine_type(char *name); - -/** - * qos_delete_cmd_line(): delete the - * command line present in node mapped with key @name. - * - * This function is called when the QMP query returns a node with - * { "abstract" : true } attribute. - */ -void qos_delete_cmd_line(const char *name); - -/** - * qos_graph_node_set_availability(): sets the node identified - * by @node with availability @av. - */ -void qos_graph_node_set_availability(const char *node, bool av); - -#endif diff --git a/tests/libqos/rtas.c b/tests/libqos/rtas.c deleted file mode 100644 index d81ff4274d..0000000000 --- a/tests/libqos/rtas.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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/rtas.h" - -static void qrtas_copy_args(QTestState *qts, uint64_t target_args, - uint32_t nargs, uint32_t *args) -{ - int i; - - for (i = 0; i < nargs; i++) { - qtest_writel(qts, target_args + i * sizeof(uint32_t), args[i]); - } -} - -static void qrtas_copy_ret(QTestState *qts, uint64_t target_ret, - uint32_t nret, uint32_t *ret) -{ - int i; - - for (i = 0; i < nret; i++) { - ret[i] = qtest_readl(qts, target_ret + i * sizeof(uint32_t)); - } -} - -static uint64_t qrtas_call(QTestState *qts, QGuestAllocator *alloc, - const char *name, - uint32_t nargs, uint32_t *args, - uint32_t nret, uint32_t *ret) -{ - uint64_t res; - uint64_t target_args, target_ret; - - target_args = guest_alloc(alloc, nargs * sizeof(uint32_t)); - target_ret = guest_alloc(alloc, nret * sizeof(uint32_t)); - - qrtas_copy_args(qts, target_args, nargs, args); - res = qtest_rtas_call(qts, name, nargs, target_args, nret, target_ret); - qrtas_copy_ret(qts, target_ret, nret, ret); - - guest_free(alloc, target_ret); - guest_free(alloc, target_args); - - return res; -} - -int qrtas_get_time_of_day(QTestState *qts, QGuestAllocator *alloc, - struct tm *tm, uint32_t *ns) -{ - int res; - uint32_t ret[8]; - - res = qrtas_call(qts, alloc, "get-time-of-day", 0, NULL, 8, ret); - if (res != 0) { - return res; - } - - res = ret[0]; - memset(tm, 0, sizeof(*tm)); - tm->tm_year = ret[1] - 1900; - tm->tm_mon = ret[2] - 1; - tm->tm_mday = ret[3]; - tm->tm_hour = ret[4]; - tm->tm_min = ret[5]; - tm->tm_sec = ret[6]; - *ns = ret[7]; - - return res; -} - -uint32_t qrtas_ibm_read_pci_config(QTestState *qts, QGuestAllocator *alloc, - uint64_t buid, - uint32_t addr, uint32_t size) -{ - int res; - uint32_t args[4], ret[2]; - - args[0] = addr; - args[1] = buid >> 32; - args[2] = buid & 0xffffffff; - args[3] = size; - res = qrtas_call(qts, alloc, "ibm,read-pci-config", 4, args, 2, ret); - if (res != 0) { - return -1; - } - - if (ret[0] != 0) { - return -1; - } - - return ret[1]; -} - -int qrtas_ibm_write_pci_config(QTestState *qts, QGuestAllocator *alloc, - uint64_t buid, - uint32_t addr, uint32_t size, uint32_t val) -{ - int res; - uint32_t args[5], ret[1]; - - args[0] = addr; - args[1] = buid >> 32; - args[2] = buid & 0xffffffff; - args[3] = size; - args[4] = val; - res = qrtas_call(qts, alloc, "ibm,write-pci-config", 5, args, 1, ret); - if (res != 0) { - return -1; - } - - if (ret[0] != 0) { - return -1; - } - - return 0; -} diff --git a/tests/libqos/rtas.h b/tests/libqos/rtas.h deleted file mode 100644 index 459e23aaf4..0000000000 --- a/tests/libqos/rtas.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 LIBQOS_RTAS_H -#define LIBQOS_RTAS_H -#include "libqos/malloc.h" - -int qrtas_get_time_of_day(QTestState *qts, QGuestAllocator *alloc, - struct tm *tm, uint32_t *ns); -uint32_t qrtas_ibm_read_pci_config(QTestState *qts, QGuestAllocator *alloc, - uint64_t buid, uint32_t addr, uint32_t size); -int qrtas_ibm_write_pci_config(QTestState *qts, QGuestAllocator *alloc, - uint64_t buid, uint32_t addr, uint32_t size, - uint32_t val); -#endif /* LIBQOS_RTAS_H */ diff --git a/tests/libqos/sdhci.c b/tests/libqos/sdhci.c deleted file mode 100644 index 309794bc52..0000000000 --- a/tests/libqos/sdhci.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "libqos/qgraph.h" -#include "pci.h" -#include "qemu/module.h" -#include "sdhci.h" -#include "hw/pci/pci.h" - -static void set_qsdhci_fields(QSDHCI *s, uint8_t version, uint8_t baseclock, - bool sdma, uint64_t reg) -{ - s->props.version = version; - s->props.baseclock = baseclock; - s->props.capab.sdma = sdma; - s->props.capab.reg = reg; -} - -/* Memory mapped implementation of QSDHCI */ - -static uint16_t sdhci_mm_readw(QSDHCI *s, uint32_t reg) -{ - QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci); - return qtest_readw(smm->qts, smm->addr + reg); -} - -static uint64_t sdhci_mm_readq(QSDHCI *s, uint32_t reg) -{ - QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci); - return qtest_readq(smm->qts, smm->addr + reg); -} - -static void sdhci_mm_writeq(QSDHCI *s, uint32_t reg, uint64_t val) -{ - QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci); - qtest_writeq(smm->qts, smm->addr + reg, val); -} - -static void *sdhci_mm_get_driver(void *obj, const char *interface) -{ - QSDHCI_MemoryMapped *smm = obj; - if (!g_strcmp0(interface, "sdhci")) { - return &smm->sdhci; - } - fprintf(stderr, "%s not present in generic-sdhci\n", interface); - g_assert_not_reached(); -} - -void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts, - uint32_t addr, QSDHCIProperties *common) -{ - sdhci->obj.get_driver = sdhci_mm_get_driver; - sdhci->sdhci.readw = sdhci_mm_readw; - sdhci->sdhci.readq = sdhci_mm_readq; - sdhci->sdhci.writeq = sdhci_mm_writeq; - memcpy(&sdhci->sdhci.props, common, sizeof(QSDHCIProperties)); - sdhci->addr = addr; - sdhci->qts = qts; -} - -/* PCI implementation of QSDHCI */ - -static uint16_t sdhci_pci_readw(QSDHCI *s, uint32_t reg) -{ - QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci); - return qpci_io_readw(&spci->dev, spci->mem_bar, reg); -} - -static uint64_t sdhci_pci_readq(QSDHCI *s, uint32_t reg) -{ - QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci); - return qpci_io_readq(&spci->dev, spci->mem_bar, reg); -} - -static void sdhci_pci_writeq(QSDHCI *s, uint32_t reg, uint64_t val) -{ - QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci); - return qpci_io_writeq(&spci->dev, spci->mem_bar, reg, val); -} - -static void *sdhci_pci_get_driver(void *object, const char *interface) -{ - QSDHCI_PCI *spci = object; - if (!g_strcmp0(interface, "sdhci")) { - return &spci->sdhci; - } - - fprintf(stderr, "%s not present in sdhci-pci\n", interface); - g_assert_not_reached(); -} - -static void sdhci_pci_start_hw(QOSGraphObject *obj) -{ - QSDHCI_PCI *spci = (QSDHCI_PCI *)obj; - qpci_device_enable(&spci->dev); -} - -static void sdhci_destructor(QOSGraphObject *obj) -{ - QSDHCI_PCI *spci = (QSDHCI_PCI *)obj; - qpci_iounmap(&spci->dev, spci->mem_bar); -} - -static void *sdhci_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr) -{ - QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1); - QPCIBus *bus = pci_bus; - uint64_t barsize; - - qpci_device_init(&spci->dev, bus, addr); - spci->mem_bar = qpci_iomap(&spci->dev, 0, &barsize); - spci->sdhci.readw = sdhci_pci_readw; - spci->sdhci.readq = sdhci_pci_readq; - spci->sdhci.writeq = sdhci_pci_writeq; - set_qsdhci_fields(&spci->sdhci, 2, 0, 1, 0x057834b4); - - spci->obj.get_driver = sdhci_pci_get_driver; - spci->obj.start_hw = sdhci_pci_start_hw; - spci->obj.destructor = sdhci_destructor; - return &spci->obj; -} - -static void qsdhci_register_nodes(void) -{ - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - .vendor_id = PCI_VENDOR_ID_REDHAT, - .device_id = PCI_DEVICE_ID_REDHAT_SDHCI, - }; - - QOSGraphEdgeOptions opts = { - .extra_device_opts = "addr=04.0", - }; - - /* generic-sdhci */ - qos_node_create_driver("generic-sdhci", NULL); - qos_node_produces("generic-sdhci", "sdhci"); - - /* sdhci-pci */ - add_qpci_address(&opts, &addr); - qos_node_create_driver("sdhci-pci", sdhci_pci_create); - qos_node_produces("sdhci-pci", "sdhci"); - qos_node_consumes("sdhci-pci", "pci-bus", &opts); - -} - -libqos_init(qsdhci_register_nodes); diff --git a/tests/libqos/sdhci.h b/tests/libqos/sdhci.h deleted file mode 100644 index a88b45ae9d..0000000000 --- a/tests/libqos/sdhci.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef QGRAPH_QSDHCI_H -#define QGRAPH_QSDHCI_H - -#include "libqos/qgraph.h" -#include "pci.h" - -typedef struct QSDHCI QSDHCI; -typedef struct QSDHCI_MemoryMapped QSDHCI_MemoryMapped; -typedef struct QSDHCI_PCI QSDHCI_PCI; -typedef struct QSDHCIProperties QSDHCIProperties; - -/* Properties common to all QSDHCI devices */ -struct QSDHCIProperties { - uint8_t version; - uint8_t baseclock; - struct { - bool sdma; - uint64_t reg; - } capab; -}; - -struct QSDHCI { - uint16_t (*readw)(QSDHCI *s, uint32_t reg); - uint64_t (*readq)(QSDHCI *s, uint32_t reg); - void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val); - QSDHCIProperties props; -}; - -/* Memory Mapped implementation of QSDHCI */ -struct QSDHCI_MemoryMapped { - QOSGraphObject obj; - QTestState *qts; - QSDHCI sdhci; - uint64_t addr; -}; - -/* PCI implementation of QSDHCI */ -struct QSDHCI_PCI { - QOSGraphObject obj; - QPCIDevice dev; - QSDHCI sdhci; - QPCIBar mem_bar; -}; - -/** - * qos_init_sdhci_mm(): external constructor used by all drivers/machines - * that "contain" a #QSDHCI_MemoryMapped driver - */ -void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts, - uint32_t addr, QSDHCIProperties *common); - -#endif diff --git a/tests/libqos/tpci200.c b/tests/libqos/tpci200.c deleted file mode 100644 index ae590a456e..0000000000 --- a/tests/libqos/tpci200.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * QTest testcase for tpci200 PCI-IndustryPack bridge - * - * 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 QTpci200 QTpci200; -typedef struct QIpack QIpack; - -struct QIpack { - -}; -struct QTpci200 { - QOSGraphObject obj; - QPCIDevice dev; - QIpack ipack; -}; - -/* tpci200 */ -static void *tpci200_get_driver(void *obj, const char *interface) -{ - QTpci200 *tpci200 = obj; - if (!g_strcmp0(interface, "ipack")) { - return &tpci200->ipack; - } - if (!g_strcmp0(interface, "pci-device")) { - return &tpci200->dev; - } - - fprintf(stderr, "%s not present in tpci200\n", interface); - g_assert_not_reached(); -} - -static void *tpci200_create(void *pci_bus, QGuestAllocator *alloc, void *addr) -{ - QTpci200 *tpci200 = g_new0(QTpci200, 1); - QPCIBus *bus = pci_bus; - - qpci_device_init(&tpci200->dev, bus, addr); - tpci200->obj.get_driver = tpci200_get_driver; - return &tpci200->obj; -} - -static void tpci200_register_nodes(void) -{ - QOSGraphEdgeOptions opts = { - .extra_device_opts = "addr=04.0,id=ipack0", - }; - add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) }); - - qos_node_create_driver("tpci200", tpci200_create); - qos_node_consumes("tpci200", "pci-bus", &opts); - qos_node_produces("tpci200", "ipack"); - qos_node_produces("tpci200", "pci-device"); -} - -libqos_init(tpci200_register_nodes); diff --git a/tests/libqos/usb.c b/tests/libqos/usb.c deleted file mode 100644 index d7a9cb3c72..0000000000 --- a/tests/libqos/usb.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * common code shared by usb tests - * - * Copyright (c) 2014 Red Hat, Inc - * - * Authors: - * Gerd Hoffmann - * John Snow - * Igor Mammedov - * - * 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 "hw/usb/uhci-regs.h" -#include "libqos/usb.h" - -void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, uint32_t devfn, int bar) -{ - hc->dev = qpci_device_find(pcibus, devfn); - g_assert(hc->dev != NULL); - qpci_device_enable(hc->dev); - hc->bar = qpci_iomap(hc->dev, bar, NULL); -} - -void uhci_deinit(struct qhc *hc) -{ - g_free(hc->dev); -} - -void uhci_port_test(struct qhc *hc, int port, uint16_t expect) -{ - uint16_t value = qpci_io_readw(hc->dev, hc->bar, 0x10 + 2 * port); - uint16_t mask = ~(UHCI_PORT_WRITE_CLEAR | UHCI_PORT_RSVD1); - - g_assert((value & mask) == (expect & mask)); -} - -void usb_test_hotplug(QTestState *qts, const char *hcd_id, const char *port, - void (*port_check)(void)) -{ - char *id = g_strdup_printf("usbdev%s", port); - char *bus = g_strdup_printf("%s.0", hcd_id); - - qtest_qmp_device_add(qts, "usb-tablet", id, "{'port': %s, 'bus': %s}", - port, bus); - - if (port_check) { - port_check(); - } - - qtest_qmp_device_del(qts, id); - - g_free(bus); - g_free(id); -} diff --git a/tests/libqos/usb.h b/tests/libqos/usb.h deleted file mode 100644 index eeced39a2f..0000000000 --- a/tests/libqos/usb.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef LIBQOS_USB_H -#define LIBQOS_USB_H - -#include "libqos/pci-pc.h" - -struct qhc { - QPCIDevice *dev; - QPCIBar bar; -}; - -void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, - uint32_t devfn, int bar); -void uhci_port_test(struct qhc *hc, int port, uint16_t expect); -void uhci_deinit(struct qhc *hc); - -void usb_test_hotplug(QTestState *qts, const char *bus_name, const char *port, - void (*port_check)(void)); -#endif diff --git a/tests/libqos/virtio-9p.c b/tests/libqos/virtio-9p.c deleted file mode 100644 index 77dbfb62ad..0000000000 --- a/tests/libqos/virtio-9p.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "standard-headers/linux/virtio_ids.h" -#include "libqos/virtio-9p.h" -#include "libqos/qgraph.h" - -static QGuestAllocator *alloc; - -static void virtio_9p_cleanup(QVirtio9P *interface) -{ - qvirtqueue_cleanup(interface->vdev->bus, interface->vq, alloc); -} - -static void virtio_9p_setup(QVirtio9P *interface) -{ - uint64_t features; - - features = qvirtio_get_features(interface->vdev); - features &= ~(QVIRTIO_F_BAD_FEATURE | (1ull << VIRTIO_RING_F_EVENT_IDX)); - qvirtio_set_features(interface->vdev, features); - - interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0); - qvirtio_set_driver_ok(interface->vdev); -} - -/* virtio-9p-device */ -static void virtio_9p_device_destructor(QOSGraphObject *obj) -{ - QVirtio9PDevice *v_9p = (QVirtio9PDevice *) obj; - QVirtio9P *v9p = &v_9p->v9p; - - virtio_9p_cleanup(v9p); -} - -static void virtio_9p_device_start_hw(QOSGraphObject *obj) -{ - QVirtio9PDevice *v_9p = (QVirtio9PDevice *) obj; - QVirtio9P *v9p = &v_9p->v9p; - - virtio_9p_setup(v9p); -} - -static void *virtio_9p_get_driver(QVirtio9P *v_9p, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-9p")) { - return v_9p; - } - if (!g_strcmp0(interface, "virtio")) { - return v_9p->vdev; - } - - fprintf(stderr, "%s not present in virtio-9p-device\n", interface); - g_assert_not_reached(); -} - -static void *virtio_9p_device_get_driver(void *object, const char *interface) -{ - QVirtio9PDevice *v_9p = object; - return virtio_9p_get_driver(&v_9p->v9p, interface); -} - -static void *virtio_9p_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtio9PDevice *virtio_device = g_new0(QVirtio9PDevice, 1); - QVirtio9P *interface = &virtio_device->v9p; - - interface->vdev = virtio_dev; - alloc = t_alloc; - - virtio_device->obj.destructor = virtio_9p_device_destructor; - virtio_device->obj.get_driver = virtio_9p_device_get_driver; - virtio_device->obj.start_hw = virtio_9p_device_start_hw; - - return &virtio_device->obj; -} - -/* virtio-9p-pci */ -static void virtio_9p_pci_destructor(QOSGraphObject *obj) -{ - QVirtio9PPCI *v9_pci = (QVirtio9PPCI *) obj; - QVirtio9P *interface = &v9_pci->v9p; - QOSGraphObject *pci_vobj = &v9_pci->pci_vdev.obj; - - virtio_9p_cleanup(interface); - qvirtio_pci_destructor(pci_vobj); -} - -static void virtio_9p_pci_start_hw(QOSGraphObject *obj) -{ - QVirtio9PPCI *v9_pci = (QVirtio9PPCI *) obj; - QVirtio9P *interface = &v9_pci->v9p; - QOSGraphObject *pci_vobj = &v9_pci->pci_vdev.obj; - - qvirtio_pci_start_hw(pci_vobj); - virtio_9p_setup(interface); -} - -static void *virtio_9p_pci_get_driver(void *object, const char *interface) -{ - QVirtio9PPCI *v_9p = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_9p->pci_vdev.pdev; - } - return virtio_9p_get_driver(&v_9p->v9p, interface); -} - -static void *virtio_9p_pci_create(void *pci_bus, QGuestAllocator *t_alloc, - void *addr) -{ - QVirtio9PPCI *v9_pci = g_new0(QVirtio9PPCI, 1); - QVirtio9P *interface = &v9_pci->v9p; - QOSGraphObject *obj = &v9_pci->pci_vdev.obj; - - virtio_pci_init(&v9_pci->pci_vdev, pci_bus, addr); - interface->vdev = &v9_pci->pci_vdev.vdev; - alloc = t_alloc; - - g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_9P); - - obj->destructor = virtio_9p_pci_destructor; - obj->start_hw = virtio_9p_pci_start_hw; - obj->get_driver = virtio_9p_pci_get_driver; - - return obj; -} - -static void virtio_9p_register_nodes(void) -{ - const char *str_simple = "fsdev=fsdev0,mount_tag=" MOUNT_TAG; - const char *str_addr = "fsdev=fsdev0,addr=04.0,mount_tag=" MOUNT_TAG; - - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - }; - - QOSGraphEdgeOptions opts = { - .before_cmd_line = "-fsdev synth,id=fsdev0", - }; - - /* virtio-9p-device */ - opts.extra_device_opts = str_simple, - qos_node_create_driver("virtio-9p-device", virtio_9p_device_create); - qos_node_consumes("virtio-9p-device", "virtio-bus", &opts); - qos_node_produces("virtio-9p-device", "virtio"); - qos_node_produces("virtio-9p-device", "virtio-9p"); - - /* virtio-9p-pci */ - opts.extra_device_opts = str_addr; - add_qpci_address(&opts, &addr); - qos_node_create_driver("virtio-9p-pci", virtio_9p_pci_create); - qos_node_consumes("virtio-9p-pci", "pci-bus", &opts); - qos_node_produces("virtio-9p-pci", "pci-device"); - qos_node_produces("virtio-9p-pci", "virtio"); - qos_node_produces("virtio-9p-pci", "virtio-9p"); - -} - -libqos_init(virtio_9p_register_nodes); diff --git a/tests/libqos/virtio-9p.h b/tests/libqos/virtio-9p.h deleted file mode 100644 index b54e89b3a1..0000000000 --- a/tests/libqos/virtio-9p.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_9P_H -#define TESTS_LIBQOS_VIRTIO_9P_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtio9P QVirtio9P; -typedef struct QVirtio9PPCI QVirtio9PPCI; -typedef struct QVirtio9PDevice QVirtio9PDevice; - -#define MOUNT_TAG "qtest" - -struct QVirtio9P { - QVirtioDevice *vdev; - QVirtQueue *vq; -}; - -struct QVirtio9PPCI { - QVirtioPCIDevice pci_vdev; - QVirtio9P v9p; -}; - -struct QVirtio9PDevice { - QOSGraphObject obj; - QVirtio9P v9p; -}; - -#endif diff --git a/tests/libqos/virtio-balloon.c b/tests/libqos/virtio-balloon.c deleted file mode 100644 index 42a4c5831e..0000000000 --- a/tests/libqos/virtio-balloon.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-balloon.h" - -/* virtio-balloon-device */ -static void *qvirtio_balloon_get_driver(QVirtioBalloon *v_balloon, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-balloon")) { - return v_balloon; - } - if (!g_strcmp0(interface, "virtio")) { - return v_balloon->vdev; - } - - fprintf(stderr, "%s not present in virtio-balloon-device\n", interface); - g_assert_not_reached(); -} - -static void *qvirtio_balloon_device_get_driver(void *object, - const char *interface) -{ - QVirtioBalloonDevice *v_balloon = object; - return qvirtio_balloon_get_driver(&v_balloon->balloon, interface); -} - -static void *virtio_balloon_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioBalloonDevice *virtio_bdevice = g_new0(QVirtioBalloonDevice, 1); - QVirtioBalloon *interface = &virtio_bdevice->balloon; - - interface->vdev = virtio_dev; - - virtio_bdevice->obj.get_driver = qvirtio_balloon_device_get_driver; - - return &virtio_bdevice->obj; -} - -/* virtio-balloon-pci */ -static void *qvirtio_balloon_pci_get_driver(void *object, - const char *interface) -{ - QVirtioBalloonPCI *v_balloon = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_balloon->pci_vdev.pdev; - } - return qvirtio_balloon_get_driver(&v_balloon->balloon, interface); -} - -static void *virtio_balloon_pci_create(void *pci_bus, QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioBalloonPCI *virtio_bpci = g_new0(QVirtioBalloonPCI, 1); - QVirtioBalloon *interface = &virtio_bpci->balloon; - QOSGraphObject *obj = &virtio_bpci->pci_vdev.obj; - - - virtio_pci_init(&virtio_bpci->pci_vdev, pci_bus, addr); - interface->vdev = &virtio_bpci->pci_vdev.vdev; - - obj->get_driver = qvirtio_balloon_pci_get_driver; - - return obj; -} - -static void virtio_balloon_register_nodes(void) -{ - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - }; - - QOSGraphEdgeOptions opts = { - .extra_device_opts = "addr=04.0", - }; - - /* virtio-balloon-device */ - qos_node_create_driver("virtio-balloon-device", - virtio_balloon_device_create); - qos_node_consumes("virtio-balloon-device", "virtio-bus", NULL); - qos_node_produces("virtio-balloon-device", "virtio"); - qos_node_produces("virtio-balloon-device", "virtio-balloon"); - - /* virtio-balloon-pci */ - add_qpci_address(&opts, &addr); - qos_node_create_driver("virtio-balloon-pci", virtio_balloon_pci_create); - qos_node_consumes("virtio-balloon-pci", "pci-bus", &opts); - qos_node_produces("virtio-balloon-pci", "pci-device"); - qos_node_produces("virtio-balloon-pci", "virtio"); - qos_node_produces("virtio-balloon-pci", "virtio-balloon"); -} - -libqos_init(virtio_balloon_register_nodes); diff --git a/tests/libqos/virtio-balloon.h b/tests/libqos/virtio-balloon.h deleted file mode 100644 index 52661cc87d..0000000000 --- a/tests/libqos/virtio-balloon.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_BALLOON_H -#define TESTS_LIBQOS_VIRTIO_BALLOON_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtioBalloon QVirtioBalloon; -typedef struct QVirtioBalloonPCI QVirtioBalloonPCI; -typedef struct QVirtioBalloonDevice QVirtioBalloonDevice; - -struct QVirtioBalloon { - QVirtioDevice *vdev; -}; - -struct QVirtioBalloonPCI { - QVirtioPCIDevice pci_vdev; - QVirtioBalloon balloon; -}; - -struct QVirtioBalloonDevice { - QOSGraphObject obj; - QVirtioBalloon balloon; -}; - -#endif diff --git a/tests/libqos/virtio-blk.c b/tests/libqos/virtio-blk.c deleted file mode 100644 index 726e93c5c1..0000000000 --- a/tests/libqos/virtio-blk.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "standard-headers/linux/virtio_blk.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-blk.h" - -#define PCI_SLOT 0x04 -#define PCI_FN 0x00 - -/* virtio-blk-device */ -static void *qvirtio_blk_get_driver(QVirtioBlk *v_blk, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-blk")) { - return v_blk; - } - if (!g_strcmp0(interface, "virtio")) { - return v_blk->vdev; - } - - fprintf(stderr, "%s not present in virtio-blk-device\n", interface); - g_assert_not_reached(); -} - -static void *qvirtio_blk_device_get_driver(void *object, - const char *interface) -{ - QVirtioBlkDevice *v_blk = object; - return qvirtio_blk_get_driver(&v_blk->blk, interface); -} - -static void *virtio_blk_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioBlkDevice *virtio_blk = g_new0(QVirtioBlkDevice, 1); - QVirtioBlk *interface = &virtio_blk->blk; - - interface->vdev = virtio_dev; - - virtio_blk->obj.get_driver = qvirtio_blk_device_get_driver; - - return &virtio_blk->obj; -} - -/* virtio-blk-pci */ -static void *qvirtio_blk_pci_get_driver(void *object, const char *interface) -{ - QVirtioBlkPCI *v_blk = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_blk->pci_vdev.pdev; - } - return qvirtio_blk_get_driver(&v_blk->blk, interface); -} - -static void *virtio_blk_pci_create(void *pci_bus, QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioBlkPCI *virtio_blk = g_new0(QVirtioBlkPCI, 1); - QVirtioBlk *interface = &virtio_blk->blk; - QOSGraphObject *obj = &virtio_blk->pci_vdev.obj; - - virtio_pci_init(&virtio_blk->pci_vdev, pci_bus, addr); - interface->vdev = &virtio_blk->pci_vdev.vdev; - - g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_BLOCK); - - obj->get_driver = qvirtio_blk_pci_get_driver; - - return obj; -} - -static void virtio_blk_register_nodes(void) -{ - /* FIXME: every test using these two nodes needs to setup a - * -drive,id=drive0 otherwise QEMU is not going to start. - * Therefore, we do not include "produces" edge for virtio - * and pci-device yet. - */ - - char *arg = g_strdup_printf("id=drv0,drive=drive0,addr=%x.%x", - PCI_SLOT, PCI_FN); - - QPCIAddress addr = { - .devfn = QPCI_DEVFN(PCI_SLOT, PCI_FN), - }; - - QOSGraphEdgeOptions opts = { }; - - /* virtio-blk-device */ - opts.extra_device_opts = "drive=drive0"; - qos_node_create_driver("virtio-blk-device", virtio_blk_device_create); - qos_node_consumes("virtio-blk-device", "virtio-bus", &opts); - qos_node_produces("virtio-blk-device", "virtio-blk"); - - /* virtio-blk-pci */ - opts.extra_device_opts = arg; - add_qpci_address(&opts, &addr); - qos_node_create_driver("virtio-blk-pci", virtio_blk_pci_create); - qos_node_consumes("virtio-blk-pci", "pci-bus", &opts); - qos_node_produces("virtio-blk-pci", "virtio-blk"); - - g_free(arg); -} - -libqos_init(virtio_blk_register_nodes); diff --git a/tests/libqos/virtio-blk.h b/tests/libqos/virtio-blk.h deleted file mode 100644 index c05adc659d..0000000000 --- a/tests/libqos/virtio-blk.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_BLK_H -#define TESTS_LIBQOS_VIRTIO_BLK_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtioBlk QVirtioBlk; -typedef struct QVirtioBlkPCI QVirtioBlkPCI; -typedef struct QVirtioBlkDevice QVirtioBlkDevice; - -/* virtqueue is created in each test */ -struct QVirtioBlk { - QVirtioDevice *vdev; -}; - -struct QVirtioBlkPCI { - QVirtioPCIDevice pci_vdev; - QVirtioBlk blk; -}; - -struct QVirtioBlkDevice { - QOSGraphObject obj; - QVirtioBlk blk; -}; - -#endif diff --git a/tests/libqos/virtio-mmio.c b/tests/libqos/virtio-mmio.c deleted file mode 100644 index e0a2bd7bc6..0000000000 --- a/tests/libqos/virtio-mmio.c +++ /dev/null @@ -1,266 +0,0 @@ -/* - * libqos virtio MMIO driver - * - * 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.h" -#include "qemu/module.h" -#include "libqos/virtio.h" -#include "libqos/virtio-mmio.h" -#include "libqos/malloc.h" -#include "libqos/qgraph.h" -#include "standard-headers/linux/virtio_ring.h" - -static uint8_t qvirtio_mmio_config_readb(QVirtioDevice *d, uint64_t off) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return qtest_readb(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); -} - -static uint16_t qvirtio_mmio_config_readw(QVirtioDevice *d, uint64_t off) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return qtest_readw(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); -} - -static uint32_t qvirtio_mmio_config_readl(QVirtioDevice *d, uint64_t off) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); -} - -static uint64_t qvirtio_mmio_config_readq(QVirtioDevice *d, uint64_t off) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return qtest_readq(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); -} - -static uint64_t qvirtio_mmio_get_features(QVirtioDevice *d) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - uint64_t lo; - uint64_t hi = 0; - - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES_SEL, 0); - lo = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES); - - if (dev->version >= 2) { - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES_SEL, 1); - hi = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES); - } - - return (hi << 32) | lo; -} - -static void qvirtio_mmio_set_features(QVirtioDevice *d, uint64_t features) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - dev->features = features; - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES_SEL, 0); - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES, features); - - if (dev->version >= 2) { - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES_SEL, 1); - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES, - features >> 32); - } -} - -static uint64_t qvirtio_mmio_get_guest_features(QVirtioDevice *d) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return dev->features; -} - -static uint8_t qvirtio_mmio_get_status(QVirtioDevice *d) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return (uint8_t)qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_STATUS); -} - -static void qvirtio_mmio_set_status(QVirtioDevice *d, uint8_t status) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_STATUS, (uint32_t)status); -} - -static bool qvirtio_mmio_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - uint32_t isr; - - isr = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 1; - if (isr != 0) { - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 1); - return true; - } - - return false; -} - -static bool qvirtio_mmio_get_config_isr_status(QVirtioDevice *d) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - uint32_t isr; - - isr = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 2; - if (isr != 0) { - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 2); - return true; - } - - return false; -} - -static void qvirtio_mmio_wait_config_isr_status(QVirtioDevice *d, - gint64 timeout_us) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - gint64 start_time = g_get_monotonic_time(); - - do { - g_assert(g_get_monotonic_time() - start_time <= timeout_us); - qtest_clock_step(dev->qts, 100); - } while (!qvirtio_mmio_get_config_isr_status(d)); -} - -static void qvirtio_mmio_queue_select(QVirtioDevice *d, uint16_t index) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_SEL, (uint32_t)index); - - g_assert_cmphex(qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_PFN), ==, 0); -} - -static uint16_t qvirtio_mmio_get_queue_size(QVirtioDevice *d) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - return (uint16_t)qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NUM_MAX); -} - -static void qvirtio_mmio_set_queue_address(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - uint64_t pfn = vq->desc / dev->page_size; - - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_PFN, pfn); -} - -static QVirtQueue *qvirtio_mmio_virtqueue_setup(QVirtioDevice *d, - QGuestAllocator *alloc, uint16_t index) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - QVirtQueue *vq; - uint64_t addr; - - vq = g_malloc0(sizeof(*vq)); - vq->vdev = d; - qvirtio_mmio_queue_select(d, index); - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_ALIGN, dev->page_size); - - vq->index = index; - vq->size = qvirtio_mmio_get_queue_size(d); - vq->free_head = 0; - vq->num_free = vq->size; - vq->align = dev->page_size; - vq->indirect = dev->features & (1ull << VIRTIO_RING_F_INDIRECT_DESC); - vq->event = dev->features & (1ull << VIRTIO_RING_F_EVENT_IDX); - - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NUM, vq->size); - - /* Check different than 0 */ - g_assert_cmpint(vq->size, !=, 0); - - /* Check power of 2 */ - g_assert_cmpint(vq->size & (vq->size - 1), ==, 0); - - addr = guest_alloc(alloc, qvring_size(vq->size, dev->page_size)); - qvring_init(dev->qts, alloc, vq, addr); - qvirtio_mmio_set_queue_address(d, vq); - - return vq; -} - -static void qvirtio_mmio_virtqueue_cleanup(QVirtQueue *vq, - QGuestAllocator *alloc) -{ - guest_free(alloc, vq->desc); - g_free(vq); -} - -static void qvirtio_mmio_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); - qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NOTIFY, vq->index); -} - -const QVirtioBus qvirtio_mmio = { - .config_readb = qvirtio_mmio_config_readb, - .config_readw = qvirtio_mmio_config_readw, - .config_readl = qvirtio_mmio_config_readl, - .config_readq = qvirtio_mmio_config_readq, - .get_features = qvirtio_mmio_get_features, - .set_features = qvirtio_mmio_set_features, - .get_guest_features = qvirtio_mmio_get_guest_features, - .get_status = qvirtio_mmio_get_status, - .set_status = qvirtio_mmio_set_status, - .get_queue_isr_status = qvirtio_mmio_get_queue_isr_status, - .wait_config_isr_status = qvirtio_mmio_wait_config_isr_status, - .queue_select = qvirtio_mmio_queue_select, - .get_queue_size = qvirtio_mmio_get_queue_size, - .set_queue_address = qvirtio_mmio_set_queue_address, - .virtqueue_setup = qvirtio_mmio_virtqueue_setup, - .virtqueue_cleanup = qvirtio_mmio_virtqueue_cleanup, - .virtqueue_kick = qvirtio_mmio_virtqueue_kick, -}; - -static void *qvirtio_mmio_get_driver(void *obj, const char *interface) -{ - QVirtioMMIODevice *virtio_mmio = obj; - if (!g_strcmp0(interface, "virtio-bus")) { - return &virtio_mmio->vdev; - } - fprintf(stderr, "%s not present in virtio-mmio\n", interface); - g_assert_not_reached(); -} - -static void qvirtio_mmio_start_hw(QOSGraphObject *obj) -{ - QVirtioMMIODevice *dev = (QVirtioMMIODevice *) obj; - qvirtio_start_device(&dev->vdev); -} - -void qvirtio_mmio_init_device(QVirtioMMIODevice *dev, QTestState *qts, - uint64_t addr, uint32_t page_size) -{ - uint32_t magic; - magic = qtest_readl(qts, addr + QVIRTIO_MMIO_MAGIC_VALUE); - g_assert(magic == ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)); - - dev->version = qtest_readl(qts, addr + QVIRTIO_MMIO_VERSION); - g_assert(dev->version == 1 || dev->version == 2); - - dev->qts = qts; - dev->addr = addr; - dev->page_size = page_size; - dev->vdev.device_type = qtest_readl(qts, addr + QVIRTIO_MMIO_DEVICE_ID); - dev->vdev.bus = &qvirtio_mmio; - - qtest_writel(qts, addr + QVIRTIO_MMIO_GUEST_PAGE_SIZE, page_size); - - dev->obj.get_driver = qvirtio_mmio_get_driver; - dev->obj.start_hw = qvirtio_mmio_start_hw; -} - -static void virtio_mmio_register_nodes(void) -{ - qos_node_create_driver("virtio-mmio", NULL); - qos_node_produces("virtio-mmio", "virtio-bus"); -} - -libqos_init(virtio_mmio_register_nodes); diff --git a/tests/libqos/virtio-mmio.h b/tests/libqos/virtio-mmio.h deleted file mode 100644 index 0e45778b07..0000000000 --- a/tests/libqos/virtio-mmio.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * libqos virtio MMIO definitions - * - * 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. - */ - -#ifndef LIBQOS_VIRTIO_MMIO_H -#define LIBQOS_VIRTIO_MMIO_H - -#include "libqos/virtio.h" -#include "libqos/qgraph.h" - -#define QVIRTIO_MMIO_MAGIC_VALUE 0x000 -#define QVIRTIO_MMIO_VERSION 0x004 -#define QVIRTIO_MMIO_DEVICE_ID 0x008 -#define QVIRTIO_MMIO_VENDOR_ID 0x00C -#define QVIRTIO_MMIO_HOST_FEATURES 0x010 -#define QVIRTIO_MMIO_HOST_FEATURES_SEL 0x014 -#define QVIRTIO_MMIO_GUEST_FEATURES 0x020 -#define QVIRTIO_MMIO_GUEST_FEATURES_SEL 0x024 -#define QVIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 -#define QVIRTIO_MMIO_QUEUE_SEL 0x030 -#define QVIRTIO_MMIO_QUEUE_NUM_MAX 0x034 -#define QVIRTIO_MMIO_QUEUE_NUM 0x038 -#define QVIRTIO_MMIO_QUEUE_ALIGN 0x03C -#define QVIRTIO_MMIO_QUEUE_PFN 0x040 -#define QVIRTIO_MMIO_QUEUE_NOTIFY 0x050 -#define QVIRTIO_MMIO_INTERRUPT_STATUS 0x060 -#define QVIRTIO_MMIO_INTERRUPT_ACK 0x064 -#define QVIRTIO_MMIO_DEVICE_STATUS 0x070 -#define QVIRTIO_MMIO_DEVICE_SPECIFIC 0x100 - -typedef struct QVirtioMMIODevice { - QOSGraphObject obj; - QVirtioDevice vdev; - QTestState *qts; - uint64_t addr; - uint32_t page_size; - uint32_t features; /* As it cannot be read later, save it */ - uint32_t version; -} QVirtioMMIODevice; - -extern const QVirtioBus qvirtio_mmio; - -void qvirtio_mmio_init_device(QVirtioMMIODevice *dev, QTestState *qts, - uint64_t addr, uint32_t page_size); - -#endif diff --git a/tests/libqos/virtio-net.c b/tests/libqos/virtio-net.c deleted file mode 100644 index 710d440c3d..0000000000 --- a/tests/libqos/virtio-net.c +++ /dev/null @@ -1,197 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-net.h" -#include "hw/virtio/virtio-net.h" - - -static QGuestAllocator *alloc; - -static void virtio_net_cleanup(QVirtioNet *interface) -{ - int i; - - for (i = 0; i < interface->n_queues; i++) { - qvirtqueue_cleanup(interface->vdev->bus, interface->queues[i], alloc); - } - g_free(interface->queues); -} - -static void virtio_net_setup(QVirtioNet *interface) -{ - QVirtioDevice *vdev = interface->vdev; - uint64_t features; - int i; - - features = qvirtio_get_features(vdev); - features &= ~(QVIRTIO_F_BAD_FEATURE | - (1ull << VIRTIO_RING_F_INDIRECT_DESC) | - (1ull << VIRTIO_RING_F_EVENT_IDX)); - qvirtio_set_features(vdev, features); - - if (features & (1ull << VIRTIO_NET_F_MQ)) { - interface->n_queues = qvirtio_config_readw(vdev, 8) * 2; - } else { - interface->n_queues = 2; - } - interface->n_queues++; /* Account for the ctrl queue */ - - interface->queues = g_new(QVirtQueue *, interface->n_queues); - for (i = 0; i < interface->n_queues; i++) { - interface->queues[i] = qvirtqueue_setup(vdev, alloc, i); - } - qvirtio_set_driver_ok(vdev); -} - -/* virtio-net-device */ -static void qvirtio_net_device_destructor(QOSGraphObject *obj) -{ - QVirtioNetDevice *v_net = (QVirtioNetDevice *) obj; - virtio_net_cleanup(&v_net->net); -} - -static void qvirtio_net_device_start_hw(QOSGraphObject *obj) -{ - QVirtioNetDevice *v_net = (QVirtioNetDevice *) obj; - QVirtioNet *interface = &v_net->net; - - virtio_net_setup(interface); -} - -static void *qvirtio_net_get_driver(QVirtioNet *v_net, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-net")) { - return v_net; - } - if (!g_strcmp0(interface, "virtio")) { - return v_net->vdev; - } - - fprintf(stderr, "%s not present in virtio-net-device\n", interface); - g_assert_not_reached(); -} - -static void *qvirtio_net_device_get_driver(void *object, - const char *interface) -{ - QVirtioNetDevice *v_net = object; - return qvirtio_net_get_driver(&v_net->net, interface); -} - -static void *virtio_net_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioNetDevice *virtio_ndevice = g_new0(QVirtioNetDevice, 1); - QVirtioNet *interface = &virtio_ndevice->net; - - interface->vdev = virtio_dev; - alloc = t_alloc; - - virtio_ndevice->obj.destructor = qvirtio_net_device_destructor; - virtio_ndevice->obj.get_driver = qvirtio_net_device_get_driver; - virtio_ndevice->obj.start_hw = qvirtio_net_device_start_hw; - - return &virtio_ndevice->obj; -} - -/* virtio-net-pci */ -static void qvirtio_net_pci_destructor(QOSGraphObject *obj) -{ - QVirtioNetPCI *v_net = (QVirtioNetPCI *) obj; - QVirtioNet *interface = &v_net->net; - QOSGraphObject *pci_vobj = &v_net->pci_vdev.obj; - - virtio_net_cleanup(interface); - qvirtio_pci_destructor(pci_vobj); -} - -static void qvirtio_net_pci_start_hw(QOSGraphObject *obj) -{ - QVirtioNetPCI *v_net = (QVirtioNetPCI *) obj; - QVirtioNet *interface = &v_net->net; - QOSGraphObject *pci_vobj = &v_net->pci_vdev.obj; - - qvirtio_pci_start_hw(pci_vobj); - virtio_net_setup(interface); -} - -static void *qvirtio_net_pci_get_driver(void *object, - const char *interface) -{ - QVirtioNetPCI *v_net = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_net->pci_vdev.pdev; - } - return qvirtio_net_get_driver(&v_net->net, interface); -} - -static void *virtio_net_pci_create(void *pci_bus, QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioNetPCI *virtio_bpci = g_new0(QVirtioNetPCI, 1); - QVirtioNet *interface = &virtio_bpci->net; - QOSGraphObject *obj = &virtio_bpci->pci_vdev.obj; - - virtio_pci_init(&virtio_bpci->pci_vdev, pci_bus, addr); - interface->vdev = &virtio_bpci->pci_vdev.vdev; - alloc = t_alloc; - - g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_NET); - - obj->destructor = qvirtio_net_pci_destructor; - obj->start_hw = qvirtio_net_pci_start_hw; - obj->get_driver = qvirtio_net_pci_get_driver; - - return obj; -} - -static void virtio_net_register_nodes(void) -{ - /* FIXME: every test using these nodes needs to setup a - * -netdev socket,id=hs0 otherwise QEMU is not going to start. - * Therefore, we do not include "produces" edge for virtio - * and pci-device yet. - */ - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - }; - - QOSGraphEdgeOptions opts = { }; - - /* virtio-net-device */ - opts.extra_device_opts = "netdev=hs0"; - qos_node_create_driver("virtio-net-device", - virtio_net_device_create); - qos_node_consumes("virtio-net-device", "virtio-bus", &opts); - qos_node_produces("virtio-net-device", "virtio-net"); - - /* virtio-net-pci */ - opts.extra_device_opts = "netdev=hs0,addr=04.0"; - add_qpci_address(&opts, &addr); - qos_node_create_driver("virtio-net-pci", virtio_net_pci_create); - qos_node_consumes("virtio-net-pci", "pci-bus", &opts); - qos_node_produces("virtio-net-pci", "virtio-net"); -} - -libqos_init(virtio_net_register_nodes); diff --git a/tests/libqos/virtio-net.h b/tests/libqos/virtio-net.h deleted file mode 100644 index 855c67d00f..0000000000 --- a/tests/libqos/virtio-net.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_NET_H -#define TESTS_LIBQOS_VIRTIO_NET_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtioNet QVirtioNet; -typedef struct QVirtioNetPCI QVirtioNetPCI; -typedef struct QVirtioNetDevice QVirtioNetDevice; - -struct QVirtioNet { - QVirtioDevice *vdev; - int n_queues; /* total number of virtqueues (rx, tx, ctrl) */ - QVirtQueue **queues; -}; - -struct QVirtioNetPCI { - QVirtioPCIDevice pci_vdev; - QVirtioNet net; -}; - -struct QVirtioNetDevice { - QOSGraphObject obj; - QVirtioNet net; -}; - -#endif diff --git a/tests/libqos/virtio-pci-modern.c b/tests/libqos/virtio-pci-modern.c deleted file mode 100644 index 18d118866f..0000000000 --- a/tests/libqos/virtio-pci-modern.c +++ /dev/null @@ -1,443 +0,0 @@ -/* - * libqos VIRTIO 1.0 PCI driver - * - * Copyright (c) 2019 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 "standard-headers/linux/pci_regs.h" -#include "standard-headers/linux/virtio_pci.h" -#include "standard-headers/linux/virtio_config.h" -#include "virtio-pci-modern.h" - -static uint8_t config_readb(QVirtioDevice *d, uint64_t addr) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readb(dev->pdev, dev->bar, dev->device_cfg_offset + addr); -} - -static uint16_t config_readw(QVirtioDevice *d, uint64_t addr) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readw(dev->pdev, dev->bar, dev->device_cfg_offset + addr); -} - -static uint32_t config_readl(QVirtioDevice *d, uint64_t addr) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readl(dev->pdev, dev->bar, dev->device_cfg_offset + addr); -} - -static uint64_t config_readq(QVirtioDevice *d, uint64_t addr) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readq(dev->pdev, dev->bar, dev->device_cfg_offset + addr); -} - -static uint64_t get_features(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint64_t lo, hi; - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - device_feature_select), - 0); - lo = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, device_feature)); - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - device_feature_select), - 1); - hi = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, device_feature)); - - return (hi << 32) | lo; -} - -static void set_features(QVirtioDevice *d, uint64_t features) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - /* Drivers must enable VIRTIO 1.0 or else use the Legacy interface */ - g_assert_cmphex(features & (1ull << VIRTIO_F_VERSION_1), !=, 0); - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - guest_feature_select), - 0); - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - guest_feature), - features); - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - guest_feature_select), - 1); - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - guest_feature), - features >> 32); -} - -static uint64_t get_guest_features(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint64_t lo, hi; - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - guest_feature_select), - 0); - lo = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, guest_feature)); - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - guest_feature_select), - 1); - hi = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, guest_feature)); - - return (hi << 32) | lo; -} - -static uint8_t get_status(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - return qpci_io_readb(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - device_status)); -} - -static void set_status(QVirtioDevice *d, uint8_t status) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - return qpci_io_writeb(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - device_status), - status); -} - -static bool get_msix_status(QVirtioPCIDevice *dev, uint32_t msix_entry, - uint32_t msix_addr, uint32_t msix_data) -{ - uint32_t data; - - g_assert_cmpint(msix_entry, !=, -1); - if (qpci_msix_masked(dev->pdev, msix_entry)) { - /* No ISR checking should be done if masked, but read anyway */ - return qpci_msix_pending(dev->pdev, msix_entry); - } - - data = qtest_readl(dev->pdev->bus->qts, msix_addr); - if (data == msix_data) { - qtest_writel(dev->pdev->bus->qts, msix_addr, 0); - return true; - } else { - return false; - } -} - -static bool get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - if (dev->pdev->msix_enabled) { - QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); - - return get_msix_status(dev, vqpci->msix_entry, vqpci->msix_addr, - vqpci->msix_data); - } - - return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 1; -} - -static bool get_config_isr_status(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - if (dev->pdev->msix_enabled) { - return get_msix_status(dev, dev->config_msix_entry, - dev->config_msix_addr, dev->config_msix_data); - } - - return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 2; -} - -static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - gint64 start_time = g_get_monotonic_time(); - - do { - g_assert(g_get_monotonic_time() - start_time <= timeout_us); - qtest_clock_step(dev->pdev->bus->qts, 100); - } while (!get_config_isr_status(d)); -} - -static void queue_select(QVirtioDevice *d, uint16_t index) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_select), - index); -} - -static uint16_t get_queue_size(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - return qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_size)); -} - -static void set_queue_address(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_desc_lo), - vq->desc); - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_desc_hi), - vq->desc >> 32); - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_avail_lo), - vq->avail); - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_avail_hi), - vq->avail >> 32); - - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_used_lo), - vq->used); - qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_used_hi), - vq->used >> 32); -} - -static QVirtQueue *virtqueue_setup(QVirtioDevice *d, QGuestAllocator *alloc, - uint16_t index) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - QVirtQueue *vq; - QVirtQueuePCI *vqpci; - uint16_t notify_off; - - vq = qvirtio_pci_virtqueue_setup_common(d, alloc, index); - vqpci = container_of(vq, QVirtQueuePCI, vq); - - notify_off = qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - queue_notify_off)); - - vqpci->notify_offset = dev->notify_cfg_offset + - notify_off * dev->notify_off_multiplier; - - qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_enable), 1); - - return vq; -} - -static void virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); - - qpci_io_writew(dev->pdev, dev->bar, vqpci->notify_offset, vq->index); -} - -static const QVirtioBus qvirtio_pci_virtio_1 = { - .config_readb = config_readb, - .config_readw = config_readw, - .config_readl = config_readl, - .config_readq = config_readq, - .get_features = get_features, - .set_features = set_features, - .get_guest_features = get_guest_features, - .get_status = get_status, - .set_status = set_status, - .get_queue_isr_status = get_queue_isr_status, - .wait_config_isr_status = wait_config_isr_status, - .queue_select = queue_select, - .get_queue_size = get_queue_size, - .set_queue_address = set_queue_address, - .virtqueue_setup = virtqueue_setup, - .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, - .virtqueue_kick = virtqueue_kick, -}; - -static void set_config_vector(QVirtioPCIDevice *d, uint16_t entry) -{ - uint16_t vector; - - qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, msix_config), entry); - vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - msix_config)); - g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); -} - -static void set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, - uint16_t entry) -{ - uint16_t vector; - - queue_select(&d->vdev, vq_idx); - qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, queue_msix_vector), - entry); - vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + - offsetof(struct virtio_pci_common_cfg, - queue_msix_vector)); - g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); -} - -static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_virtio_1 = { - .set_config_vector = set_config_vector, - .set_queue_vector = set_queue_vector, -}; - -static bool probe_device_type(QVirtioPCIDevice *dev) -{ - uint16_t vendor_id; - uint16_t device_id; - - /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ - vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); - if (vendor_id != 0x1af4) { - return false; - } - - /* - * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive - * is a virtio device" - */ - device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); - if (device_id < 0x1000 || device_id > 0x107f) { - return false; - } - - /* - * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to - * 0x103F depending on the device type" - */ - if (device_id < 0x1040) { - /* - * "Transitional devices MUST have the PCI Subsystem Device ID matching - * the Virtio Device ID" - */ - dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); - } else { - /* - * "The PCI Device ID is calculated by adding 0x1040 to the Virtio - * Device ID" - */ - dev->vdev.device_type = device_id - 0x1040; - } - - return true; -} - -/* Find the first VIRTIO 1.0 PCI structure for a given type */ -static bool find_structure(QVirtioPCIDevice *dev, uint8_t cfg_type, - uint8_t *bar, uint32_t *offset, uint32_t *length, - uint8_t *cfg_addr) -{ - uint8_t addr = 0; - - while ((addr = qpci_find_capability(dev->pdev, PCI_CAP_ID_VNDR, - addr)) != 0) { - uint8_t type; - - type = qpci_config_readb(dev->pdev, - addr + offsetof(struct virtio_pci_cap, cfg_type)); - if (type != cfg_type) { - continue; - } - - *bar = qpci_config_readb(dev->pdev, - addr + offsetof(struct virtio_pci_cap, bar)); - *offset = qpci_config_readl(dev->pdev, - addr + offsetof(struct virtio_pci_cap, offset)); - *length = qpci_config_readl(dev->pdev, - addr + offsetof(struct virtio_pci_cap, length)); - if (cfg_addr) { - *cfg_addr = addr; - } - - return true; - } - - return false; -} - -static bool probe_device_layout(QVirtioPCIDevice *dev) -{ - uint8_t bar; - uint8_t cfg_addr; - uint32_t length; - - /* - * Due to the qpci_iomap() API we only support devices that put all - * structures in the same PCI BAR. Luckily this is true with QEMU. - */ - - if (!find_structure(dev, VIRTIO_PCI_CAP_COMMON_CFG, &bar, - &dev->common_cfg_offset, &length, NULL)) { - return false; - } - dev->bar_idx = bar; - - if (!find_structure(dev, VIRTIO_PCI_CAP_NOTIFY_CFG, &bar, - &dev->notify_cfg_offset, &length, &cfg_addr)) { - return false; - } - g_assert_cmphex(bar, ==, dev->bar_idx); - - dev->notify_off_multiplier = qpci_config_readl(dev->pdev, - cfg_addr + offsetof(struct virtio_pci_notify_cap, - notify_off_multiplier)); - - if (!find_structure(dev, VIRTIO_PCI_CAP_ISR_CFG, &bar, - &dev->isr_cfg_offset, &length, NULL)) { - return false; - } - g_assert_cmphex(bar, ==, dev->bar_idx); - - if (!find_structure(dev, VIRTIO_PCI_CAP_DEVICE_CFG, &bar, - &dev->device_cfg_offset, &length, NULL)) { - return false; - } - g_assert_cmphex(bar, ==, dev->bar_idx); - - return true; -} - -/* Probe a VIRTIO 1.0 device */ -bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev) -{ - if (!probe_device_type(dev)) { - return false; - } - - if (!probe_device_layout(dev)) { - return false; - } - - dev->vdev.bus = &qvirtio_pci_virtio_1; - dev->msix_ops = &qvirtio_pci_msix_ops_virtio_1; - dev->vdev.big_endian = false; - return true; -} diff --git a/tests/libqos/virtio-pci-modern.h b/tests/libqos/virtio-pci-modern.h deleted file mode 100644 index 6bf2b207c3..0000000000 --- a/tests/libqos/virtio-pci-modern.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * libqos virtio PCI VIRTIO 1.0 definitions - * - * Copyright (c) 2019 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. - */ - -#ifndef LIBQOS_VIRTIO_PCI_MODERN_H -#define LIBQOS_VIRTIO_PCI_MODERN_H - -#include "virtio-pci.h" - -bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev); - -#endif /* LIBQOS_VIRTIO_PCI_MODERN_H */ diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c deleted file mode 100644 index 62851c29bb..0000000000 --- a/tests/libqos/virtio-pci.c +++ /dev/null @@ -1,435 +0,0 @@ -/* - * libqos virtio PCI driver - * - * 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.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" -#include "libqos/pci.h" -#include "libqos/pci-pc.h" -#include "libqos/malloc.h" -#include "libqos/malloc-pc.h" -#include "libqos/qgraph.h" -#include "standard-headers/linux/virtio_ring.h" -#include "standard-headers/linux/virtio_pci.h" - -#include "hw/pci/pci.h" -#include "hw/pci/pci_regs.h" - -#include "virtio-pci-modern.h" - -/* virtio-pci is a superclass of all virtio-xxx-pci devices; - * the relation between virtio-pci and virtio-xxx-pci is implicit, - * and therefore virtio-pci does not produce virtio and is not - * reached by any edge, not even as a "contains" edge. - * In facts, every device is a QVirtioPCIDevice with - * additional fields, since every one has its own - * number of queues and various attributes. - * Virtio-pci provides default functions to start the - * hw and destroy the object, and nodes that want to - * override them should always remember to call the - * original qvirtio_pci_destructor and qvirtio_pci_start_hw. - */ - -#define CONFIG_BASE(dev) (VIRTIO_PCI_CONFIG_OFF((dev)->pdev->msix_enabled)) - -static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, uint64_t off) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readb(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); -} - -/* PCI is always read in little-endian order - * but virtio ( < 1.0) is in guest order - * so with a big-endian guest the order has been reversed, - * reverse it again - * virtio-1.0 is always little-endian, like PCI - */ - -static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, uint64_t off) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint16_t value; - - value = qpci_io_readw(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); - if (qvirtio_is_big_endian(d)) { - value = bswap16(value); - } - return value; -} - -static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, uint64_t off) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint32_t value; - - value = qpci_io_readl(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); - if (qvirtio_is_big_endian(d)) { - value = bswap32(value); - } - return value; -} - -static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, uint64_t off) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint64_t val; - - val = qpci_io_readq(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); - if (qvirtio_is_big_endian(d)) { - val = bswap64(val); - } - - return val; -} - -static uint64_t qvirtio_pci_get_features(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readl(dev->pdev, dev->bar, VIRTIO_PCI_HOST_FEATURES); -} - -static void qvirtio_pci_set_features(QVirtioDevice *d, uint64_t features) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES, features); -} - -static uint64_t qvirtio_pci_get_guest_features(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readl(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES); -} - -static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_STATUS); -} - -static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - qpci_io_writeb(dev->pdev, dev->bar, VIRTIO_PCI_STATUS, status); -} - -static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - QVirtQueuePCI *vqpci = (QVirtQueuePCI *)vq; - uint32_t data; - - if (dev->pdev->msix_enabled) { - g_assert_cmpint(vqpci->msix_entry, !=, -1); - if (qpci_msix_masked(dev->pdev, vqpci->msix_entry)) { - /* No ISR checking should be done if masked, but read anyway */ - return qpci_msix_pending(dev->pdev, vqpci->msix_entry); - } else { - data = qtest_readl(dev->pdev->bus->qts, vqpci->msix_addr); - if (data == vqpci->msix_data) { - qtest_writel(dev->pdev->bus->qts, vqpci->msix_addr, 0); - return true; - } else { - return false; - } - } - } else { - return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_ISR) & 1; - } -} - -static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint32_t data; - - if (dev->pdev->msix_enabled) { - g_assert_cmpint(dev->config_msix_entry, !=, -1); - if (qpci_msix_masked(dev->pdev, dev->config_msix_entry)) { - /* No ISR checking should be done if masked, but read anyway */ - return qpci_msix_pending(dev->pdev, dev->config_msix_entry); - } else { - data = qtest_readl(dev->pdev->bus->qts, dev->config_msix_addr); - if (data == dev->config_msix_data) { - qtest_writel(dev->pdev->bus->qts, dev->config_msix_addr, 0); - return true; - } else { - return false; - } - } - } else { - return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_ISR) & 2; - } -} - -static void qvirtio_pci_wait_config_isr_status(QVirtioDevice *d, - gint64 timeout_us) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - gint64 start_time = g_get_monotonic_time(); - - do { - g_assert(g_get_monotonic_time() - start_time <= timeout_us); - qtest_clock_step(dev->pdev->bus->qts, 100); - } while (!qvirtio_pci_get_config_isr_status(d)); -} - -static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - qpci_io_writeb(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_SEL, index); -} - -static uint16_t qvirtio_pci_get_queue_size(QVirtioDevice *d) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - return qpci_io_readw(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NUM); -} - -static void qvirtio_pci_set_queue_address(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - uint64_t pfn = vq->desc / VIRTIO_PCI_VRING_ALIGN; - - qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_PFN, pfn); -} - -QVirtQueue *qvirtio_pci_virtqueue_setup_common(QVirtioDevice *d, - QGuestAllocator *alloc, - uint16_t index) -{ - uint64_t feat; - uint64_t addr; - QVirtQueuePCI *vqpci; - QVirtioPCIDevice *qvpcidev = container_of(d, QVirtioPCIDevice, vdev); - - vqpci = g_malloc0(sizeof(*vqpci)); - feat = d->bus->get_guest_features(d); - - d->bus->queue_select(d, index); - vqpci->vq.vdev = d; - vqpci->vq.index = index; - vqpci->vq.size = d->bus->get_queue_size(d); - vqpci->vq.free_head = 0; - vqpci->vq.num_free = vqpci->vq.size; - vqpci->vq.align = VIRTIO_PCI_VRING_ALIGN; - vqpci->vq.indirect = feat & (1ull << VIRTIO_RING_F_INDIRECT_DESC); - vqpci->vq.event = feat & (1ull << VIRTIO_RING_F_EVENT_IDX); - - vqpci->msix_entry = -1; - vqpci->msix_addr = 0; - vqpci->msix_data = 0x12345678; - - /* Check different than 0 */ - g_assert_cmpint(vqpci->vq.size, !=, 0); - - /* Check power of 2 */ - g_assert_cmpint(vqpci->vq.size & (vqpci->vq.size - 1), ==, 0); - - addr = guest_alloc(alloc, qvring_size(vqpci->vq.size, - VIRTIO_PCI_VRING_ALIGN)); - qvring_init(qvpcidev->pdev->bus->qts, alloc, &vqpci->vq, addr); - d->bus->set_queue_address(d, &vqpci->vq); - - return &vqpci->vq; -} - -void qvirtio_pci_virtqueue_cleanup_common(QVirtQueue *vq, - QGuestAllocator *alloc) -{ - QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); - - guest_free(alloc, vq->desc); - g_free(vqpci); -} - -static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) -{ - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); - qpci_io_writew(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NOTIFY, vq->index); -} - -static const QVirtioBus qvirtio_pci_legacy = { - .config_readb = qvirtio_pci_config_readb, - .config_readw = qvirtio_pci_config_readw, - .config_readl = qvirtio_pci_config_readl, - .config_readq = qvirtio_pci_config_readq, - .get_features = qvirtio_pci_get_features, - .set_features = qvirtio_pci_set_features, - .get_guest_features = qvirtio_pci_get_guest_features, - .get_status = qvirtio_pci_get_status, - .set_status = qvirtio_pci_set_status, - .get_queue_isr_status = qvirtio_pci_get_queue_isr_status, - .wait_config_isr_status = qvirtio_pci_wait_config_isr_status, - .queue_select = qvirtio_pci_queue_select, - .get_queue_size = qvirtio_pci_get_queue_size, - .set_queue_address = qvirtio_pci_set_queue_address, - .virtqueue_setup = qvirtio_pci_virtqueue_setup_common, - .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, - .virtqueue_kick = qvirtio_pci_virtqueue_kick, -}; - -static void qvirtio_pci_set_config_vector(QVirtioPCIDevice *d, uint16_t entry) -{ - uint16_t vector; - - qpci_io_writew(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR, entry); - vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR); - g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); -} - -static void qvirtio_pci_set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, - uint16_t entry) -{ - uint16_t vector; - - qvirtio_pci_queue_select(&d->vdev, vq_idx); - qpci_io_writew(d->pdev, d->bar, VIRTIO_MSI_QUEUE_VECTOR, entry); - vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_QUEUE_VECTOR); - g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); -} - -static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_legacy = { - .set_config_vector = qvirtio_pci_set_config_vector, - .set_queue_vector = qvirtio_pci_set_queue_vector, -}; - -void qvirtio_pci_device_enable(QVirtioPCIDevice *d) -{ - qpci_device_enable(d->pdev); - d->bar = qpci_iomap(d->pdev, d->bar_idx, NULL); -} - -void qvirtio_pci_device_disable(QVirtioPCIDevice *d) -{ - qpci_iounmap(d->pdev, d->bar); -} - -void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, - QGuestAllocator *alloc, uint16_t entry) -{ - uint32_t control; - uint64_t off; - - g_assert(d->pdev->msix_enabled); - off = d->pdev->msix_table_off + (entry * 16); - - g_assert_cmpint(entry, >=, 0); - g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); - vqpci->msix_entry = entry; - - vqpci->msix_addr = guest_alloc(alloc, 4); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_LOWER_ADDR, vqpci->msix_addr & ~0UL); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_UPPER_ADDR, - (vqpci->msix_addr >> 32) & ~0UL); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_DATA, vqpci->msix_data); - - control = qpci_io_readl(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_VECTOR_CTRL); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_VECTOR_CTRL, - control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); - - d->msix_ops->set_queue_vector(d, vqpci->vq.index, entry); -} - -void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, - QGuestAllocator *alloc, uint16_t entry) -{ - uint32_t control; - uint64_t off; - - g_assert(d->pdev->msix_enabled); - off = d->pdev->msix_table_off + (entry * 16); - - g_assert_cmpint(entry, >=, 0); - g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); - d->config_msix_entry = entry; - - d->config_msix_data = 0x12345678; - d->config_msix_addr = guest_alloc(alloc, 4); - - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_LOWER_ADDR, d->config_msix_addr & ~0UL); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_UPPER_ADDR, - (d->config_msix_addr >> 32) & ~0UL); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_DATA, d->config_msix_data); - - control = qpci_io_readl(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_VECTOR_CTRL); - qpci_io_writel(d->pdev, d->pdev->msix_table_bar, - off + PCI_MSIX_ENTRY_VECTOR_CTRL, - control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); - - d->msix_ops->set_config_vector(d, entry); -} - -void qvirtio_pci_destructor(QOSGraphObject *obj) -{ - QVirtioPCIDevice *dev = (QVirtioPCIDevice *)obj; - qvirtio_pci_device_disable(dev); - g_free(dev->pdev); -} - -void qvirtio_pci_start_hw(QOSGraphObject *obj) -{ - QVirtioPCIDevice *dev = (QVirtioPCIDevice *)obj; - qvirtio_pci_device_enable(dev); - qvirtio_start_device(&dev->vdev); -} - -static void qvirtio_pci_init_legacy(QVirtioPCIDevice *dev) -{ - dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); - dev->bar_idx = 0; - dev->vdev.bus = &qvirtio_pci_legacy; - dev->msix_ops = &qvirtio_pci_msix_ops_legacy; - dev->vdev.big_endian = qtest_big_endian(dev->pdev->bus->qts); -} - -static void qvirtio_pci_init_from_pcidev(QVirtioPCIDevice *dev, QPCIDevice *pci_dev) -{ - dev->pdev = pci_dev; - dev->config_msix_entry = -1; - - if (!qvirtio_pci_init_virtio_1(dev)) { - qvirtio_pci_init_legacy(dev); - } - - /* each virtio-xxx-pci device should override at least this function */ - dev->obj.get_driver = NULL; - dev->obj.start_hw = qvirtio_pci_start_hw; - dev->obj.destructor = qvirtio_pci_destructor; -} - -void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr) -{ - QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn); - g_assert_nonnull(pci_dev); - qvirtio_pci_init_from_pcidev(dev, pci_dev); -} - -QVirtioPCIDevice *virtio_pci_new(QPCIBus *bus, QPCIAddress * addr) -{ - QVirtioPCIDevice *dev; - QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn); - if (!pci_dev) { - return NULL; - } - - dev = g_new0(QVirtioPCIDevice, 1); - qvirtio_pci_init_from_pcidev(dev, pci_dev); - dev->obj.free = g_free; - return dev; -} diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h deleted file mode 100644 index 294d5567ee..0000000000 --- a/tests/libqos/virtio-pci.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * libqos virtio PCI definitions - * - * 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. - */ - -#ifndef LIBQOS_VIRTIO_PCI_H -#define LIBQOS_VIRTIO_PCI_H - -#include "libqos/virtio.h" -#include "libqos/pci.h" -#include "libqos/qgraph.h" - -typedef struct QVirtioPCIMSIXOps QVirtioPCIMSIXOps; - -typedef struct QVirtioPCIDevice { - QOSGraphObject obj; - QVirtioDevice vdev; - QPCIDevice *pdev; - QPCIBar bar; - const QVirtioPCIMSIXOps *msix_ops; - uint16_t config_msix_entry; - uint64_t config_msix_addr; - uint32_t config_msix_data; - - int bar_idx; - - /* VIRTIO 1.0 */ - uint32_t common_cfg_offset; - uint32_t notify_cfg_offset; - uint32_t notify_off_multiplier; - uint32_t isr_cfg_offset; - uint32_t device_cfg_offset; -} QVirtioPCIDevice; - -struct QVirtioPCIMSIXOps { - /* Set the Configuration Vector for MSI-X */ - void (*set_config_vector)(QVirtioPCIDevice *d, uint16_t entry); - - /* Set the Queue Vector for MSI-X */ - void (*set_queue_vector)(QVirtioPCIDevice *d, uint16_t vq_idx, - uint16_t entry); -}; - -typedef struct QVirtQueuePCI { - QVirtQueue vq; - uint16_t msix_entry; - uint64_t msix_addr; - uint32_t msix_data; - - /* VIRTIO 1.0 */ - uint64_t notify_offset; -} QVirtQueuePCI; - -void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr); -QVirtioPCIDevice *virtio_pci_new(QPCIBus *bus, QPCIAddress * addr); - -/* virtio-pci object functions available for subclasses that - * override the original start_hw and destroy - * function. All virtio-xxx-pci subclass that override must - * take care of calling these two functions in the respective - * places - */ -void qvirtio_pci_destructor(QOSGraphObject *obj); -void qvirtio_pci_start_hw(QOSGraphObject *obj); - - -void qvirtio_pci_device_enable(QVirtioPCIDevice *d); -void qvirtio_pci_device_disable(QVirtioPCIDevice *d); - -void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, - QGuestAllocator *alloc, uint16_t entry); -void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, - QGuestAllocator *alloc, uint16_t entry); - -/* Used by Legacy and Modern virtio-pci code */ -QVirtQueue *qvirtio_pci_virtqueue_setup_common(QVirtioDevice *d, - QGuestAllocator *alloc, - uint16_t index); -void qvirtio_pci_virtqueue_cleanup_common(QVirtQueue *vq, - QGuestAllocator *alloc); - -#endif diff --git a/tests/libqos/virtio-rng.c b/tests/libqos/virtio-rng.c deleted file mode 100644 index b86349e2fd..0000000000 --- a/tests/libqos/virtio-rng.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-rng.h" - -/* virtio-rng-device */ -static void *qvirtio_rng_get_driver(QVirtioRng *v_rng, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-rng")) { - return v_rng; - } - if (!g_strcmp0(interface, "virtio")) { - return v_rng->vdev; - } - - fprintf(stderr, "%s not present in virtio-rng-device\n", interface); - g_assert_not_reached(); -} - -static void *qvirtio_rng_device_get_driver(void *object, - const char *interface) -{ - QVirtioRngDevice *v_rng = object; - return qvirtio_rng_get_driver(&v_rng->rng, interface); -} - -static void *virtio_rng_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioRngDevice *virtio_rdevice = g_new0(QVirtioRngDevice, 1); - QVirtioRng *interface = &virtio_rdevice->rng; - - interface->vdev = virtio_dev; - - virtio_rdevice->obj.get_driver = qvirtio_rng_device_get_driver; - - return &virtio_rdevice->obj; -} - -/* virtio-rng-pci */ -static void *qvirtio_rng_pci_get_driver(void *object, const char *interface) -{ - QVirtioRngPCI *v_rng = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_rng->pci_vdev.pdev; - } - return qvirtio_rng_get_driver(&v_rng->rng, interface); -} - -static void *virtio_rng_pci_create(void *pci_bus, QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioRngPCI *virtio_rpci = g_new0(QVirtioRngPCI, 1); - QVirtioRng *interface = &virtio_rpci->rng; - QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj; - - virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr); - interface->vdev = &virtio_rpci->pci_vdev.vdev; - - obj->get_driver = qvirtio_rng_pci_get_driver; - - return obj; -} - -static void virtio_rng_register_nodes(void) -{ - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - }; - - QOSGraphEdgeOptions opts = { - .extra_device_opts = "addr=04.0", - }; - - /* virtio-rng-device */ - qos_node_create_driver("virtio-rng-device", virtio_rng_device_create); - qos_node_consumes("virtio-rng-device", "virtio-bus", NULL); - qos_node_produces("virtio-rng-device", "virtio"); - qos_node_produces("virtio-rng-device", "virtio-rng"); - - /* virtio-rng-pci */ - add_qpci_address(&opts, &addr); - qos_node_create_driver("virtio-rng-pci", virtio_rng_pci_create); - qos_node_consumes("virtio-rng-pci", "pci-bus", &opts); - qos_node_produces("virtio-rng-pci", "pci-device"); - qos_node_produces("virtio-rng-pci", "virtio"); - qos_node_produces("virtio-rng-pci", "virtio-rng"); -} - -libqos_init(virtio_rng_register_nodes); diff --git a/tests/libqos/virtio-rng.h b/tests/libqos/virtio-rng.h deleted file mode 100644 index 9e192f11f7..0000000000 --- a/tests/libqos/virtio-rng.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_RNG_H -#define TESTS_LIBQOS_VIRTIO_RNG_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtioRng QVirtioRng; -typedef struct QVirtioRngPCI QVirtioRngPCI; -typedef struct QVirtioRngDevice QVirtioRngDevice; - -struct QVirtioRng { - QVirtioDevice *vdev; -}; - -struct QVirtioRngPCI { - QVirtioPCIDevice pci_vdev; - QVirtioRng rng; -}; - -struct QVirtioRngDevice { - QOSGraphObject obj; - QVirtioRng rng; -}; - -#endif diff --git a/tests/libqos/virtio-scsi.c b/tests/libqos/virtio-scsi.c deleted file mode 100644 index de739bec5f..0000000000 --- a/tests/libqos/virtio-scsi.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "standard-headers/linux/virtio_ids.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-scsi.h" - -/* virtio-scsi-device */ -static void *qvirtio_scsi_get_driver(QVirtioSCSI *v_scsi, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-scsi")) { - return v_scsi; - } - if (!g_strcmp0(interface, "virtio")) { - return v_scsi->vdev; - } - - fprintf(stderr, "%s not present in virtio-scsi-device\n", interface); - g_assert_not_reached(); -} - -static void *qvirtio_scsi_device_get_driver(void *object, - const char *interface) -{ - QVirtioSCSIDevice *v_scsi = object; - return qvirtio_scsi_get_driver(&v_scsi->scsi, interface); -} - -static void *virtio_scsi_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioSCSIDevice *virtio_bdevice = g_new0(QVirtioSCSIDevice, 1); - QVirtioSCSI *interface = &virtio_bdevice->scsi; - - interface->vdev = virtio_dev; - - virtio_bdevice->obj.get_driver = qvirtio_scsi_device_get_driver; - - return &virtio_bdevice->obj; -} - -/* virtio-scsi-pci */ -static void *qvirtio_scsi_pci_get_driver(void *object, - const char *interface) -{ - QVirtioSCSIPCI *v_scsi = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_scsi->pci_vdev.pdev; - } - return qvirtio_scsi_get_driver(&v_scsi->scsi, interface); -} - -static void *virtio_scsi_pci_create(void *pci_bus, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioSCSIPCI *virtio_spci = g_new0(QVirtioSCSIPCI, 1); - QVirtioSCSI *interface = &virtio_spci->scsi; - QOSGraphObject *obj = &virtio_spci->pci_vdev.obj; - - virtio_pci_init(&virtio_spci->pci_vdev, pci_bus, addr); - interface->vdev = &virtio_spci->pci_vdev.vdev; - - g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_SCSI); - - obj->get_driver = qvirtio_scsi_pci_get_driver; - - return obj; -} - -static void virtio_scsi_register_nodes(void) -{ - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - }; - - QOSGraphEdgeOptions opts = { - .before_cmd_line = "-drive id=drv0,if=none,file=null-co://," - "file.read-zeroes=on,format=raw", - .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0", - }; - - /* virtio-scsi-device */ - opts.extra_device_opts = "id=vs0"; - qos_node_create_driver("virtio-scsi-device", - virtio_scsi_device_create); - qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts); - qos_node_produces("virtio-scsi-device", "virtio-scsi"); - - /* virtio-scsi-pci */ - opts.extra_device_opts = "id=vs0,addr=04.0"; - add_qpci_address(&opts, &addr); - qos_node_create_driver("virtio-scsi-pci", virtio_scsi_pci_create); - qos_node_consumes("virtio-scsi-pci", "pci-bus", &opts); - qos_node_produces("virtio-scsi-pci", "pci-device"); - qos_node_produces("virtio-scsi-pci", "virtio-scsi"); -} - -libqos_init(virtio_scsi_register_nodes); diff --git a/tests/libqos/virtio-scsi.h b/tests/libqos/virtio-scsi.h deleted file mode 100644 index 4ca19a6a7a..0000000000 --- a/tests/libqos/virtio-scsi.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_SCSI_H -#define TESTS_LIBQOS_VIRTIO_SCSI_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtioSCSI QVirtioSCSI; -typedef struct QVirtioSCSIPCI QVirtioSCSIPCI; -typedef struct QVirtioSCSIDevice QVirtioSCSIDevice; - -struct QVirtioSCSI { - QVirtioDevice *vdev; -}; - -struct QVirtioSCSIPCI { - QVirtioPCIDevice pci_vdev; - QVirtioSCSI scsi; -}; - -struct QVirtioSCSIDevice { - QOSGraphObject obj; - QVirtioSCSI scsi; -}; - -#endif diff --git a/tests/libqos/virtio-serial.c b/tests/libqos/virtio-serial.c deleted file mode 100644 index 3e5b8b82c7..0000000000 --- a/tests/libqos/virtio-serial.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "qemu/module.h" -#include "libqos/qgraph.h" -#include "libqos/virtio-serial.h" - -static void *qvirtio_serial_get_driver(QVirtioSerial *v_serial, - const char *interface) -{ - if (!g_strcmp0(interface, "virtio-serial")) { - return v_serial; - } - if (!g_strcmp0(interface, "virtio")) { - return v_serial->vdev; - } - - fprintf(stderr, "%s not present in virtio-serial-device\n", interface); - g_assert_not_reached(); -} - -static void *qvirtio_serial_device_get_driver(void *object, - const char *interface) -{ - QVirtioSerialDevice *v_serial = object; - return qvirtio_serial_get_driver(&v_serial->serial, interface); -} - -static void *virtio_serial_device_create(void *virtio_dev, - QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioSerialDevice *virtio_device = g_new0(QVirtioSerialDevice, 1); - QVirtioSerial *interface = &virtio_device->serial; - - interface->vdev = virtio_dev; - - virtio_device->obj.get_driver = qvirtio_serial_device_get_driver; - - return &virtio_device->obj; -} - -/* virtio-serial-pci */ -static void *qvirtio_serial_pci_get_driver(void *object, const char *interface) -{ - QVirtioSerialPCI *v_serial = object; - if (!g_strcmp0(interface, "pci-device")) { - return v_serial->pci_vdev.pdev; - } - return qvirtio_serial_get_driver(&v_serial->serial, interface); -} - -static void *virtio_serial_pci_create(void *pci_bus, QGuestAllocator *t_alloc, - void *addr) -{ - QVirtioSerialPCI *virtio_spci = g_new0(QVirtioSerialPCI, 1); - QVirtioSerial *interface = &virtio_spci->serial; - QOSGraphObject *obj = &virtio_spci->pci_vdev.obj; - - virtio_pci_init(&virtio_spci->pci_vdev, pci_bus, addr); - interface->vdev = &virtio_spci->pci_vdev.vdev; - - obj->get_driver = qvirtio_serial_pci_get_driver; - - return obj; -} - -static void virtio_serial_register_nodes(void) -{ - QPCIAddress addr = { - .devfn = QPCI_DEVFN(4, 0), - }; - - QOSGraphEdgeOptions edge_opts = { }; - - /* virtio-serial-device */ - edge_opts.extra_device_opts = "id=vser0"; - qos_node_create_driver("virtio-serial-device", - virtio_serial_device_create); - qos_node_consumes("virtio-serial-device", "virtio-bus", &edge_opts); - qos_node_produces("virtio-serial-device", "virtio"); - qos_node_produces("virtio-serial-device", "virtio-serial"); - - /* virtio-serial-pci */ - edge_opts.extra_device_opts = "id=vser0,addr=04.0"; - add_qpci_address(&edge_opts, &addr); - qos_node_create_driver("virtio-serial-pci", virtio_serial_pci_create); - qos_node_consumes("virtio-serial-pci", "pci-bus", &edge_opts); - qos_node_produces("virtio-serial-pci", "pci-device"); - qos_node_produces("virtio-serial-pci", "virtio"); - qos_node_produces("virtio-serial-pci", "virtio-serial"); -} - -libqos_init(virtio_serial_register_nodes); diff --git a/tests/libqos/virtio-serial.h b/tests/libqos/virtio-serial.h deleted file mode 100644 index 080fa8428d..0000000000 --- a/tests/libqos/virtio-serial.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#ifndef TESTS_LIBQOS_VIRTIO_SERIAL_H -#define TESTS_LIBQOS_VIRTIO_SERIAL_H - -#include "libqos/qgraph.h" -#include "libqos/virtio.h" -#include "libqos/virtio-pci.h" - -typedef struct QVirtioSerial QVirtioSerial; -typedef struct QVirtioSerialPCI QVirtioSerialPCI; -typedef struct QVirtioSerialDevice QVirtioSerialDevice; - -struct QVirtioSerial { - QVirtioDevice *vdev; -}; - -struct QVirtioSerialPCI { - QVirtioPCIDevice pci_vdev; - QVirtioSerial serial; -}; - -struct QVirtioSerialDevice { - QOSGraphObject obj; - QVirtioSerial serial; -}; - -#endif diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c deleted file mode 100644 index 9aa360620c..0000000000 --- a/tests/libqos/virtio.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * libqos virtio driver - * - * 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 "qemu/bswap.h" -#include "libqtest.h" -#include "libqos/virtio.h" -#include "standard-headers/linux/virtio_config.h" -#include "standard-headers/linux/virtio_ring.h" - -/* - * qtest_readX/writeX() functions transfer host endian from/to guest endian. - * This works great for Legacy VIRTIO devices where we need guest endian - * accesses. For VIRTIO 1.0 the vring is little-endian so the automatic guest - * endianness conversion is not wanted. - * - * The following qvirtio_readX/writeX() functions handle Legacy and VIRTIO 1.0 - * accesses seamlessly. - */ -static uint16_t qvirtio_readw(QVirtioDevice *d, QTestState *qts, uint64_t addr) -{ - uint16_t val = qtest_readw(qts, addr); - - if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { - val = bswap16(val); - } - return val; -} - -static uint32_t qvirtio_readl(QVirtioDevice *d, QTestState *qts, uint64_t addr) -{ - uint32_t val = qtest_readl(qts, addr); - - if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { - val = bswap32(val); - } - return val; -} - -static void qvirtio_writew(QVirtioDevice *d, QTestState *qts, - uint64_t addr, uint16_t val) -{ - if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { - val = bswap16(val); - } - qtest_writew(qts, addr, val); -} - -static void qvirtio_writel(QVirtioDevice *d, QTestState *qts, - uint64_t addr, uint32_t val) -{ - if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { - val = bswap32(val); - } - qtest_writel(qts, addr, val); -} - -static void qvirtio_writeq(QVirtioDevice *d, QTestState *qts, - uint64_t addr, uint64_t val) -{ - if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { - val = bswap64(val); - } - qtest_writeq(qts, addr, val); -} - -uint8_t qvirtio_config_readb(QVirtioDevice *d, uint64_t addr) -{ - g_assert_true(d->features_negotiated); - return d->bus->config_readb(d, addr); -} - -uint16_t qvirtio_config_readw(QVirtioDevice *d, uint64_t addr) -{ - g_assert_true(d->features_negotiated); - return d->bus->config_readw(d, addr); -} - -uint32_t qvirtio_config_readl(QVirtioDevice *d, uint64_t addr) -{ - g_assert_true(d->features_negotiated); - return d->bus->config_readl(d, addr); -} - -uint64_t qvirtio_config_readq(QVirtioDevice *d, uint64_t addr) -{ - g_assert_true(d->features_negotiated); - return d->bus->config_readq(d, addr); -} - -uint64_t qvirtio_get_features(QVirtioDevice *d) -{ - return d->bus->get_features(d); -} - -void qvirtio_set_features(QVirtioDevice *d, uint64_t features) -{ - d->features = features; - d->bus->set_features(d, features); - - /* - * This could be a separate function for drivers that want to access - * configuration space before setting FEATURES_OK, but no existing users - * need that and it's less code for callers if this is done implicitly. - */ - if (features & (1ull << VIRTIO_F_VERSION_1)) { - uint8_t status = d->bus->get_status(d) | - VIRTIO_CONFIG_S_FEATURES_OK; - - d->bus->set_status(d, status); - g_assert_cmphex(d->bus->get_status(d), ==, status); - } - - d->features_negotiated = true; -} - -QVirtQueue *qvirtqueue_setup(QVirtioDevice *d, - QGuestAllocator *alloc, uint16_t index) -{ - g_assert_true(d->features_negotiated); - return d->bus->virtqueue_setup(d, alloc, index); -} - -void qvirtqueue_cleanup(const QVirtioBus *bus, QVirtQueue *vq, - QGuestAllocator *alloc) -{ - return bus->virtqueue_cleanup(vq, alloc); -} - -void qvirtio_reset(QVirtioDevice *d) -{ - d->bus->set_status(d, 0); - g_assert_cmphex(d->bus->get_status(d), ==, 0); - d->features_negotiated = false; -} - -void qvirtio_set_acknowledge(QVirtioDevice *d) -{ - d->bus->set_status(d, d->bus->get_status(d) | VIRTIO_CONFIG_S_ACKNOWLEDGE); - g_assert_cmphex(d->bus->get_status(d), ==, VIRTIO_CONFIG_S_ACKNOWLEDGE); -} - -void qvirtio_set_driver(QVirtioDevice *d) -{ - d->bus->set_status(d, d->bus->get_status(d) | VIRTIO_CONFIG_S_DRIVER); - g_assert_cmphex(d->bus->get_status(d), ==, - VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_ACKNOWLEDGE); -} - -void qvirtio_set_driver_ok(QVirtioDevice *d) -{ - d->bus->set_status(d, d->bus->get_status(d) | VIRTIO_CONFIG_S_DRIVER_OK); - g_assert_cmphex(d->bus->get_status(d), ==, VIRTIO_CONFIG_S_DRIVER_OK | - VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_ACKNOWLEDGE | - (d->features & (1ull << VIRTIO_F_VERSION_1) ? - VIRTIO_CONFIG_S_FEATURES_OK : 0)); -} - -void qvirtio_wait_queue_isr(QTestState *qts, QVirtioDevice *d, - QVirtQueue *vq, gint64 timeout_us) -{ - gint64 start_time = g_get_monotonic_time(); - - for (;;) { - qtest_clock_step(qts, 100); - if (d->bus->get_queue_isr_status(d, vq)) { - return; - } - g_assert(g_get_monotonic_time() - start_time <= timeout_us); - } -} - -/* Wait for the status byte at given guest memory address to be set - * - * The virtqueue interrupt must not be raised, making this useful for testing - * event_index functionality. - */ -uint8_t qvirtio_wait_status_byte_no_isr(QTestState *qts, QVirtioDevice *d, - QVirtQueue *vq, - uint64_t addr, - gint64 timeout_us) -{ - gint64 start_time = g_get_monotonic_time(); - uint8_t val; - - while ((val = qtest_readb(qts, addr)) == 0xff) { - qtest_clock_step(qts, 100); - g_assert(!d->bus->get_queue_isr_status(d, vq)); - g_assert(g_get_monotonic_time() - start_time <= timeout_us); - } - return val; -} - -/* - * qvirtio_wait_used_elem: - * @desc_idx: The next expected vq->desc[] index in the used ring - * @len: A pointer that is filled with the length written into the buffer, may - * be NULL - * @timeout_us: How many microseconds to wait before failing - * - * This function waits for the next completed request on the used ring. - */ -void qvirtio_wait_used_elem(QTestState *qts, QVirtioDevice *d, - QVirtQueue *vq, - uint32_t desc_idx, - uint32_t *len, - gint64 timeout_us) -{ - gint64 start_time = g_get_monotonic_time(); - - for (;;) { - uint32_t got_desc_idx; - - qtest_clock_step(qts, 100); - - if (d->bus->get_queue_isr_status(d, vq) && - qvirtqueue_get_buf(qts, vq, &got_desc_idx, len)) { - g_assert_cmpint(got_desc_idx, ==, desc_idx); - return; - } - - g_assert(g_get_monotonic_time() - start_time <= timeout_us); - } -} - -void qvirtio_wait_config_isr(QVirtioDevice *d, gint64 timeout_us) -{ - d->bus->wait_config_isr_status(d, timeout_us); -} - -void qvring_init(QTestState *qts, const QGuestAllocator *alloc, QVirtQueue *vq, - uint64_t addr) -{ - int i; - - vq->desc = addr; - vq->avail = vq->desc + vq->size * sizeof(struct vring_desc); - vq->used = (uint64_t)((vq->avail + sizeof(uint16_t) * (3 + vq->size) - + vq->align - 1) & ~(vq->align - 1)); - - for (i = 0; i < vq->size - 1; i++) { - /* vq->desc[i].addr */ - qvirtio_writeq(vq->vdev, qts, vq->desc + (16 * i), 0); - /* vq->desc[i].next */ - qvirtio_writew(vq->vdev, qts, vq->desc + (16 * i) + 14, i + 1); - } - - /* vq->avail->flags */ - qvirtio_writew(vq->vdev, qts, vq->avail, 0); - /* vq->avail->idx */ - qvirtio_writew(vq->vdev, qts, vq->avail + 2, 0); - /* vq->avail->used_event */ - qvirtio_writew(vq->vdev, qts, vq->avail + 4 + (2 * vq->size), 0); - - /* vq->used->flags */ - qvirtio_writew(vq->vdev, qts, vq->used, 0); - /* vq->used->avail_event */ - qvirtio_writew(vq->vdev, qts, vq->used + 2 + - sizeof(struct vring_used_elem) * vq->size, 0); -} - -QVRingIndirectDesc *qvring_indirect_desc_setup(QTestState *qs, QVirtioDevice *d, - QGuestAllocator *alloc, - uint16_t elem) -{ - int i; - QVRingIndirectDesc *indirect = g_malloc(sizeof(*indirect)); - - indirect->index = 0; - indirect->elem = elem; - indirect->desc = guest_alloc(alloc, sizeof(struct vring_desc) * elem); - - for (i = 0; i < elem - 1; ++i) { - /* indirect->desc[i].addr */ - qvirtio_writeq(d, qs, indirect->desc + (16 * i), 0); - /* indirect->desc[i].flags */ - qvirtio_writew(d, qs, indirect->desc + (16 * i) + 12, - VRING_DESC_F_NEXT); - /* indirect->desc[i].next */ - qvirtio_writew(d, qs, indirect->desc + (16 * i) + 14, i + 1); - } - - return indirect; -} - -void qvring_indirect_desc_add(QVirtioDevice *d, QTestState *qts, - QVRingIndirectDesc *indirect, - uint64_t data, uint32_t len, bool write) -{ - uint16_t flags; - - g_assert_cmpint(indirect->index, <, indirect->elem); - - flags = qvirtio_readw(d, qts, indirect->desc + - (16 * indirect->index) + 12); - - if (write) { - flags |= VRING_DESC_F_WRITE; - } - - /* indirect->desc[indirect->index].addr */ - qvirtio_writeq(d, qts, indirect->desc + (16 * indirect->index), data); - /* indirect->desc[indirect->index].len */ - qvirtio_writel(d, qts, indirect->desc + (16 * indirect->index) + 8, len); - /* indirect->desc[indirect->index].flags */ - qvirtio_writew(d, qts, indirect->desc + (16 * indirect->index) + 12, - flags); - - indirect->index++; -} - -uint32_t qvirtqueue_add(QTestState *qts, QVirtQueue *vq, uint64_t data, - uint32_t len, bool write, bool next) -{ - uint16_t flags = 0; - vq->num_free--; - - if (write) { - flags |= VRING_DESC_F_WRITE; - } - - if (next) { - flags |= VRING_DESC_F_NEXT; - } - - /* vq->desc[vq->free_head].addr */ - qvirtio_writeq(vq->vdev, qts, vq->desc + (16 * vq->free_head), data); - /* vq->desc[vq->free_head].len */ - qvirtio_writel(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 8, len); - /* vq->desc[vq->free_head].flags */ - qvirtio_writew(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 12, flags); - - return vq->free_head++; /* Return and increase, in this order */ -} - -uint32_t qvirtqueue_add_indirect(QTestState *qts, QVirtQueue *vq, - QVRingIndirectDesc *indirect) -{ - g_assert(vq->indirect); - g_assert_cmpint(vq->size, >=, indirect->elem); - g_assert_cmpint(indirect->index, ==, indirect->elem); - - vq->num_free--; - - /* vq->desc[vq->free_head].addr */ - qvirtio_writeq(vq->vdev, qts, vq->desc + (16 * vq->free_head), - indirect->desc); - /* vq->desc[vq->free_head].len */ - qvirtio_writel(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 8, - sizeof(struct vring_desc) * indirect->elem); - /* vq->desc[vq->free_head].flags */ - qvirtio_writew(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 12, - VRING_DESC_F_INDIRECT); - - return vq->free_head++; /* Return and increase, in this order */ -} - -void qvirtqueue_kick(QTestState *qts, QVirtioDevice *d, QVirtQueue *vq, - uint32_t free_head) -{ - /* vq->avail->idx */ - uint16_t idx = qvirtio_readw(d, qts, vq->avail + 2); - /* vq->used->flags */ - uint16_t flags; - /* vq->used->avail_event */ - uint16_t avail_event; - - /* vq->avail->ring[idx % vq->size] */ - qvirtio_writew(d, qts, vq->avail + 4 + (2 * (idx % vq->size)), free_head); - /* vq->avail->idx */ - qvirtio_writew(d, qts, vq->avail + 2, idx + 1); - - /* Must read after idx is updated */ - flags = qvirtio_readw(d, qts, vq->avail); - avail_event = qvirtio_readw(d, qts, vq->used + 4 + - sizeof(struct vring_used_elem) * vq->size); - - /* < 1 because we add elements to avail queue one by one */ - if ((flags & VRING_USED_F_NO_NOTIFY) == 0 && - (!vq->event || (uint16_t)(idx-avail_event) < 1)) { - d->bus->virtqueue_kick(d, vq); - } -} - -/* - * qvirtqueue_get_buf: - * @desc_idx: A pointer that is filled with the vq->desc[] index, may be NULL - * @len: A pointer that is filled with the length written into the buffer, may - * be NULL - * - * This function gets the next used element if there is one ready. - * - * Returns: true if an element was ready, false otherwise - */ -bool qvirtqueue_get_buf(QTestState *qts, QVirtQueue *vq, uint32_t *desc_idx, - uint32_t *len) -{ - uint16_t idx; - uint64_t elem_addr, addr; - - idx = qvirtio_readw(vq->vdev, qts, - vq->used + offsetof(struct vring_used, idx)); - if (idx == vq->last_used_idx) { - return false; - } - - elem_addr = vq->used + - offsetof(struct vring_used, ring) + - (vq->last_used_idx % vq->size) * - sizeof(struct vring_used_elem); - - if (desc_idx) { - addr = elem_addr + offsetof(struct vring_used_elem, id); - *desc_idx = qvirtio_readl(vq->vdev, qts, addr); - } - - if (len) { - addr = elem_addr + offsetof(struct vring_used_elem, len); - *len = qvirtio_readw(vq->vdev, qts, addr); - } - - vq->last_used_idx++; - return true; -} - -void qvirtqueue_set_used_event(QTestState *qts, QVirtQueue *vq, uint16_t idx) -{ - g_assert(vq->event); - - /* vq->avail->used_event */ - qvirtio_writew(vq->vdev, qts, vq->avail + 4 + (2 * vq->size), idx); -} - -void qvirtio_start_device(QVirtioDevice *vdev) -{ - qvirtio_reset(vdev); - qvirtio_set_acknowledge(vdev); - qvirtio_set_driver(vdev); -} - -bool qvirtio_is_big_endian(QVirtioDevice *d) -{ - return d->big_endian; -} diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h deleted file mode 100644 index 529ef7555a..0000000000 --- a/tests/libqos/virtio.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * libqos virtio definitions - * - * 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. - */ - -#ifndef LIBQOS_VIRTIO_H -#define LIBQOS_VIRTIO_H - -#include "libqos/malloc.h" -#include "standard-headers/linux/virtio_ring.h" - -#define QVIRTIO_F_BAD_FEATURE 0x40000000ull - -typedef struct QVirtioBus QVirtioBus; - -typedef struct QVirtioDevice { - const QVirtioBus *bus; - /* Device type */ - uint16_t device_type; - uint64_t features; - bool big_endian; - bool features_negotiated; -} QVirtioDevice; - -typedef struct QVirtQueue { - QVirtioDevice *vdev; - uint64_t desc; /* This points to an array of struct vring_desc */ - uint64_t avail; /* This points to a struct vring_avail */ - uint64_t used; /* This points to a struct vring_used */ - uint16_t index; - uint32_t size; - uint32_t free_head; - uint32_t num_free; - uint32_t align; - uint16_t last_used_idx; - bool indirect; - bool event; -} QVirtQueue; - -typedef struct QVRingIndirectDesc { - uint64_t desc; /* This points to an array fo struct vring_desc */ - uint16_t index; - uint16_t elem; -} QVRingIndirectDesc; - -struct QVirtioBus { - uint8_t (*config_readb)(QVirtioDevice *d, uint64_t addr); - uint16_t (*config_readw)(QVirtioDevice *d, uint64_t addr); - uint32_t (*config_readl)(QVirtioDevice *d, uint64_t addr); - uint64_t (*config_readq)(QVirtioDevice *d, uint64_t addr); - - /* Get features of the device */ - uint64_t (*get_features)(QVirtioDevice *d); - - /* Set features of the device */ - void (*set_features)(QVirtioDevice *d, uint64_t features); - - /* Get features of the guest */ - uint64_t (*get_guest_features)(QVirtioDevice *d); - - /* Get status of the device */ - uint8_t (*get_status)(QVirtioDevice *d); - - /* Set status of the device */ - void (*set_status)(QVirtioDevice *d, uint8_t status); - - /* Get the queue ISR status of the device */ - bool (*get_queue_isr_status)(QVirtioDevice *d, QVirtQueue *vq); - - /* Wait for the configuration ISR status of the device */ - void (*wait_config_isr_status)(QVirtioDevice *d, gint64 timeout_us); - - /* Select a queue to work on */ - void (*queue_select)(QVirtioDevice *d, uint16_t index); - - /* Get the size of the selected queue */ - uint16_t (*get_queue_size)(QVirtioDevice *d); - - /* Set the address of the selected queue */ - void (*set_queue_address)(QVirtioDevice *d, QVirtQueue *vq); - - /* Setup the virtqueue specified by index */ - QVirtQueue *(*virtqueue_setup)(QVirtioDevice *d, QGuestAllocator *alloc, - uint16_t index); - - /* Free virtqueue resources */ - void (*virtqueue_cleanup)(QVirtQueue *vq, QGuestAllocator *alloc); - - /* Notify changes in virtqueue */ - void (*virtqueue_kick)(QVirtioDevice *d, QVirtQueue *vq); -}; - -static inline uint32_t qvring_size(uint32_t num, uint32_t align) -{ - return ((sizeof(struct vring_desc) * num + sizeof(uint16_t) * (3 + num) - + align - 1) & ~(align - 1)) - + sizeof(uint16_t) * 3 + sizeof(struct vring_used_elem) * num; -} - -uint8_t qvirtio_config_readb(QVirtioDevice *d, uint64_t addr); -uint16_t qvirtio_config_readw(QVirtioDevice *d, uint64_t addr); -uint32_t qvirtio_config_readl(QVirtioDevice *d, uint64_t addr); -uint64_t qvirtio_config_readq(QVirtioDevice *d, uint64_t addr); -uint64_t qvirtio_get_features(QVirtioDevice *d); -void qvirtio_set_features(QVirtioDevice *d, uint64_t features); -bool qvirtio_is_big_endian(QVirtioDevice *d); - -void qvirtio_reset(QVirtioDevice *d); -void qvirtio_set_acknowledge(QVirtioDevice *d); -void qvirtio_set_driver(QVirtioDevice *d); -void qvirtio_set_driver_ok(QVirtioDevice *d); - -void qvirtio_wait_queue_isr(QTestState *qts, QVirtioDevice *d, - QVirtQueue *vq, gint64 timeout_us); -uint8_t qvirtio_wait_status_byte_no_isr(QTestState *qts, QVirtioDevice *d, - QVirtQueue *vq, - uint64_t addr, - gint64 timeout_us); -void qvirtio_wait_used_elem(QTestState *qts, QVirtioDevice *d, - QVirtQueue *vq, - uint32_t desc_idx, - uint32_t *len, - gint64 timeout_us); -void qvirtio_wait_config_isr(QVirtioDevice *d, gint64 timeout_us); -QVirtQueue *qvirtqueue_setup(QVirtioDevice *d, - QGuestAllocator *alloc, uint16_t index); -void qvirtqueue_cleanup(const QVirtioBus *bus, QVirtQueue *vq, - QGuestAllocator *alloc); - -void qvring_init(QTestState *qts, const QGuestAllocator *alloc, QVirtQueue *vq, - uint64_t addr); -QVRingIndirectDesc *qvring_indirect_desc_setup(QTestState *qs, QVirtioDevice *d, - QGuestAllocator *alloc, - uint16_t elem); -void qvring_indirect_desc_add(QVirtioDevice *d, QTestState *qts, - QVRingIndirectDesc *indirect, - uint64_t data, uint32_t len, bool write); -uint32_t qvirtqueue_add(QTestState *qts, QVirtQueue *vq, uint64_t data, - uint32_t len, bool write, bool next); -uint32_t qvirtqueue_add_indirect(QTestState *qts, QVirtQueue *vq, - QVRingIndirectDesc *indirect); -void qvirtqueue_kick(QTestState *qts, QVirtioDevice *d, QVirtQueue *vq, - uint32_t free_head); -bool qvirtqueue_get_buf(QTestState *qts, QVirtQueue *vq, uint32_t *desc_idx, - uint32_t *len); - -void qvirtqueue_set_used_event(QTestState *qts, QVirtQueue *vq, uint16_t idx); - -void qvirtio_start_device(QVirtioDevice *vdev); - -#endif diff --git a/tests/libqos/x86_64_pc-machine.c b/tests/libqos/x86_64_pc-machine.c deleted file mode 100644 index 6dfa705217..0000000000 --- a/tests/libqos/x86_64_pc-machine.c +++ /dev/null @@ -1,115 +0,0 @@ -/* - * libqos driver framework - * - * Copyright (c) 2018 Emanuele Giuseppe Esposito - * - * 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 - */ - -#include "qemu/osdep.h" -#include "libqtest.h" -#include "libqos/qgraph.h" -#include "pci-pc.h" -#include "qemu/module.h" -#include "malloc-pc.h" - -typedef struct QX86PCMachine QX86PCMachine; -typedef struct i440FX_pcihost i440FX_pcihost; -typedef struct QSDHCI_PCI QSDHCI_PCI; - -struct i440FX_pcihost { - QOSGraphObject obj; - QPCIBusPC pci; -}; - -struct QX86PCMachine { - QOSGraphObject obj; - QGuestAllocator alloc; - i440FX_pcihost bridge; -}; - -/* i440FX_pcihost */ - -static QOSGraphObject *i440FX_host_get_device(void *obj, const char *device) -{ - i440FX_pcihost *host = obj; - if (!g_strcmp0(device, "pci-bus-pc")) { - return &host->pci.obj; - } - fprintf(stderr, "%s not present in i440FX-pcihost\n", device); - g_assert_not_reached(); -} - -static void qos_create_i440FX_host(i440FX_pcihost *host, - QTestState *qts, - QGuestAllocator *alloc) -{ - host->obj.get_device = i440FX_host_get_device; - qpci_init_pc(&host->pci, qts, alloc); -} - -/* x86_64/pc machine */ - -static void pc_destructor(QOSGraphObject *obj) -{ - QX86PCMachine *machine = (QX86PCMachine *) obj; - alloc_destroy(&machine->alloc); -} - -static void *pc_get_driver(void *object, const char *interface) -{ - QX86PCMachine *machine = object; - if (!g_strcmp0(interface, "memory")) { - return &machine->alloc; - } - - fprintf(stderr, "%s not present in x86_64/pc\n", interface); - g_assert_not_reached(); -} - -static QOSGraphObject *pc_get_device(void *obj, const char *device) -{ - QX86PCMachine *machine = obj; - if (!g_strcmp0(device, "i440FX-pcihost")) { - return &machine->bridge.obj; - } - - fprintf(stderr, "%s not present in x86_64/pc\n", device); - g_assert_not_reached(); -} - -static void *qos_create_machine_pc(QTestState *qts) -{ - QX86PCMachine *machine = g_new0(QX86PCMachine, 1); - machine->obj.get_device = pc_get_device; - machine->obj.get_driver = pc_get_driver; - machine->obj.destructor = pc_destructor; - pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS); - qos_create_i440FX_host(&machine->bridge, qts, &machine->alloc); - - return &machine->obj; -} - -static void pc_machine_register_nodes(void) -{ - qos_node_create_machine("i386/pc", qos_create_machine_pc); - qos_node_contains("i386/pc", "i440FX-pcihost", NULL); - - qos_node_create_machine("x86_64/pc", qos_create_machine_pc); - qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL); - - qos_node_create_driver("i440FX-pcihost", NULL); - qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL); -} - -libqos_init(pc_machine_register_nodes); diff --git a/tests/qtest/Makefile.include b/tests/qtest/Makefile.include index 816d0d90ea..e6bb4ab28c 100644 --- a/tests/qtest/Makefile.include +++ b/tests/qtest/Makefile.include @@ -155,52 +155,52 @@ check-qtest-s390x-y += cpu-plug-test check-qtest-s390x-y += migration-test # libqos / qgraph : -libqgraph-obj-y = tests/libqos/qgraph.o - -libqos-obj-y = $(libqgraph-obj-y) tests/libqos/pci.o tests/libqos/fw_cfg.o -libqos-obj-y += tests/libqos/malloc.o -libqos-obj-y += tests/libqos/libqos.o -libqos-spapr-obj-y = $(libqos-obj-y) tests/libqos/malloc-spapr.o -libqos-spapr-obj-y += tests/libqos/libqos-spapr.o -libqos-spapr-obj-y += tests/libqos/rtas.o -libqos-spapr-obj-y += tests/libqos/pci-spapr.o -libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o -libqos-pc-obj-y += tests/libqos/malloc-pc.o tests/libqos/libqos-pc.o -libqos-pc-obj-y += tests/libqos/ahci.o -libqos-usb-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/libqos/usb.o +libqgraph-obj-y = tests/qtest/libqos/qgraph.o + +libqos-obj-y = $(libqgraph-obj-y) tests/qtest/libqos/pci.o tests/qtest/libqos/fw_cfg.o +libqos-obj-y += tests/qtest/libqos/malloc.o +libqos-obj-y += tests/qtest/libqos/libqos.o +libqos-spapr-obj-y = $(libqos-obj-y) tests/qtest/libqos/malloc-spapr.o +libqos-spapr-obj-y += tests/qtest/libqos/libqos-spapr.o +libqos-spapr-obj-y += tests/qtest/libqos/rtas.o +libqos-spapr-obj-y += tests/qtest/libqos/pci-spapr.o +libqos-pc-obj-y = $(libqos-obj-y) tests/qtest/libqos/pci-pc.o +libqos-pc-obj-y += tests/qtest/libqos/malloc-pc.o tests/qtest/libqos/libqos-pc.o +libqos-pc-obj-y += tests/qtest/libqos/ahci.o +libqos-usb-obj-y = $(libqos-spapr-obj-y) $(libqos-pc-obj-y) tests/qtest/libqos/usb.o # qos devices: qos-test-obj-y = tests/qtest/qos-test.o $(libqgraph-obj-y) qos-test-obj-y += $(libqos-pc-obj-y) $(libqos-spapr-obj-y) -qos-test-obj-y += tests/libqos/e1000e.o -qos-test-obj-y += tests/libqos/i2c.o -qos-test-obj-y += tests/libqos/i2c-imx.o -qos-test-obj-y += tests/libqos/i2c-omap.o -qos-test-obj-y += tests/libqos/sdhci.o -qos-test-obj-y += tests/libqos/tpci200.o -qos-test-obj-y += tests/libqos/virtio.o -qos-test-obj-$(CONFIG_VIRTFS) += tests/libqos/virtio-9p.o -qos-test-obj-y += tests/libqos/virtio-balloon.o -qos-test-obj-y += tests/libqos/virtio-blk.o -qos-test-obj-y += tests/libqos/virtio-mmio.o -qos-test-obj-y += tests/libqos/virtio-net.o -qos-test-obj-y += tests/libqos/virtio-pci.o -qos-test-obj-y += tests/libqos/virtio-pci-modern.o -qos-test-obj-y += tests/libqos/virtio-rng.o -qos-test-obj-y += tests/libqos/virtio-scsi.o -qos-test-obj-y += tests/libqos/virtio-serial.o +qos-test-obj-y += tests/qtest/libqos/e1000e.o +qos-test-obj-y += tests/qtest/libqos/i2c.o +qos-test-obj-y += tests/qtest/libqos/i2c-imx.o +qos-test-obj-y += tests/qtest/libqos/i2c-omap.o +qos-test-obj-y += tests/qtest/libqos/sdhci.o +qos-test-obj-y += tests/qtest/libqos/tpci200.o +qos-test-obj-y += tests/qtest/libqos/virtio.o +qos-test-obj-$(CONFIG_VIRTFS) += tests/qtest/libqos/virtio-9p.o +qos-test-obj-y += tests/qtest/libqos/virtio-balloon.o +qos-test-obj-y += tests/qtest/libqos/virtio-blk.o +qos-test-obj-y += tests/qtest/libqos/virtio-mmio.o +qos-test-obj-y += tests/qtest/libqos/virtio-net.o +qos-test-obj-y += tests/qtest/libqos/virtio-pci.o +qos-test-obj-y += tests/qtest/libqos/virtio-pci-modern.o +qos-test-obj-y += tests/qtest/libqos/virtio-rng.o +qos-test-obj-y += tests/qtest/libqos/virtio-scsi.o +qos-test-obj-y += tests/qtest/libqos/virtio-serial.o # qos machines: -qos-test-obj-y += tests/libqos/aarch64-xlnx-zcu102-machine.o -qos-test-obj-y += tests/libqos/arm-imx25-pdk-machine.o -qos-test-obj-y += tests/libqos/arm-n800-machine.o -qos-test-obj-y += tests/libqos/arm-raspi2-machine.o -qos-test-obj-y += tests/libqos/arm-sabrelite-machine.o -qos-test-obj-y += tests/libqos/arm-smdkc210-machine.o -qos-test-obj-y += tests/libqos/arm-virt-machine.o -qos-test-obj-y += tests/libqos/arm-xilinx-zynq-a9-machine.o -qos-test-obj-y += tests/libqos/ppc64_pseries-machine.o -qos-test-obj-y += tests/libqos/x86_64_pc-machine.o +qos-test-obj-y += tests/qtest/libqos/aarch64-xlnx-zcu102-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-imx25-pdk-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-n800-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-raspi2-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-sabrelite-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-smdkc210-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-virt-machine.o +qos-test-obj-y += tests/qtest/libqos/arm-xilinx-zynq-a9-machine.o +qos-test-obj-y += tests/qtest/libqos/ppc64_pseries-machine.o +qos-test-obj-y += tests/qtest/libqos/x86_64_pc-machine.o # qos tests: qos-test-obj-y += tests/qtest/ac97-test.o diff --git a/tests/qtest/libqos/aarch64-xlnx-zcu102-machine.c b/tests/qtest/libqos/aarch64-xlnx-zcu102-machine.c new file mode 100644 index 0000000000..1d5de5af00 --- /dev/null +++ b/tests/qtest/libqos/aarch64-xlnx-zcu102-machine.c @@ -0,0 +1,95 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "sdhci.h" + +typedef struct QXlnxZCU102Machine QXlnxZCU102Machine; + +struct QXlnxZCU102Machine { + QOSGraphObject obj; + QGuestAllocator alloc; + QSDHCI_MemoryMapped sdhci; +}; + +#define ARM_PAGE_SIZE 4096 +#define XLNX_ZCU102_RAM_ADDR 0 +#define XLNX_ZCU102_RAM_SIZE 0x20000000 + +static void *xlnx_zcu102_get_driver(void *object, const char *interface) +{ + QXlnxZCU102Machine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in aarch64/xlnx-zcu102\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *xlnx_zcu102_get_device(void *obj, const char *device) +{ + QXlnxZCU102Machine *machine = obj; + if (!g_strcmp0(device, "generic-sdhci")) { + return &machine->sdhci.obj; + } + + fprintf(stderr, "%s not present in aarch64/xlnx-zcu102\n", device); + g_assert_not_reached(); +} + +static void xlnx_zcu102_destructor(QOSGraphObject *obj) +{ + QXlnxZCU102Machine *machine = (QXlnxZCU102Machine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_aarch64_xlnx_zcu102(QTestState *qts) +{ + QXlnxZCU102Machine *machine = g_new0(QXlnxZCU102Machine, 1); + + alloc_init(&machine->alloc, 0, + XLNX_ZCU102_RAM_ADDR + (1 << 20), + XLNX_ZCU102_RAM_ADDR + XLNX_ZCU102_RAM_SIZE, + ARM_PAGE_SIZE); + + machine->obj.get_device = xlnx_zcu102_get_device; + machine->obj.get_driver = xlnx_zcu102_get_driver; + machine->obj.destructor = xlnx_zcu102_destructor; + /* Datasheet: UG1085 (v1.7) */ + qos_init_sdhci_mm(&machine->sdhci, qts, 0xff160000, &(QSDHCIProperties) { + .version = 3, + .baseclock = 0, + .capab.sdma = true, + .capab.reg = 0x280737ec6481 + }); + return &machine->obj; +} + +static void xlnx_zcu102_register_nodes(void) +{ + qos_node_create_machine("aarch64/xlnx-zcu102", + qos_create_machine_aarch64_xlnx_zcu102); + qos_node_contains("aarch64/xlnx-zcu102", "generic-sdhci", NULL); +} + +libqos_init(xlnx_zcu102_register_nodes); diff --git a/tests/qtest/libqos/ahci.c b/tests/qtest/libqos/ahci.c new file mode 100644 index 0000000000..cc1b08eabe --- /dev/null +++ b/tests/qtest/libqos/ahci.c @@ -0,0 +1,1242 @@ +/* + * libqos AHCI functions + * + * Copyright (c) 2014 John Snow + * + * 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/ahci.h" +#include "libqos/pci-pc.h" + +#include "qemu-common.h" +#include "qemu/host-utils.h" + +#include "hw/pci/pci_ids.h" +#include "hw/pci/pci_regs.h" + +typedef struct AHCICommandProp { + uint8_t cmd; /* Command Code */ + bool data; /* Data transfer command? */ + bool pio; + bool dma; + bool lba28; + bool lba48; + bool read; + bool write; + bool atapi; + bool ncq; + uint64_t size; /* Static transfer size, for commands like IDENTIFY. */ + uint32_t interrupts; /* Expected interrupts for this command. */ +} AHCICommandProp; + +AHCICommandProp ahci_command_properties[] = { + { .cmd = CMD_READ_PIO, .data = true, .pio = true, + .lba28 = true, .read = true }, + { .cmd = CMD_WRITE_PIO, .data = true, .pio = true, + .lba28 = true, .write = true }, + { .cmd = CMD_READ_PIO_EXT, .data = true, .pio = true, + .lba48 = true, .read = true }, + { .cmd = CMD_WRITE_PIO_EXT, .data = true, .pio = true, + .lba48 = true, .write = true }, + { .cmd = CMD_READ_DMA, .data = true, .dma = true, + .lba28 = true, .read = true }, + { .cmd = CMD_WRITE_DMA, .data = true, .dma = true, + .lba28 = true, .write = true }, + { .cmd = CMD_READ_DMA_EXT, .data = true, .dma = true, + .lba48 = true, .read = true }, + { .cmd = CMD_WRITE_DMA_EXT, .data = true, .dma = true, + .lba48 = true, .write = true }, + { .cmd = CMD_IDENTIFY, .data = true, .pio = true, + .size = 512, .read = true }, + { .cmd = READ_FPDMA_QUEUED, .data = true, .dma = true, + .lba48 = true, .read = true, .ncq = true }, + { .cmd = WRITE_FPDMA_QUEUED, .data = true, .dma = true, + .lba48 = true, .write = true, .ncq = true }, + { .cmd = CMD_READ_MAX, .lba28 = true }, + { .cmd = CMD_READ_MAX_EXT, .lba48 = true }, + { .cmd = CMD_FLUSH_CACHE, .data = false }, + { .cmd = CMD_PACKET, .data = true, .size = 16, + .atapi = true, .pio = true }, + { .cmd = CMD_PACKET_ID, .data = true, .pio = true, + .size = 512, .read = true } +}; + +struct AHCICommand { + /* Test Management Data */ + uint8_t name; + uint8_t port; + uint8_t slot; + uint8_t errors; + uint32_t interrupts; + uint64_t xbytes; + uint32_t prd_size; + uint32_t sector_size; + uint64_t buffer; + AHCICommandProp *props; + /* Data to be transferred to the guest */ + AHCICommandHeader header; + RegH2DFIS fis; + unsigned char *atapi_cmd; +}; + +/** + * Allocate space in the guest using information in the AHCIQState object. + */ +uint64_t ahci_alloc(AHCIQState *ahci, size_t bytes) +{ + g_assert(ahci); + g_assert(ahci->parent); + return qmalloc(ahci->parent, bytes); +} + +void ahci_free(AHCIQState *ahci, uint64_t addr) +{ + g_assert(ahci); + g_assert(ahci->parent); + qfree(ahci->parent, addr); +} + +bool is_atapi(AHCIQState *ahci, uint8_t port) +{ + return ahci_px_rreg(ahci, port, AHCI_PX_SIG) == AHCI_SIGNATURE_CDROM; +} + +/** + * Locate, verify, and return a handle to the AHCI device. + */ +QPCIDevice *get_ahci_device(QTestState *qts, uint32_t *fingerprint) +{ + QPCIDevice *ahci; + uint32_t ahci_fingerprint; + QPCIBus *pcibus; + + pcibus = qpci_new_pc(qts, NULL); + + /* Find the AHCI PCI device and verify it's the right one. */ + ahci = qpci_device_find(pcibus, QPCI_DEVFN(0x1F, 0x02)); + g_assert(ahci != NULL); + + ahci_fingerprint = qpci_config_readl(ahci, PCI_VENDOR_ID); + + switch (ahci_fingerprint) { + case AHCI_INTEL_ICH9: + break; + default: + /* Unknown device. */ + g_assert_not_reached(); + } + + if (fingerprint) { + *fingerprint = ahci_fingerprint; + } + return ahci; +} + +void free_ahci_device(QPCIDevice *dev) +{ + QPCIBus *pcibus = dev ? dev->bus : NULL; + + /* libqos doesn't have a function for this, so free it manually */ + g_free(dev); + qpci_free_pc(pcibus); +} + +/* Free all memory in-use by the AHCI device. */ +void ahci_clean_mem(AHCIQState *ahci) +{ + uint8_t port, slot; + + for (port = 0; port < 32; ++port) { + if (ahci->port[port].fb) { + ahci_free(ahci, ahci->port[port].fb); + ahci->port[port].fb = 0; + } + if (ahci->port[port].clb) { + for (slot = 0; slot < 32; slot++) { + ahci_destroy_command(ahci, port, slot); + } + ahci_free(ahci, ahci->port[port].clb); + ahci->port[port].clb = 0; + } + } +} + +/*** Logical Device Initialization ***/ + +/** + * Start the PCI device and sanity-check default operation. + */ +void ahci_pci_enable(AHCIQState *ahci) +{ + uint8_t reg; + + start_ahci_device(ahci); + + switch (ahci->fingerprint) { + case AHCI_INTEL_ICH9: + /* ICH9 has a register at PCI 0x92 that + * acts as a master port enabler mask. */ + reg = qpci_config_readb(ahci->dev, 0x92); + reg |= 0x3F; + qpci_config_writeb(ahci->dev, 0x92, reg); + /* 0...0111111b -- bit significant, ports 0-5 enabled. */ + ASSERT_BIT_SET(qpci_config_readb(ahci->dev, 0x92), 0x3F); + break; + } + +} + +/** + * Map BAR5/ABAR, and engage the PCI device. + */ +void start_ahci_device(AHCIQState *ahci) +{ + /* Map AHCI's ABAR (BAR5) */ + ahci->hba_bar = qpci_iomap(ahci->dev, 5, &ahci->barsize); + + /* turns on pci.cmd.iose, pci.cmd.mse and pci.cmd.bme */ + qpci_device_enable(ahci->dev); +} + +/** + * Test and initialize the AHCI's HBA memory areas. + * Initialize and start any ports with devices attached. + * Bring the HBA into the idle state. + */ +void ahci_hba_enable(AHCIQState *ahci) +{ + /* Bits of interest in this section: + * GHC.AE Global Host Control / AHCI Enable + * PxCMD.ST Port Command: Start + * PxCMD.SUD "Spin Up Device" + * PxCMD.POD "Power On Device" + * PxCMD.FRE "FIS Receive Enable" + * PxCMD.FR "FIS Receive Running" + * PxCMD.CR "Command List Running" + */ + uint32_t reg, ports_impl; + uint16_t i; + uint8_t num_cmd_slots; + + g_assert(ahci != NULL); + + /* Set GHC.AE to 1 */ + ahci_set(ahci, AHCI_GHC, AHCI_GHC_AE); + reg = ahci_rreg(ahci, AHCI_GHC); + ASSERT_BIT_SET(reg, AHCI_GHC_AE); + + /* Cache CAP and CAP2. */ + ahci->cap = ahci_rreg(ahci, AHCI_CAP); + ahci->cap2 = ahci_rreg(ahci, AHCI_CAP2); + + /* Read CAP.NCS, how many command slots do we have? */ + num_cmd_slots = ((ahci->cap & AHCI_CAP_NCS) >> ctzl(AHCI_CAP_NCS)) + 1; + g_test_message("Number of Command Slots: %u", num_cmd_slots); + + /* Determine which ports are implemented. */ + ports_impl = ahci_rreg(ahci, AHCI_PI); + + for (i = 0; ports_impl; ports_impl >>= 1, ++i) { + if (!(ports_impl & 0x01)) { + continue; + } + + g_test_message("Initializing port %u", i); + + reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); + if (BITCLR(reg, AHCI_PX_CMD_ST | AHCI_PX_CMD_CR | + AHCI_PX_CMD_FRE | AHCI_PX_CMD_FR)) { + g_test_message("port is idle"); + } else { + g_test_message("port needs to be idled"); + ahci_px_clr(ahci, i, AHCI_PX_CMD, + (AHCI_PX_CMD_ST | AHCI_PX_CMD_FRE)); + /* The port has 500ms to disengage. */ + usleep(500000); + reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_FR); + g_test_message("port is now idle"); + /* The spec does allow for possibly needing a PORT RESET + * or HBA reset if we fail to idle the port. */ + } + + /* Allocate Memory for the Command List Buffer & FIS Buffer */ + /* PxCLB space ... 0x20 per command, as in 4.2.2 p 36 */ + ahci->port[i].clb = ahci_alloc(ahci, num_cmd_slots * 0x20); + qtest_memset(ahci->parent->qts, ahci->port[i].clb, 0x00, + num_cmd_slots * 0x20); + g_test_message("CLB: 0x%08" PRIx64, ahci->port[i].clb); + ahci_px_wreg(ahci, i, AHCI_PX_CLB, ahci->port[i].clb); + g_assert_cmphex(ahci->port[i].clb, ==, + ahci_px_rreg(ahci, i, AHCI_PX_CLB)); + + /* PxFB space ... 0x100, as in 4.2.1 p 35 */ + ahci->port[i].fb = ahci_alloc(ahci, 0x100); + qtest_memset(ahci->parent->qts, ahci->port[i].fb, 0x00, 0x100); + g_test_message("FB: 0x%08" PRIx64, ahci->port[i].fb); + ahci_px_wreg(ahci, i, AHCI_PX_FB, ahci->port[i].fb); + g_assert_cmphex(ahci->port[i].fb, ==, + ahci_px_rreg(ahci, i, AHCI_PX_FB)); + + /* Clear PxSERR, PxIS, then IS.IPS[x] by writing '1's. */ + ahci_px_wreg(ahci, i, AHCI_PX_SERR, 0xFFFFFFFF); + ahci_px_wreg(ahci, i, AHCI_PX_IS, 0xFFFFFFFF); + ahci_wreg(ahci, AHCI_IS, (1 << i)); + + /* Verify Interrupts Cleared */ + reg = ahci_px_rreg(ahci, i, AHCI_PX_SERR); + g_assert_cmphex(reg, ==, 0); + + reg = ahci_px_rreg(ahci, i, AHCI_PX_IS); + g_assert_cmphex(reg, ==, 0); + + reg = ahci_rreg(ahci, AHCI_IS); + ASSERT_BIT_CLEAR(reg, (1 << i)); + + /* Enable All Interrupts: */ + ahci_px_wreg(ahci, i, AHCI_PX_IE, 0xFFFFFFFF); + reg = ahci_px_rreg(ahci, i, AHCI_PX_IE); + g_assert_cmphex(reg, ==, ~((uint32_t)AHCI_PX_IE_RESERVED)); + + /* Enable the FIS Receive Engine. */ + ahci_px_set(ahci, i, AHCI_PX_CMD, AHCI_PX_CMD_FRE); + reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); + ASSERT_BIT_SET(reg, AHCI_PX_CMD_FR); + + /* AHCI 1.3 spec: if !STS.BSY, !STS.DRQ and PxSSTS.DET indicates + * physical presence, a device is present and may be started. However, + * PxSERR.DIAG.X /may/ need to be cleared a priori. */ + reg = ahci_px_rreg(ahci, i, AHCI_PX_SERR); + if (BITSET(reg, AHCI_PX_SERR_DIAG_X)) { + ahci_px_set(ahci, i, AHCI_PX_SERR, AHCI_PX_SERR_DIAG_X); + } + + reg = ahci_px_rreg(ahci, i, AHCI_PX_TFD); + if (BITCLR(reg, AHCI_PX_TFD_STS_BSY | AHCI_PX_TFD_STS_DRQ)) { + reg = ahci_px_rreg(ahci, i, AHCI_PX_SSTS); + if ((reg & AHCI_PX_SSTS_DET) == SSTS_DET_ESTABLISHED) { + /* Device Found: set PxCMD.ST := 1 */ + ahci_px_set(ahci, i, AHCI_PX_CMD, AHCI_PX_CMD_ST); + ASSERT_BIT_SET(ahci_px_rreg(ahci, i, AHCI_PX_CMD), + AHCI_PX_CMD_CR); + g_test_message("Started Device %u", i); + } else if ((reg & AHCI_PX_SSTS_DET)) { + /* Device present, but in some unknown state. */ + g_assert_not_reached(); + } + } + } + + /* Enable GHC.IE */ + ahci_set(ahci, AHCI_GHC, AHCI_GHC_IE); + reg = ahci_rreg(ahci, AHCI_GHC); + ASSERT_BIT_SET(reg, AHCI_GHC_IE); + + ahci->enabled = true; + /* TODO: The device should now be idling and waiting for commands. + * In the future, a small test-case to inspect the Register D2H FIS + * and clear the initial interrupts might be good. */ +} + +/** + * Pick the first implemented and running port + */ +unsigned ahci_port_select(AHCIQState *ahci) +{ + uint32_t ports, reg; + unsigned i; + + ports = ahci_rreg(ahci, AHCI_PI); + for (i = 0; i < 32; ports >>= 1, ++i) { + if (ports == 0) { + i = 32; + } + + if (!(ports & 0x01)) { + continue; + } + + reg = ahci_px_rreg(ahci, i, AHCI_PX_CMD); + if (BITSET(reg, AHCI_PX_CMD_ST)) { + break; + } + } + g_assert(i < 32); + return i; +} + +/** + * Clear a port's interrupts and status information prior to a test. + */ +void ahci_port_clear(AHCIQState *ahci, uint8_t port) +{ + uint32_t reg; + + /* Clear out this port's interrupts (ignore the init register d2h fis) */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); + ahci_px_wreg(ahci, port, AHCI_PX_IS, reg); + g_assert_cmphex(ahci_px_rreg(ahci, port, AHCI_PX_IS), ==, 0); + + /* Wipe the FIS-Receive Buffer */ + qtest_memset(ahci->parent->qts, ahci->port[port].fb, 0x00, 0x100); +} + +/** + * Check a port for errors. + */ +void ahci_port_check_error(AHCIQState *ahci, uint8_t port, + uint32_t imask, uint8_t emask) +{ + uint32_t reg; + + /* The upper 9 bits of the IS register all indicate errors. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); + reg &= ~imask; + reg >>= 23; + g_assert_cmphex(reg, ==, 0); + + /* The Sata Error Register should be empty. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_SERR); + g_assert_cmphex(reg, ==, 0); + + /* The TFD also has two error sections. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); + if (!emask) { + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_ERR); + } else { + ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_ERR); + } + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR & (~emask << 8)); + ASSERT_BIT_SET(reg, AHCI_PX_TFD_ERR & (emask << 8)); +} + +void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port, + uint32_t intr_mask) +{ + uint32_t reg; + + /* Check for expected interrupts */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); + ASSERT_BIT_SET(reg, intr_mask); + + /* Clear expected interrupts and assert all interrupts now cleared. */ + ahci_px_wreg(ahci, port, AHCI_PX_IS, intr_mask); + g_assert_cmphex(ahci_px_rreg(ahci, port, AHCI_PX_IS), ==, 0); +} + +void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot) +{ + uint32_t reg; + + /* Assert that the command slot is no longer busy (NCQ) */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_SACT); + ASSERT_BIT_CLEAR(reg, (1 << slot)); + + /* Non-NCQ */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_CI); + ASSERT_BIT_CLEAR(reg, (1 << slot)); + + /* And assert that we are generally not busy. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_BSY); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_DRQ); +} + +void ahci_port_check_d2h_sanity(AHCIQState *ahci, uint8_t port, uint8_t slot) +{ + RegD2HFIS *d2h = g_malloc0(0x20); + uint32_t reg; + + qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x40, d2h, 0x20); + g_assert_cmphex(d2h->fis_type, ==, 0x34); + + reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); + g_assert_cmphex((reg & AHCI_PX_TFD_ERR) >> 8, ==, d2h->error); + g_assert_cmphex((reg & AHCI_PX_TFD_STS), ==, d2h->status); + + g_free(d2h); +} + +void ahci_port_check_pio_sanity(AHCIQState *ahci, AHCICommand *cmd) +{ + PIOSetupFIS *pio = g_malloc0(0x20); + uint8_t port = cmd->port; + + /* We cannot check the Status or E_Status registers, because + * the status may have again changed between the PIO Setup FIS + * and the conclusion of the command with the D2H Register FIS. */ + qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x20, pio, 0x20); + g_assert_cmphex(pio->fis_type, ==, 0x5f); + + /* Data transferred by PIO will either be: + * (1) 12 or 16 bytes for an ATAPI command packet (QEMU always uses 12), or + * (2) Actual data from the drive. + * If we do both, (2) winds up erasing any evidence of (1). + */ + if (cmd->props->atapi && (cmd->xbytes == 0 || cmd->props->dma)) { + g_assert(le16_to_cpu(pio->tx_count) == 12 || + le16_to_cpu(pio->tx_count) == 16); + } else { + /* The AHCI test suite here does not test any PIO command that specifies + * a DRQ block larger than one sector (like 0xC4), so this should always + * be one sector or less. */ + size_t pio_len = ((cmd->xbytes % cmd->sector_size) ? + (cmd->xbytes % cmd->sector_size) : cmd->sector_size); + g_assert_cmphex(le16_to_cpu(pio->tx_count), ==, pio_len); + } + g_free(pio); +} + +void ahci_port_check_cmd_sanity(AHCIQState *ahci, AHCICommand *cmd) +{ + AHCICommandHeader cmdh; + + ahci_get_command_header(ahci, cmd->port, cmd->slot, &cmdh); + /* Physical Region Descriptor Byte Count is not required to work for NCQ. */ + if (!cmd->props->ncq) { + g_assert_cmphex(cmd->xbytes, ==, cmdh.prdbc); + } +} + +/* Get the command in #slot of port #port. */ +void ahci_get_command_header(AHCIQState *ahci, uint8_t port, + uint8_t slot, AHCICommandHeader *cmd) +{ + uint64_t ba = ahci->port[port].clb; + ba += slot * sizeof(AHCICommandHeader); + qtest_memread(ahci->parent->qts, ba, cmd, sizeof(AHCICommandHeader)); + + cmd->flags = le16_to_cpu(cmd->flags); + cmd->prdtl = le16_to_cpu(cmd->prdtl); + cmd->prdbc = le32_to_cpu(cmd->prdbc); + cmd->ctba = le64_to_cpu(cmd->ctba); +} + +/* Set the command in #slot of port #port. */ +void ahci_set_command_header(AHCIQState *ahci, uint8_t port, + uint8_t slot, AHCICommandHeader *cmd) +{ + AHCICommandHeader tmp = { .flags = 0 }; + uint64_t ba = ahci->port[port].clb; + ba += slot * sizeof(AHCICommandHeader); + + tmp.flags = cpu_to_le16(cmd->flags); + tmp.prdtl = cpu_to_le16(cmd->prdtl); + tmp.prdbc = cpu_to_le32(cmd->prdbc); + tmp.ctba = cpu_to_le64(cmd->ctba); + + qtest_memwrite(ahci->parent->qts, ba, &tmp, sizeof(AHCICommandHeader)); +} + +void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot) +{ + AHCICommandHeader cmd; + + /* Obtain the Nth Command Header */ + ahci_get_command_header(ahci, port, slot, &cmd); + if (cmd.ctba == 0) { + /* No address in it, so just return -- it's empty. */ + goto tidy; + } + + /* Free the Table */ + ahci_free(ahci, cmd.ctba); + + tidy: + /* NULL the header. */ + memset(&cmd, 0x00, sizeof(cmd)); + ahci_set_command_header(ahci, port, slot, &cmd); + ahci->port[port].ctba[slot] = 0; + ahci->port[port].prdtl[slot] = 0; +} + +void ahci_write_fis(AHCIQState *ahci, AHCICommand *cmd) +{ + RegH2DFIS tmp = cmd->fis; + uint64_t addr = cmd->header.ctba; + + /* NCQ commands use exclusively 8 bit fields and needs no adjustment. + * Only the count field needs to be adjusted for non-NCQ commands. + * The auxiliary FIS fields are defined per-command and are not currently + * implemented in libqos/ahci.o, but may or may not need to be flipped. */ + if (!cmd->props->ncq) { + tmp.count = cpu_to_le16(tmp.count); + } + + qtest_memwrite(ahci->parent->qts, addr, &tmp, sizeof(tmp)); +} + +unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port) +{ + unsigned i; + unsigned j; + uint32_t reg; + + reg = ahci_px_rreg(ahci, port, AHCI_PX_CI); + + /* Pick the least recently used command slot that's available */ + for (i = 0; i < 32; ++i) { + j = ((ahci->port[port].next + i) % 32); + if (reg & (1 << j)) { + continue; + } + ahci_destroy_command(ahci, port, j); + ahci->port[port].next = (j + 1) % 32; + return j; + } + + g_test_message("All command slots were busy."); + g_assert_not_reached(); +} + +inline unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd) +{ + /* Each PRD can describe up to 4MiB */ + g_assert_cmphex(bytes_per_prd, <=, 4096 * 1024); + g_assert_cmphex(bytes_per_prd & 0x01, ==, 0x00); + return (bytes + bytes_per_prd - 1) / bytes_per_prd; +} + +const AHCIOpts default_opts = { .size = 0 }; + +/** + * ahci_exec: execute a given command on a specific + * AHCI port. + * + * @ahci: The device to send the command to + * @port: The port number of the SATA device we wish + * to have execute this command + * @op: The S/ATA command to execute, or if opts.atapi + * is true, the SCSI command code. + * @opts: Optional arguments to modify execution behavior. + */ +void ahci_exec(AHCIQState *ahci, uint8_t port, + uint8_t op, const AHCIOpts *opts_in) +{ + AHCICommand *cmd; + int rc; + AHCIOpts *opts; + + opts = g_memdup((opts_in == NULL ? &default_opts : opts_in), + sizeof(AHCIOpts)); + + /* No guest buffer provided, create one. */ + if (opts->size && !opts->buffer) { + opts->buffer = ahci_alloc(ahci, opts->size); + g_assert(opts->buffer); + qtest_memset(ahci->parent->qts, opts->buffer, 0x00, opts->size); + } + + /* Command creation */ + if (opts->atapi) { + uint16_t bcl = opts->set_bcl ? opts->bcl : ATAPI_SECTOR_SIZE; + cmd = ahci_atapi_command_create(op, bcl, opts->atapi_dma); + } else { + cmd = ahci_command_create(op); + } + ahci_command_adjust(cmd, opts->lba, opts->buffer, + opts->size, opts->prd_size); + + if (opts->pre_cb) { + rc = opts->pre_cb(ahci, cmd, opts); + g_assert_cmpint(rc, ==, 0); + } + + /* Write command to memory and issue it */ + ahci_command_commit(ahci, cmd, port); + ahci_command_issue_async(ahci, cmd); + if (opts->error) { + qtest_qmp_eventwait(ahci->parent->qts, "STOP"); + } + if (opts->mid_cb) { + rc = opts->mid_cb(ahci, cmd, opts); + g_assert_cmpint(rc, ==, 0); + } + if (opts->error) { + qtest_qmp_send(ahci->parent->qts, "{'execute':'cont' }"); + qtest_qmp_eventwait(ahci->parent->qts, "RESUME"); + } + + /* Wait for command to complete and verify sanity */ + ahci_command_wait(ahci, cmd); + ahci_command_verify(ahci, cmd); + if (opts->post_cb) { + rc = opts->post_cb(ahci, cmd, opts); + g_assert_cmpint(rc, ==, 0); + } + ahci_command_free(cmd); + if (opts->buffer != opts_in->buffer) { + ahci_free(ahci, opts->buffer); + } + g_free(opts); +} + +/* Issue a command, expecting it to fail and STOP the VM */ +AHCICommand *ahci_guest_io_halt(AHCIQState *ahci, uint8_t port, + uint8_t ide_cmd, uint64_t buffer, + size_t bufsize, uint64_t sector) +{ + AHCICommand *cmd; + + cmd = ahci_command_create(ide_cmd); + ahci_command_adjust(cmd, sector, buffer, bufsize, 0); + ahci_command_commit(ahci, cmd, port); + ahci_command_issue_async(ahci, cmd); + qtest_qmp_eventwait(ahci->parent->qts, "STOP"); + + return cmd; +} + +/* Resume a previously failed command and verify/finalize */ +void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd) +{ + /* Complete the command */ + qtest_qmp_send(ahci->parent->qts, "{'execute':'cont' }"); + qtest_qmp_eventwait(ahci->parent->qts, "RESUME"); + ahci_command_wait(ahci, cmd); + ahci_command_verify(ahci, cmd); + ahci_command_free(cmd); +} + +/* Given a guest buffer address, perform an IO operation */ +void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, + uint64_t buffer, size_t bufsize, uint64_t sector) +{ + AHCICommand *cmd; + cmd = ahci_command_create(ide_cmd); + ahci_command_set_buffer(cmd, buffer); + ahci_command_set_size(cmd, bufsize); + if (sector) { + ahci_command_set_offset(cmd, sector); + } + ahci_command_commit(ahci, cmd, port); + ahci_command_issue(ahci, cmd); + ahci_command_verify(ahci, cmd); + ahci_command_free(cmd); +} + +static AHCICommandProp *ahci_command_find(uint8_t command_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ahci_command_properties); i++) { + if (ahci_command_properties[i].cmd == command_name) { + return &ahci_command_properties[i]; + } + } + + return NULL; +} + +/* Given a HOST buffer, create a buffer address and perform an IO operation. */ +void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, + void *buffer, size_t bufsize, uint64_t sector) +{ + uint64_t ptr; + AHCICommandProp *props; + + props = ahci_command_find(ide_cmd); + g_assert(props); + ptr = ahci_alloc(ahci, bufsize); + g_assert(!bufsize || ptr); + qtest_memset(ahci->parent->qts, ptr, 0x00, bufsize); + + if (bufsize && props->write) { + qtest_bufwrite(ahci->parent->qts, ptr, buffer, bufsize); + } + + ahci_guest_io(ahci, port, ide_cmd, ptr, bufsize, sector); + + if (bufsize && props->read) { + qtest_bufread(ahci->parent->qts, ptr, buffer, bufsize); + } + + ahci_free(ahci, ptr); +} + +/** + * Initializes a basic command header in memory. + * We assume that this is for an ATA command using RegH2DFIS. + */ +static void command_header_init(AHCICommand *cmd) +{ + AHCICommandHeader *hdr = &cmd->header; + AHCICommandProp *props = cmd->props; + + hdr->flags = 5; /* RegH2DFIS is 5 DW long. Must be < 32 */ + hdr->flags |= CMDH_CLR_BSY; /* Clear the BSY bit when done */ + if (props->write) { + hdr->flags |= CMDH_WRITE; + } + if (props->atapi) { + hdr->flags |= CMDH_ATAPI; + } + /* Other flags: PREFETCH, RESET, and BIST */ + hdr->prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size); + hdr->prdbc = 0; + hdr->ctba = 0; +} + +static void command_table_init(AHCICommand *cmd) +{ + RegH2DFIS *fis = &(cmd->fis); + uint16_t sect_count = (cmd->xbytes / cmd->sector_size); + + fis->fis_type = REG_H2D_FIS; + fis->flags = REG_H2D_FIS_CMD; /* "Command" bit */ + fis->command = cmd->name; + + if (cmd->props->ncq) { + NCQFIS *ncqfis = (NCQFIS *)fis; + /* NCQ is weird and re-uses FIS frames for unrelated data. + * See SATA 3.2, 13.6.4.1 READ FPDMA QUEUED for an example. */ + ncqfis->sector_low = sect_count & 0xFF; + ncqfis->sector_hi = (sect_count >> 8) & 0xFF; + ncqfis->device = NCQ_DEVICE_MAGIC; + /* Force Unit Access is bit 7 in the device register */ + ncqfis->tag = 0; /* bits 3-7 are the NCQ tag */ + ncqfis->prio = 0; /* bits 6,7 are a prio tag */ + /* RARC bit is bit 0 of TAG field */ + } else { + fis->feature_low = 0x00; + fis->feature_high = 0x00; + if (cmd->props->lba28 || cmd->props->lba48) { + fis->device = ATA_DEVICE_LBA; + } + fis->count = (cmd->xbytes / cmd->sector_size); + } + fis->icc = 0x00; + fis->control = 0x00; + memset(fis->aux, 0x00, ARRAY_SIZE(fis->aux)); +} + +void ahci_command_enable_atapi_dma(AHCICommand *cmd) +{ + RegH2DFIS *fis = &(cmd->fis); + g_assert(cmd->props->atapi); + fis->feature_low |= 0x01; + /* PIO is still used to transfer the ATAPI command */ + g_assert(cmd->props->pio); + cmd->props->dma = true; + /* BUG: We expect the DMA Setup interrupt for DMA commands */ + /* cmd->interrupts |= AHCI_PX_IS_DSS; */ +} + +AHCICommand *ahci_command_create(uint8_t command_name) +{ + AHCICommandProp *props = ahci_command_find(command_name); + AHCICommand *cmd; + + g_assert(props); + cmd = g_new0(AHCICommand, 1); + g_assert(!(props->dma && props->pio) || props->atapi); + g_assert(!(props->lba28 && props->lba48)); + g_assert(!(props->read && props->write)); + g_assert(!props->size || props->data); + g_assert(!props->ncq || props->lba48); + + /* Defaults and book-keeping */ + cmd->props = g_memdup(props, sizeof(AHCICommandProp)); + cmd->name = command_name; + cmd->xbytes = props->size; + cmd->prd_size = 4096; + cmd->buffer = 0xabad1dea; + cmd->sector_size = props->atapi ? ATAPI_SECTOR_SIZE : AHCI_SECTOR_SIZE; + + if (!cmd->props->ncq) { + cmd->interrupts = AHCI_PX_IS_DHRS; + } + /* BUG: We expect the DPS interrupt for data commands */ + /* cmd->interrupts |= props->data ? AHCI_PX_IS_DPS : 0; */ + /* BUG: We expect the DMA Setup interrupt for DMA commands */ + /* cmd->interrupts |= props->dma ? AHCI_PX_IS_DSS : 0; */ + cmd->interrupts |= props->ncq ? AHCI_PX_IS_SDBS : 0; + + command_header_init(cmd); + command_table_init(cmd); + + return cmd; +} + +AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl, bool dma) +{ + AHCICommand *cmd = ahci_command_create(CMD_PACKET); + cmd->atapi_cmd = g_malloc0(16); + cmd->atapi_cmd[0] = scsi_cmd; + stw_le_p(&cmd->fis.lba_lo[1], bcl); + if (dma) { + ahci_command_enable_atapi_dma(cmd); + } else { + cmd->interrupts |= bcl ? AHCI_PX_IS_PSS : 0; + } + return cmd; +} + +void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port, + bool ready, uint8_t expected_sense) +{ + AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_TEST_UNIT_READY, 0, false); + ahci_command_set_size(cmd, 0); + if (!ready) { + cmd->interrupts |= AHCI_PX_IS_TFES; + cmd->errors |= expected_sense << 4; + } + ahci_command_commit(ahci, cmd, port); + ahci_command_issue(ahci, cmd); + ahci_command_verify(ahci, cmd); + ahci_command_free(cmd); +} + +static int copy_buffer(AHCIQState *ahci, AHCICommand *cmd, + const AHCIOpts *opts) +{ + unsigned char *rx = opts->opaque; + qtest_bufread(ahci->parent->qts, opts->buffer, rx, opts->size); + return 0; +} + +void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port, + uint8_t *sense, uint8_t *asc) +{ + unsigned char *rx; + AHCIOpts opts = { + .size = 18, + .atapi = true, + .post_cb = copy_buffer, + }; + rx = g_malloc(18); + opts.opaque = rx; + + ahci_exec(ahci, port, CMD_ATAPI_REQUEST_SENSE, &opts); + + *sense = rx[2]; + *asc = rx[12]; + + g_free(rx); +} + +void ahci_atapi_eject(AHCIQState *ahci, uint8_t port) +{ + AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0, false); + ahci_command_set_size(cmd, 0); + + cmd->atapi_cmd[4] = 0x02; /* loej = true */ + ahci_command_commit(ahci, cmd, port); + ahci_command_issue(ahci, cmd); + ahci_command_verify(ahci, cmd); + ahci_command_free(cmd); +} + +void ahci_atapi_load(AHCIQState *ahci, uint8_t port) +{ + AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0, false); + ahci_command_set_size(cmd, 0); + + cmd->atapi_cmd[4] = 0x03; /* loej,start = true */ + ahci_command_commit(ahci, cmd, port); + ahci_command_issue(ahci, cmd); + ahci_command_verify(ahci, cmd); + ahci_command_free(cmd); +} + +void ahci_command_free(AHCICommand *cmd) +{ + g_free(cmd->atapi_cmd); + g_free(cmd->props); + g_free(cmd); +} + +void ahci_command_set_flags(AHCICommand *cmd, uint16_t cmdh_flags) +{ + cmd->header.flags |= cmdh_flags; +} + +void ahci_command_clr_flags(AHCICommand *cmd, uint16_t cmdh_flags) +{ + cmd->header.flags &= ~cmdh_flags; +} + +static void ahci_atapi_command_set_offset(AHCICommand *cmd, uint64_t lba) +{ + unsigned char *cbd = cmd->atapi_cmd; + g_assert(cbd); + + switch (cbd[0]) { + case CMD_ATAPI_READ_10: + case CMD_ATAPI_READ_CD: + g_assert_cmpuint(lba, <=, UINT32_MAX); + stl_be_p(&cbd[2], lba); + break; + case CMD_ATAPI_REQUEST_SENSE: + case CMD_ATAPI_TEST_UNIT_READY: + case CMD_ATAPI_START_STOP_UNIT: + g_assert_cmpuint(lba, ==, 0x00); + break; + default: + /* SCSI doesn't have uniform packet formats, + * so you have to add support for it manually. Sorry! */ + fprintf(stderr, "The Libqos AHCI driver does not support the " + "set_offset operation for ATAPI command 0x%02x, " + "please add support.\n", + cbd[0]); + g_assert_not_reached(); + } +} + +void ahci_command_set_offset(AHCICommand *cmd, uint64_t lba_sect) +{ + RegH2DFIS *fis = &(cmd->fis); + + if (cmd->props->atapi) { + ahci_atapi_command_set_offset(cmd, lba_sect); + return; + } else if (!cmd->props->data && !lba_sect) { + /* Not meaningful, ignore. */ + return; + } else if (cmd->props->lba28) { + g_assert_cmphex(lba_sect, <=, 0xFFFFFFF); + } else if (cmd->props->lba48 || cmd->props->ncq) { + g_assert_cmphex(lba_sect, <=, 0xFFFFFFFFFFFF); + } else { + /* Can't set offset if we don't know the format. */ + g_assert_not_reached(); + } + + /* LBA28 uses the low nibble of the device/control register for LBA24:27 */ + fis->lba_lo[0] = (lba_sect & 0xFF); + fis->lba_lo[1] = (lba_sect >> 8) & 0xFF; + fis->lba_lo[2] = (lba_sect >> 16) & 0xFF; + if (cmd->props->lba28) { + fis->device = (fis->device & 0xF0) | ((lba_sect >> 24) & 0x0F); + } + fis->lba_hi[0] = (lba_sect >> 24) & 0xFF; + fis->lba_hi[1] = (lba_sect >> 32) & 0xFF; + fis->lba_hi[2] = (lba_sect >> 40) & 0xFF; +} + +void ahci_command_set_buffer(AHCICommand *cmd, uint64_t buffer) +{ + cmd->buffer = buffer; +} + +static void ahci_atapi_set_size(AHCICommand *cmd, uint64_t xbytes) +{ + unsigned char *cbd = cmd->atapi_cmd; + uint64_t nsectors = xbytes / ATAPI_SECTOR_SIZE; + uint32_t tmp; + g_assert(cbd); + + switch (cbd[0]) { + case CMD_ATAPI_READ_10: + g_assert_cmpuint(nsectors, <=, UINT16_MAX); + stw_be_p(&cbd[7], nsectors); + break; + case CMD_ATAPI_READ_CD: + /* 24bit BE store */ + g_assert_cmpuint(nsectors, <, 1ULL << 24); + tmp = nsectors; + cbd[6] = (tmp & 0xFF0000) >> 16; + cbd[7] = (tmp & 0xFF00) >> 8; + cbd[8] = (tmp & 0xFF); + break; + case CMD_ATAPI_REQUEST_SENSE: + g_assert_cmpuint(xbytes, <=, UINT8_MAX); + cbd[4] = (uint8_t)xbytes; + break; + case CMD_ATAPI_TEST_UNIT_READY: + case CMD_ATAPI_START_STOP_UNIT: + g_assert_cmpuint(xbytes, ==, 0); + break; + default: + /* SCSI doesn't have uniform packet formats, + * so you have to add support for it manually. Sorry! */ + fprintf(stderr, "The Libqos AHCI driver does not support the set_size " + "operation for ATAPI command 0x%02x, please add support.\n", + cbd[0]); + g_assert_not_reached(); + } +} + +void ahci_command_set_sizes(AHCICommand *cmd, uint64_t xbytes, + unsigned prd_size) +{ + uint16_t sect_count; + + /* Each PRD can describe up to 4MiB, and must not be odd. */ + g_assert_cmphex(prd_size, <=, 4096 * 1024); + g_assert_cmphex(prd_size & 0x01, ==, 0x00); + if (prd_size) { + cmd->prd_size = prd_size; + } + cmd->xbytes = xbytes; + sect_count = (cmd->xbytes / cmd->sector_size); + + if (cmd->props->ncq) { + NCQFIS *nfis = (NCQFIS *)&(cmd->fis); + nfis->sector_low = sect_count & 0xFF; + nfis->sector_hi = (sect_count >> 8) & 0xFF; + } else if (cmd->props->atapi) { + ahci_atapi_set_size(cmd, xbytes); + } else { + /* For writes, the PIO Setup FIS interrupt only comes from DRQs + * after the first. + */ + if (cmd->props->pio && sect_count > (cmd->props->read ? 0 : 1)) { + cmd->interrupts |= AHCI_PX_IS_PSS; + } + cmd->fis.count = sect_count; + } + cmd->header.prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size); +} + +void ahci_command_set_size(AHCICommand *cmd, uint64_t xbytes) +{ + ahci_command_set_sizes(cmd, xbytes, cmd->prd_size); +} + +void ahci_command_set_prd_size(AHCICommand *cmd, unsigned prd_size) +{ + ahci_command_set_sizes(cmd, cmd->xbytes, prd_size); +} + +void ahci_command_adjust(AHCICommand *cmd, uint64_t offset, uint64_t buffer, + uint64_t xbytes, unsigned prd_size) +{ + ahci_command_set_sizes(cmd, xbytes, prd_size); + ahci_command_set_buffer(cmd, buffer); + ahci_command_set_offset(cmd, offset); +} + +void ahci_command_commit(AHCIQState *ahci, AHCICommand *cmd, uint8_t port) +{ + uint16_t i, prdtl; + uint64_t table_size, table_ptr, remaining; + PRD prd; + + /* This command is now tied to this port/command slot */ + cmd->port = port; + cmd->slot = ahci_pick_cmd(ahci, port); + + if (cmd->props->ncq) { + NCQFIS *nfis = (NCQFIS *)&cmd->fis; + nfis->tag = (cmd->slot << 3) & 0xFC; + } + + /* Create a buffer for the command table */ + prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size); + table_size = CMD_TBL_SIZ(prdtl); + table_ptr = ahci_alloc(ahci, table_size); + g_assert(table_ptr); + /* AHCI 1.3: Must be aligned to 0x80 */ + g_assert((table_ptr & 0x7F) == 0x00); + cmd->header.ctba = table_ptr; + + /* Commit the command header (part of the Command List Buffer) */ + ahci_set_command_header(ahci, port, cmd->slot, &(cmd->header)); + /* Now, write the command table (FIS, ACMD, and PRDT) -- FIS first, */ + ahci_write_fis(ahci, cmd); + /* Then ATAPI CMD, if needed */ + if (cmd->props->atapi) { + qtest_memwrite(ahci->parent->qts, table_ptr + 0x40, cmd->atapi_cmd, 16); + } + + /* Construct and write the PRDs to the command table */ + g_assert_cmphex(prdtl, ==, cmd->header.prdtl); + remaining = cmd->xbytes; + for (i = 0; i < prdtl; ++i) { + prd.dba = cpu_to_le64(cmd->buffer + (cmd->prd_size * i)); + prd.res = 0; + if (remaining > cmd->prd_size) { + /* Note that byte count is 0-based. */ + prd.dbc = cpu_to_le32(cmd->prd_size - 1); + remaining -= cmd->prd_size; + } else { + /* Again, dbc is 0-based. */ + prd.dbc = cpu_to_le32(remaining - 1); + remaining = 0; + } + prd.dbc |= cpu_to_le32(0x80000000); /* Request DPS Interrupt */ + + /* Commit the PRD entry to the Command Table */ + qtest_memwrite(ahci->parent->qts, table_ptr + 0x80 + (i * sizeof(PRD)), + &prd, sizeof(PRD)); + } + + /* Bookmark the PRDTL and CTBA values */ + ahci->port[port].ctba[cmd->slot] = table_ptr; + ahci->port[port].prdtl[cmd->slot] = prdtl; +} + +void ahci_command_issue_async(AHCIQState *ahci, AHCICommand *cmd) +{ + if (cmd->props->ncq) { + ahci_px_wreg(ahci, cmd->port, AHCI_PX_SACT, (1 << cmd->slot)); + } + + ahci_px_wreg(ahci, cmd->port, AHCI_PX_CI, (1 << cmd->slot)); +} + +void ahci_command_wait(AHCIQState *ahci, AHCICommand *cmd) +{ + /* We can't rely on STS_BSY until the command has started processing. + * Therefore, we also use the Command Issue bit as indication of + * a command in-flight. */ + +#define RSET(REG, MASK) (BITSET(ahci_px_rreg(ahci, cmd->port, (REG)), (MASK))) + + while (RSET(AHCI_PX_TFD, AHCI_PX_TFD_STS_BSY) || + RSET(AHCI_PX_CI, 1 << cmd->slot) || + (cmd->props->ncq && RSET(AHCI_PX_SACT, 1 << cmd->slot))) { + usleep(50); + } + +} + +void ahci_command_issue(AHCIQState *ahci, AHCICommand *cmd) +{ + ahci_command_issue_async(ahci, cmd); + ahci_command_wait(ahci, cmd); +} + +void ahci_command_verify(AHCIQState *ahci, AHCICommand *cmd) +{ + uint8_t slot = cmd->slot; + uint8_t port = cmd->port; + + ahci_port_check_error(ahci, port, cmd->interrupts, cmd->errors); + ahci_port_check_interrupts(ahci, port, cmd->interrupts); + ahci_port_check_nonbusy(ahci, port, slot); + ahci_port_check_cmd_sanity(ahci, cmd); + if (cmd->interrupts & AHCI_PX_IS_DHRS) { + ahci_port_check_d2h_sanity(ahci, port, slot); + } + if (cmd->props->pio) { + ahci_port_check_pio_sanity(ahci, cmd); + } +} + +uint8_t ahci_command_slot(AHCICommand *cmd) +{ + return cmd->slot; +} diff --git a/tests/qtest/libqos/ahci.h b/tests/qtest/libqos/ahci.h new file mode 100644 index 0000000000..f05b3e5fce --- /dev/null +++ b/tests/qtest/libqos/ahci.h @@ -0,0 +1,651 @@ +#ifndef LIBQOS_AHCI_H +#define LIBQOS_AHCI_H + +/* + * AHCI qtest library functions and definitions + * + * Copyright (c) 2014 John Snow + * + * 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 "libqos/libqos.h" +#include "libqos/pci.h" +#include "libqos/malloc-pc.h" + +/*** Supplementary PCI Config Space IDs & Masks ***/ +#define PCI_DEVICE_ID_INTEL_Q35_AHCI (0x2922) +#define PCI_MSI_FLAGS_RESERVED (0xFF00) +#define PCI_PM_CTRL_RESERVED (0xFC) +#define PCI_BCC(REG32) ((REG32) >> 24) +#define PCI_PI(REG32) (((REG32) >> 8) & 0xFF) +#define PCI_SCC(REG32) (((REG32) >> 16) & 0xFF) + +/*** Recognized AHCI Device Types ***/ +#define AHCI_INTEL_ICH9 (PCI_DEVICE_ID_INTEL_Q35_AHCI << 16 | \ + PCI_VENDOR_ID_INTEL) + +/*** AHCI/HBA Register Offsets and Bitmasks ***/ +#define AHCI_CAP (0) +#define AHCI_CAP_NP (0x1F) +#define AHCI_CAP_SXS (0x20) +#define AHCI_CAP_EMS (0x40) +#define AHCI_CAP_CCCS (0x80) +#define AHCI_CAP_NCS (0x1F00) +#define AHCI_CAP_PSC (0x2000) +#define AHCI_CAP_SSC (0x4000) +#define AHCI_CAP_PMD (0x8000) +#define AHCI_CAP_FBSS (0x10000) +#define AHCI_CAP_SPM (0x20000) +#define AHCI_CAP_SAM (0x40000) +#define AHCI_CAP_RESERVED (0x80000) +#define AHCI_CAP_ISS (0xF00000) +#define AHCI_CAP_SCLO (0x1000000) +#define AHCI_CAP_SAL (0x2000000) +#define AHCI_CAP_SALP (0x4000000) +#define AHCI_CAP_SSS (0x8000000) +#define AHCI_CAP_SMPS (0x10000000) +#define AHCI_CAP_SSNTF (0x20000000) +#define AHCI_CAP_SNCQ (0x40000000) +#define AHCI_CAP_S64A (0x80000000) + +#define AHCI_GHC (1) +#define AHCI_GHC_HR (0x01) +#define AHCI_GHC_IE (0x02) +#define AHCI_GHC_MRSM (0x04) +#define AHCI_GHC_RESERVED (0x7FFFFFF8) +#define AHCI_GHC_AE (0x80000000) + +#define AHCI_IS (2) +#define AHCI_PI (3) +#define AHCI_VS (4) + +#define AHCI_CCCCTL (5) +#define AHCI_CCCCTL_EN (0x01) +#define AHCI_CCCCTL_RESERVED (0x06) +#define AHCI_CCCCTL_CC (0xFF00) +#define AHCI_CCCCTL_TV (0xFFFF0000) + +#define AHCI_CCCPORTS (6) +#define AHCI_EMLOC (7) + +#define AHCI_EMCTL (8) +#define AHCI_EMCTL_STSMR (0x01) +#define AHCI_EMCTL_CTLTM (0x100) +#define AHCI_EMCTL_CTLRST (0x200) +#define AHCI_EMCTL_RESERVED (0xF0F0FCFE) + +#define AHCI_CAP2 (9) +#define AHCI_CAP2_BOH (0x01) +#define AHCI_CAP2_NVMP (0x02) +#define AHCI_CAP2_APST (0x04) +#define AHCI_CAP2_RESERVED (0xFFFFFFF8) + +#define AHCI_BOHC (10) +#define AHCI_RESERVED (11) +#define AHCI_NVMHCI (24) +#define AHCI_VENDOR (40) +#define AHCI_PORTS (64) + +/*** Port Memory Offsets & Bitmasks ***/ +#define AHCI_PX_CLB (0) +#define AHCI_PX_CLB_RESERVED (0x1FF) + +#define AHCI_PX_CLBU (1) + +#define AHCI_PX_FB (2) +#define AHCI_PX_FB_RESERVED (0xFF) + +#define AHCI_PX_FBU (3) + +#define AHCI_PX_IS (4) +#define AHCI_PX_IS_DHRS (0x1) +#define AHCI_PX_IS_PSS (0x2) +#define AHCI_PX_IS_DSS (0x4) +#define AHCI_PX_IS_SDBS (0x8) +#define AHCI_PX_IS_UFS (0x10) +#define AHCI_PX_IS_DPS (0x20) +#define AHCI_PX_IS_PCS (0x40) +#define AHCI_PX_IS_DMPS (0x80) +#define AHCI_PX_IS_RESERVED (0x23FFF00) +#define AHCI_PX_IS_PRCS (0x400000) +#define AHCI_PX_IS_IPMS (0x800000) +#define AHCI_PX_IS_OFS (0x1000000) +#define AHCI_PX_IS_INFS (0x4000000) +#define AHCI_PX_IS_IFS (0x8000000) +#define AHCI_PX_IS_HBDS (0x10000000) +#define AHCI_PX_IS_HBFS (0x20000000) +#define AHCI_PX_IS_TFES (0x40000000) +#define AHCI_PX_IS_CPDS (0x80000000) + +#define AHCI_PX_IE (5) +#define AHCI_PX_IE_DHRE (0x1) +#define AHCI_PX_IE_PSE (0x2) +#define AHCI_PX_IE_DSE (0x4) +#define AHCI_PX_IE_SDBE (0x8) +#define AHCI_PX_IE_UFE (0x10) +#define AHCI_PX_IE_DPE (0x20) +#define AHCI_PX_IE_PCE (0x40) +#define AHCI_PX_IE_DMPE (0x80) +#define AHCI_PX_IE_RESERVED (0x23FFF00) +#define AHCI_PX_IE_PRCE (0x400000) +#define AHCI_PX_IE_IPME (0x800000) +#define AHCI_PX_IE_OFE (0x1000000) +#define AHCI_PX_IE_INFE (0x4000000) +#define AHCI_PX_IE_IFE (0x8000000) +#define AHCI_PX_IE_HBDE (0x10000000) +#define AHCI_PX_IE_HBFE (0x20000000) +#define AHCI_PX_IE_TFEE (0x40000000) +#define AHCI_PX_IE_CPDE (0x80000000) + +#define AHCI_PX_CMD (6) +#define AHCI_PX_CMD_ST (0x1) +#define AHCI_PX_CMD_SUD (0x2) +#define AHCI_PX_CMD_POD (0x4) +#define AHCI_PX_CMD_CLO (0x8) +#define AHCI_PX_CMD_FRE (0x10) +#define AHCI_PX_CMD_RESERVED (0xE0) +#define AHCI_PX_CMD_CCS (0x1F00) +#define AHCI_PX_CMD_MPSS (0x2000) +#define AHCI_PX_CMD_FR (0x4000) +#define AHCI_PX_CMD_CR (0x8000) +#define AHCI_PX_CMD_CPS (0x10000) +#define AHCI_PX_CMD_PMA (0x20000) +#define AHCI_PX_CMD_HPCP (0x40000) +#define AHCI_PX_CMD_MPSP (0x80000) +#define AHCI_PX_CMD_CPD (0x100000) +#define AHCI_PX_CMD_ESP (0x200000) +#define AHCI_PX_CMD_FBSCP (0x400000) +#define AHCI_PX_CMD_APSTE (0x800000) +#define AHCI_PX_CMD_ATAPI (0x1000000) +#define AHCI_PX_CMD_DLAE (0x2000000) +#define AHCI_PX_CMD_ALPE (0x4000000) +#define AHCI_PX_CMD_ASP (0x8000000) +#define AHCI_PX_CMD_ICC (0xF0000000) + +#define AHCI_PX_RES1 (7) + +#define AHCI_PX_TFD (8) +#define AHCI_PX_TFD_STS (0xFF) +#define AHCI_PX_TFD_STS_ERR (0x01) +#define AHCI_PX_TFD_STS_CS1 (0x06) +#define AHCI_PX_TFD_STS_DRQ (0x08) +#define AHCI_PX_TFD_STS_CS2 (0x70) +#define AHCI_PX_TFD_STS_BSY (0x80) +#define AHCI_PX_TFD_ERR (0xFF00) +#define AHCI_PX_TFD_RESERVED (0xFFFF0000) + +#define AHCI_PX_SIG (9) +#define AHCI_PX_SIG_SECTOR_COUNT (0xFF) +#define AHCI_PX_SIG_LBA_LOW (0xFF00) +#define AHCI_PX_SIG_LBA_MID (0xFF0000) +#define AHCI_PX_SIG_LBA_HIGH (0xFF000000) + +#define AHCI_PX_SSTS (10) +#define AHCI_PX_SSTS_DET (0x0F) +#define AHCI_PX_SSTS_SPD (0xF0) +#define AHCI_PX_SSTS_IPM (0xF00) +#define AHCI_PX_SSTS_RESERVED (0xFFFFF000) +#define SSTS_DET_NO_DEVICE (0x00) +#define SSTS_DET_PRESENT (0x01) +#define SSTS_DET_ESTABLISHED (0x03) +#define SSTS_DET_OFFLINE (0x04) + +#define AHCI_PX_SCTL (11) + +#define AHCI_PX_SERR (12) +#define AHCI_PX_SERR_ERR (0xFFFF) +#define AHCI_PX_SERR_DIAG (0xFFFF0000) +#define AHCI_PX_SERR_DIAG_X (0x04000000) + +#define AHCI_PX_SACT (13) +#define AHCI_PX_CI (14) +#define AHCI_PX_SNTF (15) + +#define AHCI_PX_FBS (16) +#define AHCI_PX_FBS_EN (0x1) +#define AHCI_PX_FBS_DEC (0x2) +#define AHCI_PX_FBS_SDE (0x4) +#define AHCI_PX_FBS_DEV (0xF00) +#define AHCI_PX_FBS_ADO (0xF000) +#define AHCI_PX_FBS_DWE (0xF0000) +#define AHCI_PX_FBS_RESERVED (0xFFF000F8) + +#define AHCI_PX_RES2 (17) +#define AHCI_PX_VS (28) + +#define HBA_DATA_REGION_SIZE (256) +#define HBA_PORT_DATA_SIZE (128) +#define HBA_PORT_NUM_REG (HBA_PORT_DATA_SIZE/4) + +#define AHCI_VERSION_0_95 (0x00000905) +#define AHCI_VERSION_1_0 (0x00010000) +#define AHCI_VERSION_1_1 (0x00010100) +#define AHCI_VERSION_1_2 (0x00010200) +#define AHCI_VERSION_1_3 (0x00010300) + +#define AHCI_SECTOR_SIZE (512) +#define ATAPI_SECTOR_SIZE (2048) + +#define AHCI_SIGNATURE_CDROM (0xeb140101) +#define AHCI_SIGNATURE_DISK (0x00000101) + +/* FIS types */ +enum { + REG_H2D_FIS = 0x27, + REG_D2H_FIS = 0x34, + DMA_ACTIVATE_FIS = 0x39, + DMA_SETUP_FIS = 0x41, + DATA_FIS = 0x46, + BIST_ACTIVATE_FIS = 0x58, + PIO_SETUP_FIS = 0x5F, + SDB_FIS = 0xA1 +}; + +/* FIS flags */ +#define REG_H2D_FIS_CMD 0x80 + +/* ATA Commands */ +enum { + /* DMA */ + CMD_READ_DMA = 0xC8, + CMD_READ_DMA_EXT = 0x25, + CMD_WRITE_DMA = 0xCA, + CMD_WRITE_DMA_EXT = 0x35, + /* PIO */ + CMD_READ_PIO = 0x20, + CMD_READ_PIO_EXT = 0x24, + CMD_WRITE_PIO = 0x30, + CMD_WRITE_PIO_EXT = 0x34, + /* Misc */ + CMD_READ_MAX = 0xF8, + CMD_READ_MAX_EXT = 0x27, + CMD_FLUSH_CACHE = 0xE7, + CMD_IDENTIFY = 0xEC, + CMD_PACKET = 0xA0, + CMD_PACKET_ID = 0xA1, + /* NCQ */ + READ_FPDMA_QUEUED = 0x60, + WRITE_FPDMA_QUEUED = 0x61, +}; + +/* ATAPI Commands */ +enum { + CMD_ATAPI_TEST_UNIT_READY = 0x00, + CMD_ATAPI_REQUEST_SENSE = 0x03, + CMD_ATAPI_START_STOP_UNIT = 0x1b, + CMD_ATAPI_READ_10 = 0x28, + CMD_ATAPI_READ_CD = 0xbe, +}; + +enum { + SENSE_NO_SENSE = 0x00, + SENSE_NOT_READY = 0x02, + SENSE_UNIT_ATTENTION = 0x06, +}; + +enum { + ASC_MEDIUM_MAY_HAVE_CHANGED = 0x28, + ASC_MEDIUM_NOT_PRESENT = 0x3a, +}; + +/* AHCI Command Header Flags & Masks*/ +#define CMDH_CFL (0x1F) +#define CMDH_ATAPI (0x20) +#define CMDH_WRITE (0x40) +#define CMDH_PREFETCH (0x80) +#define CMDH_RESET (0x100) +#define CMDH_BIST (0x200) +#define CMDH_CLR_BSY (0x400) +#define CMDH_RES (0x800) +#define CMDH_PMP (0xF000) + +/* ATA device register masks */ +#define ATA_DEVICE_MAGIC 0xA0 /* used in ata1-3 */ +#define ATA_DEVICE_LBA 0x40 +#define NCQ_DEVICE_MAGIC 0x40 /* for ncq device registers */ +#define ATA_DEVICE_DRIVE 0x10 +#define ATA_DEVICE_HEAD 0x0F + +/*** Structures ***/ + +typedef struct AHCIPortQState { + uint64_t fb; + uint64_t clb; + uint64_t ctba[32]; + uint16_t prdtl[32]; + uint8_t next; /** Next Command Slot to Use **/ +} AHCIPortQState; + +typedef struct AHCIQState { + QOSState *parent; + QPCIDevice *dev; + QPCIBar hba_bar; + uint64_t barsize; + uint32_t fingerprint; + uint32_t cap; + uint32_t cap2; + AHCIPortQState port[32]; + bool enabled; +} AHCIQState; + +/** + * Generic FIS structure. + */ +typedef struct FIS { + uint8_t fis_type; + uint8_t flags; + char data[0]; +} __attribute__((__packed__)) FIS; + +/** + * Register device-to-host FIS structure. + */ +typedef struct RegD2HFIS { + /* DW0 */ + uint8_t fis_type; + uint8_t flags; + uint8_t status; + uint8_t error; + /* DW1 */ + uint8_t lba_lo[3]; + uint8_t device; + /* DW2 */ + uint8_t lba_hi[3]; + uint8_t res0; + /* DW3 */ + uint16_t count; + uint16_t res1; + /* DW4 */ + uint32_t res2; +} __attribute__((__packed__)) RegD2HFIS; + +/** + * Register device-to-host FIS structure; + * PIO Setup variety. + */ +typedef struct PIOSetupFIS { + /* DW0 */ + uint8_t fis_type; + uint8_t flags; + uint8_t status; + uint8_t error; + /* DW1 */ + uint8_t lba_lo[3]; + uint8_t device; + /* DW2 */ + uint8_t lba_hi[3]; + uint8_t res0; + /* DW3 */ + uint16_t count; + uint8_t res1; + uint8_t e_status; + /* DW4 */ + uint16_t tx_count; + uint16_t res2; +} __attribute__((__packed__)) PIOSetupFIS; + +/** + * Register host-to-device FIS structure. + */ +typedef struct RegH2DFIS { + /* DW0 */ + uint8_t fis_type; + uint8_t flags; + uint8_t command; + uint8_t feature_low; + /* DW1 */ + uint8_t lba_lo[3]; + uint8_t device; + /* DW2 */ + uint8_t lba_hi[3]; + uint8_t feature_high; + /* DW3 */ + uint16_t count; + uint8_t icc; + uint8_t control; + /* DW4 */ + uint8_t aux[4]; +} __attribute__((__packed__)) RegH2DFIS; + +/** + * Register host-to-device FIS structure, for NCQ commands. + * Actually just a RegH2DFIS, but with fields repurposed. + * Repurposed fields are annotated below. + */ +typedef struct NCQFIS { + /* DW0 */ + uint8_t fis_type; + uint8_t flags; + uint8_t command; + uint8_t sector_low; /* H2D: Feature 7:0 */ + /* DW1 */ + uint8_t lba_lo[3]; + uint8_t device; + /* DW2 */ + uint8_t lba_hi[3]; + uint8_t sector_hi; /* H2D: Feature 15:8 */ + /* DW3 */ + uint8_t tag; /* H2D: Count 0:7 */ + uint8_t prio; /* H2D: Count 15:8 */ + uint8_t icc; + uint8_t control; + /* DW4 */ + uint8_t aux[4]; +} __attribute__((__packed__)) NCQFIS; + +/** + * Command List entry structure. + * The command list contains between 1-32 of these structures. + */ +typedef struct AHCICommandHeader { + uint16_t flags; /* Cmd-Fis-Len, PMP#, and flags. */ + uint16_t prdtl; /* Phys Region Desc. Table Length */ + uint32_t prdbc; /* Phys Region Desc. Byte Count */ + uint64_t ctba; /* Command Table Descriptor Base Address */ + uint32_t res[4]; +} __attribute__((__packed__)) AHCICommandHeader; + +/** + * Physical Region Descriptor; pointed to by the Command List Header, + * struct ahci_command. + */ +typedef struct PRD { + uint64_t dba; /* Data Base Address */ + uint32_t res; /* Reserved */ + uint32_t dbc; /* Data Byte Count (0-indexed) & Interrupt Flag (bit 2^31) */ +} __attribute__((__packed__)) PRD; + +/* Opaque, defined within ahci.c */ +typedef struct AHCICommand AHCICommand; + +/* Options to ahci_exec */ +typedef struct AHCIOpts { + size_t size; /* Size of transfer */ + unsigned prd_size; /* Size per-each PRD */ + bool set_bcl; /* Override the default BCL of ATAPI_SECTOR_SIZE */ + unsigned bcl; /* Byte Count Limit, for ATAPI PIO */ + uint64_t lba; /* Starting LBA offset */ + uint64_t buffer; /* Pointer to source or destination guest buffer */ + bool atapi; /* ATAPI command? */ + bool atapi_dma; /* Use DMA for ATAPI? */ + bool error; + int (*pre_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *); + int (*mid_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *); + int (*post_cb)(AHCIQState*, AHCICommand*, const struct AHCIOpts *); + void *opaque; +} AHCIOpts; + +/*** Macro Utilities ***/ +#define BITANY(data, mask) (((data) & (mask)) != 0) +#define BITSET(data, mask) (((data) & (mask)) == (mask)) +#define BITCLR(data, mask) (((data) & (mask)) == 0) +#define ASSERT_BIT_SET(data, mask) g_assert_cmphex((data) & (mask), ==, (mask)) +#define ASSERT_BIT_CLEAR(data, mask) g_assert_cmphex((data) & (mask), ==, 0) + +/* For calculating how big the PRD table needs to be: */ +#define CMD_TBL_SIZ(n) ((0x80 + ((n) * sizeof(PRD)) + 0x7F) & ~0x7F) + +/* Helpers for reading/writing AHCI HBA register values */ + +static inline uint32_t ahci_mread(AHCIQState *ahci, size_t offset) +{ + return qpci_io_readl(ahci->dev, ahci->hba_bar, offset); +} + +static inline void ahci_mwrite(AHCIQState *ahci, size_t offset, uint32_t value) +{ + qpci_io_writel(ahci->dev, ahci->hba_bar, offset, value); +} + +static inline uint32_t ahci_rreg(AHCIQState *ahci, uint32_t reg_num) +{ + return ahci_mread(ahci, 4 * reg_num); +} + +static inline void ahci_wreg(AHCIQState *ahci, uint32_t reg_num, uint32_t value) +{ + ahci_mwrite(ahci, 4 * reg_num, value); +} + +static inline void ahci_set(AHCIQState *ahci, uint32_t reg_num, uint32_t mask) +{ + ahci_wreg(ahci, reg_num, ahci_rreg(ahci, reg_num) | mask); +} + +static inline void ahci_clr(AHCIQState *ahci, uint32_t reg_num, uint32_t mask) +{ + ahci_wreg(ahci, reg_num, ahci_rreg(ahci, reg_num) & ~mask); +} + +static inline size_t ahci_px_offset(uint8_t port, uint32_t reg_num) +{ + return AHCI_PORTS + (HBA_PORT_NUM_REG * port) + reg_num; +} + +static inline uint32_t ahci_px_rreg(AHCIQState *ahci, uint8_t port, + uint32_t reg_num) +{ + return ahci_rreg(ahci, ahci_px_offset(port, reg_num)); +} + +static inline void ahci_px_wreg(AHCIQState *ahci, uint8_t port, + uint32_t reg_num, uint32_t value) +{ + ahci_wreg(ahci, ahci_px_offset(port, reg_num), value); +} + +static inline void ahci_px_set(AHCIQState *ahci, uint8_t port, + uint32_t reg_num, uint32_t mask) +{ + ahci_px_wreg(ahci, port, reg_num, + ahci_px_rreg(ahci, port, reg_num) | mask); +} + +static inline void ahci_px_clr(AHCIQState *ahci, uint8_t port, + uint32_t reg_num, uint32_t mask) +{ + ahci_px_wreg(ahci, port, reg_num, + ahci_px_rreg(ahci, port, reg_num) & ~mask); +} + +/*** Prototypes ***/ +uint64_t ahci_alloc(AHCIQState *ahci, size_t bytes); +void ahci_free(AHCIQState *ahci, uint64_t addr); +void ahci_clean_mem(AHCIQState *ahci); + +/* Device management */ +QPCIDevice *get_ahci_device(QTestState *qts, uint32_t *fingerprint); +void free_ahci_device(QPCIDevice *dev); +void ahci_pci_enable(AHCIQState *ahci); +void start_ahci_device(AHCIQState *ahci); +void ahci_hba_enable(AHCIQState *ahci); + +/* Port Management */ +unsigned ahci_port_select(AHCIQState *ahci); +void ahci_port_clear(AHCIQState *ahci, uint8_t port); + +/* Command header / table management */ +unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port); +void ahci_get_command_header(AHCIQState *ahci, uint8_t port, + uint8_t slot, AHCICommandHeader *cmd); +void ahci_set_command_header(AHCIQState *ahci, uint8_t port, + uint8_t slot, AHCICommandHeader *cmd); +void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot); + +/* AHCI sanity check routines */ +void ahci_port_check_error(AHCIQState *ahci, uint8_t port, + uint32_t imask, uint8_t emask); +void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port, + uint32_t intr_mask); +void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot); +void ahci_port_check_d2h_sanity(AHCIQState *ahci, uint8_t port, uint8_t slot); +void ahci_port_check_pio_sanity(AHCIQState *ahci, AHCICommand *cmd); +void ahci_port_check_cmd_sanity(AHCIQState *ahci, AHCICommand *cmd); + +/* Misc */ +bool is_atapi(AHCIQState *ahci, uint8_t port); +unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd); + +/* Command: Macro level execution */ +void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, + uint64_t gbuffer, size_t size, uint64_t sector); +AHCICommand *ahci_guest_io_halt(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, + uint64_t gbuffer, size_t size, uint64_t sector); +void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd); +void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd, + void *buffer, size_t bufsize, uint64_t sector); +void ahci_exec(AHCIQState *ahci, uint8_t port, + uint8_t op, const AHCIOpts *opts); +void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port, bool ready, + uint8_t expected_sense); +void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port, + uint8_t *sense, uint8_t *asc); +void ahci_atapi_eject(AHCIQState *ahci, uint8_t port); +void ahci_atapi_load(AHCIQState *ahci, uint8_t port); + +/* Command: Fine-grained lifecycle */ +AHCICommand *ahci_command_create(uint8_t command_name); +AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl, bool dma); +void ahci_command_commit(AHCIQState *ahci, AHCICommand *cmd, uint8_t port); +void ahci_command_issue(AHCIQState *ahci, AHCICommand *cmd); +void ahci_command_issue_async(AHCIQState *ahci, AHCICommand *cmd); +void ahci_command_wait(AHCIQState *ahci, AHCICommand *cmd); +void ahci_command_verify(AHCIQState *ahci, AHCICommand *cmd); +void ahci_command_free(AHCICommand *cmd); + +/* Command: adjustments */ +void ahci_command_set_flags(AHCICommand *cmd, uint16_t cmdh_flags); +void ahci_command_clr_flags(AHCICommand *cmd, uint16_t cmdh_flags); +void ahci_command_set_offset(AHCICommand *cmd, uint64_t lba_sect); +void ahci_command_set_buffer(AHCICommand *cmd, uint64_t buffer); +void ahci_command_set_size(AHCICommand *cmd, uint64_t xbytes); +void ahci_command_set_prd_size(AHCICommand *cmd, unsigned prd_size); +void ahci_command_set_sizes(AHCICommand *cmd, uint64_t xbytes, + unsigned prd_size); +void ahci_command_set_acmd(AHCICommand *cmd, void *acmd); +void ahci_command_enable_atapi_dma(AHCICommand *cmd); +void ahci_command_adjust(AHCICommand *cmd, uint64_t lba_sect, uint64_t gbuffer, + uint64_t xbytes, unsigned prd_size); + +/* Command: Misc */ +uint8_t ahci_command_slot(AHCICommand *cmd); +void ahci_write_fis(AHCIQState *ahci, AHCICommand *cmd); + +#endif diff --git a/tests/qtest/libqos/arm-imx25-pdk-machine.c b/tests/qtest/libqos/arm-imx25-pdk-machine.c new file mode 100644 index 0000000000..25066fb8a9 --- /dev/null +++ b/tests/qtest/libqos/arm-imx25-pdk-machine.c @@ -0,0 +1,92 @@ +/* + * libqos driver framework + * + * Copyright (c) 2019 Red Hat, Inc. + * + * Author: Paolo Bonzini + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "libqos/i2c.h" + +#define ARM_PAGE_SIZE 4096 +#define IMX25_PDK_RAM_START 0x80000000 +#define IMX25_PDK_RAM_END 0x88000000 + +typedef struct QIMX25PDKMachine QIMX25PDKMachine; + +struct QIMX25PDKMachine { + QOSGraphObject obj; + QGuestAllocator alloc; + IMXI2C i2c_1; +}; + +static void *imx25_pdk_get_driver(void *object, const char *interface) +{ + QIMX25PDKMachine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/imx25_pdk\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *imx25_pdk_get_device(void *obj, const char *device) +{ + QIMX25PDKMachine *machine = obj; + if (!g_strcmp0(device, "imx.i2c")) { + return &machine->i2c_1.obj; + } + + fprintf(stderr, "%s not present in arm/imx25_pdk\n", device); + g_assert_not_reached(); +} + +static void imx25_pdk_destructor(QOSGraphObject *obj) +{ + QIMX25PDKMachine *machine = (QIMX25PDKMachine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_arm_imx25_pdk(QTestState *qts) +{ + QIMX25PDKMachine *machine = g_new0(QIMX25PDKMachine, 1); + + alloc_init(&machine->alloc, 0, + IMX25_PDK_RAM_START, + IMX25_PDK_RAM_END, + ARM_PAGE_SIZE); + machine->obj.get_device = imx25_pdk_get_device; + machine->obj.get_driver = imx25_pdk_get_driver; + machine->obj.destructor = imx25_pdk_destructor; + + imx_i2c_init(&machine->i2c_1, qts, 0x43f80000); + return &machine->obj; +} + +static void imx25_pdk_register_nodes(void) +{ + QOSGraphEdgeOptions edge = { + .extra_device_opts = "bus=i2c-bus.0" + }; + qos_node_create_machine("arm/imx25-pdk", qos_create_machine_arm_imx25_pdk); + qos_node_contains("arm/imx25-pdk", "imx.i2c", &edge, NULL); +} + +libqos_init(imx25_pdk_register_nodes); diff --git a/tests/qtest/libqos/arm-n800-machine.c b/tests/qtest/libqos/arm-n800-machine.c new file mode 100644 index 0000000000..87279bdb26 --- /dev/null +++ b/tests/qtest/libqos/arm-n800-machine.c @@ -0,0 +1,92 @@ +/* + * libqos driver framework + * + * Copyright (c) 2019 Red Hat, Inc. + * + * Author: Paolo Bonzini + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "libqos/i2c.h" + +#define ARM_PAGE_SIZE 4096 +#define N800_RAM_START 0x80000000 +#define N800_RAM_END 0x88000000 + +typedef struct QN800Machine QN800Machine; + +struct QN800Machine { + QOSGraphObject obj; + QGuestAllocator alloc; + OMAPI2C i2c_1; +}; + +static void *n800_get_driver(void *object, const char *interface) +{ + QN800Machine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/n800\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *n800_get_device(void *obj, const char *device) +{ + QN800Machine *machine = obj; + if (!g_strcmp0(device, "omap_i2c")) { + return &machine->i2c_1.obj; + } + + fprintf(stderr, "%s not present in arm/n800\n", device); + g_assert_not_reached(); +} + +static void n800_destructor(QOSGraphObject *obj) +{ + QN800Machine *machine = (QN800Machine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_arm_n800(QTestState *qts) +{ + QN800Machine *machine = g_new0(QN800Machine, 1); + + alloc_init(&machine->alloc, 0, + N800_RAM_START, + N800_RAM_END, + ARM_PAGE_SIZE); + machine->obj.get_device = n800_get_device; + machine->obj.get_driver = n800_get_driver; + machine->obj.destructor = n800_destructor; + + omap_i2c_init(&machine->i2c_1, qts, 0x48070000); + return &machine->obj; +} + +static void n800_register_nodes(void) +{ + QOSGraphEdgeOptions edge = { + .extra_device_opts = "bus=i2c-bus.0" + }; + qos_node_create_machine("arm/n800", qos_create_machine_arm_n800); + qos_node_contains("arm/n800", "omap_i2c", &edge, NULL); +} + +libqos_init(n800_register_nodes); diff --git a/tests/qtest/libqos/arm-raspi2-machine.c b/tests/qtest/libqos/arm-raspi2-machine.c new file mode 100644 index 0000000000..12a7cb7e4b --- /dev/null +++ b/tests/qtest/libqos/arm-raspi2-machine.c @@ -0,0 +1,92 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "sdhci.h" + +#define ARM_PAGE_SIZE 4096 +#define RASPI2_RAM_ADDR 0 +#define RASPI2_RAM_SIZE 0x20000000 + +typedef struct QRaspi2Machine QRaspi2Machine; + +struct QRaspi2Machine { + QOSGraphObject obj; + QGuestAllocator alloc; + QSDHCI_MemoryMapped sdhci; +}; + +static void *raspi2_get_driver(void *object, const char *interface) +{ + QRaspi2Machine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/raspi2\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *raspi2_get_device(void *obj, const char *device) +{ + QRaspi2Machine *machine = obj; + if (!g_strcmp0(device, "generic-sdhci")) { + return &machine->sdhci.obj; + } + + fprintf(stderr, "%s not present in arm/raspi2\n", device); + g_assert_not_reached(); +} + +static void raspi2_destructor(QOSGraphObject *obj) +{ + QRaspi2Machine *machine = (QRaspi2Machine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_arm_raspi2(QTestState *qts) +{ + QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1); + + alloc_init(&machine->alloc, 0, + RASPI2_RAM_ADDR + (1 << 20), + RASPI2_RAM_ADDR + RASPI2_RAM_SIZE, + ARM_PAGE_SIZE); + machine->obj.get_device = raspi2_get_device; + machine->obj.get_driver = raspi2_get_driver; + machine->obj.destructor = raspi2_destructor; + qos_init_sdhci_mm(&machine->sdhci, qts, 0x3f300000, &(QSDHCIProperties) { + .version = 3, + .baseclock = 52, + .capab.sdma = false, + .capab.reg = 0x052134b4 + }); + return &machine->obj; +} + +static void raspi2_register_nodes(void) +{ + qos_node_create_machine("arm/raspi2", qos_create_machine_arm_raspi2); + qos_node_contains("arm/raspi2", "generic-sdhci", NULL); +} + +libqos_init(raspi2_register_nodes); diff --git a/tests/qtest/libqos/arm-sabrelite-machine.c b/tests/qtest/libqos/arm-sabrelite-machine.c new file mode 100644 index 0000000000..e6df43795a --- /dev/null +++ b/tests/qtest/libqos/arm-sabrelite-machine.c @@ -0,0 +1,92 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "sdhci.h" + +#define ARM_PAGE_SIZE 4096 +#define SABRELITE_RAM_START 0x10000000 +#define SABRELITE_RAM_END 0x30000000 + +typedef struct QSabreliteMachine QSabreliteMachine; + +struct QSabreliteMachine { + QOSGraphObject obj; + QGuestAllocator alloc; + QSDHCI_MemoryMapped sdhci; +}; + +static void *sabrelite_get_driver(void *object, const char *interface) +{ + QSabreliteMachine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/sabrelite\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *sabrelite_get_device(void *obj, const char *device) +{ + QSabreliteMachine *machine = obj; + if (!g_strcmp0(device, "generic-sdhci")) { + return &machine->sdhci.obj; + } + + fprintf(stderr, "%s not present in arm/sabrelite\n", device); + g_assert_not_reached(); +} + +static void sabrelite_destructor(QOSGraphObject *obj) +{ + QSabreliteMachine *machine = (QSabreliteMachine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_arm_sabrelite(QTestState *qts) +{ + QSabreliteMachine *machine = g_new0(QSabreliteMachine, 1); + + alloc_init(&machine->alloc, 0, + SABRELITE_RAM_START, + SABRELITE_RAM_END, + ARM_PAGE_SIZE); + machine->obj.get_device = sabrelite_get_device; + machine->obj.get_driver = sabrelite_get_driver; + machine->obj.destructor = sabrelite_destructor; + qos_init_sdhci_mm(&machine->sdhci, qts, 0x02190000, &(QSDHCIProperties) { + .version = 3, + .baseclock = 0, + .capab.sdma = true, + .capab.reg = 0x057834b4, + }); + return &machine->obj; +} + +static void sabrelite_register_nodes(void) +{ + qos_node_create_machine("arm/sabrelite", qos_create_machine_arm_sabrelite); + qos_node_contains("arm/sabrelite", "generic-sdhci", NULL); +} + +libqos_init(sabrelite_register_nodes); diff --git a/tests/qtest/libqos/arm-smdkc210-machine.c b/tests/qtest/libqos/arm-smdkc210-machine.c new file mode 100644 index 0000000000..215b628a7d --- /dev/null +++ b/tests/qtest/libqos/arm-smdkc210-machine.c @@ -0,0 +1,92 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "sdhci.h" + +#define ARM_PAGE_SIZE 4096 +#define SMDKC210_RAM_ADDR 0x40000000ull +#define SMDKC210_RAM_SIZE 0x40000000ull + +typedef struct QSmdkc210Machine QSmdkc210Machine; + +struct QSmdkc210Machine { + QOSGraphObject obj; + QGuestAllocator alloc; + QSDHCI_MemoryMapped sdhci; +}; + +static void *smdkc210_get_driver(void *object, const char *interface) +{ + QSmdkc210Machine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/smdkc210\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *smdkc210_get_device(void *obj, const char *device) +{ + QSmdkc210Machine *machine = obj; + if (!g_strcmp0(device, "generic-sdhci")) { + return &machine->sdhci.obj; + } + + fprintf(stderr, "%s not present in arm/smdkc210\n", device); + g_assert_not_reached(); +} + +static void smdkc210_destructor(QOSGraphObject *obj) +{ + QSmdkc210Machine *machine = (QSmdkc210Machine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_arm_smdkc210(QTestState *qts) +{ + QSmdkc210Machine *machine = g_new0(QSmdkc210Machine, 1); + + alloc_init(&machine->alloc, 0, + SMDKC210_RAM_ADDR, + SMDKC210_RAM_ADDR + SMDKC210_RAM_SIZE, + ARM_PAGE_SIZE); + machine->obj.get_device = smdkc210_get_device; + machine->obj.get_driver = smdkc210_get_driver; + machine->obj.destructor = smdkc210_destructor; + qos_init_sdhci_mm(&machine->sdhci, qts, 0x12510000, &(QSDHCIProperties) { + .version = 2, + .baseclock = 0, + .capab.sdma = true, + .capab.reg = 0x5e80080, + }); + return &machine->obj; +} + +static void smdkc210_register_nodes(void) +{ + qos_node_create_machine("arm/smdkc210", qos_create_machine_arm_smdkc210); + qos_node_contains("arm/smdkc210", "generic-sdhci", NULL); +} + +libqos_init(smdkc210_register_nodes); diff --git a/tests/qtest/libqos/arm-virt-machine.c b/tests/qtest/libqos/arm-virt-machine.c new file mode 100644 index 0000000000..96ffe3ee5c --- /dev/null +++ b/tests/qtest/libqos/arm-virt-machine.c @@ -0,0 +1,91 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-mmio.h" + +#define ARM_PAGE_SIZE 4096 +#define VIRTIO_MMIO_BASE_ADDR 0x0A003E00 +#define ARM_VIRT_RAM_ADDR 0x40000000 +#define ARM_VIRT_RAM_SIZE 0x20000000 +#define VIRTIO_MMIO_SIZE 0x00000200 + +typedef struct QVirtMachine QVirtMachine; + +struct QVirtMachine { + QOSGraphObject obj; + QGuestAllocator alloc; + QVirtioMMIODevice virtio_mmio; +}; + +static void virt_destructor(QOSGraphObject *obj) +{ + QVirtMachine *machine = (QVirtMachine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *virt_get_driver(void *object, const char *interface) +{ + QVirtMachine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/virtio\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *virt_get_device(void *obj, const char *device) +{ + QVirtMachine *machine = obj; + if (!g_strcmp0(device, "virtio-mmio")) { + return &machine->virtio_mmio.obj; + } + + fprintf(stderr, "%s not present in arm/virtio\n", device); + g_assert_not_reached(); +} + +static void *qos_create_machine_arm_virt(QTestState *qts) +{ + QVirtMachine *machine = g_new0(QVirtMachine, 1); + + alloc_init(&machine->alloc, 0, + ARM_VIRT_RAM_ADDR, + ARM_VIRT_RAM_ADDR + ARM_VIRT_RAM_SIZE, + ARM_PAGE_SIZE); + qvirtio_mmio_init_device(&machine->virtio_mmio, qts, VIRTIO_MMIO_BASE_ADDR, + VIRTIO_MMIO_SIZE); + + machine->obj.get_device = virt_get_device; + machine->obj.get_driver = virt_get_driver; + machine->obj.destructor = virt_destructor; + return machine; +} + +static void virtio_mmio_register_nodes(void) +{ + qos_node_create_machine("arm/virt", qos_create_machine_arm_virt); + qos_node_contains("arm/virt", "virtio-mmio", NULL); +} + +libqos_init(virtio_mmio_register_nodes); diff --git a/tests/qtest/libqos/arm-xilinx-zynq-a9-machine.c b/tests/qtest/libqos/arm-xilinx-zynq-a9-machine.c new file mode 100644 index 0000000000..5bc95f2e73 --- /dev/null +++ b/tests/qtest/libqos/arm-xilinx-zynq-a9-machine.c @@ -0,0 +1,95 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "sdhci.h" + +typedef struct QXilinxZynqA9Machine QXilinxZynqA9Machine; + +struct QXilinxZynqA9Machine { + QOSGraphObject obj; + QGuestAllocator alloc; + QSDHCI_MemoryMapped sdhci; +}; + +#define ARM_PAGE_SIZE 4096 +#define XILINX_ZYNQ_A9_RAM_ADDR 0 +#define XILINX_ZYNQ_A9_RAM_SIZE 0x20000000 + +static void *xilinx_zynq_a9_get_driver(void *object, const char *interface) +{ + QXilinxZynqA9Machine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in arm/xilinx-zynq-a9\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *xilinx_zynq_a9_get_device(void *obj, const char *device) +{ + QXilinxZynqA9Machine *machine = obj; + if (!g_strcmp0(device, "generic-sdhci")) { + return &machine->sdhci.obj; + } + + fprintf(stderr, "%s not present in arm/xilinx-zynq-a9\n", device); + g_assert_not_reached(); +} + +static void xilinx_zynq_a9_destructor(QOSGraphObject *obj) +{ + QXilinxZynqA9Machine *machine = (QXilinxZynqA9Machine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *qos_create_machine_arm_xilinx_zynq_a9(QTestState *qts) +{ + QXilinxZynqA9Machine *machine = g_new0(QXilinxZynqA9Machine, 1); + + alloc_init(&machine->alloc, 0, + XILINX_ZYNQ_A9_RAM_ADDR + (1 << 20), + XILINX_ZYNQ_A9_RAM_ADDR + XILINX_ZYNQ_A9_RAM_SIZE, + ARM_PAGE_SIZE); + + machine->obj.get_device = xilinx_zynq_a9_get_device; + machine->obj.get_driver = xilinx_zynq_a9_get_driver; + machine->obj.destructor = xilinx_zynq_a9_destructor; + /* Datasheet: UG585 (v1.12.1) */ + qos_init_sdhci_mm(&machine->sdhci, qts, 0xe0100000, &(QSDHCIProperties) { + .version = 2, + .baseclock = 0, + .capab.sdma = true, + .capab.reg = 0x69ec0080, + }); + return &machine->obj; +} + +static void xilinx_zynq_a9_register_nodes(void) +{ + qos_node_create_machine("arm/xilinx-zynq-a9", + qos_create_machine_arm_xilinx_zynq_a9); + qos_node_contains("arm/xilinx-zynq-a9", "generic-sdhci", NULL); +} + +libqos_init(xilinx_zynq_a9_register_nodes); diff --git a/tests/qtest/libqos/e1000e.c b/tests/qtest/libqos/e1000e.c new file mode 100644 index 0000000000..560e7a2bb2 --- /dev/null +++ b/tests/qtest/libqos/e1000e.c @@ -0,0 +1,266 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.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/qgraph.h" +#include "e1000e.h" + +#define E1000E_IMS (0x00d0) + +#define E1000E_STATUS (0x0008) +#define E1000E_STATUS_LU BIT(1) +#define E1000E_STATUS_ASDV1000 BIT(9) + +#define E1000E_CTRL (0x0000) +#define E1000E_CTRL_RESET BIT(26) + +#define E1000E_RCTL (0x0100) +#define E1000E_RCTL_EN BIT(1) +#define E1000E_RCTL_UPE BIT(3) +#define E1000E_RCTL_MPE BIT(4) + +#define E1000E_RFCTL (0x5008) +#define E1000E_RFCTL_EXTEN BIT(15) + +#define E1000E_TCTL (0x0400) +#define E1000E_TCTL_EN BIT(1) + +#define E1000E_CTRL_EXT (0x0018) +#define E1000E_CTRL_EXT_DRV_LOAD BIT(28) +#define E1000E_CTRL_EXT_TXLSFLOW BIT(22) + +#define E1000E_IVAR (0x00E4) +#define E1000E_IVAR_TEST_CFG ((E1000E_RX0_MSG_ID << 0) | BIT(3) | \ + (E1000E_TX0_MSG_ID << 8) | BIT(11) | \ + (E1000E_OTHER_MSG_ID << 16) | BIT(19) | \ + BIT(31)) + +#define E1000E_RING_LEN (0x1000) + +#define E1000E_TDBAL (0x3800) + +#define E1000E_TDBAH (0x3804) +#define E1000E_TDH (0x3810) + +#define E1000E_RDBAL (0x2800) +#define E1000E_RDBAH (0x2804) +#define E1000E_RDH (0x2810) + +#define E1000E_TXD_LEN (16) +#define E1000E_RXD_LEN (16) + +static void e1000e_macreg_write(QE1000E *d, uint32_t reg, uint32_t val) +{ + QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); + qpci_io_writel(&d_pci->pci_dev, d_pci->mac_regs, reg, val); +} + +static uint32_t e1000e_macreg_read(QE1000E *d, uint32_t reg) +{ + QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); + return qpci_io_readl(&d_pci->pci_dev, d_pci->mac_regs, reg); +} + +void e1000e_tx_ring_push(QE1000E *d, void *descr) +{ + QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); + uint32_t tail = e1000e_macreg_read(d, E1000E_TDT); + uint32_t len = e1000e_macreg_read(d, E1000E_TDLEN) / E1000E_TXD_LEN; + + qtest_memwrite(d_pci->pci_dev.bus->qts, d->tx_ring + tail * E1000E_TXD_LEN, + descr, E1000E_TXD_LEN); + e1000e_macreg_write(d, E1000E_TDT, (tail + 1) % len); + + /* Read WB data for the packet transmitted */ + qtest_memread(d_pci->pci_dev.bus->qts, d->tx_ring + tail * E1000E_TXD_LEN, + descr, E1000E_TXD_LEN); +} + +void e1000e_rx_ring_push(QE1000E *d, void *descr) +{ + QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); + uint32_t tail = e1000e_macreg_read(d, E1000E_RDT); + uint32_t len = e1000e_macreg_read(d, E1000E_RDLEN) / E1000E_RXD_LEN; + + qtest_memwrite(d_pci->pci_dev.bus->qts, d->rx_ring + tail * E1000E_RXD_LEN, + descr, E1000E_RXD_LEN); + e1000e_macreg_write(d, E1000E_RDT, (tail + 1) % len); + + /* Read WB data for the packet received */ + qtest_memread(d_pci->pci_dev.bus->qts, d->rx_ring + tail * E1000E_RXD_LEN, + descr, E1000E_RXD_LEN); +} + +static void e1000e_foreach_callback(QPCIDevice *dev, int devfn, void *data) +{ + QPCIDevice *res = data; + memcpy(res, dev, sizeof(QPCIDevice)); + g_free(dev); +} + +void e1000e_wait_isr(QE1000E *d, uint16_t msg_id) +{ + QE1000E_PCI *d_pci = container_of(d, QE1000E_PCI, e1000e); + guint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; + + do { + if (qpci_msix_pending(&d_pci->pci_dev, msg_id)) { + return; + } + qtest_clock_step(d_pci->pci_dev.bus->qts, 10000); + } while (g_get_monotonic_time() < end_time); + + g_error("Timeout expired"); +} + +static void e1000e_pci_destructor(QOSGraphObject *obj) +{ + QE1000E_PCI *epci = (QE1000E_PCI *) obj; + qpci_iounmap(&epci->pci_dev, epci->mac_regs); + qpci_msix_disable(&epci->pci_dev); +} + +static void e1000e_pci_start_hw(QOSGraphObject *obj) +{ + QE1000E_PCI *d = (QE1000E_PCI *) obj; + uint32_t val; + + /* Enable the device */ + qpci_device_enable(&d->pci_dev); + + /* Reset the device */ + val = e1000e_macreg_read(&d->e1000e, E1000E_CTRL); + e1000e_macreg_write(&d->e1000e, E1000E_CTRL, val | E1000E_CTRL_RESET); + + /* Enable and configure MSI-X */ + qpci_msix_enable(&d->pci_dev); + e1000e_macreg_write(&d->e1000e, E1000E_IVAR, E1000E_IVAR_TEST_CFG); + + /* Check the device status - link and speed */ + val = e1000e_macreg_read(&d->e1000e, E1000E_STATUS); + g_assert_cmphex(val & (E1000E_STATUS_LU | E1000E_STATUS_ASDV1000), + ==, E1000E_STATUS_LU | E1000E_STATUS_ASDV1000); + + /* Initialize TX/RX logic */ + e1000e_macreg_write(&d->e1000e, E1000E_RCTL, 0); + e1000e_macreg_write(&d->e1000e, E1000E_TCTL, 0); + + /* Notify the device that the driver is ready */ + val = e1000e_macreg_read(&d->e1000e, E1000E_CTRL_EXT); + e1000e_macreg_write(&d->e1000e, E1000E_CTRL_EXT, + val | E1000E_CTRL_EXT_DRV_LOAD | E1000E_CTRL_EXT_TXLSFLOW); + + e1000e_macreg_write(&d->e1000e, E1000E_TDBAL, + (uint32_t) d->e1000e.tx_ring); + e1000e_macreg_write(&d->e1000e, E1000E_TDBAH, + (uint32_t) (d->e1000e.tx_ring >> 32)); + e1000e_macreg_write(&d->e1000e, E1000E_TDLEN, E1000E_RING_LEN); + e1000e_macreg_write(&d->e1000e, E1000E_TDT, 0); + e1000e_macreg_write(&d->e1000e, E1000E_TDH, 0); + + /* Enable transmit */ + e1000e_macreg_write(&d->e1000e, E1000E_TCTL, E1000E_TCTL_EN); + e1000e_macreg_write(&d->e1000e, E1000E_RDBAL, + (uint32_t)d->e1000e.rx_ring); + e1000e_macreg_write(&d->e1000e, E1000E_RDBAH, + (uint32_t)(d->e1000e.rx_ring >> 32)); + e1000e_macreg_write(&d->e1000e, E1000E_RDLEN, E1000E_RING_LEN); + e1000e_macreg_write(&d->e1000e, E1000E_RDT, 0); + e1000e_macreg_write(&d->e1000e, E1000E_RDH, 0); + + /* Enable receive */ + e1000e_macreg_write(&d->e1000e, E1000E_RFCTL, E1000E_RFCTL_EXTEN); + e1000e_macreg_write(&d->e1000e, E1000E_RCTL, E1000E_RCTL_EN | + E1000E_RCTL_UPE | + E1000E_RCTL_MPE); + + /* Enable all interrupts */ + e1000e_macreg_write(&d->e1000e, E1000E_IMS, 0xFFFFFFFF); + +} + +static void *e1000e_pci_get_driver(void *obj, const char *interface) +{ + QE1000E_PCI *epci = obj; + if (!g_strcmp0(interface, "e1000e-if")) { + return &epci->e1000e; + } + + /* implicit contains */ + if (!g_strcmp0(interface, "pci-device")) { + return &epci->pci_dev; + } + + fprintf(stderr, "%s not present in e1000e\n", interface); + g_assert_not_reached(); +} + +static void *e1000e_pci_create(void *pci_bus, QGuestAllocator *alloc, + void *addr) +{ + QE1000E_PCI *d = g_new0(QE1000E_PCI, 1); + QPCIBus *bus = pci_bus; + QPCIAddress *address = addr; + + qpci_device_foreach(bus, address->vendor_id, address->device_id, + e1000e_foreach_callback, &d->pci_dev); + + /* Map BAR0 (mac registers) */ + d->mac_regs = qpci_iomap(&d->pci_dev, 0, NULL); + + /* Allocate and setup TX ring */ + d->e1000e.tx_ring = guest_alloc(alloc, E1000E_RING_LEN); + g_assert(d->e1000e.tx_ring != 0); + + /* Allocate and setup RX ring */ + d->e1000e.rx_ring = guest_alloc(alloc, E1000E_RING_LEN); + g_assert(d->e1000e.rx_ring != 0); + + d->obj.get_driver = e1000e_pci_get_driver; + d->obj.start_hw = e1000e_pci_start_hw; + d->obj.destructor = e1000e_pci_destructor; + + return &d->obj; +} + +static void e1000e_register_nodes(void) +{ + QPCIAddress addr = { + .vendor_id = 0x8086, + .device_id = 0x10D3, + }; + + /* FIXME: every test using this node needs to setup a -netdev socket,id=hs0 + * otherwise QEMU is not going to start */ + QOSGraphEdgeOptions opts = { + .extra_device_opts = "netdev=hs0", + }; + add_qpci_address(&opts, &addr); + + qos_node_create_driver("e1000e", e1000e_pci_create); + qos_node_consumes("e1000e", "pci-bus", &opts); +} + +libqos_init(e1000e_register_nodes); diff --git a/tests/qtest/libqos/e1000e.h b/tests/qtest/libqos/e1000e.h new file mode 100644 index 0000000000..dc4ab10f58 --- /dev/null +++ b/tests/qtest/libqos/e1000e.h @@ -0,0 +1,53 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef QGRAPH_E1000E_H +#define QGRAPH_E1000E_H + +#include "libqos/qgraph.h" +#include "pci.h" + +#define E1000E_RX0_MSG_ID (0) +#define E1000E_TX0_MSG_ID (1) +#define E1000E_OTHER_MSG_ID (2) + +#define E1000E_TDLEN (0x3808) +#define E1000E_TDT (0x3818) +#define E1000E_RDLEN (0x2808) +#define E1000E_RDT (0x2818) + +typedef struct QE1000E QE1000E; +typedef struct QE1000E_PCI QE1000E_PCI; + +struct QE1000E { + uint64_t tx_ring; + uint64_t rx_ring; +}; + +struct QE1000E_PCI { + QOSGraphObject obj; + QPCIDevice pci_dev; + QPCIBar mac_regs; + QE1000E e1000e; +}; + +void e1000e_wait_isr(QE1000E *d, uint16_t msg_id); +void e1000e_tx_ring_push(QE1000E *d, void *descr); +void e1000e_rx_ring_push(QE1000E *d, void *descr); + +#endif diff --git a/tests/qtest/libqos/fw_cfg.c b/tests/qtest/libqos/fw_cfg.c new file mode 100644 index 0000000000..1f46258f96 --- /dev/null +++ b/tests/qtest/libqos/fw_cfg.c @@ -0,0 +1,164 @@ +/* + * libqos fw_cfg support + * + * Copyright IBM, Corp. 2012-2013 + * Copyright (C) 2013 Red Hat Inc. + * + * Authors: + * Anthony Liguori + * Markus Armbruster + * + * 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 "qemu/bswap.h" +#include "hw/nvram/fw_cfg.h" + +void qfw_cfg_select(QFWCFG *fw_cfg, uint16_t key) +{ + fw_cfg->select(fw_cfg, key); +} + +void qfw_cfg_read_data(QFWCFG *fw_cfg, void *data, size_t len) +{ + fw_cfg->read(fw_cfg, data, len); +} + +void qfw_cfg_get(QFWCFG *fw_cfg, uint16_t key, void *data, size_t len) +{ + qfw_cfg_select(fw_cfg, key); + qfw_cfg_read_data(fw_cfg, data, len); +} + +uint16_t qfw_cfg_get_u16(QFWCFG *fw_cfg, uint16_t key) +{ + uint16_t value; + qfw_cfg_get(fw_cfg, key, &value, sizeof(value)); + return le16_to_cpu(value); +} + +uint32_t qfw_cfg_get_u32(QFWCFG *fw_cfg, uint16_t key) +{ + uint32_t value; + qfw_cfg_get(fw_cfg, key, &value, sizeof(value)); + return le32_to_cpu(value); +} + +uint64_t qfw_cfg_get_u64(QFWCFG *fw_cfg, uint16_t key) +{ + uint64_t value; + qfw_cfg_get(fw_cfg, key, &value, sizeof(value)); + return le64_to_cpu(value); +} + +static void mm_fw_cfg_select(QFWCFG *fw_cfg, uint16_t key) +{ + qtest_writew(fw_cfg->qts, fw_cfg->base, key); +} + +/* + * The caller need check the return value. When the return value is + * nonzero, it means that some bytes have been transferred. + * + * If the fw_cfg file in question is smaller than the allocated & passed-in + * buffer, then the buffer has been populated only in part. + * + * If the fw_cfg file in question is larger than the passed-in + * buffer, then the return value explains how much room would have been + * necessary in total. And, while the caller's buffer has been fully + * populated, it has received only a starting slice of the fw_cfg file. + */ +size_t qfw_cfg_get_file(QFWCFG *fw_cfg, const char *filename, + void *data, size_t buflen) +{ + uint32_t count; + uint32_t i; + unsigned char *filesbuf = NULL; + size_t dsize; + FWCfgFile *pdir_entry; + size_t filesize = 0; + + qfw_cfg_get(fw_cfg, FW_CFG_FILE_DIR, &count, sizeof(count)); + count = be32_to_cpu(count); + dsize = sizeof(uint32_t) + count * sizeof(struct fw_cfg_file); + filesbuf = g_malloc(dsize); + qfw_cfg_get(fw_cfg, FW_CFG_FILE_DIR, filesbuf, dsize); + pdir_entry = (FWCfgFile *)(filesbuf + sizeof(uint32_t)); + for (i = 0; i < count; ++i, ++pdir_entry) { + if (!strcmp(pdir_entry->name, filename)) { + uint32_t len = be32_to_cpu(pdir_entry->size); + uint16_t sel = be16_to_cpu(pdir_entry->select); + filesize = len; + if (len > buflen) { + len = buflen; + } + qfw_cfg_get(fw_cfg, sel, data, len); + break; + } + } + g_free(filesbuf); + return filesize; +} + +static void mm_fw_cfg_read(QFWCFG *fw_cfg, void *data, size_t len) +{ + uint8_t *ptr = data; + int i; + + for (i = 0; i < len; i++) { + ptr[i] = qtest_readb(fw_cfg->qts, fw_cfg->base + 2); + } +} + +QFWCFG *mm_fw_cfg_init(QTestState *qts, uint64_t base) +{ + QFWCFG *fw_cfg = g_malloc0(sizeof(*fw_cfg)); + + fw_cfg->base = base; + fw_cfg->qts = qts; + fw_cfg->select = mm_fw_cfg_select; + fw_cfg->read = mm_fw_cfg_read; + + return fw_cfg; +} + +void mm_fw_cfg_uninit(QFWCFG *fw_cfg) +{ + g_free(fw_cfg); +} + +static void io_fw_cfg_select(QFWCFG *fw_cfg, uint16_t key) +{ + qtest_outw(fw_cfg->qts, fw_cfg->base, key); +} + +static void io_fw_cfg_read(QFWCFG *fw_cfg, void *data, size_t len) +{ + uint8_t *ptr = data; + int i; + + for (i = 0; i < len; i++) { + ptr[i] = qtest_inb(fw_cfg->qts, fw_cfg->base + 1); + } +} + +QFWCFG *io_fw_cfg_init(QTestState *qts, uint16_t base) +{ + QFWCFG *fw_cfg = g_malloc0(sizeof(*fw_cfg)); + + fw_cfg->base = base; + fw_cfg->qts = qts; + fw_cfg->select = io_fw_cfg_select; + fw_cfg->read = io_fw_cfg_read; + + return fw_cfg; +} + +void io_fw_cfg_uninit(QFWCFG *fw_cfg) +{ + g_free(fw_cfg); +} diff --git a/tests/qtest/libqos/fw_cfg.h b/tests/qtest/libqos/fw_cfg.h new file mode 100644 index 0000000000..13325cc4ff --- /dev/null +++ b/tests/qtest/libqos/fw_cfg.h @@ -0,0 +1,52 @@ +/* + * libqos fw_cfg support + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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 LIBQOS_FW_CFG_H +#define LIBQOS_FW_CFG_H + +#include "libqtest.h" + +typedef struct QFWCFG QFWCFG; + +struct QFWCFG +{ + uint64_t base; + QTestState *qts; + void (*select)(QFWCFG *fw_cfg, uint16_t key); + void (*read)(QFWCFG *fw_cfg, void *data, size_t len); +}; + +void qfw_cfg_select(QFWCFG *fw_cfg, uint16_t key); +void qfw_cfg_read_data(QFWCFG *fw_cfg, void *data, size_t len); +void qfw_cfg_get(QFWCFG *fw_cfg, uint16_t key, void *data, size_t len); +uint16_t qfw_cfg_get_u16(QFWCFG *fw_cfg, uint16_t key); +uint32_t qfw_cfg_get_u32(QFWCFG *fw_cfg, uint16_t key); +uint64_t qfw_cfg_get_u64(QFWCFG *fw_cfg, uint16_t key); +size_t qfw_cfg_get_file(QFWCFG *fw_cfg, const char *filename, + void *data, size_t buflen); + +QFWCFG *mm_fw_cfg_init(QTestState *qts, uint64_t base); +void mm_fw_cfg_uninit(QFWCFG *fw_cfg); +QFWCFG *io_fw_cfg_init(QTestState *qts, uint16_t base); +void io_fw_cfg_uninit(QFWCFG *fw_cfg); + +static inline QFWCFG *pc_fw_cfg_init(QTestState *qts) +{ + return io_fw_cfg_init(qts, 0x510); +} + +static inline void pc_fw_cfg_uninit(QFWCFG *fw_cfg) +{ + io_fw_cfg_uninit(fw_cfg); +} + +#endif diff --git a/tests/qtest/libqos/i2c-imx.c b/tests/qtest/libqos/i2c-imx.c new file mode 100644 index 0000000000..f33ece55a3 --- /dev/null +++ b/tests/qtest/libqos/i2c-imx.c @@ -0,0 +1,216 @@ +/* + * QTest i.MX I2C driver + * + * 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 . + */ + +#include "qemu/osdep.h" +#include "libqos/i2c.h" + + +#include "libqtest.h" + +#include "hw/i2c/imx_i2c.h" + +enum IMXI2CDirection { + IMX_I2C_READ, + IMX_I2C_WRITE, +}; + +static void imx_i2c_set_slave_addr(IMXI2C *s, uint8_t addr, + enum IMXI2CDirection direction) +{ + qtest_writeb(s->parent.qts, s->addr + I2DR_ADDR, + (addr << 1) | (direction == IMX_I2C_READ ? 1 : 0)); +} + +static void imx_i2c_send(I2CAdapter *i2c, uint8_t addr, + const uint8_t *buf, uint16_t len) +{ + IMXI2C *s = container_of(i2c, IMXI2C, parent); + uint8_t data; + uint8_t status; + uint16_t size = 0; + + if (!len) { + return; + } + + /* set the bus for write */ + data = I2CR_IEN | + I2CR_IIEN | + I2CR_MSTA | + I2CR_MTX | + I2CR_TXAK; + + qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) != 0); + + /* set the slave address */ + imx_i2c_set_slave_addr(s, addr, IMX_I2C_WRITE); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) != 0); + g_assert((status & I2SR_RXAK) == 0); + + /* ack the interrupt */ + qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) == 0); + + while (size < len) { + /* check we are still busy */ + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) != 0); + + /* write the data */ + qtest_writeb(i2c->qts, s->addr + I2DR_ADDR, buf[size]); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) != 0); + g_assert((status & I2SR_RXAK) == 0); + + /* ack the interrupt */ + qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) == 0); + + size++; + } + + /* release the bus */ + data &= ~(I2CR_MSTA | I2CR_MTX); + qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) == 0); +} + +static void imx_i2c_recv(I2CAdapter *i2c, uint8_t addr, + uint8_t *buf, uint16_t len) +{ + IMXI2C *s = container_of(i2c, IMXI2C, parent); + uint8_t data; + uint8_t status; + uint16_t size = 0; + + if (!len) { + return; + } + + /* set the bus for write */ + data = I2CR_IEN | + I2CR_IIEN | + I2CR_MSTA | + I2CR_MTX | + I2CR_TXAK; + + qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) != 0); + + /* set the slave address */ + imx_i2c_set_slave_addr(s, addr, IMX_I2C_READ); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) != 0); + g_assert((status & I2SR_RXAK) == 0); + + /* ack the interrupt */ + qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) == 0); + + /* set the bus for read */ + data &= ~I2CR_MTX; + /* if only one byte don't ack */ + if (len != 1) { + data &= ~I2CR_TXAK; + } + qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) != 0); + + /* dummy read */ + qtest_readb(i2c->qts, s->addr + I2DR_ADDR); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) != 0); + + /* ack the interrupt */ + qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) == 0); + + while (size < len) { + /* check we are still busy */ + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) != 0); + + if (size == (len - 1)) { + /* stop the read transaction */ + data &= ~(I2CR_MSTA | I2CR_MTX); + } else { + /* ack the data read */ + data |= I2CR_TXAK; + } + qtest_writeb(i2c->qts, s->addr + I2CR_ADDR, data); + + /* read the data */ + buf[size] = qtest_readb(i2c->qts, s->addr + I2DR_ADDR); + + if (size != (len - 1)) { + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) != 0); + + /* ack the interrupt */ + qtest_writeb(i2c->qts, s->addr + I2SR_ADDR, 0); + } + + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IIF) == 0); + + size++; + } + + status = qtest_readb(i2c->qts, s->addr + I2SR_ADDR); + g_assert((status & I2SR_IBB) == 0); +} + +static void *imx_i2c_get_driver(void *obj, const char *interface) +{ + IMXI2C *s = obj; + if (!g_strcmp0(interface, "i2c-bus")) { + return &s->parent; + } + fprintf(stderr, "%s not present in imx-i2c\n", interface); + g_assert_not_reached(); +} + +void imx_i2c_init(IMXI2C *s, QTestState *qts, uint64_t addr) +{ + s->addr = addr; + + s->obj.get_driver = imx_i2c_get_driver; + + s->parent.send = imx_i2c_send; + s->parent.recv = imx_i2c_recv; + s->parent.qts = qts; +} + +static void imx_i2c_register_nodes(void) +{ + qos_node_create_driver("imx.i2c", NULL); + qos_node_produces("imx.i2c", "i2c-bus"); +} + +libqos_init(imx_i2c_register_nodes); diff --git a/tests/qtest/libqos/i2c-omap.c b/tests/qtest/libqos/i2c-omap.c new file mode 100644 index 0000000000..9ae8214fa8 --- /dev/null +++ b/tests/qtest/libqos/i2c-omap.c @@ -0,0 +1,196 @@ +/* + * QTest I2C driver + * + * 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 "libqos/i2c.h" + + +#include "qemu/bswap.h" +#include "libqtest.h" + +enum OMAPI2CRegisters { + OMAP_I2C_REV = 0x00, + OMAP_I2C_STAT = 0x08, + OMAP_I2C_CNT = 0x18, + OMAP_I2C_DATA = 0x1c, + OMAP_I2C_CON = 0x24, + OMAP_I2C_SA = 0x2c, +}; + +enum OMAPI2CSTATBits { + OMAP_I2C_STAT_NACK = 1 << 1, + OMAP_I2C_STAT_ARDY = 1 << 2, + OMAP_I2C_STAT_RRDY = 1 << 3, + OMAP_I2C_STAT_XRDY = 1 << 4, + OMAP_I2C_STAT_ROVR = 1 << 11, + OMAP_I2C_STAT_SBD = 1 << 15, +}; + +enum OMAPI2CCONBits { + OMAP_I2C_CON_STT = 1 << 0, + OMAP_I2C_CON_STP = 1 << 1, + OMAP_I2C_CON_TRX = 1 << 9, + OMAP_I2C_CON_MST = 1 << 10, + OMAP_I2C_CON_BE = 1 << 14, + OMAP_I2C_CON_I2C_EN = 1 << 15, +}; + + +static void omap_i2c_set_slave_addr(OMAPI2C *s, uint8_t addr) +{ + uint16_t data = addr; + + qtest_writew(s->parent.qts, s->addr + OMAP_I2C_SA, data); + data = qtest_readw(s->parent.qts, s->addr + OMAP_I2C_SA); + g_assert_cmphex(data, ==, addr); +} + +static void omap_i2c_send(I2CAdapter *i2c, uint8_t addr, + const uint8_t *buf, uint16_t len) +{ + OMAPI2C *s = container_of(i2c, OMAPI2C, parent); + uint16_t data; + + omap_i2c_set_slave_addr(s, addr); + + data = len; + qtest_writew(i2c->qts, s->addr + OMAP_I2C_CNT, data); + + data = OMAP_I2C_CON_I2C_EN | + OMAP_I2C_CON_TRX | + OMAP_I2C_CON_MST | + OMAP_I2C_CON_STT | + OMAP_I2C_CON_STP; + qtest_writew(i2c->qts, s->addr + OMAP_I2C_CON, data); + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); + g_assert((data & OMAP_I2C_CON_STP) != 0); + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); + g_assert((data & OMAP_I2C_STAT_NACK) == 0); + + while (len > 1) { + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); + g_assert((data & OMAP_I2C_STAT_XRDY) != 0); + + data = buf[0] | ((uint16_t)buf[1] << 8); + qtest_writew(i2c->qts, s->addr + OMAP_I2C_DATA, data); + buf = (uint8_t *)buf + 2; + len -= 2; + } + if (len == 1) { + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); + g_assert((data & OMAP_I2C_STAT_XRDY) != 0); + + data = buf[0]; + qtest_writew(i2c->qts, s->addr + OMAP_I2C_DATA, data); + } + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); + g_assert((data & OMAP_I2C_CON_STP) == 0); +} + +static void omap_i2c_recv(I2CAdapter *i2c, uint8_t addr, + uint8_t *buf, uint16_t len) +{ + OMAPI2C *s = container_of(i2c, OMAPI2C, parent); + uint16_t data, stat; + uint16_t orig_len = len; + + omap_i2c_set_slave_addr(s, addr); + + data = len; + qtest_writew(i2c->qts, s->addr + OMAP_I2C_CNT, data); + + data = OMAP_I2C_CON_I2C_EN | + OMAP_I2C_CON_MST | + OMAP_I2C_CON_STT | + OMAP_I2C_CON_STP; + qtest_writew(i2c->qts, s->addr + OMAP_I2C_CON, data); + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); + g_assert((data & OMAP_I2C_STAT_NACK) == 0); + + while (len > 0) { + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); + if (len <= 4) { + g_assert((data & OMAP_I2C_CON_STP) == 0); + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CNT); + g_assert_cmpuint(data, ==, orig_len); + } else { + g_assert((data & OMAP_I2C_CON_STP) != 0); + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CNT); + g_assert_cmpuint(data, ==, len - 4); + } + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); + g_assert((data & OMAP_I2C_STAT_RRDY) != 0); + g_assert((data & OMAP_I2C_STAT_ROVR) == 0); + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_DATA); + + stat = qtest_readw(i2c->qts, s->addr + OMAP_I2C_STAT); + + if (unlikely(len == 1)) { + g_assert((stat & OMAP_I2C_STAT_SBD) != 0); + + buf[0] = data & 0xff; + buf++; + len--; + } else { + buf[0] = data & 0xff; + buf[1] = data >> 8; + buf += 2; + len -= 2; + } + } + + data = qtest_readw(i2c->qts, s->addr + OMAP_I2C_CON); + g_assert((data & OMAP_I2C_CON_STP) == 0); +} + +static void *omap_i2c_get_driver(void *obj, const char *interface) +{ + OMAPI2C *s = obj; + if (!g_strcmp0(interface, "i2c-bus")) { + return &s->parent; + } + fprintf(stderr, "%s not present in omap_i2c\n", interface); + g_assert_not_reached(); +} + +static void omap_i2c_start_hw(QOSGraphObject *object) +{ + OMAPI2C *s = (OMAPI2C *) object; + uint16_t data; + + /* verify the mmio address by looking for a known signature */ + data = qtest_readw(s->parent.qts, s->addr + OMAP_I2C_REV); + g_assert_cmphex(data, ==, 0x34); +} + +void omap_i2c_init(OMAPI2C *s, QTestState *qts, uint64_t addr) +{ + s->addr = addr; + + s->obj.get_driver = omap_i2c_get_driver; + s->obj.start_hw = omap_i2c_start_hw; + + s->parent.send = omap_i2c_send; + s->parent.recv = omap_i2c_recv; + s->parent.qts = qts; +} + +static void omap_i2c_register_nodes(void) +{ + qos_node_create_driver("omap_i2c", NULL); + qos_node_produces("omap_i2c", "i2c-bus"); +} + +libqos_init(omap_i2c_register_nodes); diff --git a/tests/qtest/libqos/i2c.c b/tests/qtest/libqos/i2c.c new file mode 100644 index 0000000000..156114e745 --- /dev/null +++ b/tests/qtest/libqos/i2c.c @@ -0,0 +1,85 @@ +/* + * QTest I2C driver + * + * 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 "libqos/i2c.h" +#include "libqtest.h" + +void i2c_send(QI2CDevice *i2cdev, const uint8_t *buf, uint16_t len) +{ + i2cdev->bus->send(i2cdev->bus, i2cdev->addr, buf, len); +} + +void i2c_recv(QI2CDevice *i2cdev, uint8_t *buf, uint16_t len) +{ + i2cdev->bus->recv(i2cdev->bus, i2cdev->addr, buf, len); +} + +void i2c_read_block(QI2CDevice *i2cdev, uint8_t reg, + uint8_t *buf, uint16_t len) +{ + i2c_send(i2cdev, ®, 1); + i2c_recv(i2cdev, buf, len); +} + +void i2c_write_block(QI2CDevice *i2cdev, uint8_t reg, + const uint8_t *buf, uint16_t len) +{ + uint8_t *cmd = g_malloc(len + 1); + cmd[0] = reg; + memcpy(&cmd[1], buf, len); + i2c_send(i2cdev, cmd, len + 1); + g_free(cmd); +} + +uint8_t i2c_get8(QI2CDevice *i2cdev, uint8_t reg) +{ + uint8_t resp[1]; + i2c_read_block(i2cdev, reg, resp, sizeof(resp)); + return resp[0]; +} + +uint16_t i2c_get16(QI2CDevice *i2cdev, uint8_t reg) +{ + uint8_t resp[2]; + i2c_read_block(i2cdev, reg, resp, sizeof(resp)); + return (resp[0] << 8) | resp[1]; +} + +void i2c_set8(QI2CDevice *i2cdev, uint8_t reg, uint8_t value) +{ + i2c_write_block(i2cdev, reg, &value, 1); +} + +void i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value) +{ + uint8_t data[2]; + + data[0] = value >> 8; + data[1] = value & 255; + i2c_write_block(i2cdev, reg, data, sizeof(data)); +} + +void *i2c_device_create(void *i2c_bus, QGuestAllocator *alloc, void *addr) +{ + QI2CDevice *i2cdev = g_new0(QI2CDevice, 1); + + i2cdev->bus = i2c_bus; + if (addr) { + i2cdev->addr = ((QI2CAddress *)addr)->addr; + } + return &i2cdev->obj; +} + +void add_qi2c_address(QOSGraphEdgeOptions *opts, QI2CAddress *addr) +{ + g_assert(addr); + + opts->arg = addr; + opts->size_arg = sizeof(QI2CAddress); +} diff --git a/tests/qtest/libqos/i2c.h b/tests/qtest/libqos/i2c.h new file mode 100644 index 0000000000..945b65b34c --- /dev/null +++ b/tests/qtest/libqos/i2c.h @@ -0,0 +1,82 @@ +/* + * I2C libqos + * + * 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. + */ +#ifndef LIBQOS_I2C_H +#define LIBQOS_I2C_H + +#include "libqtest.h" +#include "libqos/qgraph.h" + +typedef struct I2CAdapter I2CAdapter; +struct I2CAdapter { + void (*send)(I2CAdapter *adapter, uint8_t addr, + const uint8_t *buf, uint16_t len); + void (*recv)(I2CAdapter *adapter, uint8_t addr, + uint8_t *buf, uint16_t len); + + QTestState *qts; +}; + +typedef struct QI2CAddress QI2CAddress; +struct QI2CAddress { + uint8_t addr; +}; + +typedef struct QI2CDevice QI2CDevice; +struct QI2CDevice { + /* + * For now, all devices are simple enough that there is no need for + * them to define their own constructor and get_driver functions. + * Therefore, QOSGraphObject is included directly in QI2CDevice; + * the tests expect to get a QI2CDevice rather than doing something + * like obj->get_driver("i2c-device"). + * + * In fact there is no i2c-device interface even, because there are + * no generic I2C tests). + */ + QOSGraphObject obj; + I2CAdapter *bus; + uint8_t addr; +}; + +void *i2c_device_create(void *i2c_bus, QGuestAllocator *alloc, void *addr); +void add_qi2c_address(QOSGraphEdgeOptions *opts, QI2CAddress *addr); + +void i2c_send(QI2CDevice *dev, const uint8_t *buf, uint16_t len); +void i2c_recv(QI2CDevice *dev, uint8_t *buf, uint16_t len); + +void i2c_read_block(QI2CDevice *dev, uint8_t reg, + uint8_t *buf, uint16_t len); +void i2c_write_block(QI2CDevice *dev, uint8_t reg, + const uint8_t *buf, uint16_t len); +uint8_t i2c_get8(QI2CDevice *dev, uint8_t reg); +uint16_t i2c_get16(QI2CDevice *dev, uint8_t reg); +void i2c_set8(QI2CDevice *dev, uint8_t reg, uint8_t value); +void i2c_set16(QI2CDevice *dev, uint8_t reg, uint16_t value); + +/* i2c-omap.c */ +typedef struct OMAPI2C { + QOSGraphObject obj; + I2CAdapter parent; + + uint64_t addr; +} OMAPI2C; + +void omap_i2c_init(OMAPI2C *s, QTestState *qts, uint64_t addr); + +/* i2c-imx.c */ +typedef struct IMXI2C { + QOSGraphObject obj; + I2CAdapter parent; + + uint64_t addr; +} IMXI2C; + +void imx_i2c_init(IMXI2C *s, QTestState *qts, uint64_t addr); + +#endif diff --git a/tests/qtest/libqos/libqos-pc.c b/tests/qtest/libqos/libqos-pc.c new file mode 100644 index 0000000000..d04abc548b --- /dev/null +++ b/tests/qtest/libqos/libqos-pc.c @@ -0,0 +1,35 @@ +#include "qemu/osdep.h" +#include "libqos/libqos-pc.h" +#include "libqos/malloc-pc.h" +#include "libqos/pci-pc.h" + +static QOSOps qos_ops = { + .alloc_init = pc_alloc_init, + .qpci_new = qpci_new_pc, + .qpci_free = qpci_free_pc, + .shutdown = qtest_pc_shutdown, +}; + +QOSState *qtest_pc_vboot(const char *cmdline_fmt, va_list ap) +{ + return qtest_vboot(&qos_ops, cmdline_fmt, ap); +} + +QOSState *qtest_pc_boot(const char *cmdline_fmt, ...) +{ + QOSState *qs; + va_list ap; + + va_start(ap, cmdline_fmt); + qs = qtest_vboot(&qos_ops, cmdline_fmt, ap); + va_end(ap); + + qtest_irq_intercept_in(qs->qts, "ioapic"); + + return qs; +} + +void qtest_pc_shutdown(QOSState *qs) +{ + return qtest_common_shutdown(qs); +} diff --git a/tests/qtest/libqos/libqos-pc.h b/tests/qtest/libqos/libqos-pc.h new file mode 100644 index 0000000000..a0e4c45516 --- /dev/null +++ b/tests/qtest/libqos/libqos-pc.h @@ -0,0 +1,10 @@ +#ifndef LIBQOS_PC_H +#define LIBQOS_PC_H + +#include "libqos/libqos.h" + +QOSState *qtest_pc_vboot(const char *cmdline_fmt, va_list ap); +QOSState *qtest_pc_boot(const char *cmdline_fmt, ...); +void qtest_pc_shutdown(QOSState *qs); + +#endif diff --git a/tests/qtest/libqos/libqos-spapr.c b/tests/qtest/libqos/libqos-spapr.c new file mode 100644 index 0000000000..8766d543ce --- /dev/null +++ b/tests/qtest/libqos/libqos-spapr.c @@ -0,0 +1,33 @@ +#include "qemu/osdep.h" +#include "libqos/libqos-spapr.h" +#include "libqos/malloc-spapr.h" +#include "libqos/pci-spapr.h" + +static QOSOps qos_ops = { + .alloc_init = spapr_alloc_init, + .qpci_new = qpci_new_spapr, + .qpci_free = qpci_free_spapr, + .shutdown = qtest_spapr_shutdown, +}; + +QOSState *qtest_spapr_vboot(const char *cmdline_fmt, va_list ap) +{ + return qtest_vboot(&qos_ops, cmdline_fmt, ap); +} + +QOSState *qtest_spapr_boot(const char *cmdline_fmt, ...) +{ + QOSState *qs; + va_list ap; + + va_start(ap, cmdline_fmt); + qs = qtest_vboot(&qos_ops, cmdline_fmt, ap); + va_end(ap); + + return qs; +} + +void qtest_spapr_shutdown(QOSState *qs) +{ + return qtest_common_shutdown(qs); +} diff --git a/tests/qtest/libqos/libqos-spapr.h b/tests/qtest/libqos/libqos-spapr.h new file mode 100644 index 0000000000..dcb5c43ad3 --- /dev/null +++ b/tests/qtest/libqos/libqos-spapr.h @@ -0,0 +1,10 @@ +#ifndef LIBQOS_SPAPR_H +#define LIBQOS_SPAPR_H + +#include "libqos/libqos.h" + +QOSState *qtest_spapr_vboot(const char *cmdline_fmt, va_list ap); +QOSState *qtest_spapr_boot(const char *cmdline_fmt, ...); +void qtest_spapr_shutdown(QOSState *qs); + +#endif diff --git a/tests/qtest/libqos/libqos.c b/tests/qtest/libqos/libqos.c new file mode 100644 index 0000000000..f229eb2cb8 --- /dev/null +++ b/tests/qtest/libqos/libqos.c @@ -0,0 +1,240 @@ +#include "qemu/osdep.h" +#include + +#include "libqtest.h" +#include "libqos/libqos.h" +#include "libqos/pci.h" +#include "qapi/qmp/qdict.h" + +/*** Test Setup & Teardown ***/ + +/** + * Launch QEMU with the given command line, + * and then set up interrupts and our guest malloc interface. + * Never returns NULL: + * Terminates the application in case an error is encountered. + */ +QOSState *qtest_vboot(QOSOps *ops, const char *cmdline_fmt, va_list ap) +{ + char *cmdline; + + QOSState *qs = g_new0(QOSState, 1); + + cmdline = g_strdup_vprintf(cmdline_fmt, ap); + qs->qts = qtest_init(cmdline); + qs->ops = ops; + if (ops) { + ops->alloc_init(&qs->alloc, qs->qts, ALLOC_NO_FLAGS); + qs->pcibus = ops->qpci_new(qs->qts, &qs->alloc); + } + + g_free(cmdline); + return qs; +} + +/** + * Launch QEMU with the given command line, + * and then set up interrupts and our guest malloc interface. + */ +QOSState *qtest_boot(QOSOps *ops, const char *cmdline_fmt, ...) +{ + QOSState *qs; + va_list ap; + + va_start(ap, cmdline_fmt); + qs = qtest_vboot(ops, cmdline_fmt, ap); + va_end(ap); + + return qs; +} + +/** + * Tear down the QEMU instance. + */ +void qtest_common_shutdown(QOSState *qs) +{ + if (qs->ops) { + if (qs->pcibus && qs->ops->qpci_free) { + qs->ops->qpci_free(qs->pcibus); + qs->pcibus = NULL; + } + } + alloc_destroy(&qs->alloc); + qtest_quit(qs->qts); + g_free(qs); +} + +void qtest_shutdown(QOSState *qs) +{ + if (qs->ops && qs->ops->shutdown) { + qs->ops->shutdown(qs); + } else { + qtest_common_shutdown(qs); + } +} + +static QDict *qmp_execute(QTestState *qts, const char *command) +{ + return qtest_qmp(qts, "{ 'execute': %s }", command); +} + +void migrate(QOSState *from, QOSState *to, const char *uri) +{ + const char *st; + QDict *rsp, *sub; + bool running; + + /* Is the machine currently running? */ + rsp = qmp_execute(from->qts, "query-status"); + g_assert(qdict_haskey(rsp, "return")); + sub = qdict_get_qdict(rsp, "return"); + g_assert(qdict_haskey(sub, "running")); + running = qdict_get_bool(sub, "running"); + qobject_unref(rsp); + + /* Issue the migrate command. */ + rsp = qtest_qmp(from->qts, + "{ 'execute': 'migrate', 'arguments': { 'uri': %s }}", + uri); + g_assert(qdict_haskey(rsp, "return")); + qobject_unref(rsp); + + /* Wait for STOP event, but only if we were running: */ + if (running) { + qtest_qmp_eventwait(from->qts, "STOP"); + } + + /* If we were running, we can wait for an event. */ + if (running) { + migrate_allocator(&from->alloc, &to->alloc); + qtest_qmp_eventwait(to->qts, "RESUME"); + return; + } + + /* Otherwise, we need to wait: poll until migration is completed. */ + while (1) { + rsp = qmp_execute(from->qts, "query-migrate"); + g_assert(qdict_haskey(rsp, "return")); + sub = qdict_get_qdict(rsp, "return"); + g_assert(qdict_haskey(sub, "status")); + st = qdict_get_str(sub, "status"); + + /* "setup", "active", "completed", "failed", "cancelled" */ + if (strcmp(st, "completed") == 0) { + qobject_unref(rsp); + break; + } + + if ((strcmp(st, "setup") == 0) || (strcmp(st, "active") == 0) + || (strcmp(st, "wait-unplug") == 0)) { + qobject_unref(rsp); + g_usleep(5000); + continue; + } + + fprintf(stderr, "Migration did not complete, status: %s\n", st); + g_assert_not_reached(); + } + + migrate_allocator(&from->alloc, &to->alloc); +} + +bool have_qemu_img(void) +{ + char *rpath; + const char *path = getenv("QTEST_QEMU_IMG"); + if (!path) { + return false; + } + + rpath = realpath(path, NULL); + if (!rpath) { + return false; + } else { + free(rpath); + return true; + } +} + +void mkimg(const char *file, const char *fmt, unsigned size_mb) +{ + gchar *cli; + bool ret; + int rc; + GError *err = NULL; + char *qemu_img_path; + gchar *out, *out2; + char *qemu_img_abs_path; + + 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); + + cli = g_strdup_printf("%s create -f %s %s %uM", qemu_img_abs_path, + fmt, file, size_mb); + ret = g_spawn_command_line_sync(cli, &out, &out2, &rc, &err); + if (err || !g_spawn_check_exit_status(rc, &err)) { + fprintf(stderr, "%s\n", err->message); + g_error_free(err); + } + g_assert(ret && !err); + + g_free(out); + g_free(out2); + g_free(cli); + free(qemu_img_abs_path); +} + +void mkqcow2(const char *file, unsigned size_mb) +{ + return mkimg(file, "qcow2", size_mb); +} + +void prepare_blkdebug_script(const char *debug_fn, const char *event) +{ + FILE *debug_file = fopen(debug_fn, "w"); + int ret; + + fprintf(debug_file, "[inject-error]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "errno = \"5\"\n"); + fprintf(debug_file, "state = \"1\"\n"); + fprintf(debug_file, "immediately = \"off\"\n"); + fprintf(debug_file, "once = \"on\"\n"); + + fprintf(debug_file, "[set-state]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "new_state = \"2\"\n"); + fflush(debug_file); + g_assert(!ferror(debug_file)); + + ret = fclose(debug_file); + g_assert(ret == 0); +} + +void generate_pattern(void *buffer, size_t len, size_t cycle_len) +{ + int i, j; + unsigned char *tx = (unsigned char *)buffer; + unsigned char p; + size_t *sx; + + /* Write an indicative pattern that varies and is unique per-cycle */ + p = rand() % 256; + for (i = 0; i < len; i++) { + tx[i] = p++ % 256; + if (i % cycle_len == 0) { + p = rand() % 256; + } + } + + /* force uniqueness by writing an id per-cycle */ + for (i = 0; i < len / cycle_len; i++) { + j = i * cycle_len; + if (j + sizeof(*sx) <= len) { + sx = (size_t *)&tx[j]; + *sx = i; + } + } +} diff --git a/tests/qtest/libqos/libqos.h b/tests/qtest/libqos/libqos.h new file mode 100644 index 0000000000..8e971c25a3 --- /dev/null +++ b/tests/qtest/libqos/libqos.h @@ -0,0 +1,45 @@ +#ifndef LIBQOS_H +#define LIBQOS_H + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/malloc.h" + +typedef struct QOSState QOSState; + +typedef struct QOSOps { + void (*alloc_init)(QGuestAllocator *, QTestState *, QAllocOpts); + QPCIBus *(*qpci_new)(QTestState *qts, QGuestAllocator *alloc); + void (*qpci_free)(QPCIBus *bus); + void (*shutdown)(QOSState *); +} QOSOps; + +struct QOSState { + QTestState *qts; + QGuestAllocator alloc; + QPCIBus *pcibus; + QOSOps *ops; +}; + +QOSState *qtest_vboot(QOSOps *ops, const char *cmdline_fmt, va_list ap); +QOSState *qtest_boot(QOSOps *ops, const char *cmdline_fmt, ...); +void qtest_common_shutdown(QOSState *qs); +void qtest_shutdown(QOSState *qs); +bool have_qemu_img(void); +void mkimg(const char *file, const char *fmt, unsigned size_mb); +void mkqcow2(const char *file, unsigned size_mb); +void migrate(QOSState *from, QOSState *to, const char *uri); +void prepare_blkdebug_script(const char *debug_fn, const char *event); +void generate_pattern(void *buffer, size_t len, size_t cycle_len); + +static inline uint64_t qmalloc(QOSState *q, size_t bytes) +{ + return guest_alloc(&q->alloc, bytes); +} + +static inline void qfree(QOSState *q, uint64_t addr) +{ + guest_free(&q->alloc, addr); +} + +#endif diff --git a/tests/qtest/libqos/malloc-pc.c b/tests/qtest/libqos/malloc-pc.c new file mode 100644 index 0000000000..6f92ce4135 --- /dev/null +++ b/tests/qtest/libqos/malloc-pc.c @@ -0,0 +1,33 @@ +/* + * libqos malloc support for PC + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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/malloc-pc.h" +#include "libqos/fw_cfg.h" + +#include "standard-headers/linux/qemu_fw_cfg.h" + +#include "qemu-common.h" + +#define PAGE_SIZE (4096) + +void pc_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags) +{ + uint64_t ram_size; + QFWCFG *fw_cfg = pc_fw_cfg_init(qts); + + ram_size = qfw_cfg_get_u64(fw_cfg, FW_CFG_RAM_SIZE); + alloc_init(s, flags, 1 << 20, MIN(ram_size, 0xE0000000), PAGE_SIZE); + + /* clean-up */ + pc_fw_cfg_uninit(fw_cfg); +} diff --git a/tests/qtest/libqos/malloc-pc.h b/tests/qtest/libqos/malloc-pc.h new file mode 100644 index 0000000000..21e75ae004 --- /dev/null +++ b/tests/qtest/libqos/malloc-pc.h @@ -0,0 +1,20 @@ +/* + * libqos malloc support for PC + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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 LIBQOS_MALLOC_PC_H +#define LIBQOS_MALLOC_PC_H + +#include "libqos/malloc.h" + +void pc_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags); + +#endif diff --git a/tests/qtest/libqos/malloc-spapr.c b/tests/qtest/libqos/malloc-spapr.c new file mode 100644 index 0000000000..2a6b7e3776 --- /dev/null +++ b/tests/qtest/libqos/malloc-spapr.c @@ -0,0 +1,23 @@ +/* + * libqos malloc support for SPAPR + * + * 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/malloc-spapr.h" + +#include "qemu-common.h" + +#define PAGE_SIZE 4096 + +/* Memory must be a multiple of 256 MB, + * so we have at least 256MB + */ +#define SPAPR_MIN_SIZE 0x10000000 + +void spapr_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags) +{ + alloc_init(s, flags, 1 << 20, SPAPR_MIN_SIZE, PAGE_SIZE); +} diff --git a/tests/qtest/libqos/malloc-spapr.h b/tests/qtest/libqos/malloc-spapr.h new file mode 100644 index 0000000000..e5fe9bfc4b --- /dev/null +++ b/tests/qtest/libqos/malloc-spapr.h @@ -0,0 +1,15 @@ +/* + * libqos malloc support for SPAPR + * + * 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 LIBQOS_MALLOC_SPAPR_H +#define LIBQOS_MALLOC_SPAPR_H + +#include "libqos/malloc.h" + +void spapr_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags); + +#endif diff --git a/tests/qtest/libqos/malloc.c b/tests/qtest/libqos/malloc.c new file mode 100644 index 0000000000..615422a5c4 --- /dev/null +++ b/tests/qtest/libqos/malloc.c @@ -0,0 +1,347 @@ +/* + * libqos malloc support + * + * Copyright (c) 2014 + * + * Author: + * John Snow + * + * 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/malloc.h" +#include "qemu-common.h" +#include "qemu/host-utils.h" + +typedef struct MemBlock { + QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME; + uint64_t size; + uint64_t addr; +} MemBlock; + +#define DEFAULT_PAGE_SIZE 4096 + +static void mlist_delete(MemList *list, MemBlock *node) +{ + g_assert(list && node); + QTAILQ_REMOVE(list, node, MLIST_ENTNAME); + g_free(node); +} + +static MemBlock *mlist_find_key(MemList *head, uint64_t addr) +{ + MemBlock *node; + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (node->addr == addr) { + return node; + } + } + return NULL; +} + +static MemBlock *mlist_find_space(MemList *head, uint64_t size) +{ + MemBlock *node; + + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (node->size >= size) { + return node; + } + } + return NULL; +} + +static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr) +{ + MemBlock *node; + g_assert(head && insr); + + QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { + if (insr->addr < node->addr) { + QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME); + return insr; + } + } + + QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME); + return insr; +} + +static inline uint64_t mlist_boundary(MemBlock *node) +{ + return node->size + node->addr; +} + +static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right) +{ + g_assert(head && left && right); + + left->size += right->size; + mlist_delete(head, right); + return left; +} + +static void mlist_coalesce(MemList *head, MemBlock *node) +{ + g_assert(node); + MemBlock *left; + MemBlock *right; + char merge; + + do { + merge = 0; + left = QTAILQ_PREV(node, MLIST_ENTNAME); + right = QTAILQ_NEXT(node, MLIST_ENTNAME); + + /* clowns to the left of me */ + if (left && mlist_boundary(left) == node->addr) { + node = mlist_join(head, left, node); + merge = 1; + } + + /* jokers to the right */ + if (right && mlist_boundary(node) == right->addr) { + node = mlist_join(head, node, right); + merge = 1; + } + + } while (merge); +} + +static MemBlock *mlist_new(uint64_t addr, uint64_t size) +{ + MemBlock *block; + + if (!size) { + return NULL; + } + block = g_new0(MemBlock, 1); + + block->addr = addr; + block->size = size; + + return block; +} + +static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode, + uint64_t size) +{ + uint64_t addr; + MemBlock *usednode; + + g_assert(freenode); + g_assert_cmpint(freenode->size, >=, size); + + addr = freenode->addr; + if (freenode->size == size) { + /* re-use this freenode as our used node */ + QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME); + usednode = freenode; + } else { + /* adjust the free node and create a new used node */ + freenode->addr += size; + freenode->size -= size; + usednode = mlist_new(addr, size); + } + + mlist_sort_insert(s->used, usednode); + return addr; +} + +/* To assert the correctness of the list. + * Used only if ALLOC_PARANOID is set. */ +static void mlist_check(QGuestAllocator *s) +{ + MemBlock *node; + uint64_t addr = s->start > 0 ? s->start - 1 : 0; + uint64_t next = s->start; + + QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) { + g_assert_cmpint(node->addr, >, addr); + g_assert_cmpint(node->addr, >=, next); + addr = node->addr; + next = node->addr + node->size; + } + + addr = s->start > 0 ? s->start - 1 : 0; + next = s->start; + QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) { + g_assert_cmpint(node->addr, >, addr); + g_assert_cmpint(node->addr, >=, next); + addr = node->addr; + next = node->addr + node->size; + } +} + +static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size) +{ + MemBlock *node; + + node = mlist_find_space(s->free, size); + if (!node) { + fprintf(stderr, "Out of guest memory.\n"); + g_assert_not_reached(); + } + return mlist_fulfill(s, node, size); +} + +static void mlist_free(QGuestAllocator *s, uint64_t addr) +{ + MemBlock *node; + + if (addr == 0) { + return; + } + + node = mlist_find_key(s->used, addr); + if (!node) { + fprintf(stderr, "Error: no record found for an allocation at " + "0x%016" PRIx64 ".\n", + addr); + g_assert_not_reached(); + } + + /* Rip it out of the used list and re-insert back into the free list. */ + QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME); + mlist_sort_insert(s->free, node); + mlist_coalesce(s->free, node); +} + +/* + * Mostly for valgrind happiness, but it does offer + * a chokepoint for debugging guest memory leaks, too. + */ +void alloc_destroy(QGuestAllocator *allocator) +{ + MemBlock *node; + MemBlock *tmp; + QAllocOpts mask; + + /* Check for guest leaks, and destroy the list. */ + QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) { + if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) { + fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; " + "size 0x%016" PRIx64 ".\n", + node->addr, node->size); + } + if (allocator->opts & (ALLOC_LEAK_ASSERT)) { + g_assert_not_reached(); + } + g_free(node); + } + + /* If we have previously asserted that there are no leaks, then there + * should be only one node here with a specific address and size. */ + mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID; + QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) { + if ((allocator->opts & mask) == mask) { + if ((node->addr != allocator->start) || + (node->size != allocator->end - allocator->start)) { + fprintf(stderr, "Free list is corrupted.\n"); + g_assert_not_reached(); + } + } + + g_free(node); + } + + g_free(allocator->used); + g_free(allocator->free); +} + +uint64_t guest_alloc(QGuestAllocator *allocator, size_t size) +{ + uint64_t rsize = size; + uint64_t naddr; + + if (!size) { + return 0; + } + + rsize += (allocator->page_size - 1); + rsize &= -allocator->page_size; + g_assert_cmpint((allocator->start + rsize), <=, allocator->end); + g_assert_cmpint(rsize, >=, size); + + naddr = mlist_alloc(allocator, rsize); + if (allocator->opts & ALLOC_PARANOID) { + mlist_check(allocator); + } + + return naddr; +} + +void guest_free(QGuestAllocator *allocator, uint64_t addr) +{ + if (!addr) { + return; + } + mlist_free(allocator, addr); + if (allocator->opts & ALLOC_PARANOID) { + mlist_check(allocator); + } +} + +void alloc_init(QGuestAllocator *s, QAllocOpts opts, + uint64_t start, uint64_t end, + size_t page_size) +{ + MemBlock *node; + + s->opts = opts; + s->start = start; + s->end = end; + + s->used = g_new(MemList, 1); + s->free = g_new(MemList, 1); + QTAILQ_INIT(s->used); + QTAILQ_INIT(s->free); + + node = mlist_new(s->start, s->end - s->start); + QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME); + + s->page_size = page_size; +} + +void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts) +{ + allocator->opts |= opts; +} + +void migrate_allocator(QGuestAllocator *src, + QGuestAllocator *dst) +{ + MemBlock *node, *tmp; + MemList *tmpused, *tmpfree; + + /* The general memory layout should be equivalent, + * though opts can differ. */ + g_assert_cmphex(src->start, ==, dst->start); + g_assert_cmphex(src->end, ==, dst->end); + + /* Destroy (silently, regardless of options) the dest-list: */ + QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) { + g_free(node); + } + QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) { + g_free(node); + } + + tmpused = dst->used; + tmpfree = dst->free; + + /* Inherit the lists of the source allocator: */ + dst->used = src->used; + dst->free = src->free; + + /* Source is now re-initialized, the source memory is 'invalid' now: */ + src->used = tmpused; + src->free = tmpfree; + QTAILQ_INIT(src->used); + QTAILQ_INIT(src->free); + node = mlist_new(src->start, src->end - src->start); + QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME); + return; +} diff --git a/tests/qtest/libqos/malloc.h b/tests/qtest/libqos/malloc.h new file mode 100644 index 0000000000..4d1a2e2bef --- /dev/null +++ b/tests/qtest/libqos/malloc.h @@ -0,0 +1,50 @@ +/* + * libqos malloc support + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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 LIBQOS_MALLOC_H +#define LIBQOS_MALLOC_H + +#include "qemu/queue.h" +#include "libqtest.h" + +typedef enum { + ALLOC_NO_FLAGS = 0x00, + ALLOC_LEAK_WARN = 0x01, + ALLOC_LEAK_ASSERT = 0x02, + ALLOC_PARANOID = 0x04 +} QAllocOpts; + +typedef QTAILQ_HEAD(MemList, MemBlock) MemList; + +typedef struct QGuestAllocator { + QAllocOpts opts; + uint64_t start; + uint64_t end; + uint32_t page_size; + + MemList *used; + MemList *free; +} QGuestAllocator; + +/* Always returns page aligned values */ +uint64_t guest_alloc(QGuestAllocator *allocator, size_t size); +void guest_free(QGuestAllocator *allocator, uint64_t addr); +void migrate_allocator(QGuestAllocator *src, QGuestAllocator *dst); + +void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts); + +void alloc_init(QGuestAllocator *alloc, QAllocOpts flags, + uint64_t start, uint64_t end, + size_t page_size); +void alloc_destroy(QGuestAllocator *allocator); + +#endif diff --git a/tests/qtest/libqos/pci-pc.c b/tests/qtest/libqos/pci-pc.c new file mode 100644 index 0000000000..0bc591d1da --- /dev/null +++ b/tests/qtest/libqos/pci-pc.c @@ -0,0 +1,200 @@ +/* + * libqos PCI bindings for PC + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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-pc.h" +#include "qapi/qmp/qdict.h" +#include "hw/pci/pci_regs.h" + +#include "qemu/module.h" + +#define ACPI_PCIHP_ADDR 0xae00 +#define PCI_EJ_BASE 0x0008 + +static uint8_t qpci_pc_pio_readb(QPCIBus *bus, uint32_t addr) +{ + return qtest_inb(bus->qts, addr); +} + +static void qpci_pc_pio_writeb(QPCIBus *bus, uint32_t addr, uint8_t val) +{ + qtest_outb(bus->qts, addr, val); +} + +static uint16_t qpci_pc_pio_readw(QPCIBus *bus, uint32_t addr) +{ + return qtest_inw(bus->qts, addr); +} + +static void qpci_pc_pio_writew(QPCIBus *bus, uint32_t addr, uint16_t val) +{ + qtest_outw(bus->qts, addr, val); +} + +static uint32_t qpci_pc_pio_readl(QPCIBus *bus, uint32_t addr) +{ + return qtest_inl(bus->qts, addr); +} + +static void qpci_pc_pio_writel(QPCIBus *bus, uint32_t addr, uint32_t val) +{ + qtest_outl(bus->qts, addr, val); +} + +static uint64_t qpci_pc_pio_readq(QPCIBus *bus, uint32_t addr) +{ + return (uint64_t)qtest_inl(bus->qts, addr) + + ((uint64_t)qtest_inl(bus->qts, addr + 4) << 32); +} + +static void qpci_pc_pio_writeq(QPCIBus *bus, uint32_t addr, uint64_t val) +{ + qtest_outl(bus->qts, addr, val & 0xffffffff); + qtest_outl(bus->qts, addr + 4, val >> 32); +} + +static void qpci_pc_memread(QPCIBus *bus, uint32_t addr, void *buf, size_t len) +{ + qtest_memread(bus->qts, addr, buf, len); +} + +static void qpci_pc_memwrite(QPCIBus *bus, uint32_t addr, + const void *buf, size_t len) +{ + qtest_memwrite(bus->qts, addr, buf, len); +} + +static uint8_t qpci_pc_config_readb(QPCIBus *bus, int devfn, uint8_t offset) +{ + qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); + return qtest_inb(bus->qts, 0xcfc); +} + +static uint16_t qpci_pc_config_readw(QPCIBus *bus, int devfn, uint8_t offset) +{ + qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); + return qtest_inw(bus->qts, 0xcfc); +} + +static uint32_t qpci_pc_config_readl(QPCIBus *bus, int devfn, uint8_t offset) +{ + qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); + return qtest_inl(bus->qts, 0xcfc); +} + +static void qpci_pc_config_writeb(QPCIBus *bus, int devfn, uint8_t offset, uint8_t value) +{ + qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); + qtest_outb(bus->qts, 0xcfc, value); +} + +static void qpci_pc_config_writew(QPCIBus *bus, int devfn, uint8_t offset, uint16_t value) +{ + qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); + qtest_outw(bus->qts, 0xcfc, value); +} + +static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint32_t value) +{ + qtest_outl(bus->qts, 0xcf8, (1U << 31) | (devfn << 8) | offset); + qtest_outl(bus->qts, 0xcfc, value); +} + +static void *qpci_pc_get_driver(void *obj, const char *interface) +{ + QPCIBusPC *qpci = obj; + if (!g_strcmp0(interface, "pci-bus")) { + return &qpci->bus; + } + fprintf(stderr, "%s not present in pci-bus-pc\n", interface); + g_assert_not_reached(); +} + +void qpci_init_pc(QPCIBusPC *qpci, QTestState *qts, QGuestAllocator *alloc) +{ + assert(qts); + + /* tests can use pci-bus */ + qpci->bus.has_buggy_msi = false; + + qpci->bus.pio_readb = qpci_pc_pio_readb; + qpci->bus.pio_readw = qpci_pc_pio_readw; + qpci->bus.pio_readl = qpci_pc_pio_readl; + qpci->bus.pio_readq = qpci_pc_pio_readq; + + qpci->bus.pio_writeb = qpci_pc_pio_writeb; + qpci->bus.pio_writew = qpci_pc_pio_writew; + qpci->bus.pio_writel = qpci_pc_pio_writel; + qpci->bus.pio_writeq = qpci_pc_pio_writeq; + + qpci->bus.memread = qpci_pc_memread; + qpci->bus.memwrite = qpci_pc_memwrite; + + qpci->bus.config_readb = qpci_pc_config_readb; + qpci->bus.config_readw = qpci_pc_config_readw; + qpci->bus.config_readl = qpci_pc_config_readl; + + qpci->bus.config_writeb = qpci_pc_config_writeb; + qpci->bus.config_writew = qpci_pc_config_writew; + qpci->bus.config_writel = qpci_pc_config_writel; + + qpci->bus.qts = qts; + qpci->bus.pio_alloc_ptr = 0xc000; + qpci->bus.mmio_alloc_ptr = 0xE0000000; + qpci->bus.mmio_limit = 0x100000000ULL; + + qpci->obj.get_driver = qpci_pc_get_driver; +} + +QPCIBus *qpci_new_pc(QTestState *qts, QGuestAllocator *alloc) +{ + QPCIBusPC *qpci = g_new0(QPCIBusPC, 1); + qpci_init_pc(qpci, qts, alloc); + + return &qpci->bus; +} + +void qpci_free_pc(QPCIBus *bus) +{ + QPCIBusPC *s; + + if (!bus) { + return; + } + s = container_of(bus, QPCIBusPC, bus); + + g_free(s); +} + +void qpci_unplug_acpi_device_test(QTestState *qts, const char *id, uint8_t slot) +{ + QDict *response; + + response = qtest_qmp(qts, "{'execute': 'device_del'," + " 'arguments': {'id': %s}}", id); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + qobject_unref(response); + + qtest_outb(qts, ACPI_PCIHP_ADDR + PCI_EJ_BASE, 1 << slot); + + qtest_qmp_eventwait(qts, "DEVICE_DELETED"); +} + +static void qpci_pc_register_nodes(void) +{ + qos_node_create_driver("pci-bus-pc", NULL); + qos_node_produces("pci-bus-pc", "pci-bus"); +} + +libqos_init(qpci_pc_register_nodes); diff --git a/tests/qtest/libqos/pci-pc.h b/tests/qtest/libqos/pci-pc.h new file mode 100644 index 0000000000..4690005232 --- /dev/null +++ b/tests/qtest/libqos/pci-pc.h @@ -0,0 +1,49 @@ +/* + * libqos PCI bindings for PC + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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 LIBQOS_PCI_PC_H +#define LIBQOS_PCI_PC_H + +#include "libqos/pci.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" + +typedef struct QPCIBusPC { + QOSGraphObject obj; + QPCIBus bus; +} QPCIBusPC; + +/* qpci_init_pc(): + * @ret: A valid QPCIBusPC * pointer + * @qts: The %QTestState for this PC machine + * @alloc: A previously initialized @alloc providing memory for @qts + * + * This function initializes an already allocated + * QPCIBusPC object. + */ +void qpci_init_pc(QPCIBusPC *ret, QTestState *qts, QGuestAllocator *alloc); + +/* qpci_pc_new(): + * @qts: The %QTestState for this PC machine + * @alloc: A previously initialized @alloc providing memory for @qts + * + * This function creates a new QPCIBusPC object, + * and properly initialize its fields. + * + * Returns the QPCIBus *bus field of a newly + * allocated QPCIBusPC. + */ +QPCIBus *qpci_new_pc(QTestState *qts, QGuestAllocator *alloc); + +void qpci_free_pc(QPCIBus *bus); + +#endif diff --git a/tests/qtest/libqos/pci-spapr.c b/tests/qtest/libqos/pci-spapr.c new file mode 100644 index 0000000000..d6f8c01cb7 --- /dev/null +++ b/tests/qtest/libqos/pci-spapr.c @@ -0,0 +1,232 @@ +/* + * libqos PCI bindings for SPAPR + * + * 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-spapr.h" +#include "libqos/rtas.h" +#include "libqos/qgraph.h" + +#include "hw/pci/pci_regs.h" + +#include "qemu/host-utils.h" +#include "qemu/module.h" + +/* + * PCI devices are always little-endian + * SPAPR by default is big-endian + * so PCI accessors need to swap data endianness + */ + +static uint8_t qpci_spapr_pio_readb(QPCIBus *bus, uint32_t addr) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + return qtest_readb(bus->qts, s->pio_cpu_base + addr); +} + +static void qpci_spapr_pio_writeb(QPCIBus *bus, uint32_t addr, uint8_t val) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + qtest_writeb(bus->qts, s->pio_cpu_base + addr, val); +} + +static uint16_t qpci_spapr_pio_readw(QPCIBus *bus, uint32_t addr) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + return bswap16(qtest_readw(bus->qts, s->pio_cpu_base + addr)); +} + +static void qpci_spapr_pio_writew(QPCIBus *bus, uint32_t addr, uint16_t val) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + qtest_writew(bus->qts, s->pio_cpu_base + addr, bswap16(val)); +} + +static uint32_t qpci_spapr_pio_readl(QPCIBus *bus, uint32_t addr) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + return bswap32(qtest_readl(bus->qts, s->pio_cpu_base + addr)); +} + +static void qpci_spapr_pio_writel(QPCIBus *bus, uint32_t addr, uint32_t val) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + qtest_writel(bus->qts, s->pio_cpu_base + addr, bswap32(val)); +} + +static uint64_t qpci_spapr_pio_readq(QPCIBus *bus, uint32_t addr) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + return bswap64(qtest_readq(bus->qts, s->pio_cpu_base + addr)); +} + +static void qpci_spapr_pio_writeq(QPCIBus *bus, uint32_t addr, uint64_t val) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + qtest_writeq(bus->qts, s->pio_cpu_base + addr, bswap64(val)); +} + +static void qpci_spapr_memread(QPCIBus *bus, uint32_t addr, + void *buf, size_t len) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + qtest_memread(bus->qts, s->mmio32_cpu_base + addr, buf, len); +} + +static void qpci_spapr_memwrite(QPCIBus *bus, uint32_t addr, + const void *buf, size_t len) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + qtest_memwrite(bus->qts, s->mmio32_cpu_base + addr, buf, len); +} + +static uint8_t qpci_spapr_config_readb(QPCIBus *bus, int devfn, uint8_t offset) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + uint32_t config_addr = (devfn << 8) | offset; + return qrtas_ibm_read_pci_config(bus->qts, s->alloc, s->buid, + config_addr, 1); +} + +static uint16_t qpci_spapr_config_readw(QPCIBus *bus, int devfn, uint8_t offset) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + uint32_t config_addr = (devfn << 8) | offset; + return qrtas_ibm_read_pci_config(bus->qts, s->alloc, s->buid, + config_addr, 2); +} + +static uint32_t qpci_spapr_config_readl(QPCIBus *bus, int devfn, uint8_t offset) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + uint32_t config_addr = (devfn << 8) | offset; + return qrtas_ibm_read_pci_config(bus->qts, s->alloc, s->buid, + config_addr, 4); +} + +static void qpci_spapr_config_writeb(QPCIBus *bus, int devfn, uint8_t offset, + uint8_t value) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + uint32_t config_addr = (devfn << 8) | offset; + qrtas_ibm_write_pci_config(bus->qts, s->alloc, s->buid, + config_addr, 1, value); +} + +static void qpci_spapr_config_writew(QPCIBus *bus, int devfn, uint8_t offset, + uint16_t value) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + uint32_t config_addr = (devfn << 8) | offset; + qrtas_ibm_write_pci_config(bus->qts, s->alloc, s->buid, + config_addr, 2, value); +} + +static void qpci_spapr_config_writel(QPCIBus *bus, int devfn, uint8_t offset, + uint32_t value) +{ + QPCIBusSPAPR *s = container_of(bus, QPCIBusSPAPR, bus); + uint32_t config_addr = (devfn << 8) | offset; + qrtas_ibm_write_pci_config(bus->qts, s->alloc, s->buid, + config_addr, 4, value); +} + +#define SPAPR_PCI_BASE (1ULL << 45) + +#define SPAPR_PCI_MMIO32_WIN_SIZE 0x80000000 /* 2 GiB */ +#define SPAPR_PCI_IO_WIN_SIZE 0x10000 + +static void *qpci_spapr_get_driver(void *obj, const char *interface) +{ + QPCIBusSPAPR *qpci = obj; + if (!g_strcmp0(interface, "pci-bus")) { + return &qpci->bus; + } + fprintf(stderr, "%s not present in pci-bus-spapr", interface); + g_assert_not_reached(); +} + +void qpci_init_spapr(QPCIBusSPAPR *qpci, QTestState *qts, + QGuestAllocator *alloc) +{ + assert(qts); + + /* tests cannot use spapr, needs to be fixed first */ + qpci->bus.has_buggy_msi = true; + + qpci->alloc = alloc; + + qpci->bus.pio_readb = qpci_spapr_pio_readb; + qpci->bus.pio_readw = qpci_spapr_pio_readw; + qpci->bus.pio_readl = qpci_spapr_pio_readl; + qpci->bus.pio_readq = qpci_spapr_pio_readq; + + qpci->bus.pio_writeb = qpci_spapr_pio_writeb; + qpci->bus.pio_writew = qpci_spapr_pio_writew; + qpci->bus.pio_writel = qpci_spapr_pio_writel; + qpci->bus.pio_writeq = qpci_spapr_pio_writeq; + + qpci->bus.memread = qpci_spapr_memread; + qpci->bus.memwrite = qpci_spapr_memwrite; + + qpci->bus.config_readb = qpci_spapr_config_readb; + qpci->bus.config_readw = qpci_spapr_config_readw; + qpci->bus.config_readl = qpci_spapr_config_readl; + + qpci->bus.config_writeb = qpci_spapr_config_writeb; + qpci->bus.config_writew = qpci_spapr_config_writew; + qpci->bus.config_writel = qpci_spapr_config_writel; + + /* FIXME: We assume the default location of the PHB for now. + * Ideally we'd parse the device tree deposited in the guest to + * get the window locations */ + qpci->buid = 0x800000020000000ULL; + + qpci->pio_cpu_base = SPAPR_PCI_BASE; + qpci->pio.pci_base = 0; + qpci->pio.size = SPAPR_PCI_IO_WIN_SIZE; + + /* 32-bit portion of the MMIO window is at PCI address 2..4 GiB */ + qpci->mmio32_cpu_base = SPAPR_PCI_BASE; + qpci->mmio32.pci_base = SPAPR_PCI_MMIO32_WIN_SIZE; + qpci->mmio32.size = SPAPR_PCI_MMIO32_WIN_SIZE; + + qpci->bus.qts = qts; + qpci->bus.pio_alloc_ptr = 0xc000; + qpci->bus.mmio_alloc_ptr = qpci->mmio32.pci_base; + qpci->bus.mmio_limit = qpci->mmio32.pci_base + qpci->mmio32.size; + + qpci->obj.get_driver = qpci_spapr_get_driver; +} + +QPCIBus *qpci_new_spapr(QTestState *qts, QGuestAllocator *alloc) +{ + QPCIBusSPAPR *qpci = g_new0(QPCIBusSPAPR, 1); + qpci_init_spapr(qpci, qts, alloc); + + return &qpci->bus; +} + +void qpci_free_spapr(QPCIBus *bus) +{ + QPCIBusSPAPR *s; + + if (!bus) { + return; + } + s = container_of(bus, QPCIBusSPAPR, bus); + + g_free(s); +} + +static void qpci_spapr_register_nodes(void) +{ + qos_node_create_driver("pci-bus-spapr", NULL); + qos_node_produces("pci-bus-spapr", "pci-bus"); +} + +libqos_init(qpci_spapr_register_nodes); diff --git a/tests/qtest/libqos/pci-spapr.h b/tests/qtest/libqos/pci-spapr.h new file mode 100644 index 0000000000..d9e25631c6 --- /dev/null +++ b/tests/qtest/libqos/pci-spapr.h @@ -0,0 +1,41 @@ +/* + * libqos PCI bindings for SPAPR + * + * 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 LIBQOS_PCI_SPAPR_H +#define LIBQOS_PCI_SPAPR_H + +#include "libqos/malloc.h" +#include "libqos/pci.h" +#include "libqos/qgraph.h" + +/* From include/hw/pci-host/spapr.h */ + +typedef struct QPCIWindow { + uint64_t pci_base; /* window address in PCI space */ + uint64_t size; /* window size */ +} QPCIWindow; + +typedef struct QPCIBusSPAPR { + QOSGraphObject obj; + QPCIBus bus; + QGuestAllocator *alloc; + + uint64_t buid; + + uint64_t pio_cpu_base; + QPCIWindow pio; + + uint64_t mmio32_cpu_base; + QPCIWindow mmio32; +} QPCIBusSPAPR; + +void qpci_init_spapr(QPCIBusSPAPR *ret, QTestState *qts, + QGuestAllocator *alloc); +QPCIBus *qpci_new_spapr(QTestState *qts, QGuestAllocator *alloc); +void qpci_free_spapr(QPCIBus *bus); + +#endif diff --git a/tests/qtest/libqos/pci.c b/tests/qtest/libqos/pci.c new file mode 100644 index 0000000000..2309a724e4 --- /dev/null +++ b/tests/qtest/libqos/pci.c @@ -0,0 +1,457 @@ +/* + * libqos PCI bindings + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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/pci.h" + +#include "hw/pci/pci_regs.h" +#include "qemu/host-utils.h" +#include "libqos/qgraph.h" + +void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, + void (*func)(QPCIDevice *dev, int devfn, void *data), + void *data) +{ + int slot; + + for (slot = 0; slot < 32; slot++) { + int fn; + + for (fn = 0; fn < 8; fn++) { + QPCIDevice *dev; + + dev = qpci_device_find(bus, QPCI_DEVFN(slot, fn)); + if (!dev) { + continue; + } + + if (vendor_id != -1 && + qpci_config_readw(dev, PCI_VENDOR_ID) != vendor_id) { + g_free(dev); + continue; + } + + if (device_id != -1 && + qpci_config_readw(dev, PCI_DEVICE_ID) != device_id) { + g_free(dev); + continue; + } + + func(dev, QPCI_DEVFN(slot, fn), data); + } + } +} + +bool qpci_has_buggy_msi(QPCIDevice *dev) +{ + return dev->bus->has_buggy_msi; +} + +bool qpci_check_buggy_msi(QPCIDevice *dev) +{ + if (qpci_has_buggy_msi(dev)) { + g_test_skip("Skipping due to incomplete support for MSI"); + return true; + } + return false; +} + +static void qpci_device_set(QPCIDevice *dev, QPCIBus *bus, int devfn) +{ + g_assert(dev); + + dev->bus = bus; + dev->devfn = devfn; +} + +QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn) +{ + QPCIDevice *dev; + + dev = g_malloc0(sizeof(*dev)); + qpci_device_set(dev, bus, devfn); + + if (qpci_config_readw(dev, PCI_VENDOR_ID) == 0xFFFF) { + g_free(dev); + return NULL; + } + + return dev; +} + +void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr) +{ + uint16_t vendor_id, device_id; + + qpci_device_set(dev, bus, addr->devfn); + vendor_id = qpci_config_readw(dev, PCI_VENDOR_ID); + device_id = qpci_config_readw(dev, PCI_DEVICE_ID); + g_assert(!addr->vendor_id || vendor_id == addr->vendor_id); + g_assert(!addr->device_id || device_id == addr->device_id); +} + +void qpci_device_enable(QPCIDevice *dev) +{ + uint16_t cmd; + + /* FIXME -- does this need to be a bus callout? */ + cmd = qpci_config_readw(dev, PCI_COMMAND); + cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + qpci_config_writew(dev, PCI_COMMAND, cmd); + + /* Verify the bits are now set. */ + cmd = qpci_config_readw(dev, PCI_COMMAND); + g_assert_cmphex(cmd & PCI_COMMAND_IO, ==, PCI_COMMAND_IO); + g_assert_cmphex(cmd & PCI_COMMAND_MEMORY, ==, PCI_COMMAND_MEMORY); + g_assert_cmphex(cmd & PCI_COMMAND_MASTER, ==, PCI_COMMAND_MASTER); +} + +/** + * qpci_find_capability: + * @dev: the PCI device + * @id: the PCI Capability ID (PCI_CAP_ID_*) + * @start_addr: 0 to begin iteration or the last return value to continue + * iteration + * + * Iterate over the PCI Capabilities List. + * + * Returns: PCI Configuration Space offset of the capabililty structure or + * 0 if no further matching capability is found + */ +uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id, uint8_t start_addr) +{ + uint8_t cap; + uint8_t addr; + + if (start_addr) { + addr = qpci_config_readb(dev, start_addr + PCI_CAP_LIST_NEXT); + } else { + addr = qpci_config_readb(dev, PCI_CAPABILITY_LIST); + } + + do { + cap = qpci_config_readb(dev, addr); + if (cap != id) { + addr = qpci_config_readb(dev, addr + PCI_CAP_LIST_NEXT); + } + } while (cap != id && addr != 0); + + return addr; +} + +void qpci_msix_enable(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t val; + uint32_t table; + uint8_t bir_table; + uint8_t bir_pba; + + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); + g_assert_cmphex(addr, !=, 0); + + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, val | PCI_MSIX_FLAGS_ENABLE); + + table = qpci_config_readl(dev, addr + PCI_MSIX_TABLE); + bir_table = table & PCI_MSIX_FLAGS_BIRMASK; + dev->msix_table_bar = qpci_iomap(dev, bir_table, NULL); + dev->msix_table_off = table & ~PCI_MSIX_FLAGS_BIRMASK; + + table = qpci_config_readl(dev, addr + PCI_MSIX_PBA); + bir_pba = table & PCI_MSIX_FLAGS_BIRMASK; + if (bir_pba != bir_table) { + dev->msix_pba_bar = qpci_iomap(dev, bir_pba, NULL); + } else { + dev->msix_pba_bar = dev->msix_table_bar; + } + dev->msix_pba_off = table & ~PCI_MSIX_FLAGS_BIRMASK; + + dev->msix_enabled = true; +} + +void qpci_msix_disable(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t val; + + g_assert(dev->msix_enabled); + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); + g_assert_cmphex(addr, !=, 0); + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + qpci_config_writew(dev, addr + PCI_MSIX_FLAGS, + val & ~PCI_MSIX_FLAGS_ENABLE); + + if (dev->msix_pba_bar.addr != dev->msix_table_bar.addr) { + qpci_iounmap(dev, dev->msix_pba_bar); + } + qpci_iounmap(dev, dev->msix_table_bar); + + dev->msix_enabled = 0; + dev->msix_table_off = 0; + dev->msix_pba_off = 0; +} + +bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry) +{ + uint32_t pba_entry; + uint8_t bit_n = entry % 32; + uint64_t off = (entry / 32) * PCI_MSIX_ENTRY_SIZE / 4; + + g_assert(dev->msix_enabled); + pba_entry = qpci_io_readl(dev, dev->msix_pba_bar, dev->msix_pba_off + off); + qpci_io_writel(dev, dev->msix_pba_bar, dev->msix_pba_off + off, + pba_entry & ~(1 << bit_n)); + return (pba_entry & (1 << bit_n)) != 0; +} + +bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry) +{ + uint8_t addr; + uint16_t val; + uint64_t vector_off = dev->msix_table_off + entry * PCI_MSIX_ENTRY_SIZE; + + g_assert(dev->msix_enabled); + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); + g_assert_cmphex(addr, !=, 0); + val = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + + if (val & PCI_MSIX_FLAGS_MASKALL) { + return true; + } else { + return (qpci_io_readl(dev, dev->msix_table_bar, + vector_off + PCI_MSIX_ENTRY_VECTOR_CTRL) + & PCI_MSIX_ENTRY_CTRL_MASKBIT) != 0; + } +} + +uint16_t qpci_msix_table_size(QPCIDevice *dev) +{ + uint8_t addr; + uint16_t control; + + addr = qpci_find_capability(dev, PCI_CAP_ID_MSIX, 0); + g_assert_cmphex(addr, !=, 0); + + control = qpci_config_readw(dev, addr + PCI_MSIX_FLAGS); + return (control & PCI_MSIX_FLAGS_QSIZE) + 1; +} + +uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset) +{ + return dev->bus->config_readb(dev->bus, dev->devfn, offset); +} + +uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset) +{ + return dev->bus->config_readw(dev->bus, dev->devfn, offset); +} + +uint32_t qpci_config_readl(QPCIDevice *dev, uint8_t offset) +{ + return dev->bus->config_readl(dev->bus, dev->devfn, offset); +} + + +void qpci_config_writeb(QPCIDevice *dev, uint8_t offset, uint8_t value) +{ + dev->bus->config_writeb(dev->bus, dev->devfn, offset, value); +} + +void qpci_config_writew(QPCIDevice *dev, uint8_t offset, uint16_t value) +{ + dev->bus->config_writew(dev->bus, dev->devfn, offset, value); +} + +void qpci_config_writel(QPCIDevice *dev, uint8_t offset, uint32_t value) +{ + dev->bus->config_writel(dev->bus, dev->devfn, offset, value); +} + +uint8_t qpci_io_readb(QPCIDevice *dev, QPCIBar token, uint64_t off) +{ + if (token.addr < QPCI_PIO_LIMIT) { + return dev->bus->pio_readb(dev->bus, token.addr + off); + } else { + uint8_t val; + dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); + return val; + } +} + +uint16_t qpci_io_readw(QPCIDevice *dev, QPCIBar token, uint64_t off) +{ + if (token.addr < QPCI_PIO_LIMIT) { + return dev->bus->pio_readw(dev->bus, token.addr + off); + } else { + uint16_t val; + dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); + return le16_to_cpu(val); + } +} + +uint32_t qpci_io_readl(QPCIDevice *dev, QPCIBar token, uint64_t off) +{ + if (token.addr < QPCI_PIO_LIMIT) { + return dev->bus->pio_readl(dev->bus, token.addr + off); + } else { + uint32_t val; + dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); + return le32_to_cpu(val); + } +} + +uint64_t qpci_io_readq(QPCIDevice *dev, QPCIBar token, uint64_t off) +{ + if (token.addr < QPCI_PIO_LIMIT) { + return dev->bus->pio_readq(dev->bus, token.addr + off); + } else { + uint64_t val; + dev->bus->memread(dev->bus, token.addr + off, &val, sizeof(val)); + return le64_to_cpu(val); + } +} + +void qpci_io_writeb(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint8_t value) +{ + if (token.addr < QPCI_PIO_LIMIT) { + dev->bus->pio_writeb(dev->bus, token.addr + off, value); + } else { + dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); + } +} + +void qpci_io_writew(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint16_t value) +{ + if (token.addr < QPCI_PIO_LIMIT) { + dev->bus->pio_writew(dev->bus, token.addr + off, value); + } else { + value = cpu_to_le16(value); + dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); + } +} + +void qpci_io_writel(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint32_t value) +{ + if (token.addr < QPCI_PIO_LIMIT) { + dev->bus->pio_writel(dev->bus, token.addr + off, value); + } else { + value = cpu_to_le32(value); + dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); + } +} + +void qpci_io_writeq(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint64_t value) +{ + if (token.addr < QPCI_PIO_LIMIT) { + dev->bus->pio_writeq(dev->bus, token.addr + off, value); + } else { + value = cpu_to_le64(value); + dev->bus->memwrite(dev->bus, token.addr + off, &value, sizeof(value)); + } +} + +void qpci_memread(QPCIDevice *dev, QPCIBar token, uint64_t off, + void *buf, size_t len) +{ + g_assert(token.addr >= QPCI_PIO_LIMIT); + dev->bus->memread(dev->bus, token.addr + off, buf, len); +} + +void qpci_memwrite(QPCIDevice *dev, QPCIBar token, uint64_t off, + const void *buf, size_t len) +{ + g_assert(token.addr >= QPCI_PIO_LIMIT); + dev->bus->memwrite(dev->bus, token.addr + off, buf, len); +} + +QPCIBar qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr) +{ + QPCIBus *bus = dev->bus; + static const int bar_reg_map[] = { + PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2, + PCI_BASE_ADDRESS_3, PCI_BASE_ADDRESS_4, PCI_BASE_ADDRESS_5, + }; + QPCIBar bar; + int bar_reg; + uint32_t addr, size; + uint32_t io_type; + uint64_t loc; + + g_assert(barno >= 0 && barno <= 5); + bar_reg = bar_reg_map[barno]; + + qpci_config_writel(dev, bar_reg, 0xFFFFFFFF); + addr = qpci_config_readl(dev, bar_reg); + + io_type = addr & PCI_BASE_ADDRESS_SPACE; + if (io_type == PCI_BASE_ADDRESS_SPACE_IO) { + addr &= PCI_BASE_ADDRESS_IO_MASK; + } else { + addr &= PCI_BASE_ADDRESS_MEM_MASK; + } + + g_assert(addr); /* Must have *some* size bits */ + + size = 1U << ctz32(addr); + if (sizeptr) { + *sizeptr = size; + } + + if (io_type == PCI_BASE_ADDRESS_SPACE_IO) { + loc = QEMU_ALIGN_UP(bus->pio_alloc_ptr, size); + + g_assert(loc >= bus->pio_alloc_ptr); + g_assert(loc + size <= QPCI_PIO_LIMIT); /* Keep PIO below 64kiB */ + + bus->pio_alloc_ptr = loc + size; + + qpci_config_writel(dev, bar_reg, loc | PCI_BASE_ADDRESS_SPACE_IO); + } else { + loc = QEMU_ALIGN_UP(bus->mmio_alloc_ptr, size); + + /* Check for space */ + g_assert(loc >= bus->mmio_alloc_ptr); + g_assert(loc + size <= bus->mmio_limit); + + bus->mmio_alloc_ptr = loc + size; + + qpci_config_writel(dev, bar_reg, loc); + } + + bar.addr = loc; + return bar; +} + +void qpci_iounmap(QPCIDevice *dev, QPCIBar bar) +{ + /* FIXME */ +} + +QPCIBar qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr) +{ + QPCIBar bar = { .addr = addr }; + return bar; +} + +void add_qpci_address(QOSGraphEdgeOptions *opts, QPCIAddress *addr) +{ + g_assert(addr); + g_assert(opts); + + opts->arg = addr; + opts->size_arg = sizeof(QPCIAddress); +} diff --git a/tests/qtest/libqos/pci.h b/tests/qtest/libqos/pci.h new file mode 100644 index 0000000000..590c175190 --- /dev/null +++ b/tests/qtest/libqos/pci.h @@ -0,0 +1,129 @@ +/* + * libqos PCI bindings + * + * Copyright IBM, Corp. 2012-2013 + * + * Authors: + * Anthony Liguori + * + * 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 LIBQOS_PCI_H +#define LIBQOS_PCI_H + +#include "libqtest.h" +#include "libqos/qgraph.h" + +#define QPCI_PIO_LIMIT 0x10000 + +#define QPCI_DEVFN(dev, fn) (((dev) << 3) | (fn)) + +typedef struct QPCIDevice QPCIDevice; +typedef struct QPCIBus QPCIBus; +typedef struct QPCIBar QPCIBar; +typedef struct QPCIAddress QPCIAddress; + +struct QPCIBus { + uint8_t (*pio_readb)(QPCIBus *bus, uint32_t addr); + uint16_t (*pio_readw)(QPCIBus *bus, uint32_t addr); + uint32_t (*pio_readl)(QPCIBus *bus, uint32_t addr); + uint64_t (*pio_readq)(QPCIBus *bus, uint32_t addr); + + void (*pio_writeb)(QPCIBus *bus, uint32_t addr, uint8_t value); + void (*pio_writew)(QPCIBus *bus, uint32_t addr, uint16_t value); + void (*pio_writel)(QPCIBus *bus, uint32_t addr, uint32_t value); + void (*pio_writeq)(QPCIBus *bus, uint32_t addr, uint64_t value); + + void (*memread)(QPCIBus *bus, uint32_t addr, void *buf, size_t len); + void (*memwrite)(QPCIBus *bus, uint32_t addr, const void *buf, size_t len); + + uint8_t (*config_readb)(QPCIBus *bus, int devfn, uint8_t offset); + uint16_t (*config_readw)(QPCIBus *bus, int devfn, uint8_t offset); + uint32_t (*config_readl)(QPCIBus *bus, int devfn, uint8_t offset); + + void (*config_writeb)(QPCIBus *bus, int devfn, + uint8_t offset, uint8_t value); + void (*config_writew)(QPCIBus *bus, int devfn, + uint8_t offset, uint16_t value); + void (*config_writel)(QPCIBus *bus, int devfn, + uint8_t offset, uint32_t value); + + QTestState *qts; + uint16_t pio_alloc_ptr; + uint64_t mmio_alloc_ptr, mmio_limit; + bool has_buggy_msi; /* TRUE for spapr, FALSE for pci */ + +}; + +struct QPCIBar { + uint64_t addr; +}; + +struct QPCIDevice +{ + QPCIBus *bus; + int devfn; + bool msix_enabled; + QPCIBar msix_table_bar, msix_pba_bar; + uint64_t msix_table_off, msix_pba_off; +}; + +struct QPCIAddress { + uint32_t devfn; + uint16_t vendor_id; + uint16_t device_id; +}; + +void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id, + void (*func)(QPCIDevice *dev, int devfn, void *data), + void *data); +QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn); +void qpci_device_init(QPCIDevice *dev, QPCIBus *bus, QPCIAddress *addr); + +bool qpci_has_buggy_msi(QPCIDevice *dev); +bool qpci_check_buggy_msi(QPCIDevice *dev); + +void qpci_device_enable(QPCIDevice *dev); +uint8_t qpci_find_capability(QPCIDevice *dev, uint8_t id, uint8_t start_addr); +void qpci_msix_enable(QPCIDevice *dev); +void qpci_msix_disable(QPCIDevice *dev); +bool qpci_msix_pending(QPCIDevice *dev, uint16_t entry); +bool qpci_msix_masked(QPCIDevice *dev, uint16_t entry); +uint16_t qpci_msix_table_size(QPCIDevice *dev); + +uint8_t qpci_config_readb(QPCIDevice *dev, uint8_t offset); +uint16_t qpci_config_readw(QPCIDevice *dev, uint8_t offset); +uint32_t qpci_config_readl(QPCIDevice *dev, uint8_t offset); + +void qpci_config_writeb(QPCIDevice *dev, uint8_t offset, uint8_t value); +void qpci_config_writew(QPCIDevice *dev, uint8_t offset, uint16_t value); +void qpci_config_writel(QPCIDevice *dev, uint8_t offset, uint32_t value); + +uint8_t qpci_io_readb(QPCIDevice *dev, QPCIBar token, uint64_t off); +uint16_t qpci_io_readw(QPCIDevice *dev, QPCIBar token, uint64_t off); +uint32_t qpci_io_readl(QPCIDevice *dev, QPCIBar token, uint64_t off); +uint64_t qpci_io_readq(QPCIDevice *dev, QPCIBar token, uint64_t off); + +void qpci_io_writeb(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint8_t value); +void qpci_io_writew(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint16_t value); +void qpci_io_writel(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint32_t value); +void qpci_io_writeq(QPCIDevice *dev, QPCIBar token, uint64_t off, + uint64_t value); + +void qpci_memread(QPCIDevice *bus, QPCIBar token, uint64_t off, + void *buf, size_t len); +void qpci_memwrite(QPCIDevice *bus, QPCIBar token, uint64_t off, + const void *buf, size_t len); +QPCIBar qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr); +void qpci_iounmap(QPCIDevice *dev, QPCIBar addr); +QPCIBar qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr); + +void qpci_unplug_acpi_device_test(QTestState *qs, const char *id, uint8_t slot); + +void add_qpci_address(QOSGraphEdgeOptions *opts, QPCIAddress *addr); +#endif diff --git a/tests/qtest/libqos/ppc64_pseries-machine.c b/tests/qtest/libqos/ppc64_pseries-machine.c new file mode 100644 index 0000000000..867f27a3c8 --- /dev/null +++ b/tests/qtest/libqos/ppc64_pseries-machine.c @@ -0,0 +1,112 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/qgraph.h" +#include "pci-spapr.h" +#include "qemu/module.h" +#include "libqos/malloc-spapr.h" + +typedef struct QSPAPR_pci_host QSPAPR_pci_host; +typedef struct Qppc64_pseriesMachine Qppc64_pseriesMachine; + +struct QSPAPR_pci_host { + QOSGraphObject obj; + QPCIBusSPAPR pci; +}; + +struct Qppc64_pseriesMachine { + QOSGraphObject obj; + QGuestAllocator alloc; + QSPAPR_pci_host bridge; +}; + +/* QSPAPR_pci_host */ + +static QOSGraphObject *QSPAPR_host_get_device(void *obj, const char *device) +{ + QSPAPR_pci_host *host = obj; + if (!g_strcmp0(device, "pci-bus-spapr")) { + return &host->pci.obj; + } + fprintf(stderr, "%s not present in QSPAPR_pci_host\n", device); + g_assert_not_reached(); +} + +static void qos_create_QSPAPR_host(QSPAPR_pci_host *host, + QTestState *qts, + QGuestAllocator *alloc) +{ + host->obj.get_device = QSPAPR_host_get_device; + qpci_init_spapr(&host->pci, qts, alloc); +} + +/* ppc64/pseries machine */ + +static void spapr_destructor(QOSGraphObject *obj) +{ + Qppc64_pseriesMachine *machine = (Qppc64_pseriesMachine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *spapr_get_driver(void *object, const char *interface) +{ + Qppc64_pseriesMachine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in ppc64/pseries\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *spapr_get_device(void *obj, const char *device) +{ + Qppc64_pseriesMachine *machine = obj; + if (!g_strcmp0(device, "spapr-pci-host-bridge")) { + return &machine->bridge.obj; + } + + fprintf(stderr, "%s not present in ppc64/pseries\n", device); + g_assert_not_reached(); +} + +static void *qos_create_machine_spapr(QTestState *qts) +{ + Qppc64_pseriesMachine *machine = g_new0(Qppc64_pseriesMachine, 1); + machine->obj.get_device = spapr_get_device; + machine->obj.get_driver = spapr_get_driver; + machine->obj.destructor = spapr_destructor; + spapr_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS); + + qos_create_QSPAPR_host(&machine->bridge, qts, &machine->alloc); + + return &machine->obj; +} + +static void spapr_machine_register_nodes(void) +{ + qos_node_create_machine("ppc64/pseries", qos_create_machine_spapr); + qos_node_create_driver("spapr-pci-host-bridge", NULL); + qos_node_contains("ppc64/pseries", "spapr-pci-host-bridge", NULL); + qos_node_contains("spapr-pci-host-bridge", "pci-bus-spapr", NULL); +} + +libqos_init(spapr_machine_register_nodes); + diff --git a/tests/qtest/libqos/qgraph.c b/tests/qtest/libqos/qgraph.c new file mode 100644 index 0000000000..7a7ae2a19e --- /dev/null +++ b/tests/qtest/libqos/qgraph.c @@ -0,0 +1,759 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/queue.h" +#include "libqos/qgraph_internal.h" +#include "libqos/qgraph.h" + +#define QGRAPH_PRINT_DEBUG 0 +#define QOS_ROOT "" +typedef struct QOSStackElement QOSStackElement; + +/* Graph Edge.*/ +struct QOSGraphEdge { + QOSEdgeType type; + char *dest; + void *arg; /* just for QEDGE_CONTAINS + * and QEDGE_CONSUMED_BY */ + char *extra_device_opts; /* added to -device option, "," is + * automatically added + */ + char *before_cmd_line; /* added before node cmd_line */ + char *after_cmd_line; /* added after -device options */ + char *edge_name; /* used by QEDGE_CONTAINS */ + QSLIST_ENTRY(QOSGraphEdge) edge_list; +}; + +typedef QSLIST_HEAD(, QOSGraphEdge) QOSGraphEdgeList; + +/** + * Stack used to keep track of the discovered path when using + * the DFS algorithm + */ +struct QOSStackElement { + QOSGraphNode *node; + QOSStackElement *parent; + QOSGraphEdge *parent_edge; + int length; +}; + +/* Each enty in these hash table will consist of pair. */ +static GHashTable *edge_table; +static GHashTable *node_table; + +/* stack used by the DFS algorithm to store the path from machine to test */ +static QOSStackElement qos_node_stack[QOS_PATH_MAX_ELEMENT_SIZE]; +static int qos_node_tos; + +/** + * add_edge(): creates an edge of type @type + * from @source to @dest node, and inserts it in the + * edges hash table + * + * Nodes @source and @dest do not necessarily need to exist. + * Possibility to add also options (see #QOSGraphEdgeOptions) + * edge->edge_name is used as identifier for get_device relationships, + * so by default is equal to @dest. + */ +static void add_edge(const char *source, const char *dest, + QOSEdgeType type, QOSGraphEdgeOptions *opts) +{ + char *key; + QOSGraphEdgeList *list = g_hash_table_lookup(edge_table, source); + QOSGraphEdgeOptions def_opts = { }; + + if (!list) { + list = g_new0(QOSGraphEdgeList, 1); + key = g_strdup(source); + g_hash_table_insert(edge_table, key, list); + } + + if (!opts) { + opts = &def_opts; + } + + QOSGraphEdge *edge = g_new0(QOSGraphEdge, 1); + edge->type = type; + edge->dest = g_strdup(dest); + edge->edge_name = g_strdup(opts->edge_name ?: dest); + edge->arg = g_memdup(opts->arg, opts->size_arg); + + edge->before_cmd_line = + opts->before_cmd_line ? g_strconcat(" ", opts->before_cmd_line, NULL) : NULL; + edge->extra_device_opts = + opts->extra_device_opts ? g_strconcat(",", opts->extra_device_opts, NULL) : NULL; + edge->after_cmd_line = + opts->after_cmd_line ? g_strconcat(" ", opts->after_cmd_line, NULL) : NULL; + + QSLIST_INSERT_HEAD(list, edge, edge_list); +} + +/* destroy_edges(): frees all edges inside a given @list */ +static void destroy_edges(void *list) +{ + QOSGraphEdge *temp; + QOSGraphEdgeList *elist = list; + + while (!QSLIST_EMPTY(elist)) { + temp = QSLIST_FIRST(elist); + QSLIST_REMOVE_HEAD(elist, edge_list); + g_free(temp->dest); + g_free(temp->before_cmd_line); + g_free(temp->after_cmd_line); + g_free(temp->extra_device_opts); + g_free(temp->edge_name); + g_free(temp->arg); + g_free(temp); + } + g_free(elist); +} + +/** + * create_node(): creates a node @name of type @type + * and inserts it to the nodes hash table. + * By default, node is not available. + */ +static QOSGraphNode *create_node(const char *name, QOSNodeType type) +{ + if (g_hash_table_lookup(node_table, name)) { + g_printerr("Node %s already created\n", name); + abort(); + } + + QOSGraphNode *node = g_new0(QOSGraphNode, 1); + node->type = type; + node->available = false; + node->name = g_strdup(name); + g_hash_table_insert(node_table, node->name, node); + return node; +} + +/** + * destroy_node(): frees a node @val from the nodes hash table. + * Note that node->name is not free'd since it will represent the + * hash table key + */ +static void destroy_node(void *val) +{ + QOSGraphNode *node = val; + g_free(node->command_line); + g_free(node); +} + +/** + * destroy_string(): frees @key from the nodes hash table. + * Actually frees the node->name + */ +static void destroy_string(void *key) +{ + g_free(key); +} + +/** + * search_node(): search for a node @key in the nodes hash table + * Returns the QOSGraphNode if found, #NULL otherwise + */ +static QOSGraphNode *search_node(const char *key) +{ + return g_hash_table_lookup(node_table, key); +} + +/** + * get_edgelist(): returns the edge list (value) assigned to + * the @key in the edge hash table. + * This list will contain all edges with source equal to @key + * + * Returns: on success: the %QOSGraphEdgeList + * otherwise: abort() + */ +static QOSGraphEdgeList *get_edgelist(const char *key) +{ + return g_hash_table_lookup(edge_table, key); +} + +/** + * search_list_edges(): search for an edge with destination @dest + * in the given @edgelist. + * + * Returns: on success: the %QOSGraphEdge + * otherwise: #NULL + */ +static QOSGraphEdge *search_list_edges(QOSGraphEdgeList *edgelist, + const char *dest) +{ + QOSGraphEdge *tmp, *next; + if (!edgelist) { + return NULL; + } + QSLIST_FOREACH_SAFE(tmp, edgelist, edge_list, next) { + if (g_strcmp0(tmp->dest, dest) == 0) { + break; + } + } + return tmp; +} + +/** + * search_machine(): search for a machine @name in the node hash + * table. A machine is the child of the root node. + * This function forces the research in the childs of the root, + * to check the node is a proper machine + * + * Returns: on success: the %QOSGraphNode + * otherwise: #NULL + */ +static QOSGraphNode *search_machine(const char *name) +{ + QOSGraphNode *n; + QOSGraphEdgeList *root_list = get_edgelist(QOS_ROOT); + QOSGraphEdge *e = search_list_edges(root_list, name); + if (!e) { + return NULL; + } + n = search_node(e->dest); + if (n->type == QNODE_MACHINE) { + return n; + } + return NULL; +} + +/** + * create_interface(): checks if there is already + * a node @node in the node hash table, if not + * creates a node @node of type #QNODE_INTERFACE + * and inserts it. If there is one, check it's + * a #QNODE_INTERFACE and abort() if it's not. + */ +static void create_interface(const char *node) +{ + QOSGraphNode *interface; + interface = search_node(node); + if (!interface) { + create_node(node, QNODE_INTERFACE); + } else if (interface->type != QNODE_INTERFACE) { + fprintf(stderr, "Error: Node %s is not an interface\n", node); + abort(); + } +} + +/** + * build_machine_cmd_line(): builds the command line for the machine + * @node. The node name must be a valid qemu identifier, since it + * will be used to build the command line. + * + * It is also possible to pass an optional @args that will be + * concatenated to the command line. + * + * For machines, prepend -M to the machine name. ", @rgs" is added + * after the -M command. + */ +static void build_machine_cmd_line(QOSGraphNode *node, const char *args) +{ + char *machine = qos_get_machine_type(node->name); + if (args) { + node->command_line = g_strconcat("-M ", machine, ",", args, NULL); + } else { + node->command_line = g_strconcat("-M ", machine, " ", NULL); + } +} + +/** + * build_driver_cmd_line(): builds the command line for the driver + * @node. The node name must be a valid qemu identifier, since it + * will be used to build the command line. + * + * Driver do not need additional command line, since it will be + * provided by the edge options. + * + * For drivers, prepend -device to the node name. + */ +static void build_driver_cmd_line(QOSGraphNode *node) +{ + node->command_line = g_strconcat(" -device ", node->name, NULL); +} + +/* qos_print_cb(): callback prints all path found by the DFS algorithm. */ +static void qos_print_cb(QOSGraphNode *path, int length) +{ + #if QGRAPH_PRINT_DEBUG + printf("%d elements\n", length); + + if (!path) { + return; + } + + while (path->path_edge) { + printf("%s ", path->name); + switch (path->path_edge->type) { + case QEDGE_PRODUCES: + printf("--PRODUCES--> "); + break; + case QEDGE_CONSUMED_BY: + printf("--CONSUMED_BY--> "); + break; + case QEDGE_CONTAINS: + printf("--CONTAINS--> "); + break; + } + path = search_node(path->path_edge->dest); + } + + printf("%s\n\n", path->name); + #endif +} + +/* qos_push(): push a node @el and edge @e in the qos_node_stack */ +static void qos_push(QOSGraphNode *el, QOSStackElement *parent, + QOSGraphEdge *e) +{ + int len = 0; /* root is not counted */ + if (qos_node_tos == QOS_PATH_MAX_ELEMENT_SIZE) { + g_printerr("QOSStack: full stack, cannot push"); + abort(); + } + + if (parent) { + len = parent->length + 1; + } + qos_node_stack[qos_node_tos++] = (QOSStackElement) { + .node = el, + .parent = parent, + .parent_edge = e, + .length = len, + }; +} + +/* qos_tos(): returns the top of stack, without popping */ +static QOSStackElement *qos_tos(void) +{ + return &qos_node_stack[qos_node_tos - 1]; +} + +/* qos_pop(): pops an element from the tos, setting it unvisited*/ +static QOSStackElement *qos_pop(void) +{ + if (qos_node_tos == 0) { + g_printerr("QOSStack: empty stack, cannot pop"); + abort(); + } + QOSStackElement *e = qos_tos(); + e->node->visited = false; + qos_node_tos--; + return e; +} + +/** + * qos_reverse_path(): reverses the found path, going from + * test-to-machine to machine-to-test + */ +static QOSGraphNode *qos_reverse_path(QOSStackElement *el) +{ + if (!el) { + return NULL; + } + + el->node->path_edge = NULL; + + while (el->parent) { + el->parent->node->path_edge = el->parent_edge; + el = el->parent; + } + + return el->node; +} + +/** + * qos_traverse_graph(): graph-walking algorithm, using Depth First Search it + * starts from the root @machine and walks all possible path until it + * reaches a test node. + * At that point, it reverses the path found and invokes the @callback. + * + * Being Depth First Search, time complexity is O(|V| + |E|), while + * space is O(|V|). In this case, the maximum stack size is set by + * QOS_PATH_MAX_ELEMENT_SIZE. + */ +static void qos_traverse_graph(QOSGraphNode *root, QOSTestCallback callback) +{ + QOSGraphNode *v, *dest_node, *path; + QOSStackElement *s_el; + QOSGraphEdge *e, *next; + QOSGraphEdgeList *list; + + qos_push(root, NULL, NULL); + + while (qos_node_tos > 0) { + s_el = qos_tos(); + v = s_el->node; + if (v->visited) { + qos_pop(); + continue; + } + v->visited = true; + list = get_edgelist(v->name); + if (!list) { + qos_pop(); + if (v->type == QNODE_TEST) { + v->visited = false; + path = qos_reverse_path(s_el); + callback(path, s_el->length); + } + } else { + QSLIST_FOREACH_SAFE(e, list, edge_list, next) { + dest_node = search_node(e->dest); + + if (!dest_node) { + fprintf(stderr, "node %s in %s -> %s does not exist\n", + e->dest, v->name, e->dest); + abort(); + } + + if (!dest_node->visited && dest_node->available) { + qos_push(dest_node, s_el, e); + } + } + } + } +} + +/* QGRAPH API*/ + +QOSGraphNode *qos_graph_get_node(const char *key) +{ + return search_node(key); +} + +bool qos_graph_has_node(const char *node) +{ + QOSGraphNode *n = search_node(node); + return n != NULL; +} + +QOSNodeType qos_graph_get_node_type(const char *node) +{ + QOSGraphNode *n = search_node(node); + if (n) { + return n->type; + } + return -1; +} + +bool qos_graph_get_node_availability(const char *node) +{ + QOSGraphNode *n = search_node(node); + if (n) { + return n->available; + } + return false; +} + +QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest) +{ + QOSGraphEdgeList *list = get_edgelist(node); + return search_list_edges(list, dest); +} + +QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge) +{ + if (!edge) { + return -1; + } + return edge->type;; +} + +char *qos_graph_edge_get_dest(QOSGraphEdge *edge) +{ + if (!edge) { + return NULL; + } + return edge->dest; +} + +void *qos_graph_edge_get_arg(QOSGraphEdge *edge) +{ + if (!edge) { + return NULL; + } + return edge->arg; +} + +char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge) +{ + if (!edge) { + return NULL; + } + return edge->after_cmd_line; +} + +char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge) +{ + if (!edge) { + return NULL; + } + return edge->before_cmd_line; +} + +char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge) +{ + if (!edge) { + return NULL; + } + return edge->extra_device_opts; +} + +char *qos_graph_edge_get_name(QOSGraphEdge *edge) +{ + if (!edge) { + return NULL; + } + return edge->edge_name; +} + +bool qos_graph_has_edge(const char *start, const char *dest) +{ + QOSGraphEdgeList *list = get_edgelist(start); + QOSGraphEdge *e = search_list_edges(list, dest); + return e != NULL; +} + +QOSGraphNode *qos_graph_get_machine(const char *node) +{ + return search_machine(node); +} + +bool qos_graph_has_machine(const char *node) +{ + QOSGraphNode *m = search_machine(node); + return m != NULL; +} + +void qos_print_graph(void) +{ + qos_graph_foreach_test_path(qos_print_cb); +} + +void qos_graph_init(void) +{ + if (!node_table) { + node_table = g_hash_table_new_full(g_str_hash, g_str_equal, + destroy_string, destroy_node); + create_node(QOS_ROOT, QNODE_DRIVER); + } + + if (!edge_table) { + edge_table = g_hash_table_new_full(g_str_hash, g_str_equal, + destroy_string, destroy_edges); + } +} + +void qos_graph_destroy(void) +{ + if (node_table) { + g_hash_table_destroy(node_table); + } + + if (edge_table) { + g_hash_table_destroy(edge_table); + } + + node_table = NULL; + edge_table = NULL; +} + +void qos_node_destroy(void *key) +{ + g_hash_table_remove(node_table, key); +} + +void qos_edge_destroy(void *key) +{ + g_hash_table_remove(edge_table, key); +} + +void qos_add_test(const char *name, const char *interface, + QOSTestFunc test_func, QOSGraphTestOptions *opts) +{ + QOSGraphNode *node; + char *test_name = g_strdup_printf("%s-tests/%s", interface, name);; + QOSGraphTestOptions def_opts = { }; + + if (!opts) { + opts = &def_opts; + } + node = create_node(test_name, QNODE_TEST); + node->u.test.function = test_func; + node->u.test.arg = opts->arg; + assert(!opts->edge.arg); + assert(!opts->edge.size_arg); + + node->u.test.before = opts->before; + node->u.test.subprocess = opts->subprocess; + node->available = true; + add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge); + g_free(test_name); +} + +void qos_node_create_machine(const char *name, QOSCreateMachineFunc function) +{ + qos_node_create_machine_args(name, function, NULL); +} + +void qos_node_create_machine_args(const char *name, + QOSCreateMachineFunc function, + const char *opts) +{ + QOSGraphNode *node = create_node(name, QNODE_MACHINE); + build_machine_cmd_line(node, opts); + node->u.machine.constructor = function; + add_edge(QOS_ROOT, name, QEDGE_CONTAINS, NULL); +} + +void qos_node_create_driver(const char *name, QOSCreateDriverFunc function) +{ + QOSGraphNode *node = create_node(name, QNODE_DRIVER); + build_driver_cmd_line(node); + node->u.driver.constructor = function; +} + +void qos_node_contains(const char *container, const char *contained, + QOSGraphEdgeOptions *opts, ...) +{ + va_list va; + + if (opts == NULL) { + add_edge(container, contained, QEDGE_CONTAINS, NULL); + return; + } + + va_start(va, opts); + do { + add_edge(container, contained, QEDGE_CONTAINS, opts); + opts = va_arg(va, QOSGraphEdgeOptions *); + } while (opts != NULL); + + va_end(va); +} + +void qos_node_produces(const char *producer, const char *interface) +{ + create_interface(interface); + add_edge(producer, interface, QEDGE_PRODUCES, NULL); +} + +void qos_node_consumes(const char *consumer, const char *interface, + QOSGraphEdgeOptions *opts) +{ + create_interface(interface); + add_edge(interface, consumer, QEDGE_CONSUMED_BY, opts); +} + +void qos_graph_node_set_availability(const char *node, bool av) +{ + QOSGraphEdgeList *elist; + QOSGraphNode *n = search_node(node); + QOSGraphEdge *e, *next; + if (!n) { + return; + } + n->available = av; + elist = get_edgelist(node); + if (!elist) { + return; + } + QSLIST_FOREACH_SAFE(e, elist, edge_list, next) { + if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) { + qos_graph_node_set_availability(e->dest, av); + } + } +} + +void qos_graph_foreach_test_path(QOSTestCallback fn) +{ + QOSGraphNode *root = qos_graph_get_node(QOS_ROOT); + qos_traverse_graph(root, fn); +} + +QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts) +{ + QOSGraphObject *obj; + + g_assert(node->type == QNODE_MACHINE); + obj = node->u.machine.constructor(qts); + obj->free = g_free; + return obj; +} + +QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, + QGuestAllocator *alloc, void *arg) +{ + QOSGraphObject *obj; + + g_assert(node->type == QNODE_DRIVER); + obj = node->u.driver.constructor(parent, alloc, arg); + obj->free = g_free; + return obj; +} + +void qos_object_destroy(QOSGraphObject *obj) +{ + if (!obj) { + return; + } + if (obj->destructor) { + obj->destructor(obj); + } + if (obj->free) { + obj->free(obj); + } +} + +void qos_object_queue_destroy(QOSGraphObject *obj) +{ + g_test_queue_destroy((GDestroyNotify) qos_object_destroy, obj); +} + +void qos_object_start_hw(QOSGraphObject *obj) +{ + if (obj->start_hw) { + obj->start_hw(obj); + } +} + +char *qos_get_machine_type(char *name) +{ + while (*name != '\0' && *name != '/') { + name++; + } + + if (!*name || !name[1]) { + fprintf(stderr, "Machine name has to be of the form /\n"); + abort(); + } + + return name + 1; +} + +void qos_delete_cmd_line(const char *name) +{ + QOSGraphNode *node = search_node(name); + if (node) { + g_free(node->command_line); + node->command_line = NULL; + } +} diff --git a/tests/qtest/libqos/qgraph.h b/tests/qtest/libqos/qgraph.h new file mode 100644 index 0000000000..3a25dda4b2 --- /dev/null +++ b/tests/qtest/libqos/qgraph.h @@ -0,0 +1,574 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef QGRAPH_H +#define QGRAPH_H + +#include +#include "qemu/module.h" +#include "malloc.h" + +/* maximum path length */ +#define QOS_PATH_MAX_ELEMENT_SIZE 50 + +typedef struct QOSGraphObject QOSGraphObject; +typedef struct QOSGraphNode QOSGraphNode; +typedef struct QOSGraphEdge QOSGraphEdge; +typedef struct QOSGraphNodeOptions QOSGraphNodeOptions; +typedef struct QOSGraphEdgeOptions QOSGraphEdgeOptions; +typedef struct QOSGraphTestOptions QOSGraphTestOptions; + +/* Constructor for drivers, machines and test */ +typedef void *(*QOSCreateDriverFunc) (void *parent, QGuestAllocator *alloc, + void *addr); +typedef void *(*QOSCreateMachineFunc) (QTestState *qts); +typedef void (*QOSTestFunc) (void *parent, void *arg, QGuestAllocator *alloc); + +/* QOSGraphObject functions */ +typedef void *(*QOSGetDriver) (void *object, const char *interface); +typedef QOSGraphObject *(*QOSGetDevice) (void *object, const char *name); +typedef void (*QOSDestructorFunc) (QOSGraphObject *object); +typedef void (*QOSStartFunct) (QOSGraphObject *object); + +/* Test options functions */ +typedef void *(*QOSBeforeTest) (GString *cmd_line, void *arg); + +/** + * SECTION: qgraph.h + * @title: Qtest Driver Framework + * @short_description: interfaces to organize drivers and tests + * as nodes in a graph + * + * This Qgraph API provides all basic functions to create a graph + * and instantiate nodes representing machines, drivers and tests + * representing their relations with CONSUMES, PRODUCES, and CONTAINS + * edges. + * + * The idea is to have a framework where each test asks for a specific + * driver, and the framework takes care of allocating the proper devices + * required and passing the correct command line arguments to QEMU. + * + * A node can be of four types: + * - QNODE_MACHINE: for example "arm/raspi2" + * - QNODE_DRIVER: for example "generic-sdhci" + * - QNODE_INTERFACE: for example "sdhci" (interface for all "-sdhci" drivers) + * an interface is not explicitly created, it will be auto- + * matically instantiated when a node consumes or produces + * it. + * - QNODE_TEST: for example "sdhci-test", consumes an interface and tests + * the functions provided + * + * Notes for the nodes: + * - QNODE_MACHINE: each machine struct must have a QGuestAllocator and + * implement get_driver to return the allocator passing + * "memory". The function can also return NULL if the + * allocator is not set. + * - QNODE_DRIVER: driver names must be unique, and machines and nodes + * planned to be "consumed" by other nodes must match QEMU + * drivers name, otherwise they won't be discovered + * + * An edge relation between two nodes (drivers or machines) X and Y can be: + * - X CONSUMES Y: Y can be plugged into X + * - X PRODUCES Y: X provides the interface Y + * - X CONTAINS Y: Y is part of X component + * + * Basic framework steps are the following: + * - All nodes and edges are created in their respective + * machine/driver/test files + * - The framework starts QEMU and asks for a list of available devices + * and machines (note that only machines and "consumed" nodes are mapped + * 1:1 with QEMU devices) + * - The framework walks the graph starting from the available machines and + * performs a Depth First Search for tests + * - Once a test is found, the path is walked again and all drivers are + * allocated accordingly and the final interface is passed to the test + * - The test is executed + * - Unused objects are cleaned and the path discovery is continued + * + * Depending on the QEMU binary used, only some drivers/machines will be + * available and only test that are reached by them will be executed. + * + * + * Creating new driver an its interface + * + #include "libqos/qgraph.h" + + struct My_driver { + QOSGraphObject obj; + Node_produced prod; + Node_contained cont; + } + + static void my_destructor(QOSGraphObject *obj) + { + g_free(obj); + } + + static void my_get_driver(void *object, const char *interface) { + My_driver *dev = object; + if (!g_strcmp0(interface, "my_interface")) { + return &dev->prod; + } + abort(); + } + + static void my_get_device(void *object, const char *device) { + My_driver *dev = object; + if (!g_strcmp0(device, "my_driver_contained")) { + return &dev->cont; + } + abort(); + } + + static void *my_driver_constructor(void *node_consumed, + QOSGraphObject *alloc) + { + My_driver dev = g_new(My_driver, 1); + // get the node pointed by the produce edge + dev->obj.get_driver = my_get_driver; + // get the node pointed by the contains + dev->obj.get_device = my_get_device; + // free the object + dev->obj.destructor = my_destructor; + do_something_with_node_consumed(node_consumed); + // set all fields of contained device + init_contained_device(&dev->cont); + return &dev->obj; + } + + static void register_my_driver(void) + { + qos_node_create_driver("my_driver", my_driver_constructor); + // contained drivers don't need a constructor, + // they will be init by the parent. + qos_node_create_driver("my_driver_contained", NULL); + + // For the sake of this example, assume machine x86_64/pc contains + // "other_node". + // This relation, along with the machine and "other_node" creation, + // should be defined in the x86_64_pc-machine.c file. + // "my_driver" will then consume "other_node" + qos_node_contains("my_driver", "my_driver_contained"); + qos_node_produces("my_driver", "my_interface"); + qos_node_consumes("my_driver", "other_node"); + } + * + * + * + * In the above example, all possible types of relations are created: + * node "my_driver" consumes, contains and produces other nodes. + * more specifically: + * x86_64/pc -->contains--> other_node <--consumes-- my_driver + * | + * my_driver_contained <--contains--+ + * | + * my_interface <--produces--+ + * + * or inverting the consumes edge in consumed_by: + * + * x86_64/pc -->contains--> other_node --consumed_by--> my_driver + * | + * my_driver_contained <--contains--+ + * | + * my_interface <--produces--+ + * + * + * Creating new test + * + * #include "libqos/qgraph.h" + * + * static void my_test_function(void *obj, void *data) + * { + * Node_produced *interface_to_test = obj; + * // test interface_to_test + * } + * + * static void register_my_test(void) + * { + * qos_add_test("my_interface", "my_test", my_test_function); + * } + * + * libqos_init(register_my_test); + * + * + * + * + * Here a new test is created, consuming "my_interface" node + * and creating a valid path from a machine to a test. + * Final graph will be like this: + * x86_64/pc -->contains--> other_node <--consumes-- my_driver + * | + * my_driver_contained <--contains--+ + * | + * my_test --consumes--> my_interface <--produces--+ + * + * or inverting the consumes edge in consumed_by: + * + * x86_64/pc -->contains--> other_node --consumed_by--> my_driver + * | + * my_driver_contained <--contains--+ + * | + * my_test <--consumed_by-- my_interface <--produces--+ + * + * Assuming there the binary is + * QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64 + * a valid test path will be: + * "/x86_64/pc/other_node/my_driver/my_interface/my_test". + * + * Additional examples are also in libqos/test-qgraph.c + * + * Command line: + * Command line is built by using node names and optional arguments + * passed by the user when building the edges. + * + * There are three types of command line arguments: + * - in node : created from the node name. For example, machines will + * have "-M " to its command line, while devices + * "-device ". It is automatically done by the + * framework. + * - after node : added as additional argument to the node name. + * This argument is added optionally when creating edges, + * by setting the parameter @after_cmd_line and + * @extra_edge_opts in #QOSGraphEdgeOptions. + * The framework automatically adds + * a comma before @extra_edge_opts, + * because it is going to add attributes + * after the destination node pointed by + * the edge containing these options, and automatically + * adds a space before @after_cmd_line, because it + * adds an additional device, not an attribute. + * - before node : added as additional argument to the node name. + * This argument is added optionally when creating edges, + * by setting the parameter @before_cmd_line in + * #QOSGraphEdgeOptions. This attribute + * is going to add attributes before the destination node + * pointed by the edge containing these options. It is + * helpful to commands that are not node-representable, + * such as "-fdsev" or "-netdev". + * + * While adding command line in edges is always used, not all nodes names are + * used in every path walk: this is because the contained or produced ones + * are already added by QEMU, so only nodes that "consumes" will be used to + * build the command line. Also, nodes that will have { "abstract" : true } + * as QMP attribute will loose their command line, since they are not proper + * devices to be added in QEMU. + * + * Example: + * + QOSGraphEdgeOptions opts = { + .arg = NULL, + .size_arg = 0, + .after_cmd_line = "-device other", + .before_cmd_line = "-netdev something", + .extra_edge_opts = "addr=04.0", + }; + QOSGraphNode * node = qos_node_create_driver("my_node", constructor); + qos_node_consumes_args("my_node", "interface", &opts); + * + * Will produce the following command line: + * "-netdev something -device my_node,addr=04.0 -device other" + */ + +/** + * Edge options to be passed to the contains/consumes *_args function. + */ +struct QOSGraphEdgeOptions { + void *arg; /* + * optional arg that will be used by + * dest edge + */ + uint32_t size_arg; /* + * optional arg size that will be used by + * dest edge + */ + const char *extra_device_opts;/* + *optional additional command line for dest + * edge, used to add additional attributes + * *after* the node command line, the + * framework automatically prepends "," + * to this argument. + */ + const char *before_cmd_line; /* + * optional additional command line for dest + * edge, used to add additional attributes + * *before* the node command line, usually + * other non-node represented commands, + * like "-fdsev synt" + */ + const char *after_cmd_line; /* + * optional extra command line to be added + * after the device command. This option + * is used to add other devices + * command line that depend on current node. + * Automatically prepends " " to this + * argument + */ + const char *edge_name; /* + * optional edge to differentiate multiple + * devices with same node name + */ +}; + +/** + * Test options to be passed to the test functions. + */ +struct QOSGraphTestOptions { + QOSGraphEdgeOptions edge; /* edge arguments that will be used by test. + * Note that test *does not* use edge_name, + * and uses instead arg and size_arg as + * data arg for its test function. + */ + void *arg; /* passed to the .before function, or to the + * test function if there is no .before + * function + */ + QOSBeforeTest before; /* executed before the test. Can add + * additional parameters to the command line + * and modify the argument to the test function. + */ + bool subprocess; /* run the test in a subprocess */ +}; + +/** + * Each driver, test or machine of this framework will have a + * QOSGraphObject as first field. + * + * This set of functions offered by QOSGraphObject are executed + * in different stages of the framework: + * - get_driver / get_device : Once a machine-to-test path has been + * found, the framework traverses it again and allocates all the + * nodes, using the provided constructor. To satisfy their relations, + * i.e. for produces or contains, where a struct constructor needs + * an external parameter represented by the previous node, + * the framework will call get_device (for contains) or + * get_driver (for produces), depending on the edge type, passing + * them the name of the next node to be taken and getting from them + * the corresponding pointer to the actual structure of the next node to + * be used in the path. + * + * - start_hw: This function is executed after all the path objects + * have been allocated, but before the test is run. It starts the hw, setting + * the initial configurations (*_device_enable) and making it ready for the + * test. + * + * - destructor: Opposite to the node constructor, destroys the object. + * This function is called after the test has been executed, and performs + * a complete cleanup of each node allocated field. In case no constructor + * is provided, no destructor will be called. + * + */ +struct QOSGraphObject { + /* for produces edges, returns void * */ + QOSGetDriver get_driver; + /* for contains edges, returns a QOSGraphObject * */ + QOSGetDevice get_device; + /* start the hw, get ready for the test */ + QOSStartFunct start_hw; + /* destroy this QOSGraphObject */ + QOSDestructorFunc destructor; + /* free the memory associated to the QOSGraphObject and its contained + * children */ + GDestroyNotify free; +}; + +/** + * qos_graph_init(): initialize the framework, creates two hash + * tables: one for the nodes and another for the edges. + */ +void qos_graph_init(void); + +/** + * qos_graph_destroy(): deallocates all the hash tables, + * freeing all nodes and edges. + */ +void qos_graph_destroy(void); + +/** + * qos_node_destroy(): removes and frees a node from the, + * nodes hash table. + */ +void qos_node_destroy(void *key); + +/** + * qos_edge_destroy(): removes and frees an edge from the, + * edges hash table. + */ +void qos_edge_destroy(void *key); + +/** + * qos_add_test(): adds a test node @name to the nodes hash table. + * + * The test will consume a @interface node, and once the + * graph walking algorithm has found it, the @test_func will be + * executed. It also has the possibility to + * add an optional @opts (see %QOSGraphNodeOptions). + * + * For tests, opts->edge.arg and size_arg represent the arg to pass + * to @test_func + */ +void qos_add_test(const char *name, const char *interface, + QOSTestFunc test_func, + QOSGraphTestOptions *opts); + +/** + * qos_node_create_machine(): creates the machine @name and + * adds it to the node hash table. + * + * This node will be of type QNODE_MACHINE and have @function + * as constructor + */ +void qos_node_create_machine(const char *name, QOSCreateMachineFunc function); + +/** + * qos_node_create_machine_args(): same as qos_node_create_machine, + * but with the possibility to add an optional ", @opts" after -M machine + * command line. + */ +void qos_node_create_machine_args(const char *name, + QOSCreateMachineFunc function, + const char *opts); + +/** + * qos_node_create_driver(): creates the driver @name and + * adds it to the node hash table. + * + * This node will be of type QNODE_DRIVER and have @function + * as constructor + */ +void qos_node_create_driver(const char *name, QOSCreateDriverFunc function); + +/** + * qos_node_contains(): creates one or more edges of type QEDGE_CONTAINS + * and adds them to the edge list mapped to @container in the + * edge hash table. + * + * The edges will have @container as source and @contained as destination. + * + * If @opts is NULL, a single edge will be added with no options. + * If @opts is non-NULL, the arguments after @contained represent a + * NULL-terminated list of %QOSGraphEdgeOptions structs, and an + * edge will be added for each of them. + * + * This function can be useful when there are multiple devices + * with the same node name contained in a machine/other node + * + * For example, if "arm/raspi2" contains 2 "generic-sdhci" + * devices, the right commands will be: + * qos_node_create_machine("arm/raspi2"); + * qos_node_create_driver("generic-sdhci", constructor); + * //assume rest of the fields are set NULL + * QOSGraphEdgeOptions op1 = { .edge_name = "emmc" }; + * QOSGraphEdgeOptions op2 = { .edge_name = "sdcard" }; + * qos_node_contains("arm/raspi2", "generic-sdhci", &op1, &op2, NULL); + * + * Of course this also requires that the @container's get_device function + * should implement a case for "emmc" and "sdcard". + * + * For contains, op1.arg and op1.size_arg represent the arg to pass + * to @contained constructor to properly initialize it. + */ +void qos_node_contains(const char *container, const char *contained, + QOSGraphEdgeOptions *opts, ...); + +/** + * qos_node_produces(): creates an edge of type QEDGE_PRODUCES and + * adds it to the edge list mapped to @producer in the + * edge hash table. + * + * This edge will have @producer as source and @interface as destination. + */ +void qos_node_produces(const char *producer, const char *interface); + +/** + * qos_node_consumes(): creates an edge of type QEDGE_CONSUMED_BY and + * adds it to the edge list mapped to @interface in the + * edge hash table. + * + * This edge will have @interface as source and @consumer as destination. + * It also has the possibility to add an optional @opts + * (see %QOSGraphEdgeOptions) + */ +void qos_node_consumes(const char *consumer, const char *interface, + QOSGraphEdgeOptions *opts); + +/** + * qos_invalidate_command_line(): invalidates current command line, so that + * qgraph framework cannot try to cache the current command line and + * forces QEMU to restart. + */ +void qos_invalidate_command_line(void); + +/** + * qos_get_current_command_line(): return the command line required by the + * machine and driver objects. This is the same string that was passed to + * the test's "before" callback, if any. + */ +const char *qos_get_current_command_line(void); + +/** + * qos_allocate_objects(): + * @qts: The #QTestState that will be referred to by the machine object. + * @alloc: Where to store the allocator for the machine object, or %NULL. + * + * Allocate driver objects for the current test + * path, but relative to the QTestState @qts. + * + * Returns a test object just like the one that was passed to + * the test function, but relative to @qts. + */ +void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc); + +/** + * qos_object_destroy(): calls the destructor for @obj + */ +void qos_object_destroy(QOSGraphObject *obj); + +/** + * qos_object_queue_destroy(): queue the destructor for @obj so that it is + * called at the end of the test + */ +void qos_object_queue_destroy(QOSGraphObject *obj); + +/** + * qos_object_start_hw(): calls the start_hw function for @obj + */ +void qos_object_start_hw(QOSGraphObject *obj); + +/** + * qos_machine_new(): instantiate a new machine node + * @node: A machine node to be instantiated + * @qts: The #QTestState that will be referred to by the machine object. + * + * Returns a machine object. + */ +QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts); + +/** + * qos_machine_new(): instantiate a new driver node + * @node: A driver node to be instantiated + * @parent: A #QOSGraphObject to be consumed by the new driver node + * @alloc: An allocator to be used by the new driver node. + * @arg: The argument for the consumed-by edge to @node. + * + * Calls the constructor for the driver object. + */ +QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, + QGuestAllocator *alloc, void *arg); + + +#endif diff --git a/tests/qtest/libqos/qgraph_internal.h b/tests/qtest/libqos/qgraph_internal.h new file mode 100644 index 0000000000..f4734c8681 --- /dev/null +++ b/tests/qtest/libqos/qgraph_internal.h @@ -0,0 +1,257 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef QGRAPH_INTERNAL_H +#define QGRAPH_INTERNAL_H + +/* This header is declaring additional helper functions defined in + * libqos/qgraph.c + * It should not be included in tests + */ + +#include "libqos/qgraph.h" + +typedef struct QOSGraphMachine QOSGraphMachine; +typedef enum QOSEdgeType QOSEdgeType; +typedef enum QOSNodeType QOSNodeType; + +/* callback called when the walk path algorithm found a + * valid path + */ +typedef void (*QOSTestCallback) (QOSGraphNode *path, int len); + +/* edge types*/ +enum QOSEdgeType { + QEDGE_CONTAINS, + QEDGE_PRODUCES, + QEDGE_CONSUMED_BY +}; + +/* node types*/ +enum QOSNodeType { + QNODE_MACHINE, + QNODE_DRIVER, + QNODE_INTERFACE, + QNODE_TEST +}; + +/* Graph Node */ +struct QOSGraphNode { + QOSNodeType type; + bool available; /* set by QEMU via QMP, used during graph walk */ + bool visited; /* used during graph walk */ + char *name; /* used to identify the node */ + char *command_line; /* used to start QEMU at test execution */ + union { + struct { + QOSCreateDriverFunc constructor; + } driver; + struct { + QOSCreateMachineFunc constructor; + } machine; + struct { + QOSTestFunc function; + void *arg; + QOSBeforeTest before; + bool subprocess; + } test; + } u; + + /** + * only used when traversing the path, never rely on that except in the + * qos_traverse_graph callback function + */ + QOSGraphEdge *path_edge; +}; + +/** + * qos_graph_get_node(): returns the node mapped to that @key. + * It performs an hash map search O(1) + * + * Returns: on success: the %QOSGraphNode + * otherwise: #NULL + */ +QOSGraphNode *qos_graph_get_node(const char *key); + +/** + * qos_graph_has_node(): returns #TRUE if the node + * has map has a node mapped to that @key. + */ +bool qos_graph_has_node(const char *node); + +/** + * qos_graph_get_node_type(): returns the %QOSNodeType + * of the node @node. + * It performs an hash map search O(1) + * Returns: on success: the %QOSNodeType + * otherwise: #-1 + */ +QOSNodeType qos_graph_get_node_type(const char *node); + +/** + * qos_graph_get_node_availability(): returns the availability (boolean) + * of the node @node. + */ +bool qos_graph_get_node_availability(const char *node); + +/** + * qos_graph_get_edge(): returns the edge + * linking of the node @node with @dest. + * + * Returns: on success: the %QOSGraphEdge + * otherwise: #NULL + */ +QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest); + +/** + * qos_graph_edge_get_type(): returns the edge type + * of the edge @edge. + * + * Returns: on success: the %QOSEdgeType + * otherwise: #-1 + */ +QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge); + +/** + * qos_graph_edge_get_dest(): returns the name of the node + * pointed as destination of edge @edge. + * + * Returns: on success: the destination + * otherwise: #NULL + */ +char *qos_graph_edge_get_dest(QOSGraphEdge *edge); + +/** + * qos_graph_has_edge(): returns #TRUE if there + * exists an edge from @start to @dest. + */ +bool qos_graph_has_edge(const char *start, const char *dest); + +/** + * qos_graph_edge_get_arg(): returns the args assigned + * to that @edge. + * + * Returns: on success: the arg + * otherwise: #NULL + */ +void *qos_graph_edge_get_arg(QOSGraphEdge *edge); + +/** + * qos_graph_edge_get_after_cmd_line(): returns the edge + * command line that will be added after all the node arguments + * and all the before_cmd_line arguments. + * + * Returns: on success: the char* arg + * otherwise: #NULL + */ +char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge); + +/** + * qos_graph_edge_get_before_cmd_line(): returns the edge + * command line that will be added before the node command + * line argument. + * + * Returns: on success: the char* arg + * otherwise: #NULL + */ +char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge); + +/** + * qos_graph_edge_get_extra_device_opts(): returns the arg + * command line that will be added to the node command + * line argument. + * + * Returns: on success: the char* arg + * otherwise: #NULL + */ +char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge); + +/** + * qos_graph_edge_get_name(): returns the name + * assigned to the destination node (different only) + * if there are multiple devices with the same node name + * e.g. a node has two "generic-sdhci", "emmc" and "sdcard" + * there will be two edges with edge_name ="emmc" and "sdcard" + * + * Returns always the char* edge_name + */ +char *qos_graph_edge_get_name(QOSGraphEdge *edge); + +/** + * qos_graph_get_machine(): returns the machine assigned + * to that @node name. + * + * It performs a search only trough the list of machines + * (i.e. the QOS_ROOT child). + * + * Returns: on success: the %QOSGraphNode + * otherwise: #NULL + */ +QOSGraphNode *qos_graph_get_machine(const char *node); + +/** + * qos_graph_has_machine(): returns #TRUE if the node + * has map has a node mapped to that @node. + */ +bool qos_graph_has_machine(const char *node); + + +/** + * qos_print_graph(): walks the graph and prints + * all machine-to-test paths. + */ +void qos_print_graph(void); + +/** + * qos_graph_foreach_test_path(): executes the Depth First search + * algorithm and applies @fn to all discovered paths. + * + * See qos_traverse_graph() in qgraph.c for more info on + * how it works. + */ +void qos_graph_foreach_test_path(QOSTestCallback fn); + +/** + * qos_get_machine_type(): return QEMU machine type for a machine node. + * This function requires every machine @name to be in the form + * /, like "arm/raspi2" or "x86_64/pc". + * + * The function will validate the format and return a pointer to + * @machine to . For example, when passed "x86_64/pc" + * it will return "pc". + * + * Note that this function *does not* allocate any new string. + */ +char *qos_get_machine_type(char *name); + +/** + * qos_delete_cmd_line(): delete the + * command line present in node mapped with key @name. + * + * This function is called when the QMP query returns a node with + * { "abstract" : true } attribute. + */ +void qos_delete_cmd_line(const char *name); + +/** + * qos_graph_node_set_availability(): sets the node identified + * by @node with availability @av. + */ +void qos_graph_node_set_availability(const char *node, bool av); + +#endif diff --git a/tests/qtest/libqos/rtas.c b/tests/qtest/libqos/rtas.c new file mode 100644 index 0000000000..d81ff4274d --- /dev/null +++ b/tests/qtest/libqos/rtas.c @@ -0,0 +1,120 @@ +/* + * 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/rtas.h" + +static void qrtas_copy_args(QTestState *qts, uint64_t target_args, + uint32_t nargs, uint32_t *args) +{ + int i; + + for (i = 0; i < nargs; i++) { + qtest_writel(qts, target_args + i * sizeof(uint32_t), args[i]); + } +} + +static void qrtas_copy_ret(QTestState *qts, uint64_t target_ret, + uint32_t nret, uint32_t *ret) +{ + int i; + + for (i = 0; i < nret; i++) { + ret[i] = qtest_readl(qts, target_ret + i * sizeof(uint32_t)); + } +} + +static uint64_t qrtas_call(QTestState *qts, QGuestAllocator *alloc, + const char *name, + uint32_t nargs, uint32_t *args, + uint32_t nret, uint32_t *ret) +{ + uint64_t res; + uint64_t target_args, target_ret; + + target_args = guest_alloc(alloc, nargs * sizeof(uint32_t)); + target_ret = guest_alloc(alloc, nret * sizeof(uint32_t)); + + qrtas_copy_args(qts, target_args, nargs, args); + res = qtest_rtas_call(qts, name, nargs, target_args, nret, target_ret); + qrtas_copy_ret(qts, target_ret, nret, ret); + + guest_free(alloc, target_ret); + guest_free(alloc, target_args); + + return res; +} + +int qrtas_get_time_of_day(QTestState *qts, QGuestAllocator *alloc, + struct tm *tm, uint32_t *ns) +{ + int res; + uint32_t ret[8]; + + res = qrtas_call(qts, alloc, "get-time-of-day", 0, NULL, 8, ret); + if (res != 0) { + return res; + } + + res = ret[0]; + memset(tm, 0, sizeof(*tm)); + tm->tm_year = ret[1] - 1900; + tm->tm_mon = ret[2] - 1; + tm->tm_mday = ret[3]; + tm->tm_hour = ret[4]; + tm->tm_min = ret[5]; + tm->tm_sec = ret[6]; + *ns = ret[7]; + + return res; +} + +uint32_t qrtas_ibm_read_pci_config(QTestState *qts, QGuestAllocator *alloc, + uint64_t buid, + uint32_t addr, uint32_t size) +{ + int res; + uint32_t args[4], ret[2]; + + args[0] = addr; + args[1] = buid >> 32; + args[2] = buid & 0xffffffff; + args[3] = size; + res = qrtas_call(qts, alloc, "ibm,read-pci-config", 4, args, 2, ret); + if (res != 0) { + return -1; + } + + if (ret[0] != 0) { + return -1; + } + + return ret[1]; +} + +int qrtas_ibm_write_pci_config(QTestState *qts, QGuestAllocator *alloc, + uint64_t buid, + uint32_t addr, uint32_t size, uint32_t val) +{ + int res; + uint32_t args[5], ret[1]; + + args[0] = addr; + args[1] = buid >> 32; + args[2] = buid & 0xffffffff; + args[3] = size; + args[4] = val; + res = qrtas_call(qts, alloc, "ibm,write-pci-config", 5, args, 1, ret); + if (res != 0) { + return -1; + } + + if (ret[0] != 0) { + return -1; + } + + return 0; +} diff --git a/tests/qtest/libqos/rtas.h b/tests/qtest/libqos/rtas.h new file mode 100644 index 0000000000..459e23aaf4 --- /dev/null +++ b/tests/qtest/libqos/rtas.h @@ -0,0 +1,17 @@ +/* + * 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 LIBQOS_RTAS_H +#define LIBQOS_RTAS_H +#include "libqos/malloc.h" + +int qrtas_get_time_of_day(QTestState *qts, QGuestAllocator *alloc, + struct tm *tm, uint32_t *ns); +uint32_t qrtas_ibm_read_pci_config(QTestState *qts, QGuestAllocator *alloc, + uint64_t buid, uint32_t addr, uint32_t size); +int qrtas_ibm_write_pci_config(QTestState *qts, QGuestAllocator *alloc, + uint64_t buid, uint32_t addr, uint32_t size, + uint32_t val); +#endif /* LIBQOS_RTAS_H */ diff --git a/tests/qtest/libqos/sdhci.c b/tests/qtest/libqos/sdhci.c new file mode 100644 index 0000000000..309794bc52 --- /dev/null +++ b/tests/qtest/libqos/sdhci.c @@ -0,0 +1,164 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/qgraph.h" +#include "pci.h" +#include "qemu/module.h" +#include "sdhci.h" +#include "hw/pci/pci.h" + +static void set_qsdhci_fields(QSDHCI *s, uint8_t version, uint8_t baseclock, + bool sdma, uint64_t reg) +{ + s->props.version = version; + s->props.baseclock = baseclock; + s->props.capab.sdma = sdma; + s->props.capab.reg = reg; +} + +/* Memory mapped implementation of QSDHCI */ + +static uint16_t sdhci_mm_readw(QSDHCI *s, uint32_t reg) +{ + QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci); + return qtest_readw(smm->qts, smm->addr + reg); +} + +static uint64_t sdhci_mm_readq(QSDHCI *s, uint32_t reg) +{ + QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci); + return qtest_readq(smm->qts, smm->addr + reg); +} + +static void sdhci_mm_writeq(QSDHCI *s, uint32_t reg, uint64_t val) +{ + QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci); + qtest_writeq(smm->qts, smm->addr + reg, val); +} + +static void *sdhci_mm_get_driver(void *obj, const char *interface) +{ + QSDHCI_MemoryMapped *smm = obj; + if (!g_strcmp0(interface, "sdhci")) { + return &smm->sdhci; + } + fprintf(stderr, "%s not present in generic-sdhci\n", interface); + g_assert_not_reached(); +} + +void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts, + uint32_t addr, QSDHCIProperties *common) +{ + sdhci->obj.get_driver = sdhci_mm_get_driver; + sdhci->sdhci.readw = sdhci_mm_readw; + sdhci->sdhci.readq = sdhci_mm_readq; + sdhci->sdhci.writeq = sdhci_mm_writeq; + memcpy(&sdhci->sdhci.props, common, sizeof(QSDHCIProperties)); + sdhci->addr = addr; + sdhci->qts = qts; +} + +/* PCI implementation of QSDHCI */ + +static uint16_t sdhci_pci_readw(QSDHCI *s, uint32_t reg) +{ + QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci); + return qpci_io_readw(&spci->dev, spci->mem_bar, reg); +} + +static uint64_t sdhci_pci_readq(QSDHCI *s, uint32_t reg) +{ + QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci); + return qpci_io_readq(&spci->dev, spci->mem_bar, reg); +} + +static void sdhci_pci_writeq(QSDHCI *s, uint32_t reg, uint64_t val) +{ + QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci); + return qpci_io_writeq(&spci->dev, spci->mem_bar, reg, val); +} + +static void *sdhci_pci_get_driver(void *object, const char *interface) +{ + QSDHCI_PCI *spci = object; + if (!g_strcmp0(interface, "sdhci")) { + return &spci->sdhci; + } + + fprintf(stderr, "%s not present in sdhci-pci\n", interface); + g_assert_not_reached(); +} + +static void sdhci_pci_start_hw(QOSGraphObject *obj) +{ + QSDHCI_PCI *spci = (QSDHCI_PCI *)obj; + qpci_device_enable(&spci->dev); +} + +static void sdhci_destructor(QOSGraphObject *obj) +{ + QSDHCI_PCI *spci = (QSDHCI_PCI *)obj; + qpci_iounmap(&spci->dev, spci->mem_bar); +} + +static void *sdhci_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr) +{ + QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1); + QPCIBus *bus = pci_bus; + uint64_t barsize; + + qpci_device_init(&spci->dev, bus, addr); + spci->mem_bar = qpci_iomap(&spci->dev, 0, &barsize); + spci->sdhci.readw = sdhci_pci_readw; + spci->sdhci.readq = sdhci_pci_readq; + spci->sdhci.writeq = sdhci_pci_writeq; + set_qsdhci_fields(&spci->sdhci, 2, 0, 1, 0x057834b4); + + spci->obj.get_driver = sdhci_pci_get_driver; + spci->obj.start_hw = sdhci_pci_start_hw; + spci->obj.destructor = sdhci_destructor; + return &spci->obj; +} + +static void qsdhci_register_nodes(void) +{ + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + .vendor_id = PCI_VENDOR_ID_REDHAT, + .device_id = PCI_DEVICE_ID_REDHAT_SDHCI, + }; + + QOSGraphEdgeOptions opts = { + .extra_device_opts = "addr=04.0", + }; + + /* generic-sdhci */ + qos_node_create_driver("generic-sdhci", NULL); + qos_node_produces("generic-sdhci", "sdhci"); + + /* sdhci-pci */ + add_qpci_address(&opts, &addr); + qos_node_create_driver("sdhci-pci", sdhci_pci_create); + qos_node_produces("sdhci-pci", "sdhci"); + qos_node_consumes("sdhci-pci", "pci-bus", &opts); + +} + +libqos_init(qsdhci_register_nodes); diff --git a/tests/qtest/libqos/sdhci.h b/tests/qtest/libqos/sdhci.h new file mode 100644 index 0000000000..a88b45ae9d --- /dev/null +++ b/tests/qtest/libqos/sdhci.h @@ -0,0 +1,70 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef QGRAPH_QSDHCI_H +#define QGRAPH_QSDHCI_H + +#include "libqos/qgraph.h" +#include "pci.h" + +typedef struct QSDHCI QSDHCI; +typedef struct QSDHCI_MemoryMapped QSDHCI_MemoryMapped; +typedef struct QSDHCI_PCI QSDHCI_PCI; +typedef struct QSDHCIProperties QSDHCIProperties; + +/* Properties common to all QSDHCI devices */ +struct QSDHCIProperties { + uint8_t version; + uint8_t baseclock; + struct { + bool sdma; + uint64_t reg; + } capab; +}; + +struct QSDHCI { + uint16_t (*readw)(QSDHCI *s, uint32_t reg); + uint64_t (*readq)(QSDHCI *s, uint32_t reg); + void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val); + QSDHCIProperties props; +}; + +/* Memory Mapped implementation of QSDHCI */ +struct QSDHCI_MemoryMapped { + QOSGraphObject obj; + QTestState *qts; + QSDHCI sdhci; + uint64_t addr; +}; + +/* PCI implementation of QSDHCI */ +struct QSDHCI_PCI { + QOSGraphObject obj; + QPCIDevice dev; + QSDHCI sdhci; + QPCIBar mem_bar; +}; + +/** + * qos_init_sdhci_mm(): external constructor used by all drivers/machines + * that "contain" a #QSDHCI_MemoryMapped driver + */ +void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts, + uint32_t addr, QSDHCIProperties *common); + +#endif diff --git a/tests/qtest/libqos/tpci200.c b/tests/qtest/libqos/tpci200.c new file mode 100644 index 0000000000..ae590a456e --- /dev/null +++ b/tests/qtest/libqos/tpci200.c @@ -0,0 +1,66 @@ +/* + * QTest testcase for tpci200 PCI-IndustryPack bridge + * + * 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 QTpci200 QTpci200; +typedef struct QIpack QIpack; + +struct QIpack { + +}; +struct QTpci200 { + QOSGraphObject obj; + QPCIDevice dev; + QIpack ipack; +}; + +/* tpci200 */ +static void *tpci200_get_driver(void *obj, const char *interface) +{ + QTpci200 *tpci200 = obj; + if (!g_strcmp0(interface, "ipack")) { + return &tpci200->ipack; + } + if (!g_strcmp0(interface, "pci-device")) { + return &tpci200->dev; + } + + fprintf(stderr, "%s not present in tpci200\n", interface); + g_assert_not_reached(); +} + +static void *tpci200_create(void *pci_bus, QGuestAllocator *alloc, void *addr) +{ + QTpci200 *tpci200 = g_new0(QTpci200, 1); + QPCIBus *bus = pci_bus; + + qpci_device_init(&tpci200->dev, bus, addr); + tpci200->obj.get_driver = tpci200_get_driver; + return &tpci200->obj; +} + +static void tpci200_register_nodes(void) +{ + QOSGraphEdgeOptions opts = { + .extra_device_opts = "addr=04.0,id=ipack0", + }; + add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) }); + + qos_node_create_driver("tpci200", tpci200_create); + qos_node_consumes("tpci200", "pci-bus", &opts); + qos_node_produces("tpci200", "ipack"); + qos_node_produces("tpci200", "pci-device"); +} + +libqos_init(tpci200_register_nodes); diff --git a/tests/qtest/libqos/usb.c b/tests/qtest/libqos/usb.c new file mode 100644 index 0000000000..d7a9cb3c72 --- /dev/null +++ b/tests/qtest/libqos/usb.c @@ -0,0 +1,57 @@ +/* + * common code shared by usb tests + * + * Copyright (c) 2014 Red Hat, Inc + * + * Authors: + * Gerd Hoffmann + * John Snow + * Igor Mammedov + * + * 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 "hw/usb/uhci-regs.h" +#include "libqos/usb.h" + +void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, uint32_t devfn, int bar) +{ + hc->dev = qpci_device_find(pcibus, devfn); + g_assert(hc->dev != NULL); + qpci_device_enable(hc->dev); + hc->bar = qpci_iomap(hc->dev, bar, NULL); +} + +void uhci_deinit(struct qhc *hc) +{ + g_free(hc->dev); +} + +void uhci_port_test(struct qhc *hc, int port, uint16_t expect) +{ + uint16_t value = qpci_io_readw(hc->dev, hc->bar, 0x10 + 2 * port); + uint16_t mask = ~(UHCI_PORT_WRITE_CLEAR | UHCI_PORT_RSVD1); + + g_assert((value & mask) == (expect & mask)); +} + +void usb_test_hotplug(QTestState *qts, const char *hcd_id, const char *port, + void (*port_check)(void)) +{ + char *id = g_strdup_printf("usbdev%s", port); + char *bus = g_strdup_printf("%s.0", hcd_id); + + qtest_qmp_device_add(qts, "usb-tablet", id, "{'port': %s, 'bus': %s}", + port, bus); + + if (port_check) { + port_check(); + } + + qtest_qmp_device_del(qts, id); + + g_free(bus); + g_free(id); +} diff --git a/tests/qtest/libqos/usb.h b/tests/qtest/libqos/usb.h new file mode 100644 index 0000000000..eeced39a2f --- /dev/null +++ b/tests/qtest/libqos/usb.h @@ -0,0 +1,18 @@ +#ifndef LIBQOS_USB_H +#define LIBQOS_USB_H + +#include "libqos/pci-pc.h" + +struct qhc { + QPCIDevice *dev; + QPCIBar bar; +}; + +void qusb_pci_init_one(QPCIBus *pcibus, struct qhc *hc, + uint32_t devfn, int bar); +void uhci_port_test(struct qhc *hc, int port, uint16_t expect); +void uhci_deinit(struct qhc *hc); + +void usb_test_hotplug(QTestState *qts, const char *bus_name, const char *port, + void (*port_check)(void)); +#endif diff --git a/tests/qtest/libqos/virtio-9p.c b/tests/qtest/libqos/virtio-9p.c new file mode 100644 index 0000000000..77dbfb62ad --- /dev/null +++ b/tests/qtest/libqos/virtio-9p.c @@ -0,0 +1,180 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "standard-headers/linux/virtio_ids.h" +#include "libqos/virtio-9p.h" +#include "libqos/qgraph.h" + +static QGuestAllocator *alloc; + +static void virtio_9p_cleanup(QVirtio9P *interface) +{ + qvirtqueue_cleanup(interface->vdev->bus, interface->vq, alloc); +} + +static void virtio_9p_setup(QVirtio9P *interface) +{ + uint64_t features; + + features = qvirtio_get_features(interface->vdev); + features &= ~(QVIRTIO_F_BAD_FEATURE | (1ull << VIRTIO_RING_F_EVENT_IDX)); + qvirtio_set_features(interface->vdev, features); + + interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0); + qvirtio_set_driver_ok(interface->vdev); +} + +/* virtio-9p-device */ +static void virtio_9p_device_destructor(QOSGraphObject *obj) +{ + QVirtio9PDevice *v_9p = (QVirtio9PDevice *) obj; + QVirtio9P *v9p = &v_9p->v9p; + + virtio_9p_cleanup(v9p); +} + +static void virtio_9p_device_start_hw(QOSGraphObject *obj) +{ + QVirtio9PDevice *v_9p = (QVirtio9PDevice *) obj; + QVirtio9P *v9p = &v_9p->v9p; + + virtio_9p_setup(v9p); +} + +static void *virtio_9p_get_driver(QVirtio9P *v_9p, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-9p")) { + return v_9p; + } + if (!g_strcmp0(interface, "virtio")) { + return v_9p->vdev; + } + + fprintf(stderr, "%s not present in virtio-9p-device\n", interface); + g_assert_not_reached(); +} + +static void *virtio_9p_device_get_driver(void *object, const char *interface) +{ + QVirtio9PDevice *v_9p = object; + return virtio_9p_get_driver(&v_9p->v9p, interface); +} + +static void *virtio_9p_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtio9PDevice *virtio_device = g_new0(QVirtio9PDevice, 1); + QVirtio9P *interface = &virtio_device->v9p; + + interface->vdev = virtio_dev; + alloc = t_alloc; + + virtio_device->obj.destructor = virtio_9p_device_destructor; + virtio_device->obj.get_driver = virtio_9p_device_get_driver; + virtio_device->obj.start_hw = virtio_9p_device_start_hw; + + return &virtio_device->obj; +} + +/* virtio-9p-pci */ +static void virtio_9p_pci_destructor(QOSGraphObject *obj) +{ + QVirtio9PPCI *v9_pci = (QVirtio9PPCI *) obj; + QVirtio9P *interface = &v9_pci->v9p; + QOSGraphObject *pci_vobj = &v9_pci->pci_vdev.obj; + + virtio_9p_cleanup(interface); + qvirtio_pci_destructor(pci_vobj); +} + +static void virtio_9p_pci_start_hw(QOSGraphObject *obj) +{ + QVirtio9PPCI *v9_pci = (QVirtio9PPCI *) obj; + QVirtio9P *interface = &v9_pci->v9p; + QOSGraphObject *pci_vobj = &v9_pci->pci_vdev.obj; + + qvirtio_pci_start_hw(pci_vobj); + virtio_9p_setup(interface); +} + +static void *virtio_9p_pci_get_driver(void *object, const char *interface) +{ + QVirtio9PPCI *v_9p = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_9p->pci_vdev.pdev; + } + return virtio_9p_get_driver(&v_9p->v9p, interface); +} + +static void *virtio_9p_pci_create(void *pci_bus, QGuestAllocator *t_alloc, + void *addr) +{ + QVirtio9PPCI *v9_pci = g_new0(QVirtio9PPCI, 1); + QVirtio9P *interface = &v9_pci->v9p; + QOSGraphObject *obj = &v9_pci->pci_vdev.obj; + + virtio_pci_init(&v9_pci->pci_vdev, pci_bus, addr); + interface->vdev = &v9_pci->pci_vdev.vdev; + alloc = t_alloc; + + g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_9P); + + obj->destructor = virtio_9p_pci_destructor; + obj->start_hw = virtio_9p_pci_start_hw; + obj->get_driver = virtio_9p_pci_get_driver; + + return obj; +} + +static void virtio_9p_register_nodes(void) +{ + const char *str_simple = "fsdev=fsdev0,mount_tag=" MOUNT_TAG; + const char *str_addr = "fsdev=fsdev0,addr=04.0,mount_tag=" MOUNT_TAG; + + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + }; + + QOSGraphEdgeOptions opts = { + .before_cmd_line = "-fsdev synth,id=fsdev0", + }; + + /* virtio-9p-device */ + opts.extra_device_opts = str_simple, + qos_node_create_driver("virtio-9p-device", virtio_9p_device_create); + qos_node_consumes("virtio-9p-device", "virtio-bus", &opts); + qos_node_produces("virtio-9p-device", "virtio"); + qos_node_produces("virtio-9p-device", "virtio-9p"); + + /* virtio-9p-pci */ + opts.extra_device_opts = str_addr; + add_qpci_address(&opts, &addr); + qos_node_create_driver("virtio-9p-pci", virtio_9p_pci_create); + qos_node_consumes("virtio-9p-pci", "pci-bus", &opts); + qos_node_produces("virtio-9p-pci", "pci-device"); + qos_node_produces("virtio-9p-pci", "virtio"); + qos_node_produces("virtio-9p-pci", "virtio-9p"); + +} + +libqos_init(virtio_9p_register_nodes); diff --git a/tests/qtest/libqos/virtio-9p.h b/tests/qtest/libqos/virtio-9p.h new file mode 100644 index 0000000000..b54e89b3a1 --- /dev/null +++ b/tests/qtest/libqos/virtio-9p.h @@ -0,0 +1,47 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_9P_H +#define TESTS_LIBQOS_VIRTIO_9P_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtio9P QVirtio9P; +typedef struct QVirtio9PPCI QVirtio9PPCI; +typedef struct QVirtio9PDevice QVirtio9PDevice; + +#define MOUNT_TAG "qtest" + +struct QVirtio9P { + QVirtioDevice *vdev; + QVirtQueue *vq; +}; + +struct QVirtio9PPCI { + QVirtioPCIDevice pci_vdev; + QVirtio9P v9p; +}; + +struct QVirtio9PDevice { + QOSGraphObject obj; + QVirtio9P v9p; +}; + +#endif diff --git a/tests/qtest/libqos/virtio-balloon.c b/tests/qtest/libqos/virtio-balloon.c new file mode 100644 index 0000000000..42a4c5831e --- /dev/null +++ b/tests/qtest/libqos/virtio-balloon.c @@ -0,0 +1,114 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-balloon.h" + +/* virtio-balloon-device */ +static void *qvirtio_balloon_get_driver(QVirtioBalloon *v_balloon, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-balloon")) { + return v_balloon; + } + if (!g_strcmp0(interface, "virtio")) { + return v_balloon->vdev; + } + + fprintf(stderr, "%s not present in virtio-balloon-device\n", interface); + g_assert_not_reached(); +} + +static void *qvirtio_balloon_device_get_driver(void *object, + const char *interface) +{ + QVirtioBalloonDevice *v_balloon = object; + return qvirtio_balloon_get_driver(&v_balloon->balloon, interface); +} + +static void *virtio_balloon_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioBalloonDevice *virtio_bdevice = g_new0(QVirtioBalloonDevice, 1); + QVirtioBalloon *interface = &virtio_bdevice->balloon; + + interface->vdev = virtio_dev; + + virtio_bdevice->obj.get_driver = qvirtio_balloon_device_get_driver; + + return &virtio_bdevice->obj; +} + +/* virtio-balloon-pci */ +static void *qvirtio_balloon_pci_get_driver(void *object, + const char *interface) +{ + QVirtioBalloonPCI *v_balloon = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_balloon->pci_vdev.pdev; + } + return qvirtio_balloon_get_driver(&v_balloon->balloon, interface); +} + +static void *virtio_balloon_pci_create(void *pci_bus, QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioBalloonPCI *virtio_bpci = g_new0(QVirtioBalloonPCI, 1); + QVirtioBalloon *interface = &virtio_bpci->balloon; + QOSGraphObject *obj = &virtio_bpci->pci_vdev.obj; + + + virtio_pci_init(&virtio_bpci->pci_vdev, pci_bus, addr); + interface->vdev = &virtio_bpci->pci_vdev.vdev; + + obj->get_driver = qvirtio_balloon_pci_get_driver; + + return obj; +} + +static void virtio_balloon_register_nodes(void) +{ + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + }; + + QOSGraphEdgeOptions opts = { + .extra_device_opts = "addr=04.0", + }; + + /* virtio-balloon-device */ + qos_node_create_driver("virtio-balloon-device", + virtio_balloon_device_create); + qos_node_consumes("virtio-balloon-device", "virtio-bus", NULL); + qos_node_produces("virtio-balloon-device", "virtio"); + qos_node_produces("virtio-balloon-device", "virtio-balloon"); + + /* virtio-balloon-pci */ + add_qpci_address(&opts, &addr); + qos_node_create_driver("virtio-balloon-pci", virtio_balloon_pci_create); + qos_node_consumes("virtio-balloon-pci", "pci-bus", &opts); + qos_node_produces("virtio-balloon-pci", "pci-device"); + qos_node_produces("virtio-balloon-pci", "virtio"); + qos_node_produces("virtio-balloon-pci", "virtio-balloon"); +} + +libqos_init(virtio_balloon_register_nodes); diff --git a/tests/qtest/libqos/virtio-balloon.h b/tests/qtest/libqos/virtio-balloon.h new file mode 100644 index 0000000000..52661cc87d --- /dev/null +++ b/tests/qtest/libqos/virtio-balloon.h @@ -0,0 +1,44 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_BALLOON_H +#define TESTS_LIBQOS_VIRTIO_BALLOON_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtioBalloon QVirtioBalloon; +typedef struct QVirtioBalloonPCI QVirtioBalloonPCI; +typedef struct QVirtioBalloonDevice QVirtioBalloonDevice; + +struct QVirtioBalloon { + QVirtioDevice *vdev; +}; + +struct QVirtioBalloonPCI { + QVirtioPCIDevice pci_vdev; + QVirtioBalloon balloon; +}; + +struct QVirtioBalloonDevice { + QOSGraphObject obj; + QVirtioBalloon balloon; +}; + +#endif diff --git a/tests/qtest/libqos/virtio-blk.c b/tests/qtest/libqos/virtio-blk.c new file mode 100644 index 0000000000..726e93c5c1 --- /dev/null +++ b/tests/qtest/libqos/virtio-blk.c @@ -0,0 +1,125 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "standard-headers/linux/virtio_blk.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-blk.h" + +#define PCI_SLOT 0x04 +#define PCI_FN 0x00 + +/* virtio-blk-device */ +static void *qvirtio_blk_get_driver(QVirtioBlk *v_blk, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-blk")) { + return v_blk; + } + if (!g_strcmp0(interface, "virtio")) { + return v_blk->vdev; + } + + fprintf(stderr, "%s not present in virtio-blk-device\n", interface); + g_assert_not_reached(); +} + +static void *qvirtio_blk_device_get_driver(void *object, + const char *interface) +{ + QVirtioBlkDevice *v_blk = object; + return qvirtio_blk_get_driver(&v_blk->blk, interface); +} + +static void *virtio_blk_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioBlkDevice *virtio_blk = g_new0(QVirtioBlkDevice, 1); + QVirtioBlk *interface = &virtio_blk->blk; + + interface->vdev = virtio_dev; + + virtio_blk->obj.get_driver = qvirtio_blk_device_get_driver; + + return &virtio_blk->obj; +} + +/* virtio-blk-pci */ +static void *qvirtio_blk_pci_get_driver(void *object, const char *interface) +{ + QVirtioBlkPCI *v_blk = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_blk->pci_vdev.pdev; + } + return qvirtio_blk_get_driver(&v_blk->blk, interface); +} + +static void *virtio_blk_pci_create(void *pci_bus, QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioBlkPCI *virtio_blk = g_new0(QVirtioBlkPCI, 1); + QVirtioBlk *interface = &virtio_blk->blk; + QOSGraphObject *obj = &virtio_blk->pci_vdev.obj; + + virtio_pci_init(&virtio_blk->pci_vdev, pci_bus, addr); + interface->vdev = &virtio_blk->pci_vdev.vdev; + + g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_BLOCK); + + obj->get_driver = qvirtio_blk_pci_get_driver; + + return obj; +} + +static void virtio_blk_register_nodes(void) +{ + /* FIXME: every test using these two nodes needs to setup a + * -drive,id=drive0 otherwise QEMU is not going to start. + * Therefore, we do not include "produces" edge for virtio + * and pci-device yet. + */ + + char *arg = g_strdup_printf("id=drv0,drive=drive0,addr=%x.%x", + PCI_SLOT, PCI_FN); + + QPCIAddress addr = { + .devfn = QPCI_DEVFN(PCI_SLOT, PCI_FN), + }; + + QOSGraphEdgeOptions opts = { }; + + /* virtio-blk-device */ + opts.extra_device_opts = "drive=drive0"; + qos_node_create_driver("virtio-blk-device", virtio_blk_device_create); + qos_node_consumes("virtio-blk-device", "virtio-bus", &opts); + qos_node_produces("virtio-blk-device", "virtio-blk"); + + /* virtio-blk-pci */ + opts.extra_device_opts = arg; + add_qpci_address(&opts, &addr); + qos_node_create_driver("virtio-blk-pci", virtio_blk_pci_create); + qos_node_consumes("virtio-blk-pci", "pci-bus", &opts); + qos_node_produces("virtio-blk-pci", "virtio-blk"); + + g_free(arg); +} + +libqos_init(virtio_blk_register_nodes); diff --git a/tests/qtest/libqos/virtio-blk.h b/tests/qtest/libqos/virtio-blk.h new file mode 100644 index 0000000000..c05adc659d --- /dev/null +++ b/tests/qtest/libqos/virtio-blk.h @@ -0,0 +1,45 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_BLK_H +#define TESTS_LIBQOS_VIRTIO_BLK_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtioBlk QVirtioBlk; +typedef struct QVirtioBlkPCI QVirtioBlkPCI; +typedef struct QVirtioBlkDevice QVirtioBlkDevice; + +/* virtqueue is created in each test */ +struct QVirtioBlk { + QVirtioDevice *vdev; +}; + +struct QVirtioBlkPCI { + QVirtioPCIDevice pci_vdev; + QVirtioBlk blk; +}; + +struct QVirtioBlkDevice { + QOSGraphObject obj; + QVirtioBlk blk; +}; + +#endif diff --git a/tests/qtest/libqos/virtio-mmio.c b/tests/qtest/libqos/virtio-mmio.c new file mode 100644 index 0000000000..e0a2bd7bc6 --- /dev/null +++ b/tests/qtest/libqos/virtio-mmio.c @@ -0,0 +1,266 @@ +/* + * libqos virtio MMIO driver + * + * 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.h" +#include "qemu/module.h" +#include "libqos/virtio.h" +#include "libqos/virtio-mmio.h" +#include "libqos/malloc.h" +#include "libqos/qgraph.h" +#include "standard-headers/linux/virtio_ring.h" + +static uint8_t qvirtio_mmio_config_readb(QVirtioDevice *d, uint64_t off) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return qtest_readb(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); +} + +static uint16_t qvirtio_mmio_config_readw(QVirtioDevice *d, uint64_t off) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return qtest_readw(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); +} + +static uint32_t qvirtio_mmio_config_readl(QVirtioDevice *d, uint64_t off) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); +} + +static uint64_t qvirtio_mmio_config_readq(QVirtioDevice *d, uint64_t off) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return qtest_readq(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off); +} + +static uint64_t qvirtio_mmio_get_features(QVirtioDevice *d) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + uint64_t lo; + uint64_t hi = 0; + + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES_SEL, 0); + lo = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES); + + if (dev->version >= 2) { + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES_SEL, 1); + hi = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES); + } + + return (hi << 32) | lo; +} + +static void qvirtio_mmio_set_features(QVirtioDevice *d, uint64_t features) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + dev->features = features; + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES_SEL, 0); + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES, features); + + if (dev->version >= 2) { + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES_SEL, 1); + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_GUEST_FEATURES, + features >> 32); + } +} + +static uint64_t qvirtio_mmio_get_guest_features(QVirtioDevice *d) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return dev->features; +} + +static uint8_t qvirtio_mmio_get_status(QVirtioDevice *d) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return (uint8_t)qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_STATUS); +} + +static void qvirtio_mmio_set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_STATUS, (uint32_t)status); +} + +static bool qvirtio_mmio_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + uint32_t isr; + + isr = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 1; + if (isr != 0) { + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 1); + return true; + } + + return false; +} + +static bool qvirtio_mmio_get_config_isr_status(QVirtioDevice *d) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + uint32_t isr; + + isr = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 2; + if (isr != 0) { + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 2); + return true; + } + + return false; +} + +static void qvirtio_mmio_wait_config_isr_status(QVirtioDevice *d, + gint64 timeout_us) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + gint64 start_time = g_get_monotonic_time(); + + do { + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + qtest_clock_step(dev->qts, 100); + } while (!qvirtio_mmio_get_config_isr_status(d)); +} + +static void qvirtio_mmio_queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_SEL, (uint32_t)index); + + g_assert_cmphex(qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_PFN), ==, 0); +} + +static uint16_t qvirtio_mmio_get_queue_size(QVirtioDevice *d) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + return (uint16_t)qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NUM_MAX); +} + +static void qvirtio_mmio_set_queue_address(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + uint64_t pfn = vq->desc / dev->page_size; + + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_PFN, pfn); +} + +static QVirtQueue *qvirtio_mmio_virtqueue_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + QVirtQueue *vq; + uint64_t addr; + + vq = g_malloc0(sizeof(*vq)); + vq->vdev = d; + qvirtio_mmio_queue_select(d, index); + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_ALIGN, dev->page_size); + + vq->index = index; + vq->size = qvirtio_mmio_get_queue_size(d); + vq->free_head = 0; + vq->num_free = vq->size; + vq->align = dev->page_size; + vq->indirect = dev->features & (1ull << VIRTIO_RING_F_INDIRECT_DESC); + vq->event = dev->features & (1ull << VIRTIO_RING_F_EVENT_IDX); + + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NUM, vq->size); + + /* Check different than 0 */ + g_assert_cmpint(vq->size, !=, 0); + + /* Check power of 2 */ + g_assert_cmpint(vq->size & (vq->size - 1), ==, 0); + + addr = guest_alloc(alloc, qvring_size(vq->size, dev->page_size)); + qvring_init(dev->qts, alloc, vq, addr); + qvirtio_mmio_set_queue_address(d, vq); + + return vq; +} + +static void qvirtio_mmio_virtqueue_cleanup(QVirtQueue *vq, + QGuestAllocator *alloc) +{ + guest_free(alloc, vq->desc); + g_free(vq); +} + +static void qvirtio_mmio_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev); + qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NOTIFY, vq->index); +} + +const QVirtioBus qvirtio_mmio = { + .config_readb = qvirtio_mmio_config_readb, + .config_readw = qvirtio_mmio_config_readw, + .config_readl = qvirtio_mmio_config_readl, + .config_readq = qvirtio_mmio_config_readq, + .get_features = qvirtio_mmio_get_features, + .set_features = qvirtio_mmio_set_features, + .get_guest_features = qvirtio_mmio_get_guest_features, + .get_status = qvirtio_mmio_get_status, + .set_status = qvirtio_mmio_set_status, + .get_queue_isr_status = qvirtio_mmio_get_queue_isr_status, + .wait_config_isr_status = qvirtio_mmio_wait_config_isr_status, + .queue_select = qvirtio_mmio_queue_select, + .get_queue_size = qvirtio_mmio_get_queue_size, + .set_queue_address = qvirtio_mmio_set_queue_address, + .virtqueue_setup = qvirtio_mmio_virtqueue_setup, + .virtqueue_cleanup = qvirtio_mmio_virtqueue_cleanup, + .virtqueue_kick = qvirtio_mmio_virtqueue_kick, +}; + +static void *qvirtio_mmio_get_driver(void *obj, const char *interface) +{ + QVirtioMMIODevice *virtio_mmio = obj; + if (!g_strcmp0(interface, "virtio-bus")) { + return &virtio_mmio->vdev; + } + fprintf(stderr, "%s not present in virtio-mmio\n", interface); + g_assert_not_reached(); +} + +static void qvirtio_mmio_start_hw(QOSGraphObject *obj) +{ + QVirtioMMIODevice *dev = (QVirtioMMIODevice *) obj; + qvirtio_start_device(&dev->vdev); +} + +void qvirtio_mmio_init_device(QVirtioMMIODevice *dev, QTestState *qts, + uint64_t addr, uint32_t page_size) +{ + uint32_t magic; + magic = qtest_readl(qts, addr + QVIRTIO_MMIO_MAGIC_VALUE); + g_assert(magic == ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)); + + dev->version = qtest_readl(qts, addr + QVIRTIO_MMIO_VERSION); + g_assert(dev->version == 1 || dev->version == 2); + + dev->qts = qts; + dev->addr = addr; + dev->page_size = page_size; + dev->vdev.device_type = qtest_readl(qts, addr + QVIRTIO_MMIO_DEVICE_ID); + dev->vdev.bus = &qvirtio_mmio; + + qtest_writel(qts, addr + QVIRTIO_MMIO_GUEST_PAGE_SIZE, page_size); + + dev->obj.get_driver = qvirtio_mmio_get_driver; + dev->obj.start_hw = qvirtio_mmio_start_hw; +} + +static void virtio_mmio_register_nodes(void) +{ + qos_node_create_driver("virtio-mmio", NULL); + qos_node_produces("virtio-mmio", "virtio-bus"); +} + +libqos_init(virtio_mmio_register_nodes); diff --git a/tests/qtest/libqos/virtio-mmio.h b/tests/qtest/libqos/virtio-mmio.h new file mode 100644 index 0000000000..0e45778b07 --- /dev/null +++ b/tests/qtest/libqos/virtio-mmio.h @@ -0,0 +1,51 @@ +/* + * libqos virtio MMIO definitions + * + * 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. + */ + +#ifndef LIBQOS_VIRTIO_MMIO_H +#define LIBQOS_VIRTIO_MMIO_H + +#include "libqos/virtio.h" +#include "libqos/qgraph.h" + +#define QVIRTIO_MMIO_MAGIC_VALUE 0x000 +#define QVIRTIO_MMIO_VERSION 0x004 +#define QVIRTIO_MMIO_DEVICE_ID 0x008 +#define QVIRTIO_MMIO_VENDOR_ID 0x00C +#define QVIRTIO_MMIO_HOST_FEATURES 0x010 +#define QVIRTIO_MMIO_HOST_FEATURES_SEL 0x014 +#define QVIRTIO_MMIO_GUEST_FEATURES 0x020 +#define QVIRTIO_MMIO_GUEST_FEATURES_SEL 0x024 +#define QVIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 +#define QVIRTIO_MMIO_QUEUE_SEL 0x030 +#define QVIRTIO_MMIO_QUEUE_NUM_MAX 0x034 +#define QVIRTIO_MMIO_QUEUE_NUM 0x038 +#define QVIRTIO_MMIO_QUEUE_ALIGN 0x03C +#define QVIRTIO_MMIO_QUEUE_PFN 0x040 +#define QVIRTIO_MMIO_QUEUE_NOTIFY 0x050 +#define QVIRTIO_MMIO_INTERRUPT_STATUS 0x060 +#define QVIRTIO_MMIO_INTERRUPT_ACK 0x064 +#define QVIRTIO_MMIO_DEVICE_STATUS 0x070 +#define QVIRTIO_MMIO_DEVICE_SPECIFIC 0x100 + +typedef struct QVirtioMMIODevice { + QOSGraphObject obj; + QVirtioDevice vdev; + QTestState *qts; + uint64_t addr; + uint32_t page_size; + uint32_t features; /* As it cannot be read later, save it */ + uint32_t version; +} QVirtioMMIODevice; + +extern const QVirtioBus qvirtio_mmio; + +void qvirtio_mmio_init_device(QVirtioMMIODevice *dev, QTestState *qts, + uint64_t addr, uint32_t page_size); + +#endif diff --git a/tests/qtest/libqos/virtio-net.c b/tests/qtest/libqos/virtio-net.c new file mode 100644 index 0000000000..710d440c3d --- /dev/null +++ b/tests/qtest/libqos/virtio-net.c @@ -0,0 +1,197 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-net.h" +#include "hw/virtio/virtio-net.h" + + +static QGuestAllocator *alloc; + +static void virtio_net_cleanup(QVirtioNet *interface) +{ + int i; + + for (i = 0; i < interface->n_queues; i++) { + qvirtqueue_cleanup(interface->vdev->bus, interface->queues[i], alloc); + } + g_free(interface->queues); +} + +static void virtio_net_setup(QVirtioNet *interface) +{ + QVirtioDevice *vdev = interface->vdev; + uint64_t features; + int i; + + features = qvirtio_get_features(vdev); + features &= ~(QVIRTIO_F_BAD_FEATURE | + (1ull << VIRTIO_RING_F_INDIRECT_DESC) | + (1ull << VIRTIO_RING_F_EVENT_IDX)); + qvirtio_set_features(vdev, features); + + if (features & (1ull << VIRTIO_NET_F_MQ)) { + interface->n_queues = qvirtio_config_readw(vdev, 8) * 2; + } else { + interface->n_queues = 2; + } + interface->n_queues++; /* Account for the ctrl queue */ + + interface->queues = g_new(QVirtQueue *, interface->n_queues); + for (i = 0; i < interface->n_queues; i++) { + interface->queues[i] = qvirtqueue_setup(vdev, alloc, i); + } + qvirtio_set_driver_ok(vdev); +} + +/* virtio-net-device */ +static void qvirtio_net_device_destructor(QOSGraphObject *obj) +{ + QVirtioNetDevice *v_net = (QVirtioNetDevice *) obj; + virtio_net_cleanup(&v_net->net); +} + +static void qvirtio_net_device_start_hw(QOSGraphObject *obj) +{ + QVirtioNetDevice *v_net = (QVirtioNetDevice *) obj; + QVirtioNet *interface = &v_net->net; + + virtio_net_setup(interface); +} + +static void *qvirtio_net_get_driver(QVirtioNet *v_net, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-net")) { + return v_net; + } + if (!g_strcmp0(interface, "virtio")) { + return v_net->vdev; + } + + fprintf(stderr, "%s not present in virtio-net-device\n", interface); + g_assert_not_reached(); +} + +static void *qvirtio_net_device_get_driver(void *object, + const char *interface) +{ + QVirtioNetDevice *v_net = object; + return qvirtio_net_get_driver(&v_net->net, interface); +} + +static void *virtio_net_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioNetDevice *virtio_ndevice = g_new0(QVirtioNetDevice, 1); + QVirtioNet *interface = &virtio_ndevice->net; + + interface->vdev = virtio_dev; + alloc = t_alloc; + + virtio_ndevice->obj.destructor = qvirtio_net_device_destructor; + virtio_ndevice->obj.get_driver = qvirtio_net_device_get_driver; + virtio_ndevice->obj.start_hw = qvirtio_net_device_start_hw; + + return &virtio_ndevice->obj; +} + +/* virtio-net-pci */ +static void qvirtio_net_pci_destructor(QOSGraphObject *obj) +{ + QVirtioNetPCI *v_net = (QVirtioNetPCI *) obj; + QVirtioNet *interface = &v_net->net; + QOSGraphObject *pci_vobj = &v_net->pci_vdev.obj; + + virtio_net_cleanup(interface); + qvirtio_pci_destructor(pci_vobj); +} + +static void qvirtio_net_pci_start_hw(QOSGraphObject *obj) +{ + QVirtioNetPCI *v_net = (QVirtioNetPCI *) obj; + QVirtioNet *interface = &v_net->net; + QOSGraphObject *pci_vobj = &v_net->pci_vdev.obj; + + qvirtio_pci_start_hw(pci_vobj); + virtio_net_setup(interface); +} + +static void *qvirtio_net_pci_get_driver(void *object, + const char *interface) +{ + QVirtioNetPCI *v_net = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_net->pci_vdev.pdev; + } + return qvirtio_net_get_driver(&v_net->net, interface); +} + +static void *virtio_net_pci_create(void *pci_bus, QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioNetPCI *virtio_bpci = g_new0(QVirtioNetPCI, 1); + QVirtioNet *interface = &virtio_bpci->net; + QOSGraphObject *obj = &virtio_bpci->pci_vdev.obj; + + virtio_pci_init(&virtio_bpci->pci_vdev, pci_bus, addr); + interface->vdev = &virtio_bpci->pci_vdev.vdev; + alloc = t_alloc; + + g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_NET); + + obj->destructor = qvirtio_net_pci_destructor; + obj->start_hw = qvirtio_net_pci_start_hw; + obj->get_driver = qvirtio_net_pci_get_driver; + + return obj; +} + +static void virtio_net_register_nodes(void) +{ + /* FIXME: every test using these nodes needs to setup a + * -netdev socket,id=hs0 otherwise QEMU is not going to start. + * Therefore, we do not include "produces" edge for virtio + * and pci-device yet. + */ + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + }; + + QOSGraphEdgeOptions opts = { }; + + /* virtio-net-device */ + opts.extra_device_opts = "netdev=hs0"; + qos_node_create_driver("virtio-net-device", + virtio_net_device_create); + qos_node_consumes("virtio-net-device", "virtio-bus", &opts); + qos_node_produces("virtio-net-device", "virtio-net"); + + /* virtio-net-pci */ + opts.extra_device_opts = "netdev=hs0,addr=04.0"; + add_qpci_address(&opts, &addr); + qos_node_create_driver("virtio-net-pci", virtio_net_pci_create); + qos_node_consumes("virtio-net-pci", "pci-bus", &opts); + qos_node_produces("virtio-net-pci", "virtio-net"); +} + +libqos_init(virtio_net_register_nodes); diff --git a/tests/qtest/libqos/virtio-net.h b/tests/qtest/libqos/virtio-net.h new file mode 100644 index 0000000000..855c67d00f --- /dev/null +++ b/tests/qtest/libqos/virtio-net.h @@ -0,0 +1,46 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_NET_H +#define TESTS_LIBQOS_VIRTIO_NET_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtioNet QVirtioNet; +typedef struct QVirtioNetPCI QVirtioNetPCI; +typedef struct QVirtioNetDevice QVirtioNetDevice; + +struct QVirtioNet { + QVirtioDevice *vdev; + int n_queues; /* total number of virtqueues (rx, tx, ctrl) */ + QVirtQueue **queues; +}; + +struct QVirtioNetPCI { + QVirtioPCIDevice pci_vdev; + QVirtioNet net; +}; + +struct QVirtioNetDevice { + QOSGraphObject obj; + QVirtioNet net; +}; + +#endif diff --git a/tests/qtest/libqos/virtio-pci-modern.c b/tests/qtest/libqos/virtio-pci-modern.c new file mode 100644 index 0000000000..18d118866f --- /dev/null +++ b/tests/qtest/libqos/virtio-pci-modern.c @@ -0,0 +1,443 @@ +/* + * libqos VIRTIO 1.0 PCI driver + * + * Copyright (c) 2019 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 "standard-headers/linux/pci_regs.h" +#include "standard-headers/linux/virtio_pci.h" +#include "standard-headers/linux/virtio_config.h" +#include "virtio-pci-modern.h" + +static uint8_t config_readb(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readb(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint16_t config_readw(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readw(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint32_t config_readl(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readl(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint64_t config_readq(QVirtioDevice *d, uint64_t addr) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readq(dev->pdev, dev->bar, dev->device_cfg_offset + addr); +} + +static uint64_t get_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint64_t lo, hi; + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_feature_select), + 0); + lo = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, device_feature)); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_feature_select), + 1); + hi = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, device_feature)); + + return (hi << 32) | lo; +} + +static void set_features(QVirtioDevice *d, uint64_t features) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + /* Drivers must enable VIRTIO 1.0 or else use the Legacy interface */ + g_assert_cmphex(features & (1ull << VIRTIO_F_VERSION_1), !=, 0); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature_select), + 0); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature), + features); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature_select), + 1); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature), + features >> 32); +} + +static uint64_t get_guest_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint64_t lo, hi; + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature_select), + 0); + lo = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, guest_feature)); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + guest_feature_select), + 1); + hi = qpci_io_readl(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, guest_feature)); + + return (hi << 32) | lo; +} + +static uint8_t get_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + return qpci_io_readb(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_status)); +} + +static void set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + return qpci_io_writeb(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + device_status), + status); +} + +static bool get_msix_status(QVirtioPCIDevice *dev, uint32_t msix_entry, + uint32_t msix_addr, uint32_t msix_data) +{ + uint32_t data; + + g_assert_cmpint(msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, msix_entry); + } + + data = qtest_readl(dev->pdev->bus->qts, msix_addr); + if (data == msix_data) { + qtest_writel(dev->pdev->bus->qts, msix_addr, 0); + return true; + } else { + return false; + } +} + +static bool get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + if (dev->pdev->msix_enabled) { + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); + + return get_msix_status(dev, vqpci->msix_entry, vqpci->msix_addr, + vqpci->msix_data); + } + + return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 1; +} + +static bool get_config_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + if (dev->pdev->msix_enabled) { + return get_msix_status(dev, dev->config_msix_entry, + dev->config_msix_addr, dev->config_msix_data); + } + + return qpci_io_readb(dev->pdev, dev->bar, dev->isr_cfg_offset) & 2; +} + +static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + gint64 start_time = g_get_monotonic_time(); + + do { + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + qtest_clock_step(dev->pdev->bus->qts, 100); + } while (!get_config_isr_status(d)); +} + +static void queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_select), + index); +} + +static uint16_t get_queue_size(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + return qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_size)); +} + +static void set_queue_address(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_desc_lo), + vq->desc); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_desc_hi), + vq->desc >> 32); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_avail_lo), + vq->avail); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_avail_hi), + vq->avail >> 32); + + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_used_lo), + vq->used); + qpci_io_writel(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_used_hi), + vq->used >> 32); +} + +static QVirtQueue *virtqueue_setup(QVirtioDevice *d, QGuestAllocator *alloc, + uint16_t index) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + QVirtQueue *vq; + QVirtQueuePCI *vqpci; + uint16_t notify_off; + + vq = qvirtio_pci_virtqueue_setup_common(d, alloc, index); + vqpci = container_of(vq, QVirtQueuePCI, vq); + + notify_off = qpci_io_readw(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + queue_notify_off)); + + vqpci->notify_offset = dev->notify_cfg_offset + + notify_off * dev->notify_off_multiplier; + + qpci_io_writew(dev->pdev, dev->bar, dev->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_enable), 1); + + return vq; +} + +static void virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); + + qpci_io_writew(dev->pdev, dev->bar, vqpci->notify_offset, vq->index); +} + +static const QVirtioBus qvirtio_pci_virtio_1 = { + .config_readb = config_readb, + .config_readw = config_readw, + .config_readl = config_readl, + .config_readq = config_readq, + .get_features = get_features, + .set_features = set_features, + .get_guest_features = get_guest_features, + .get_status = get_status, + .set_status = set_status, + .get_queue_isr_status = get_queue_isr_status, + .wait_config_isr_status = wait_config_isr_status, + .queue_select = queue_select, + .get_queue_size = get_queue_size, + .set_queue_address = set_queue_address, + .virtqueue_setup = virtqueue_setup, + .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, + .virtqueue_kick = virtqueue_kick, +}; + +static void set_config_vector(QVirtioPCIDevice *d, uint16_t entry) +{ + uint16_t vector; + + qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, msix_config), entry); + vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + msix_config)); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static void set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, + uint16_t entry) +{ + uint16_t vector; + + queue_select(&d->vdev, vq_idx); + qpci_io_writew(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, queue_msix_vector), + entry); + vector = qpci_io_readw(d->pdev, d->bar, d->common_cfg_offset + + offsetof(struct virtio_pci_common_cfg, + queue_msix_vector)); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_virtio_1 = { + .set_config_vector = set_config_vector, + .set_queue_vector = set_queue_vector, +}; + +static bool probe_device_type(QVirtioPCIDevice *dev) +{ + uint16_t vendor_id; + uint16_t device_id; + + /* "Drivers MUST match devices with the PCI Vendor ID 0x1AF4" */ + vendor_id = qpci_config_readw(dev->pdev, PCI_VENDOR_ID); + if (vendor_id != 0x1af4) { + return false; + } + + /* + * "Any PCI device with ... PCI Device ID 0x1000 through 0x107F inclusive + * is a virtio device" + */ + device_id = qpci_config_readw(dev->pdev, PCI_DEVICE_ID); + if (device_id < 0x1000 || device_id > 0x107f) { + return false; + } + + /* + * "Devices MAY utilize a Transitional PCI Device ID range, 0x1000 to + * 0x103F depending on the device type" + */ + if (device_id < 0x1040) { + /* + * "Transitional devices MUST have the PCI Subsystem Device ID matching + * the Virtio Device ID" + */ + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); + } else { + /* + * "The PCI Device ID is calculated by adding 0x1040 to the Virtio + * Device ID" + */ + dev->vdev.device_type = device_id - 0x1040; + } + + return true; +} + +/* Find the first VIRTIO 1.0 PCI structure for a given type */ +static bool find_structure(QVirtioPCIDevice *dev, uint8_t cfg_type, + uint8_t *bar, uint32_t *offset, uint32_t *length, + uint8_t *cfg_addr) +{ + uint8_t addr = 0; + + while ((addr = qpci_find_capability(dev->pdev, PCI_CAP_ID_VNDR, + addr)) != 0) { + uint8_t type; + + type = qpci_config_readb(dev->pdev, + addr + offsetof(struct virtio_pci_cap, cfg_type)); + if (type != cfg_type) { + continue; + } + + *bar = qpci_config_readb(dev->pdev, + addr + offsetof(struct virtio_pci_cap, bar)); + *offset = qpci_config_readl(dev->pdev, + addr + offsetof(struct virtio_pci_cap, offset)); + *length = qpci_config_readl(dev->pdev, + addr + offsetof(struct virtio_pci_cap, length)); + if (cfg_addr) { + *cfg_addr = addr; + } + + return true; + } + + return false; +} + +static bool probe_device_layout(QVirtioPCIDevice *dev) +{ + uint8_t bar; + uint8_t cfg_addr; + uint32_t length; + + /* + * Due to the qpci_iomap() API we only support devices that put all + * structures in the same PCI BAR. Luckily this is true with QEMU. + */ + + if (!find_structure(dev, VIRTIO_PCI_CAP_COMMON_CFG, &bar, + &dev->common_cfg_offset, &length, NULL)) { + return false; + } + dev->bar_idx = bar; + + if (!find_structure(dev, VIRTIO_PCI_CAP_NOTIFY_CFG, &bar, + &dev->notify_cfg_offset, &length, &cfg_addr)) { + return false; + } + g_assert_cmphex(bar, ==, dev->bar_idx); + + dev->notify_off_multiplier = qpci_config_readl(dev->pdev, + cfg_addr + offsetof(struct virtio_pci_notify_cap, + notify_off_multiplier)); + + if (!find_structure(dev, VIRTIO_PCI_CAP_ISR_CFG, &bar, + &dev->isr_cfg_offset, &length, NULL)) { + return false; + } + g_assert_cmphex(bar, ==, dev->bar_idx); + + if (!find_structure(dev, VIRTIO_PCI_CAP_DEVICE_CFG, &bar, + &dev->device_cfg_offset, &length, NULL)) { + return false; + } + g_assert_cmphex(bar, ==, dev->bar_idx); + + return true; +} + +/* Probe a VIRTIO 1.0 device */ +bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev) +{ + if (!probe_device_type(dev)) { + return false; + } + + if (!probe_device_layout(dev)) { + return false; + } + + dev->vdev.bus = &qvirtio_pci_virtio_1; + dev->msix_ops = &qvirtio_pci_msix_ops_virtio_1; + dev->vdev.big_endian = false; + return true; +} diff --git a/tests/qtest/libqos/virtio-pci-modern.h b/tests/qtest/libqos/virtio-pci-modern.h new file mode 100644 index 0000000000..6bf2b207c3 --- /dev/null +++ b/tests/qtest/libqos/virtio-pci-modern.h @@ -0,0 +1,17 @@ +/* + * libqos virtio PCI VIRTIO 1.0 definitions + * + * Copyright (c) 2019 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. + */ + +#ifndef LIBQOS_VIRTIO_PCI_MODERN_H +#define LIBQOS_VIRTIO_PCI_MODERN_H + +#include "virtio-pci.h" + +bool qvirtio_pci_init_virtio_1(QVirtioPCIDevice *dev); + +#endif /* LIBQOS_VIRTIO_PCI_MODERN_H */ diff --git a/tests/qtest/libqos/virtio-pci.c b/tests/qtest/libqos/virtio-pci.c new file mode 100644 index 0000000000..62851c29bb --- /dev/null +++ b/tests/qtest/libqos/virtio-pci.c @@ -0,0 +1,435 @@ +/* + * libqos virtio PCI driver + * + * 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.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "libqos/malloc.h" +#include "libqos/malloc-pc.h" +#include "libqos/qgraph.h" +#include "standard-headers/linux/virtio_ring.h" +#include "standard-headers/linux/virtio_pci.h" + +#include "hw/pci/pci.h" +#include "hw/pci/pci_regs.h" + +#include "virtio-pci-modern.h" + +/* virtio-pci is a superclass of all virtio-xxx-pci devices; + * the relation between virtio-pci and virtio-xxx-pci is implicit, + * and therefore virtio-pci does not produce virtio and is not + * reached by any edge, not even as a "contains" edge. + * In facts, every device is a QVirtioPCIDevice with + * additional fields, since every one has its own + * number of queues and various attributes. + * Virtio-pci provides default functions to start the + * hw and destroy the object, and nodes that want to + * override them should always remember to call the + * original qvirtio_pci_destructor and qvirtio_pci_start_hw. + */ + +#define CONFIG_BASE(dev) (VIRTIO_PCI_CONFIG_OFF((dev)->pdev->msix_enabled)) + +static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readb(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); +} + +/* PCI is always read in little-endian order + * but virtio ( < 1.0) is in guest order + * so with a big-endian guest the order has been reversed, + * reverse it again + * virtio-1.0 is always little-endian, like PCI + */ + +static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint16_t value; + + value = qpci_io_readw(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); + if (qvirtio_is_big_endian(d)) { + value = bswap16(value); + } + return value; +} + +static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint32_t value; + + value = qpci_io_readl(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); + if (qvirtio_is_big_endian(d)) { + value = bswap32(value); + } + return value; +} + +static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, uint64_t off) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint64_t val; + + val = qpci_io_readq(dev->pdev, dev->bar, CONFIG_BASE(dev) + off); + if (qvirtio_is_big_endian(d)) { + val = bswap64(val); + } + + return val; +} + +static uint64_t qvirtio_pci_get_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readl(dev->pdev, dev->bar, VIRTIO_PCI_HOST_FEATURES); +} + +static void qvirtio_pci_set_features(QVirtioDevice *d, uint64_t features) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES, features); +} + +static uint64_t qvirtio_pci_get_guest_features(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readl(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES); +} + +static uint8_t qvirtio_pci_get_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_STATUS); +} + +static void qvirtio_pci_set_status(QVirtioDevice *d, uint8_t status) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writeb(dev->pdev, dev->bar, VIRTIO_PCI_STATUS, status); +} + +static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + QVirtQueuePCI *vqpci = (QVirtQueuePCI *)vq; + uint32_t data; + + if (dev->pdev->msix_enabled) { + g_assert_cmpint(vqpci->msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, vqpci->msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, vqpci->msix_entry); + } else { + data = qtest_readl(dev->pdev->bus->qts, vqpci->msix_addr); + if (data == vqpci->msix_data) { + qtest_writel(dev->pdev->bus->qts, vqpci->msix_addr, 0); + return true; + } else { + return false; + } + } + } else { + return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_ISR) & 1; + } +} + +static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint32_t data; + + if (dev->pdev->msix_enabled) { + g_assert_cmpint(dev->config_msix_entry, !=, -1); + if (qpci_msix_masked(dev->pdev, dev->config_msix_entry)) { + /* No ISR checking should be done if masked, but read anyway */ + return qpci_msix_pending(dev->pdev, dev->config_msix_entry); + } else { + data = qtest_readl(dev->pdev->bus->qts, dev->config_msix_addr); + if (data == dev->config_msix_data) { + qtest_writel(dev->pdev->bus->qts, dev->config_msix_addr, 0); + return true; + } else { + return false; + } + } + } else { + return qpci_io_readb(dev->pdev, dev->bar, VIRTIO_PCI_ISR) & 2; + } +} + +static void qvirtio_pci_wait_config_isr_status(QVirtioDevice *d, + gint64 timeout_us) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + gint64 start_time = g_get_monotonic_time(); + + do { + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + qtest_clock_step(dev->pdev->bus->qts, 100); + } while (!qvirtio_pci_get_config_isr_status(d)); +} + +static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writeb(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_SEL, index); +} + +static uint16_t qvirtio_pci_get_queue_size(QVirtioDevice *d) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + return qpci_io_readw(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NUM); +} + +static void qvirtio_pci_set_queue_address(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + uint64_t pfn = vq->desc / VIRTIO_PCI_VRING_ALIGN; + + qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_PFN, pfn); +} + +QVirtQueue *qvirtio_pci_virtqueue_setup_common(QVirtioDevice *d, + QGuestAllocator *alloc, + uint16_t index) +{ + uint64_t feat; + uint64_t addr; + QVirtQueuePCI *vqpci; + QVirtioPCIDevice *qvpcidev = container_of(d, QVirtioPCIDevice, vdev); + + vqpci = g_malloc0(sizeof(*vqpci)); + feat = d->bus->get_guest_features(d); + + d->bus->queue_select(d, index); + vqpci->vq.vdev = d; + vqpci->vq.index = index; + vqpci->vq.size = d->bus->get_queue_size(d); + vqpci->vq.free_head = 0; + vqpci->vq.num_free = vqpci->vq.size; + vqpci->vq.align = VIRTIO_PCI_VRING_ALIGN; + vqpci->vq.indirect = feat & (1ull << VIRTIO_RING_F_INDIRECT_DESC); + vqpci->vq.event = feat & (1ull << VIRTIO_RING_F_EVENT_IDX); + + vqpci->msix_entry = -1; + vqpci->msix_addr = 0; + vqpci->msix_data = 0x12345678; + + /* Check different than 0 */ + g_assert_cmpint(vqpci->vq.size, !=, 0); + + /* Check power of 2 */ + g_assert_cmpint(vqpci->vq.size & (vqpci->vq.size - 1), ==, 0); + + addr = guest_alloc(alloc, qvring_size(vqpci->vq.size, + VIRTIO_PCI_VRING_ALIGN)); + qvring_init(qvpcidev->pdev->bus->qts, alloc, &vqpci->vq, addr); + d->bus->set_queue_address(d, &vqpci->vq); + + return &vqpci->vq; +} + +void qvirtio_pci_virtqueue_cleanup_common(QVirtQueue *vq, + QGuestAllocator *alloc) +{ + QVirtQueuePCI *vqpci = container_of(vq, QVirtQueuePCI, vq); + + guest_free(alloc, vq->desc); + g_free(vqpci); +} + +static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq) +{ + QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); + qpci_io_writew(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NOTIFY, vq->index); +} + +static const QVirtioBus qvirtio_pci_legacy = { + .config_readb = qvirtio_pci_config_readb, + .config_readw = qvirtio_pci_config_readw, + .config_readl = qvirtio_pci_config_readl, + .config_readq = qvirtio_pci_config_readq, + .get_features = qvirtio_pci_get_features, + .set_features = qvirtio_pci_set_features, + .get_guest_features = qvirtio_pci_get_guest_features, + .get_status = qvirtio_pci_get_status, + .set_status = qvirtio_pci_set_status, + .get_queue_isr_status = qvirtio_pci_get_queue_isr_status, + .wait_config_isr_status = qvirtio_pci_wait_config_isr_status, + .queue_select = qvirtio_pci_queue_select, + .get_queue_size = qvirtio_pci_get_queue_size, + .set_queue_address = qvirtio_pci_set_queue_address, + .virtqueue_setup = qvirtio_pci_virtqueue_setup_common, + .virtqueue_cleanup = qvirtio_pci_virtqueue_cleanup_common, + .virtqueue_kick = qvirtio_pci_virtqueue_kick, +}; + +static void qvirtio_pci_set_config_vector(QVirtioPCIDevice *d, uint16_t entry) +{ + uint16_t vector; + + qpci_io_writew(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static void qvirtio_pci_set_queue_vector(QVirtioPCIDevice *d, uint16_t vq_idx, + uint16_t entry) +{ + uint16_t vector; + + qvirtio_pci_queue_select(&d->vdev, vq_idx); + qpci_io_writew(d->pdev, d->bar, VIRTIO_MSI_QUEUE_VECTOR, entry); + vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_QUEUE_VECTOR); + g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR); +} + +static const QVirtioPCIMSIXOps qvirtio_pci_msix_ops_legacy = { + .set_config_vector = qvirtio_pci_set_config_vector, + .set_queue_vector = qvirtio_pci_set_queue_vector, +}; + +void qvirtio_pci_device_enable(QVirtioPCIDevice *d) +{ + qpci_device_enable(d->pdev); + d->bar = qpci_iomap(d->pdev, d->bar_idx, NULL); +} + +void qvirtio_pci_device_disable(QVirtioPCIDevice *d) +{ + qpci_iounmap(d->pdev, d->bar); +} + +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry) +{ + uint32_t control; + uint64_t off; + + g_assert(d->pdev->msix_enabled); + off = d->pdev->msix_table_off + (entry * 16); + + g_assert_cmpint(entry, >=, 0); + g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); + vqpci->msix_entry = entry; + + vqpci->msix_addr = guest_alloc(alloc, 4); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_LOWER_ADDR, vqpci->msix_addr & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_UPPER_ADDR, + (vqpci->msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_DATA, vqpci->msix_data); + + control = qpci_io_readl(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + d->msix_ops->set_queue_vector(d, vqpci->vq.index, entry); +} + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry) +{ + uint32_t control; + uint64_t off; + + g_assert(d->pdev->msix_enabled); + off = d->pdev->msix_table_off + (entry * 16); + + g_assert_cmpint(entry, >=, 0); + g_assert_cmpint(entry, <, qpci_msix_table_size(d->pdev)); + d->config_msix_entry = entry; + + d->config_msix_data = 0x12345678; + d->config_msix_addr = guest_alloc(alloc, 4); + + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_LOWER_ADDR, d->config_msix_addr & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_UPPER_ADDR, + (d->config_msix_addr >> 32) & ~0UL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_DATA, d->config_msix_data); + + control = qpci_io_readl(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL); + qpci_io_writel(d->pdev, d->pdev->msix_table_bar, + off + PCI_MSIX_ENTRY_VECTOR_CTRL, + control & ~PCI_MSIX_ENTRY_CTRL_MASKBIT); + + d->msix_ops->set_config_vector(d, entry); +} + +void qvirtio_pci_destructor(QOSGraphObject *obj) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)obj; + qvirtio_pci_device_disable(dev); + g_free(dev->pdev); +} + +void qvirtio_pci_start_hw(QOSGraphObject *obj) +{ + QVirtioPCIDevice *dev = (QVirtioPCIDevice *)obj; + qvirtio_pci_device_enable(dev); + qvirtio_start_device(&dev->vdev); +} + +static void qvirtio_pci_init_legacy(QVirtioPCIDevice *dev) +{ + dev->vdev.device_type = qpci_config_readw(dev->pdev, PCI_SUBSYSTEM_ID); + dev->bar_idx = 0; + dev->vdev.bus = &qvirtio_pci_legacy; + dev->msix_ops = &qvirtio_pci_msix_ops_legacy; + dev->vdev.big_endian = qtest_big_endian(dev->pdev->bus->qts); +} + +static void qvirtio_pci_init_from_pcidev(QVirtioPCIDevice *dev, QPCIDevice *pci_dev) +{ + dev->pdev = pci_dev; + dev->config_msix_entry = -1; + + if (!qvirtio_pci_init_virtio_1(dev)) { + qvirtio_pci_init_legacy(dev); + } + + /* each virtio-xxx-pci device should override at least this function */ + dev->obj.get_driver = NULL; + dev->obj.start_hw = qvirtio_pci_start_hw; + dev->obj.destructor = qvirtio_pci_destructor; +} + +void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr) +{ + QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn); + g_assert_nonnull(pci_dev); + qvirtio_pci_init_from_pcidev(dev, pci_dev); +} + +QVirtioPCIDevice *virtio_pci_new(QPCIBus *bus, QPCIAddress * addr) +{ + QVirtioPCIDevice *dev; + QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn); + if (!pci_dev) { + return NULL; + } + + dev = g_new0(QVirtioPCIDevice, 1); + qvirtio_pci_init_from_pcidev(dev, pci_dev); + dev->obj.free = g_free; + return dev; +} diff --git a/tests/qtest/libqos/virtio-pci.h b/tests/qtest/libqos/virtio-pci.h new file mode 100644 index 0000000000..294d5567ee --- /dev/null +++ b/tests/qtest/libqos/virtio-pci.h @@ -0,0 +1,86 @@ +/* + * libqos virtio PCI definitions + * + * 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. + */ + +#ifndef LIBQOS_VIRTIO_PCI_H +#define LIBQOS_VIRTIO_PCI_H + +#include "libqos/virtio.h" +#include "libqos/pci.h" +#include "libqos/qgraph.h" + +typedef struct QVirtioPCIMSIXOps QVirtioPCIMSIXOps; + +typedef struct QVirtioPCIDevice { + QOSGraphObject obj; + QVirtioDevice vdev; + QPCIDevice *pdev; + QPCIBar bar; + const QVirtioPCIMSIXOps *msix_ops; + uint16_t config_msix_entry; + uint64_t config_msix_addr; + uint32_t config_msix_data; + + int bar_idx; + + /* VIRTIO 1.0 */ + uint32_t common_cfg_offset; + uint32_t notify_cfg_offset; + uint32_t notify_off_multiplier; + uint32_t isr_cfg_offset; + uint32_t device_cfg_offset; +} QVirtioPCIDevice; + +struct QVirtioPCIMSIXOps { + /* Set the Configuration Vector for MSI-X */ + void (*set_config_vector)(QVirtioPCIDevice *d, uint16_t entry); + + /* Set the Queue Vector for MSI-X */ + void (*set_queue_vector)(QVirtioPCIDevice *d, uint16_t vq_idx, + uint16_t entry); +}; + +typedef struct QVirtQueuePCI { + QVirtQueue vq; + uint16_t msix_entry; + uint64_t msix_addr; + uint32_t msix_data; + + /* VIRTIO 1.0 */ + uint64_t notify_offset; +} QVirtQueuePCI; + +void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr); +QVirtioPCIDevice *virtio_pci_new(QPCIBus *bus, QPCIAddress * addr); + +/* virtio-pci object functions available for subclasses that + * override the original start_hw and destroy + * function. All virtio-xxx-pci subclass that override must + * take care of calling these two functions in the respective + * places + */ +void qvirtio_pci_destructor(QOSGraphObject *obj); +void qvirtio_pci_start_hw(QOSGraphObject *obj); + + +void qvirtio_pci_device_enable(QVirtioPCIDevice *d); +void qvirtio_pci_device_disable(QVirtioPCIDevice *d); + +void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d, + QGuestAllocator *alloc, uint16_t entry); +void qvirtqueue_pci_msix_setup(QVirtioPCIDevice *d, QVirtQueuePCI *vqpci, + QGuestAllocator *alloc, uint16_t entry); + +/* Used by Legacy and Modern virtio-pci code */ +QVirtQueue *qvirtio_pci_virtqueue_setup_common(QVirtioDevice *d, + QGuestAllocator *alloc, + uint16_t index); +void qvirtio_pci_virtqueue_cleanup_common(QVirtQueue *vq, + QGuestAllocator *alloc); + +#endif diff --git a/tests/qtest/libqos/virtio-rng.c b/tests/qtest/libqos/virtio-rng.c new file mode 100644 index 0000000000..b86349e2fd --- /dev/null +++ b/tests/qtest/libqos/virtio-rng.c @@ -0,0 +1,111 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-rng.h" + +/* virtio-rng-device */ +static void *qvirtio_rng_get_driver(QVirtioRng *v_rng, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-rng")) { + return v_rng; + } + if (!g_strcmp0(interface, "virtio")) { + return v_rng->vdev; + } + + fprintf(stderr, "%s not present in virtio-rng-device\n", interface); + g_assert_not_reached(); +} + +static void *qvirtio_rng_device_get_driver(void *object, + const char *interface) +{ + QVirtioRngDevice *v_rng = object; + return qvirtio_rng_get_driver(&v_rng->rng, interface); +} + +static void *virtio_rng_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioRngDevice *virtio_rdevice = g_new0(QVirtioRngDevice, 1); + QVirtioRng *interface = &virtio_rdevice->rng; + + interface->vdev = virtio_dev; + + virtio_rdevice->obj.get_driver = qvirtio_rng_device_get_driver; + + return &virtio_rdevice->obj; +} + +/* virtio-rng-pci */ +static void *qvirtio_rng_pci_get_driver(void *object, const char *interface) +{ + QVirtioRngPCI *v_rng = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_rng->pci_vdev.pdev; + } + return qvirtio_rng_get_driver(&v_rng->rng, interface); +} + +static void *virtio_rng_pci_create(void *pci_bus, QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioRngPCI *virtio_rpci = g_new0(QVirtioRngPCI, 1); + QVirtioRng *interface = &virtio_rpci->rng; + QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj; + + virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr); + interface->vdev = &virtio_rpci->pci_vdev.vdev; + + obj->get_driver = qvirtio_rng_pci_get_driver; + + return obj; +} + +static void virtio_rng_register_nodes(void) +{ + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + }; + + QOSGraphEdgeOptions opts = { + .extra_device_opts = "addr=04.0", + }; + + /* virtio-rng-device */ + qos_node_create_driver("virtio-rng-device", virtio_rng_device_create); + qos_node_consumes("virtio-rng-device", "virtio-bus", NULL); + qos_node_produces("virtio-rng-device", "virtio"); + qos_node_produces("virtio-rng-device", "virtio-rng"); + + /* virtio-rng-pci */ + add_qpci_address(&opts, &addr); + qos_node_create_driver("virtio-rng-pci", virtio_rng_pci_create); + qos_node_consumes("virtio-rng-pci", "pci-bus", &opts); + qos_node_produces("virtio-rng-pci", "pci-device"); + qos_node_produces("virtio-rng-pci", "virtio"); + qos_node_produces("virtio-rng-pci", "virtio-rng"); +} + +libqos_init(virtio_rng_register_nodes); diff --git a/tests/qtest/libqos/virtio-rng.h b/tests/qtest/libqos/virtio-rng.h new file mode 100644 index 0000000000..9e192f11f7 --- /dev/null +++ b/tests/qtest/libqos/virtio-rng.h @@ -0,0 +1,44 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_RNG_H +#define TESTS_LIBQOS_VIRTIO_RNG_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtioRng QVirtioRng; +typedef struct QVirtioRngPCI QVirtioRngPCI; +typedef struct QVirtioRngDevice QVirtioRngDevice; + +struct QVirtioRng { + QVirtioDevice *vdev; +}; + +struct QVirtioRngPCI { + QVirtioPCIDevice pci_vdev; + QVirtioRng rng; +}; + +struct QVirtioRngDevice { + QOSGraphObject obj; + QVirtioRng rng; +}; + +#endif diff --git a/tests/qtest/libqos/virtio-scsi.c b/tests/qtest/libqos/virtio-scsi.c new file mode 100644 index 0000000000..de739bec5f --- /dev/null +++ b/tests/qtest/libqos/virtio-scsi.c @@ -0,0 +1,119 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "standard-headers/linux/virtio_ids.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-scsi.h" + +/* virtio-scsi-device */ +static void *qvirtio_scsi_get_driver(QVirtioSCSI *v_scsi, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-scsi")) { + return v_scsi; + } + if (!g_strcmp0(interface, "virtio")) { + return v_scsi->vdev; + } + + fprintf(stderr, "%s not present in virtio-scsi-device\n", interface); + g_assert_not_reached(); +} + +static void *qvirtio_scsi_device_get_driver(void *object, + const char *interface) +{ + QVirtioSCSIDevice *v_scsi = object; + return qvirtio_scsi_get_driver(&v_scsi->scsi, interface); +} + +static void *virtio_scsi_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioSCSIDevice *virtio_bdevice = g_new0(QVirtioSCSIDevice, 1); + QVirtioSCSI *interface = &virtio_bdevice->scsi; + + interface->vdev = virtio_dev; + + virtio_bdevice->obj.get_driver = qvirtio_scsi_device_get_driver; + + return &virtio_bdevice->obj; +} + +/* virtio-scsi-pci */ +static void *qvirtio_scsi_pci_get_driver(void *object, + const char *interface) +{ + QVirtioSCSIPCI *v_scsi = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_scsi->pci_vdev.pdev; + } + return qvirtio_scsi_get_driver(&v_scsi->scsi, interface); +} + +static void *virtio_scsi_pci_create(void *pci_bus, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioSCSIPCI *virtio_spci = g_new0(QVirtioSCSIPCI, 1); + QVirtioSCSI *interface = &virtio_spci->scsi; + QOSGraphObject *obj = &virtio_spci->pci_vdev.obj; + + virtio_pci_init(&virtio_spci->pci_vdev, pci_bus, addr); + interface->vdev = &virtio_spci->pci_vdev.vdev; + + g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_SCSI); + + obj->get_driver = qvirtio_scsi_pci_get_driver; + + return obj; +} + +static void virtio_scsi_register_nodes(void) +{ + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + }; + + QOSGraphEdgeOptions opts = { + .before_cmd_line = "-drive id=drv0,if=none,file=null-co://," + "file.read-zeroes=on,format=raw", + .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0", + }; + + /* virtio-scsi-device */ + opts.extra_device_opts = "id=vs0"; + qos_node_create_driver("virtio-scsi-device", + virtio_scsi_device_create); + qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts); + qos_node_produces("virtio-scsi-device", "virtio-scsi"); + + /* virtio-scsi-pci */ + opts.extra_device_opts = "id=vs0,addr=04.0"; + add_qpci_address(&opts, &addr); + qos_node_create_driver("virtio-scsi-pci", virtio_scsi_pci_create); + qos_node_consumes("virtio-scsi-pci", "pci-bus", &opts); + qos_node_produces("virtio-scsi-pci", "pci-device"); + qos_node_produces("virtio-scsi-pci", "virtio-scsi"); +} + +libqos_init(virtio_scsi_register_nodes); diff --git a/tests/qtest/libqos/virtio-scsi.h b/tests/qtest/libqos/virtio-scsi.h new file mode 100644 index 0000000000..4ca19a6a7a --- /dev/null +++ b/tests/qtest/libqos/virtio-scsi.h @@ -0,0 +1,44 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_SCSI_H +#define TESTS_LIBQOS_VIRTIO_SCSI_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtioSCSI QVirtioSCSI; +typedef struct QVirtioSCSIPCI QVirtioSCSIPCI; +typedef struct QVirtioSCSIDevice QVirtioSCSIDevice; + +struct QVirtioSCSI { + QVirtioDevice *vdev; +}; + +struct QVirtioSCSIPCI { + QVirtioPCIDevice pci_vdev; + QVirtioSCSI scsi; +}; + +struct QVirtioSCSIDevice { + QOSGraphObject obj; + QVirtioSCSI scsi; +}; + +#endif diff --git a/tests/qtest/libqos/virtio-serial.c b/tests/qtest/libqos/virtio-serial.c new file mode 100644 index 0000000000..3e5b8b82c7 --- /dev/null +++ b/tests/qtest/libqos/virtio-serial.c @@ -0,0 +1,111 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/module.h" +#include "libqos/qgraph.h" +#include "libqos/virtio-serial.h" + +static void *qvirtio_serial_get_driver(QVirtioSerial *v_serial, + const char *interface) +{ + if (!g_strcmp0(interface, "virtio-serial")) { + return v_serial; + } + if (!g_strcmp0(interface, "virtio")) { + return v_serial->vdev; + } + + fprintf(stderr, "%s not present in virtio-serial-device\n", interface); + g_assert_not_reached(); +} + +static void *qvirtio_serial_device_get_driver(void *object, + const char *interface) +{ + QVirtioSerialDevice *v_serial = object; + return qvirtio_serial_get_driver(&v_serial->serial, interface); +} + +static void *virtio_serial_device_create(void *virtio_dev, + QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioSerialDevice *virtio_device = g_new0(QVirtioSerialDevice, 1); + QVirtioSerial *interface = &virtio_device->serial; + + interface->vdev = virtio_dev; + + virtio_device->obj.get_driver = qvirtio_serial_device_get_driver; + + return &virtio_device->obj; +} + +/* virtio-serial-pci */ +static void *qvirtio_serial_pci_get_driver(void *object, const char *interface) +{ + QVirtioSerialPCI *v_serial = object; + if (!g_strcmp0(interface, "pci-device")) { + return v_serial->pci_vdev.pdev; + } + return qvirtio_serial_get_driver(&v_serial->serial, interface); +} + +static void *virtio_serial_pci_create(void *pci_bus, QGuestAllocator *t_alloc, + void *addr) +{ + QVirtioSerialPCI *virtio_spci = g_new0(QVirtioSerialPCI, 1); + QVirtioSerial *interface = &virtio_spci->serial; + QOSGraphObject *obj = &virtio_spci->pci_vdev.obj; + + virtio_pci_init(&virtio_spci->pci_vdev, pci_bus, addr); + interface->vdev = &virtio_spci->pci_vdev.vdev; + + obj->get_driver = qvirtio_serial_pci_get_driver; + + return obj; +} + +static void virtio_serial_register_nodes(void) +{ + QPCIAddress addr = { + .devfn = QPCI_DEVFN(4, 0), + }; + + QOSGraphEdgeOptions edge_opts = { }; + + /* virtio-serial-device */ + edge_opts.extra_device_opts = "id=vser0"; + qos_node_create_driver("virtio-serial-device", + virtio_serial_device_create); + qos_node_consumes("virtio-serial-device", "virtio-bus", &edge_opts); + qos_node_produces("virtio-serial-device", "virtio"); + qos_node_produces("virtio-serial-device", "virtio-serial"); + + /* virtio-serial-pci */ + edge_opts.extra_device_opts = "id=vser0,addr=04.0"; + add_qpci_address(&edge_opts, &addr); + qos_node_create_driver("virtio-serial-pci", virtio_serial_pci_create); + qos_node_consumes("virtio-serial-pci", "pci-bus", &edge_opts); + qos_node_produces("virtio-serial-pci", "pci-device"); + qos_node_produces("virtio-serial-pci", "virtio"); + qos_node_produces("virtio-serial-pci", "virtio-serial"); +} + +libqos_init(virtio_serial_register_nodes); diff --git a/tests/qtest/libqos/virtio-serial.h b/tests/qtest/libqos/virtio-serial.h new file mode 100644 index 0000000000..080fa8428d --- /dev/null +++ b/tests/qtest/libqos/virtio-serial.h @@ -0,0 +1,44 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#ifndef TESTS_LIBQOS_VIRTIO_SERIAL_H +#define TESTS_LIBQOS_VIRTIO_SERIAL_H + +#include "libqos/qgraph.h" +#include "libqos/virtio.h" +#include "libqos/virtio-pci.h" + +typedef struct QVirtioSerial QVirtioSerial; +typedef struct QVirtioSerialPCI QVirtioSerialPCI; +typedef struct QVirtioSerialDevice QVirtioSerialDevice; + +struct QVirtioSerial { + QVirtioDevice *vdev; +}; + +struct QVirtioSerialPCI { + QVirtioPCIDevice pci_vdev; + QVirtioSerial serial; +}; + +struct QVirtioSerialDevice { + QOSGraphObject obj; + QVirtioSerial serial; +}; + +#endif diff --git a/tests/qtest/libqos/virtio.c b/tests/qtest/libqos/virtio.c new file mode 100644 index 0000000000..9aa360620c --- /dev/null +++ b/tests/qtest/libqos/virtio.c @@ -0,0 +1,450 @@ +/* + * libqos virtio driver + * + * 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 "qemu/bswap.h" +#include "libqtest.h" +#include "libqos/virtio.h" +#include "standard-headers/linux/virtio_config.h" +#include "standard-headers/linux/virtio_ring.h" + +/* + * qtest_readX/writeX() functions transfer host endian from/to guest endian. + * This works great for Legacy VIRTIO devices where we need guest endian + * accesses. For VIRTIO 1.0 the vring is little-endian so the automatic guest + * endianness conversion is not wanted. + * + * The following qvirtio_readX/writeX() functions handle Legacy and VIRTIO 1.0 + * accesses seamlessly. + */ +static uint16_t qvirtio_readw(QVirtioDevice *d, QTestState *qts, uint64_t addr) +{ + uint16_t val = qtest_readw(qts, addr); + + if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { + val = bswap16(val); + } + return val; +} + +static uint32_t qvirtio_readl(QVirtioDevice *d, QTestState *qts, uint64_t addr) +{ + uint32_t val = qtest_readl(qts, addr); + + if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { + val = bswap32(val); + } + return val; +} + +static void qvirtio_writew(QVirtioDevice *d, QTestState *qts, + uint64_t addr, uint16_t val) +{ + if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { + val = bswap16(val); + } + qtest_writew(qts, addr, val); +} + +static void qvirtio_writel(QVirtioDevice *d, QTestState *qts, + uint64_t addr, uint32_t val) +{ + if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { + val = bswap32(val); + } + qtest_writel(qts, addr, val); +} + +static void qvirtio_writeq(QVirtioDevice *d, QTestState *qts, + uint64_t addr, uint64_t val) +{ + if (d->features & (1ull << VIRTIO_F_VERSION_1) && qtest_big_endian(qts)) { + val = bswap64(val); + } + qtest_writeq(qts, addr, val); +} + +uint8_t qvirtio_config_readb(QVirtioDevice *d, uint64_t addr) +{ + g_assert_true(d->features_negotiated); + return d->bus->config_readb(d, addr); +} + +uint16_t qvirtio_config_readw(QVirtioDevice *d, uint64_t addr) +{ + g_assert_true(d->features_negotiated); + return d->bus->config_readw(d, addr); +} + +uint32_t qvirtio_config_readl(QVirtioDevice *d, uint64_t addr) +{ + g_assert_true(d->features_negotiated); + return d->bus->config_readl(d, addr); +} + +uint64_t qvirtio_config_readq(QVirtioDevice *d, uint64_t addr) +{ + g_assert_true(d->features_negotiated); + return d->bus->config_readq(d, addr); +} + +uint64_t qvirtio_get_features(QVirtioDevice *d) +{ + return d->bus->get_features(d); +} + +void qvirtio_set_features(QVirtioDevice *d, uint64_t features) +{ + d->features = features; + d->bus->set_features(d, features); + + /* + * This could be a separate function for drivers that want to access + * configuration space before setting FEATURES_OK, but no existing users + * need that and it's less code for callers if this is done implicitly. + */ + if (features & (1ull << VIRTIO_F_VERSION_1)) { + uint8_t status = d->bus->get_status(d) | + VIRTIO_CONFIG_S_FEATURES_OK; + + d->bus->set_status(d, status); + g_assert_cmphex(d->bus->get_status(d), ==, status); + } + + d->features_negotiated = true; +} + +QVirtQueue *qvirtqueue_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index) +{ + g_assert_true(d->features_negotiated); + return d->bus->virtqueue_setup(d, alloc, index); +} + +void qvirtqueue_cleanup(const QVirtioBus *bus, QVirtQueue *vq, + QGuestAllocator *alloc) +{ + return bus->virtqueue_cleanup(vq, alloc); +} + +void qvirtio_reset(QVirtioDevice *d) +{ + d->bus->set_status(d, 0); + g_assert_cmphex(d->bus->get_status(d), ==, 0); + d->features_negotiated = false; +} + +void qvirtio_set_acknowledge(QVirtioDevice *d) +{ + d->bus->set_status(d, d->bus->get_status(d) | VIRTIO_CONFIG_S_ACKNOWLEDGE); + g_assert_cmphex(d->bus->get_status(d), ==, VIRTIO_CONFIG_S_ACKNOWLEDGE); +} + +void qvirtio_set_driver(QVirtioDevice *d) +{ + d->bus->set_status(d, d->bus->get_status(d) | VIRTIO_CONFIG_S_DRIVER); + g_assert_cmphex(d->bus->get_status(d), ==, + VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_ACKNOWLEDGE); +} + +void qvirtio_set_driver_ok(QVirtioDevice *d) +{ + d->bus->set_status(d, d->bus->get_status(d) | VIRTIO_CONFIG_S_DRIVER_OK); + g_assert_cmphex(d->bus->get_status(d), ==, VIRTIO_CONFIG_S_DRIVER_OK | + VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_ACKNOWLEDGE | + (d->features & (1ull << VIRTIO_F_VERSION_1) ? + VIRTIO_CONFIG_S_FEATURES_OK : 0)); +} + +void qvirtio_wait_queue_isr(QTestState *qts, QVirtioDevice *d, + QVirtQueue *vq, gint64 timeout_us) +{ + gint64 start_time = g_get_monotonic_time(); + + for (;;) { + qtest_clock_step(qts, 100); + if (d->bus->get_queue_isr_status(d, vq)) { + return; + } + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + } +} + +/* Wait for the status byte at given guest memory address to be set + * + * The virtqueue interrupt must not be raised, making this useful for testing + * event_index functionality. + */ +uint8_t qvirtio_wait_status_byte_no_isr(QTestState *qts, QVirtioDevice *d, + QVirtQueue *vq, + uint64_t addr, + gint64 timeout_us) +{ + gint64 start_time = g_get_monotonic_time(); + uint8_t val; + + while ((val = qtest_readb(qts, addr)) == 0xff) { + qtest_clock_step(qts, 100); + g_assert(!d->bus->get_queue_isr_status(d, vq)); + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + } + return val; +} + +/* + * qvirtio_wait_used_elem: + * @desc_idx: The next expected vq->desc[] index in the used ring + * @len: A pointer that is filled with the length written into the buffer, may + * be NULL + * @timeout_us: How many microseconds to wait before failing + * + * This function waits for the next completed request on the used ring. + */ +void qvirtio_wait_used_elem(QTestState *qts, QVirtioDevice *d, + QVirtQueue *vq, + uint32_t desc_idx, + uint32_t *len, + gint64 timeout_us) +{ + gint64 start_time = g_get_monotonic_time(); + + for (;;) { + uint32_t got_desc_idx; + + qtest_clock_step(qts, 100); + + if (d->bus->get_queue_isr_status(d, vq) && + qvirtqueue_get_buf(qts, vq, &got_desc_idx, len)) { + g_assert_cmpint(got_desc_idx, ==, desc_idx); + return; + } + + g_assert(g_get_monotonic_time() - start_time <= timeout_us); + } +} + +void qvirtio_wait_config_isr(QVirtioDevice *d, gint64 timeout_us) +{ + d->bus->wait_config_isr_status(d, timeout_us); +} + +void qvring_init(QTestState *qts, const QGuestAllocator *alloc, QVirtQueue *vq, + uint64_t addr) +{ + int i; + + vq->desc = addr; + vq->avail = vq->desc + vq->size * sizeof(struct vring_desc); + vq->used = (uint64_t)((vq->avail + sizeof(uint16_t) * (3 + vq->size) + + vq->align - 1) & ~(vq->align - 1)); + + for (i = 0; i < vq->size - 1; i++) { + /* vq->desc[i].addr */ + qvirtio_writeq(vq->vdev, qts, vq->desc + (16 * i), 0); + /* vq->desc[i].next */ + qvirtio_writew(vq->vdev, qts, vq->desc + (16 * i) + 14, i + 1); + } + + /* vq->avail->flags */ + qvirtio_writew(vq->vdev, qts, vq->avail, 0); + /* vq->avail->idx */ + qvirtio_writew(vq->vdev, qts, vq->avail + 2, 0); + /* vq->avail->used_event */ + qvirtio_writew(vq->vdev, qts, vq->avail + 4 + (2 * vq->size), 0); + + /* vq->used->flags */ + qvirtio_writew(vq->vdev, qts, vq->used, 0); + /* vq->used->avail_event */ + qvirtio_writew(vq->vdev, qts, vq->used + 2 + + sizeof(struct vring_used_elem) * vq->size, 0); +} + +QVRingIndirectDesc *qvring_indirect_desc_setup(QTestState *qs, QVirtioDevice *d, + QGuestAllocator *alloc, + uint16_t elem) +{ + int i; + QVRingIndirectDesc *indirect = g_malloc(sizeof(*indirect)); + + indirect->index = 0; + indirect->elem = elem; + indirect->desc = guest_alloc(alloc, sizeof(struct vring_desc) * elem); + + for (i = 0; i < elem - 1; ++i) { + /* indirect->desc[i].addr */ + qvirtio_writeq(d, qs, indirect->desc + (16 * i), 0); + /* indirect->desc[i].flags */ + qvirtio_writew(d, qs, indirect->desc + (16 * i) + 12, + VRING_DESC_F_NEXT); + /* indirect->desc[i].next */ + qvirtio_writew(d, qs, indirect->desc + (16 * i) + 14, i + 1); + } + + return indirect; +} + +void qvring_indirect_desc_add(QVirtioDevice *d, QTestState *qts, + QVRingIndirectDesc *indirect, + uint64_t data, uint32_t len, bool write) +{ + uint16_t flags; + + g_assert_cmpint(indirect->index, <, indirect->elem); + + flags = qvirtio_readw(d, qts, indirect->desc + + (16 * indirect->index) + 12); + + if (write) { + flags |= VRING_DESC_F_WRITE; + } + + /* indirect->desc[indirect->index].addr */ + qvirtio_writeq(d, qts, indirect->desc + (16 * indirect->index), data); + /* indirect->desc[indirect->index].len */ + qvirtio_writel(d, qts, indirect->desc + (16 * indirect->index) + 8, len); + /* indirect->desc[indirect->index].flags */ + qvirtio_writew(d, qts, indirect->desc + (16 * indirect->index) + 12, + flags); + + indirect->index++; +} + +uint32_t qvirtqueue_add(QTestState *qts, QVirtQueue *vq, uint64_t data, + uint32_t len, bool write, bool next) +{ + uint16_t flags = 0; + vq->num_free--; + + if (write) { + flags |= VRING_DESC_F_WRITE; + } + + if (next) { + flags |= VRING_DESC_F_NEXT; + } + + /* vq->desc[vq->free_head].addr */ + qvirtio_writeq(vq->vdev, qts, vq->desc + (16 * vq->free_head), data); + /* vq->desc[vq->free_head].len */ + qvirtio_writel(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 8, len); + /* vq->desc[vq->free_head].flags */ + qvirtio_writew(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 12, flags); + + return vq->free_head++; /* Return and increase, in this order */ +} + +uint32_t qvirtqueue_add_indirect(QTestState *qts, QVirtQueue *vq, + QVRingIndirectDesc *indirect) +{ + g_assert(vq->indirect); + g_assert_cmpint(vq->size, >=, indirect->elem); + g_assert_cmpint(indirect->index, ==, indirect->elem); + + vq->num_free--; + + /* vq->desc[vq->free_head].addr */ + qvirtio_writeq(vq->vdev, qts, vq->desc + (16 * vq->free_head), + indirect->desc); + /* vq->desc[vq->free_head].len */ + qvirtio_writel(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 8, + sizeof(struct vring_desc) * indirect->elem); + /* vq->desc[vq->free_head].flags */ + qvirtio_writew(vq->vdev, qts, vq->desc + (16 * vq->free_head) + 12, + VRING_DESC_F_INDIRECT); + + return vq->free_head++; /* Return and increase, in this order */ +} + +void qvirtqueue_kick(QTestState *qts, QVirtioDevice *d, QVirtQueue *vq, + uint32_t free_head) +{ + /* vq->avail->idx */ + uint16_t idx = qvirtio_readw(d, qts, vq->avail + 2); + /* vq->used->flags */ + uint16_t flags; + /* vq->used->avail_event */ + uint16_t avail_event; + + /* vq->avail->ring[idx % vq->size] */ + qvirtio_writew(d, qts, vq->avail + 4 + (2 * (idx % vq->size)), free_head); + /* vq->avail->idx */ + qvirtio_writew(d, qts, vq->avail + 2, idx + 1); + + /* Must read after idx is updated */ + flags = qvirtio_readw(d, qts, vq->avail); + avail_event = qvirtio_readw(d, qts, vq->used + 4 + + sizeof(struct vring_used_elem) * vq->size); + + /* < 1 because we add elements to avail queue one by one */ + if ((flags & VRING_USED_F_NO_NOTIFY) == 0 && + (!vq->event || (uint16_t)(idx-avail_event) < 1)) { + d->bus->virtqueue_kick(d, vq); + } +} + +/* + * qvirtqueue_get_buf: + * @desc_idx: A pointer that is filled with the vq->desc[] index, may be NULL + * @len: A pointer that is filled with the length written into the buffer, may + * be NULL + * + * This function gets the next used element if there is one ready. + * + * Returns: true if an element was ready, false otherwise + */ +bool qvirtqueue_get_buf(QTestState *qts, QVirtQueue *vq, uint32_t *desc_idx, + uint32_t *len) +{ + uint16_t idx; + uint64_t elem_addr, addr; + + idx = qvirtio_readw(vq->vdev, qts, + vq->used + offsetof(struct vring_used, idx)); + if (idx == vq->last_used_idx) { + return false; + } + + elem_addr = vq->used + + offsetof(struct vring_used, ring) + + (vq->last_used_idx % vq->size) * + sizeof(struct vring_used_elem); + + if (desc_idx) { + addr = elem_addr + offsetof(struct vring_used_elem, id); + *desc_idx = qvirtio_readl(vq->vdev, qts, addr); + } + + if (len) { + addr = elem_addr + offsetof(struct vring_used_elem, len); + *len = qvirtio_readw(vq->vdev, qts, addr); + } + + vq->last_used_idx++; + return true; +} + +void qvirtqueue_set_used_event(QTestState *qts, QVirtQueue *vq, uint16_t idx) +{ + g_assert(vq->event); + + /* vq->avail->used_event */ + qvirtio_writew(vq->vdev, qts, vq->avail + 4 + (2 * vq->size), idx); +} + +void qvirtio_start_device(QVirtioDevice *vdev) +{ + qvirtio_reset(vdev); + qvirtio_set_acknowledge(vdev); + qvirtio_set_driver(vdev); +} + +bool qvirtio_is_big_endian(QVirtioDevice *d) +{ + return d->big_endian; +} diff --git a/tests/qtest/libqos/virtio.h b/tests/qtest/libqos/virtio.h new file mode 100644 index 0000000000..529ef7555a --- /dev/null +++ b/tests/qtest/libqos/virtio.h @@ -0,0 +1,155 @@ +/* + * libqos virtio definitions + * + * 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. + */ + +#ifndef LIBQOS_VIRTIO_H +#define LIBQOS_VIRTIO_H + +#include "libqos/malloc.h" +#include "standard-headers/linux/virtio_ring.h" + +#define QVIRTIO_F_BAD_FEATURE 0x40000000ull + +typedef struct QVirtioBus QVirtioBus; + +typedef struct QVirtioDevice { + const QVirtioBus *bus; + /* Device type */ + uint16_t device_type; + uint64_t features; + bool big_endian; + bool features_negotiated; +} QVirtioDevice; + +typedef struct QVirtQueue { + QVirtioDevice *vdev; + uint64_t desc; /* This points to an array of struct vring_desc */ + uint64_t avail; /* This points to a struct vring_avail */ + uint64_t used; /* This points to a struct vring_used */ + uint16_t index; + uint32_t size; + uint32_t free_head; + uint32_t num_free; + uint32_t align; + uint16_t last_used_idx; + bool indirect; + bool event; +} QVirtQueue; + +typedef struct QVRingIndirectDesc { + uint64_t desc; /* This points to an array fo struct vring_desc */ + uint16_t index; + uint16_t elem; +} QVRingIndirectDesc; + +struct QVirtioBus { + uint8_t (*config_readb)(QVirtioDevice *d, uint64_t addr); + uint16_t (*config_readw)(QVirtioDevice *d, uint64_t addr); + uint32_t (*config_readl)(QVirtioDevice *d, uint64_t addr); + uint64_t (*config_readq)(QVirtioDevice *d, uint64_t addr); + + /* Get features of the device */ + uint64_t (*get_features)(QVirtioDevice *d); + + /* Set features of the device */ + void (*set_features)(QVirtioDevice *d, uint64_t features); + + /* Get features of the guest */ + uint64_t (*get_guest_features)(QVirtioDevice *d); + + /* Get status of the device */ + uint8_t (*get_status)(QVirtioDevice *d); + + /* Set status of the device */ + void (*set_status)(QVirtioDevice *d, uint8_t status); + + /* Get the queue ISR status of the device */ + bool (*get_queue_isr_status)(QVirtioDevice *d, QVirtQueue *vq); + + /* Wait for the configuration ISR status of the device */ + void (*wait_config_isr_status)(QVirtioDevice *d, gint64 timeout_us); + + /* Select a queue to work on */ + void (*queue_select)(QVirtioDevice *d, uint16_t index); + + /* Get the size of the selected queue */ + uint16_t (*get_queue_size)(QVirtioDevice *d); + + /* Set the address of the selected queue */ + void (*set_queue_address)(QVirtioDevice *d, QVirtQueue *vq); + + /* Setup the virtqueue specified by index */ + QVirtQueue *(*virtqueue_setup)(QVirtioDevice *d, QGuestAllocator *alloc, + uint16_t index); + + /* Free virtqueue resources */ + void (*virtqueue_cleanup)(QVirtQueue *vq, QGuestAllocator *alloc); + + /* Notify changes in virtqueue */ + void (*virtqueue_kick)(QVirtioDevice *d, QVirtQueue *vq); +}; + +static inline uint32_t qvring_size(uint32_t num, uint32_t align) +{ + return ((sizeof(struct vring_desc) * num + sizeof(uint16_t) * (3 + num) + + align - 1) & ~(align - 1)) + + sizeof(uint16_t) * 3 + sizeof(struct vring_used_elem) * num; +} + +uint8_t qvirtio_config_readb(QVirtioDevice *d, uint64_t addr); +uint16_t qvirtio_config_readw(QVirtioDevice *d, uint64_t addr); +uint32_t qvirtio_config_readl(QVirtioDevice *d, uint64_t addr); +uint64_t qvirtio_config_readq(QVirtioDevice *d, uint64_t addr); +uint64_t qvirtio_get_features(QVirtioDevice *d); +void qvirtio_set_features(QVirtioDevice *d, uint64_t features); +bool qvirtio_is_big_endian(QVirtioDevice *d); + +void qvirtio_reset(QVirtioDevice *d); +void qvirtio_set_acknowledge(QVirtioDevice *d); +void qvirtio_set_driver(QVirtioDevice *d); +void qvirtio_set_driver_ok(QVirtioDevice *d); + +void qvirtio_wait_queue_isr(QTestState *qts, QVirtioDevice *d, + QVirtQueue *vq, gint64 timeout_us); +uint8_t qvirtio_wait_status_byte_no_isr(QTestState *qts, QVirtioDevice *d, + QVirtQueue *vq, + uint64_t addr, + gint64 timeout_us); +void qvirtio_wait_used_elem(QTestState *qts, QVirtioDevice *d, + QVirtQueue *vq, + uint32_t desc_idx, + uint32_t *len, + gint64 timeout_us); +void qvirtio_wait_config_isr(QVirtioDevice *d, gint64 timeout_us); +QVirtQueue *qvirtqueue_setup(QVirtioDevice *d, + QGuestAllocator *alloc, uint16_t index); +void qvirtqueue_cleanup(const QVirtioBus *bus, QVirtQueue *vq, + QGuestAllocator *alloc); + +void qvring_init(QTestState *qts, const QGuestAllocator *alloc, QVirtQueue *vq, + uint64_t addr); +QVRingIndirectDesc *qvring_indirect_desc_setup(QTestState *qs, QVirtioDevice *d, + QGuestAllocator *alloc, + uint16_t elem); +void qvring_indirect_desc_add(QVirtioDevice *d, QTestState *qts, + QVRingIndirectDesc *indirect, + uint64_t data, uint32_t len, bool write); +uint32_t qvirtqueue_add(QTestState *qts, QVirtQueue *vq, uint64_t data, + uint32_t len, bool write, bool next); +uint32_t qvirtqueue_add_indirect(QTestState *qts, QVirtQueue *vq, + QVRingIndirectDesc *indirect); +void qvirtqueue_kick(QTestState *qts, QVirtioDevice *d, QVirtQueue *vq, + uint32_t free_head); +bool qvirtqueue_get_buf(QTestState *qts, QVirtQueue *vq, uint32_t *desc_idx, + uint32_t *len); + +void qvirtqueue_set_used_event(QTestState *qts, QVirtQueue *vq, uint16_t idx); + +void qvirtio_start_device(QVirtioDevice *vdev); + +#endif diff --git a/tests/qtest/libqos/x86_64_pc-machine.c b/tests/qtest/libqos/x86_64_pc-machine.c new file mode 100644 index 0000000000..6dfa705217 --- /dev/null +++ b/tests/qtest/libqos/x86_64_pc-machine.c @@ -0,0 +1,115 @@ +/* + * libqos driver framework + * + * Copyright (c) 2018 Emanuele Giuseppe Esposito + * + * 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 + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/qgraph.h" +#include "pci-pc.h" +#include "qemu/module.h" +#include "malloc-pc.h" + +typedef struct QX86PCMachine QX86PCMachine; +typedef struct i440FX_pcihost i440FX_pcihost; +typedef struct QSDHCI_PCI QSDHCI_PCI; + +struct i440FX_pcihost { + QOSGraphObject obj; + QPCIBusPC pci; +}; + +struct QX86PCMachine { + QOSGraphObject obj; + QGuestAllocator alloc; + i440FX_pcihost bridge; +}; + +/* i440FX_pcihost */ + +static QOSGraphObject *i440FX_host_get_device(void *obj, const char *device) +{ + i440FX_pcihost *host = obj; + if (!g_strcmp0(device, "pci-bus-pc")) { + return &host->pci.obj; + } + fprintf(stderr, "%s not present in i440FX-pcihost\n", device); + g_assert_not_reached(); +} + +static void qos_create_i440FX_host(i440FX_pcihost *host, + QTestState *qts, + QGuestAllocator *alloc) +{ + host->obj.get_device = i440FX_host_get_device; + qpci_init_pc(&host->pci, qts, alloc); +} + +/* x86_64/pc machine */ + +static void pc_destructor(QOSGraphObject *obj) +{ + QX86PCMachine *machine = (QX86PCMachine *) obj; + alloc_destroy(&machine->alloc); +} + +static void *pc_get_driver(void *object, const char *interface) +{ + QX86PCMachine *machine = object; + if (!g_strcmp0(interface, "memory")) { + return &machine->alloc; + } + + fprintf(stderr, "%s not present in x86_64/pc\n", interface); + g_assert_not_reached(); +} + +static QOSGraphObject *pc_get_device(void *obj, const char *device) +{ + QX86PCMachine *machine = obj; + if (!g_strcmp0(device, "i440FX-pcihost")) { + return &machine->bridge.obj; + } + + fprintf(stderr, "%s not present in x86_64/pc\n", device); + g_assert_not_reached(); +} + +static void *qos_create_machine_pc(QTestState *qts) +{ + QX86PCMachine *machine = g_new0(QX86PCMachine, 1); + machine->obj.get_device = pc_get_device; + machine->obj.get_driver = pc_get_driver; + machine->obj.destructor = pc_destructor; + pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS); + qos_create_i440FX_host(&machine->bridge, qts, &machine->alloc); + + return &machine->obj; +} + +static void pc_machine_register_nodes(void) +{ + qos_node_create_machine("i386/pc", qos_create_machine_pc); + qos_node_contains("i386/pc", "i440FX-pcihost", NULL); + + qos_node_create_machine("x86_64/pc", qos_create_machine_pc); + qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL); + + qos_node_create_driver("i440FX-pcihost", NULL); + qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL); +} + +libqos_init(pc_machine_register_nodes);