mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-06 01:03:55 -06:00
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 <pbonzini@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
This commit is contained in:
parent
833884f37a
commit
1cf4323ecd
74 changed files with 46 additions and 47 deletions
95
tests/qtest/libqos/aarch64-xlnx-zcu102-machine.c
Normal file
95
tests/qtest/libqos/aarch64-xlnx-zcu102-machine.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
1242
tests/qtest/libqos/ahci.c
Normal file
1242
tests/qtest/libqos/ahci.c
Normal file
File diff suppressed because it is too large
Load diff
651
tests/qtest/libqos/ahci.h
Normal file
651
tests/qtest/libqos/ahci.h
Normal file
|
@ -0,0 +1,651 @@
|
|||
#ifndef LIBQOS_AHCI_H
|
||||
#define LIBQOS_AHCI_H
|
||||
|
||||
/*
|
||||
* AHCI qtest library functions and definitions
|
||||
*
|
||||
* Copyright (c) 2014 John Snow <jsnow@redhat.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "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
|
92
tests/qtest/libqos/arm-imx25-pdk-machine.c
Normal file
92
tests/qtest/libqos/arm-imx25-pdk-machine.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Author: Paolo Bonzini <pbonzini@redhat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
92
tests/qtest/libqos/arm-n800-machine.c
Normal file
92
tests/qtest/libqos/arm-n800-machine.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Author: Paolo Bonzini <pbonzini@redhat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
92
tests/qtest/libqos/arm-raspi2-machine.c
Normal file
92
tests/qtest/libqos/arm-raspi2-machine.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
92
tests/qtest/libqos/arm-sabrelite-machine.c
Normal file
92
tests/qtest/libqos/arm-sabrelite-machine.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
92
tests/qtest/libqos/arm-smdkc210-machine.c
Normal file
92
tests/qtest/libqos/arm-smdkc210-machine.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
91
tests/qtest/libqos/arm-virt-machine.c
Normal file
91
tests/qtest/libqos/arm-virt-machine.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
95
tests/qtest/libqos/arm-xilinx-zynq-a9-machine.c
Normal file
95
tests/qtest/libqos/arm-xilinx-zynq-a9-machine.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
266
tests/qtest/libqos/e1000e.c
Normal file
266
tests/qtest/libqos/e1000e.c
Normal file
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
53
tests/qtest/libqos/e1000e.h
Normal file
53
tests/qtest/libqos/e1000e.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
164
tests/qtest/libqos/fw_cfg.c
Normal file
164
tests/qtest/libqos/fw_cfg.c
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* libqos fw_cfg support
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
* Copyright (C) 2013 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
* Markus Armbruster <armbru@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqos/fw_cfg.h"
|
||||
#include "libqtest.h"
|
||||
#include "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);
|
||||
}
|
52
tests/qtest/libqos/fw_cfg.h
Normal file
52
tests/qtest/libqos/fw_cfg.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* libqos fw_cfg support
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#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
|
216
tests/qtest/libqos/i2c-imx.c
Normal file
216
tests/qtest/libqos/i2c-imx.c
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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);
|
196
tests/qtest/libqos/i2c-omap.c
Normal file
196
tests/qtest/libqos/i2c-omap.c
Normal file
|
@ -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);
|
85
tests/qtest/libqos/i2c.c
Normal file
85
tests/qtest/libqos/i2c.c
Normal file
|
@ -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);
|
||||
}
|
82
tests/qtest/libqos/i2c.h
Normal file
82
tests/qtest/libqos/i2c.h
Normal file
|
@ -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
|
35
tests/qtest/libqos/libqos-pc.c
Normal file
35
tests/qtest/libqos/libqos-pc.c
Normal file
|
@ -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);
|
||||
}
|
10
tests/qtest/libqos/libqos-pc.h
Normal file
10
tests/qtest/libqos/libqos-pc.h
Normal file
|
@ -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
|
33
tests/qtest/libqos/libqos-spapr.c
Normal file
33
tests/qtest/libqos/libqos-spapr.c
Normal file
|
@ -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);
|
||||
}
|
10
tests/qtest/libqos/libqos-spapr.h
Normal file
10
tests/qtest/libqos/libqos-spapr.h
Normal file
|
@ -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
|
240
tests/qtest/libqos/libqos.c
Normal file
240
tests/qtest/libqos/libqos.c
Normal file
|
@ -0,0 +1,240 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include <sys/wait.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
45
tests/qtest/libqos/libqos.h
Normal file
45
tests/qtest/libqos/libqos.h
Normal file
|
@ -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
|
33
tests/qtest/libqos/malloc-pc.c
Normal file
33
tests/qtest/libqos/malloc-pc.c
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* libqos malloc support for PC
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
||||
}
|
20
tests/qtest/libqos/malloc-pc.h
Normal file
20
tests/qtest/libqos/malloc-pc.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* libqos malloc support for PC
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#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
|
23
tests/qtest/libqos/malloc-spapr.c
Normal file
23
tests/qtest/libqos/malloc-spapr.c
Normal file
|
@ -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);
|
||||
}
|
15
tests/qtest/libqos/malloc-spapr.h
Normal file
15
tests/qtest/libqos/malloc-spapr.h
Normal file
|
@ -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
|
347
tests/qtest/libqos/malloc.c
Normal file
347
tests/qtest/libqos/malloc.c
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* libqos malloc support
|
||||
*
|
||||
* Copyright (c) 2014
|
||||
*
|
||||
* Author:
|
||||
* John Snow <jsnow@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqos/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;
|
||||
}
|
50
tests/qtest/libqos/malloc.h
Normal file
50
tests/qtest/libqos/malloc.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* libqos malloc support
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#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
|
200
tests/qtest/libqos/pci-pc.c
Normal file
200
tests/qtest/libqos/pci-pc.c
Normal file
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* libqos PCI bindings for PC
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqtest.h"
|
||||
#include "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);
|
49
tests/qtest/libqos/pci-pc.h
Normal file
49
tests/qtest/libqos/pci-pc.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* libqos PCI bindings for PC
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#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
|
232
tests/qtest/libqos/pci-spapr.c
Normal file
232
tests/qtest/libqos/pci-spapr.c
Normal file
|
@ -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);
|
41
tests/qtest/libqos/pci-spapr.h
Normal file
41
tests/qtest/libqos/pci-spapr.h
Normal file
|
@ -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
|
457
tests/qtest/libqos/pci.c
Normal file
457
tests/qtest/libqos/pci.c
Normal file
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* libqos PCI bindings
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
||||
}
|
129
tests/qtest/libqos/pci.h
Normal file
129
tests/qtest/libqos/pci.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* libqos PCI bindings
|
||||
*
|
||||
* Copyright IBM, Corp. 2012-2013
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <aliguori@us.ibm.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#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
|
112
tests/qtest/libqos/ppc64_pseries-machine.c
Normal file
112
tests/qtest/libqos/ppc64_pseries-machine.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
||||
|
759
tests/qtest/libqos/qgraph.c
Normal file
759
tests/qtest/libqos/qgraph.c
Normal file
|
@ -0,0 +1,759 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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 <string, node/edge> 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 <machine> 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 <arch>/<machine>\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;
|
||||
}
|
||||
}
|
574
tests/qtest/libqos/qgraph.h
Normal file
574
tests/qtest/libqos/qgraph.h
Normal file
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef QGRAPH_H
|
||||
#define QGRAPH_H
|
||||
|
||||
#include <gmodule.h>
|
||||
#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.
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating new driver an its interface</title>
|
||||
* <programlisting>
|
||||
#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");
|
||||
}
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* 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--+
|
||||
*
|
||||
* <example>
|
||||
* <title>Creating new test</title>
|
||||
* <programlisting>
|
||||
* #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);
|
||||
*
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* 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 <machine>" to its command line, while devices
|
||||
* "-device <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
|
257
tests/qtest/libqos/qgraph_internal.h
Normal file
257
tests/qtest/libqos/qgraph_internal.h
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
||||
* <arch>/<machine_name>, like "arm/raspi2" or "x86_64/pc".
|
||||
*
|
||||
* The function will validate the format and return a pointer to
|
||||
* @machine to <machine_name>. 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
|
120
tests/qtest/libqos/rtas.c
Normal file
120
tests/qtest/libqos/rtas.c
Normal file
|
@ -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;
|
||||
}
|
17
tests/qtest/libqos/rtas.h
Normal file
17
tests/qtest/libqos/rtas.h
Normal file
|
@ -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 */
|
164
tests/qtest/libqos/sdhci.c
Normal file
164
tests/qtest/libqos/sdhci.c
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
70
tests/qtest/libqos/sdhci.h
Normal file
70
tests/qtest/libqos/sdhci.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
66
tests/qtest/libqos/tpci200.c
Normal file
66
tests/qtest/libqos/tpci200.c
Normal file
|
@ -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);
|
57
tests/qtest/libqos/usb.c
Normal file
57
tests/qtest/libqos/usb.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* common code shared by usb tests
|
||||
*
|
||||
* Copyright (c) 2014 Red Hat, Inc
|
||||
*
|
||||
* Authors:
|
||||
* Gerd Hoffmann <kraxel@redhat.com>
|
||||
* John Snow <jsnow@redhat.com>
|
||||
* Igor Mammedov <imammedo@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqtest.h"
|
||||
#include "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);
|
||||
}
|
18
tests/qtest/libqos/usb.h
Normal file
18
tests/qtest/libqos/usb.h
Normal file
|
@ -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
|
180
tests/qtest/libqos/virtio-9p.c
Normal file
180
tests/qtest/libqos/virtio-9p.c
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
47
tests/qtest/libqos/virtio-9p.h
Normal file
47
tests/qtest/libqos/virtio-9p.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
114
tests/qtest/libqos/virtio-balloon.c
Normal file
114
tests/qtest/libqos/virtio-balloon.c
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
44
tests/qtest/libqos/virtio-balloon.h
Normal file
44
tests/qtest/libqos/virtio-balloon.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
125
tests/qtest/libqos/virtio-blk.c
Normal file
125
tests/qtest/libqos/virtio-blk.c
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
45
tests/qtest/libqos/virtio-blk.h
Normal file
45
tests/qtest/libqos/virtio-blk.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
266
tests/qtest/libqos/virtio-mmio.c
Normal file
266
tests/qtest/libqos/virtio-mmio.c
Normal file
|
@ -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);
|
51
tests/qtest/libqos/virtio-mmio.h
Normal file
51
tests/qtest/libqos/virtio-mmio.h
Normal file
|
@ -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
|
197
tests/qtest/libqos/virtio-net.c
Normal file
197
tests/qtest/libqos/virtio-net.c
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
46
tests/qtest/libqos/virtio-net.h
Normal file
46
tests/qtest/libqos/virtio-net.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
443
tests/qtest/libqos/virtio-pci-modern.c
Normal file
443
tests/qtest/libqos/virtio-pci-modern.c
Normal file
|
@ -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;
|
||||
}
|
17
tests/qtest/libqos/virtio-pci-modern.h
Normal file
17
tests/qtest/libqos/virtio-pci-modern.h
Normal file
|
@ -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 */
|
435
tests/qtest/libqos/virtio-pci.c
Normal file
435
tests/qtest/libqos/virtio-pci.c
Normal file
|
@ -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;
|
||||
}
|
86
tests/qtest/libqos/virtio-pci.h
Normal file
86
tests/qtest/libqos/virtio-pci.h
Normal file
|
@ -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
|
111
tests/qtest/libqos/virtio-rng.c
Normal file
111
tests/qtest/libqos/virtio-rng.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
44
tests/qtest/libqos/virtio-rng.h
Normal file
44
tests/qtest/libqos/virtio-rng.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
119
tests/qtest/libqos/virtio-scsi.c
Normal file
119
tests/qtest/libqos/virtio-scsi.c
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
44
tests/qtest/libqos/virtio-scsi.h
Normal file
44
tests/qtest/libqos/virtio-scsi.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
111
tests/qtest/libqos/virtio-serial.c
Normal file
111
tests/qtest/libqos/virtio-serial.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
44
tests/qtest/libqos/virtio-serial.h
Normal file
44
tests/qtest/libqos/virtio-serial.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#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
|
450
tests/qtest/libqos/virtio.c
Normal file
450
tests/qtest/libqos/virtio.c
Normal file
|
@ -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;
|
||||
}
|
155
tests/qtest/libqos/virtio.h
Normal file
155
tests/qtest/libqos/virtio.h
Normal file
|
@ -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
|
115
tests/qtest/libqos/x86_64_pc-machine.c
Normal file
115
tests/qtest/libqos/x86_64_pc-machine.c
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* libqos driver framework
|
||||
*
|
||||
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "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);
|
Loading…
Add table
Add a link
Reference in a new issue