summary refs log tree commit diff stats
path: root/tests/libqos
diff options
context:
space:
mode:
Diffstat (limited to 'tests/libqos')
-rw-r--r--tests/libqos/aarch64-xlnx-zcu102-machine.c94
-rw-r--r--tests/libqos/ahci.c2
-rw-r--r--tests/libqos/arm-raspi2-machine.c91
-rw-r--r--tests/libqos/arm-sabrelite-machine.c91
-rw-r--r--tests/libqos/arm-smdkc210-machine.c91
-rw-r--r--tests/libqos/arm-virt-machine.c90
-rw-r--r--tests/libqos/arm-xilinx-zynq-a9-machine.c94
-rw-r--r--tests/libqos/e1000e.c260
-rw-r--r--tests/libqos/e1000e.h53
-rw-r--r--tests/libqos/libqos-pc.c5
-rw-r--r--tests/libqos/libqos-spapr.c5
-rw-r--r--tests/libqos/libqos.c13
-rw-r--r--tests/libqos/libqos.h13
-rw-r--r--tests/libqos/malloc-generic.c39
-rw-r--r--tests/libqos/malloc-generic.h21
-rw-r--r--tests/libqos/malloc-pc.c22
-rw-r--r--tests/libqos/malloc-pc.h4
-rw-r--r--tests/libqos/malloc-spapr.c19
-rw-r--r--tests/libqos/malloc-spapr.h4
-rw-r--r--tests/libqos/malloc.c42
-rw-r--r--tests/libqos/malloc.h21
-rw-r--r--tests/libqos/pci-pc.c86
-rw-r--r--tests/libqos/pci-pc.h29
-rw-r--r--tests/libqos/pci-spapr.c119
-rw-r--r--tests/libqos/pci-spapr.h26
-rw-r--r--tests/libqos/pci.c46
-rw-r--r--tests/libqos/pci.h16
-rw-r--r--tests/libqos/ppc64_pseries-machine.c111
-rw-r--r--tests/libqos/qgraph.c753
-rw-r--r--tests/libqos/qgraph.h575
-rw-r--r--tests/libqos/qgraph_internal.h257
-rw-r--r--tests/libqos/sdhci.c163
-rw-r--r--tests/libqos/sdhci.h70
-rw-r--r--tests/libqos/tpci200.c65
-rw-r--r--tests/libqos/virtio-9p.c173
-rw-r--r--tests/libqos/virtio-9p.h42
-rw-r--r--tests/libqos/virtio-balloon.c113
-rw-r--r--tests/libqos/virtio-balloon.h39
-rw-r--r--tests/libqos/virtio-blk.c124
-rw-r--r--tests/libqos/virtio-blk.h40
-rw-r--r--tests/libqos/virtio-mmio.c116
-rw-r--r--tests/libqos/virtio-mmio.h6
-rw-r--r--tests/libqos/virtio-net.c195
-rw-r--r--tests/libqos/virtio-net.h41
-rw-r--r--tests/libqos/virtio-pci.c187
-rw-r--r--tests/libqos/virtio-pci.h18
-rw-r--r--tests/libqos/virtio-rng.c110
-rw-r--r--tests/libqos/virtio-rng.h39
-rw-r--r--tests/libqos/virtio-scsi.c117
-rw-r--r--tests/libqos/virtio-scsi.h39
-rw-r--r--tests/libqos/virtio-serial.c110
-rw-r--r--tests/libqos/virtio-serial.h39
-rw-r--r--tests/libqos/virtio.c24
-rw-r--r--tests/libqos/virtio.h11
-rw-r--r--tests/libqos/x86_64_pc-machine.c114
55 files changed, 4658 insertions, 429 deletions
diff --git a/tests/libqos/aarch64-xlnx-zcu102-machine.c b/tests/libqos/aarch64-xlnx-zcu102-machine.c
new file mode 100644
index 0000000000..6fff040b37
--- /dev/null
+++ b/tests/libqos/aarch64-xlnx-zcu102-machine.c
@@ -0,0 +1,94 @@
+/*
+ * 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/malloc.h"
+#include "libqos/qgraph.h"
+#include "sdhci.h"
+
+typedef struct QXlnxZCU102Machine QXlnxZCU102Machine;
+
+struct QXlnxZCU102Machine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QSDHCI_MemoryMapped sdhci;
+};
+
+#define ARM_PAGE_SIZE          4096
+#define XLNX_ZCU102_RAM_ADDR   0
+#define XLNX_ZCU102_RAM_SIZE   0x20000000
+
+static void *xlnx_zcu102_get_driver(void *object, const char *interface)
+{
+    QXlnxZCU102Machine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in aarch64/xlnx-zcu102\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *xlnx_zcu102_get_device(void *obj, const char *device)
+{
+    QXlnxZCU102Machine *machine = obj;
+    if (!g_strcmp0(device, "generic-sdhci")) {
+        return &machine->sdhci.obj;
+    }
+
+    fprintf(stderr, "%s not present in aarch64/xlnx-zcu102\n", device);
+    g_assert_not_reached();
+}
+
+static void xlnx_zcu102_destructor(QOSGraphObject *obj)
+{
+    QXlnxZCU102Machine *machine = (QXlnxZCU102Machine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *qos_create_machine_aarch64_xlnx_zcu102(QTestState *qts)
+{
+    QXlnxZCU102Machine *machine = g_new0(QXlnxZCU102Machine, 1);
+
+    alloc_init(&machine->alloc, 0,
+               XLNX_ZCU102_RAM_ADDR + (1 << 20),
+               XLNX_ZCU102_RAM_ADDR + XLNX_ZCU102_RAM_SIZE,
+               ARM_PAGE_SIZE);
+
+    machine->obj.get_device = xlnx_zcu102_get_device;
+    machine->obj.get_driver = xlnx_zcu102_get_driver;
+    machine->obj.destructor = xlnx_zcu102_destructor;
+    /* Datasheet: UG1085 (v1.7) */
+    qos_init_sdhci_mm(&machine->sdhci, qts, 0xff160000, &(QSDHCIProperties) {
+        .version = 3,
+        .baseclock = 0,
+        .capab.sdma = true,
+        .capab.reg = 0x280737ec6481
+    });
+    return &machine->obj;
+}
+
+static void xlnx_zcu102_register_nodes(void)
+{
+    qos_node_create_machine("aarch64/xlnx-zcu102",
+                            qos_create_machine_aarch64_xlnx_zcu102);
+    qos_node_contains("aarch64/xlnx-zcu102", "generic-sdhci", NULL);
+}
+
+libqos_init(xlnx_zcu102_register_nodes);
diff --git a/tests/libqos/ahci.c b/tests/libqos/ahci.c
index 63fbc9e3c9..cc1b08eabe 100644
--- a/tests/libqos/ahci.c
+++ b/tests/libqos/ahci.c
@@ -130,7 +130,7 @@ QPCIDevice *get_ahci_device(QTestState *qts, uint32_t *fingerprint)
     uint32_t ahci_fingerprint;
     QPCIBus *pcibus;
 
-    pcibus = qpci_init_pc(qts, NULL);
+    pcibus = qpci_new_pc(qts, NULL);
 
     /* Find the AHCI PCI device and verify it's the right one. */
     ahci = qpci_device_find(pcibus, QPCI_DEVFN(0x1F, 0x02));
diff --git a/tests/libqos/arm-raspi2-machine.c b/tests/libqos/arm-raspi2-machine.c
new file mode 100644
index 0000000000..3aff670f76
--- /dev/null
+++ b/tests/libqos/arm-raspi2-machine.c
@@ -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 "libqos/malloc.h"
+#include "libqos/qgraph.h"
+#include "sdhci.h"
+
+#define ARM_PAGE_SIZE             4096
+#define RASPI2_RAM_ADDR           0
+#define RASPI2_RAM_SIZE           0x20000000
+
+typedef struct QRaspi2Machine QRaspi2Machine;
+
+struct QRaspi2Machine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QSDHCI_MemoryMapped sdhci;
+};
+
+static void *raspi2_get_driver(void *object, const char *interface)
+{
+    QRaspi2Machine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in arm/raspi2\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *raspi2_get_device(void *obj, const char *device)
+{
+    QRaspi2Machine *machine = obj;
+    if (!g_strcmp0(device, "generic-sdhci")) {
+        return &machine->sdhci.obj;
+    }
+
+    fprintf(stderr, "%s not present in arm/raspi2\n", device);
+    g_assert_not_reached();
+}
+
+static void raspi2_destructor(QOSGraphObject *obj)
+{
+    QRaspi2Machine *machine = (QRaspi2Machine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *qos_create_machine_arm_raspi2(QTestState *qts)
+{
+    QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1);
+
+    alloc_init(&machine->alloc, 0,
+               RASPI2_RAM_ADDR + (1 << 20),
+               RASPI2_RAM_ADDR + RASPI2_RAM_SIZE,
+               ARM_PAGE_SIZE);
+    machine->obj.get_device = raspi2_get_device;
+    machine->obj.get_driver = raspi2_get_driver;
+    machine->obj.destructor = raspi2_destructor;
+    qos_init_sdhci_mm(&machine->sdhci, qts, 0x3f300000, &(QSDHCIProperties) {
+        .version = 3,
+        .baseclock = 52,
+        .capab.sdma = false,
+        .capab.reg = 0x052134b4
+    });
+    return &machine->obj;
+}
+
+static void raspi2_register_nodes(void)
+{
+    qos_node_create_machine("arm/raspi2", qos_create_machine_arm_raspi2);
+    qos_node_contains("arm/raspi2", "generic-sdhci", NULL);
+}
+
+libqos_init(raspi2_register_nodes);
diff --git a/tests/libqos/arm-sabrelite-machine.c b/tests/libqos/arm-sabrelite-machine.c
new file mode 100644
index 0000000000..c4128d8686
--- /dev/null
+++ b/tests/libqos/arm-sabrelite-machine.c
@@ -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 "libqos/malloc.h"
+#include "libqos/qgraph.h"
+#include "sdhci.h"
+
+#define ARM_PAGE_SIZE            4096
+#define SABRELITE_RAM_START      0x10000000
+#define SABRELITE_RAM_END        0x30000000
+
+typedef struct QSabreliteMachine QSabreliteMachine;
+
+struct QSabreliteMachine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QSDHCI_MemoryMapped sdhci;
+};
+
+static void *sabrelite_get_driver(void *object, const char *interface)
+{
+    QSabreliteMachine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in arm/sabrelite\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *sabrelite_get_device(void *obj, const char *device)
+{
+    QSabreliteMachine *machine = obj;
+    if (!g_strcmp0(device, "generic-sdhci")) {
+        return &machine->sdhci.obj;
+    }
+
+    fprintf(stderr, "%s not present in arm/sabrelite\n", device);
+    g_assert_not_reached();
+}
+
+static void sabrelite_destructor(QOSGraphObject *obj)
+{
+    QSabreliteMachine *machine = (QSabreliteMachine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *qos_create_machine_arm_sabrelite(QTestState *qts)
+{
+    QSabreliteMachine *machine = g_new0(QSabreliteMachine, 1);
+
+    alloc_init(&machine->alloc, 0,
+               SABRELITE_RAM_START,
+               SABRELITE_RAM_END,
+               ARM_PAGE_SIZE);
+    machine->obj.get_device = sabrelite_get_device;
+    machine->obj.get_driver = sabrelite_get_driver;
+    machine->obj.destructor = sabrelite_destructor;
+    qos_init_sdhci_mm(&machine->sdhci, qts, 0x02190000, &(QSDHCIProperties) {
+        .version = 3,
+        .baseclock = 0,
+        .capab.sdma = true,
+        .capab.reg = 0x057834b4,
+    });
+    return &machine->obj;
+}
+
+static void sabrelite_register_nodes(void)
+{
+    qos_node_create_machine("arm/sabrelite", qos_create_machine_arm_sabrelite);
+    qos_node_contains("arm/sabrelite", "generic-sdhci", NULL);
+}
+
+libqos_init(sabrelite_register_nodes);
diff --git a/tests/libqos/arm-smdkc210-machine.c b/tests/libqos/arm-smdkc210-machine.c
new file mode 100644
index 0000000000..1fb9dfc0cb
--- /dev/null
+++ b/tests/libqos/arm-smdkc210-machine.c
@@ -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 "libqos/malloc.h"
+#include "libqos/qgraph.h"
+#include "sdhci.h"
+
+#define ARM_PAGE_SIZE             4096
+#define SMDKC210_RAM_ADDR         0x40000000ull
+#define SMDKC210_RAM_SIZE         0x40000000ull
+
+typedef struct QSmdkc210Machine QSmdkc210Machine;
+
+struct QSmdkc210Machine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QSDHCI_MemoryMapped sdhci;
+};
+
+static void *smdkc210_get_driver(void *object, const char *interface)
+{
+    QSmdkc210Machine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in arm/smdkc210\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *smdkc210_get_device(void *obj, const char *device)
+{
+    QSmdkc210Machine *machine = obj;
+    if (!g_strcmp0(device, "generic-sdhci")) {
+        return &machine->sdhci.obj;
+    }
+
+    fprintf(stderr, "%s not present in arm/smdkc210\n", device);
+    g_assert_not_reached();
+}
+
+static void smdkc210_destructor(QOSGraphObject *obj)
+{
+    QSmdkc210Machine *machine = (QSmdkc210Machine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *qos_create_machine_arm_smdkc210(QTestState *qts)
+{
+    QSmdkc210Machine *machine = g_new0(QSmdkc210Machine, 1);
+
+    alloc_init(&machine->alloc, 0,
+               SMDKC210_RAM_ADDR,
+               SMDKC210_RAM_ADDR + SMDKC210_RAM_SIZE,
+               ARM_PAGE_SIZE);
+    machine->obj.get_device = smdkc210_get_device;
+    machine->obj.get_driver = smdkc210_get_driver;
+    machine->obj.destructor = smdkc210_destructor;
+    qos_init_sdhci_mm(&machine->sdhci, qts, 0x12510000, &(QSDHCIProperties) {
+        .version = 2,
+        .baseclock = 0,
+        .capab.sdma = true,
+        .capab.reg = 0x5e80080,
+    });
+    return &machine->obj;
+}
+
+static void smdkc210_register_nodes(void)
+{
+    qos_node_create_machine("arm/smdkc210", qos_create_machine_arm_smdkc210);
+    qos_node_contains("arm/smdkc210", "generic-sdhci", NULL);
+}
+
+libqos_init(smdkc210_register_nodes);
diff --git a/tests/libqos/arm-virt-machine.c b/tests/libqos/arm-virt-machine.c
new file mode 100644
index 0000000000..2abc431ecf
--- /dev/null
+++ b/tests/libqos/arm-virt-machine.c
@@ -0,0 +1,90 @@
+/*
+ * 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/malloc.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-mmio.h"
+
+#define ARM_PAGE_SIZE               4096
+#define VIRTIO_MMIO_BASE_ADDR       0x0A003E00
+#define ARM_VIRT_RAM_ADDR           0x40000000
+#define ARM_VIRT_RAM_SIZE           0x20000000
+#define VIRTIO_MMIO_SIZE            0x00000200
+
+typedef struct QVirtMachine QVirtMachine;
+
+struct QVirtMachine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QVirtioMMIODevice virtio_mmio;
+};
+
+static void virt_destructor(QOSGraphObject *obj)
+{
+    QVirtMachine *machine = (QVirtMachine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *virt_get_driver(void *object, const char *interface)
+{
+    QVirtMachine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in arm/virtio\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *virt_get_device(void *obj, const char *device)
+{
+    QVirtMachine *machine = obj;
+    if (!g_strcmp0(device, "virtio-mmio")) {
+        return &machine->virtio_mmio.obj;
+    }
+
+    fprintf(stderr, "%s not present in arm/virtio\n", device);
+    g_assert_not_reached();
+}
+
+static void *qos_create_machine_arm_virt(QTestState *qts)
+{
+    QVirtMachine *machine = g_new0(QVirtMachine, 1);
+
+    alloc_init(&machine->alloc, 0,
+               ARM_VIRT_RAM_ADDR,
+               ARM_VIRT_RAM_ADDR + ARM_VIRT_RAM_SIZE,
+               ARM_PAGE_SIZE);
+    qvirtio_mmio_init_device(&machine->virtio_mmio, qts, VIRTIO_MMIO_BASE_ADDR,
+                             VIRTIO_MMIO_SIZE);
+
+    machine->obj.get_device = virt_get_device;
+    machine->obj.get_driver = virt_get_driver;
+    machine->obj.destructor = virt_destructor;
+    return machine;
+}
+
+static void virtio_mmio_register_nodes(void)
+{
+    qos_node_create_machine("arm/virt", qos_create_machine_arm_virt);
+    qos_node_contains("arm/virt", "virtio-mmio", NULL);
+}
+
+libqos_init(virtio_mmio_register_nodes);
diff --git a/tests/libqos/arm-xilinx-zynq-a9-machine.c b/tests/libqos/arm-xilinx-zynq-a9-machine.c
new file mode 100644
index 0000000000..4e199fcd48
--- /dev/null
+++ b/tests/libqos/arm-xilinx-zynq-a9-machine.c
@@ -0,0 +1,94 @@
+/*
+ * 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/malloc.h"
+#include "libqos/qgraph.h"
+#include "sdhci.h"
+
+typedef struct QXilinxZynqA9Machine QXilinxZynqA9Machine;
+
+struct QXilinxZynqA9Machine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QSDHCI_MemoryMapped sdhci;
+};
+
+#define ARM_PAGE_SIZE             4096
+#define XILINX_ZYNQ_A9_RAM_ADDR   0
+#define XILINX_ZYNQ_A9_RAM_SIZE   0x20000000
+
+static void *xilinx_zynq_a9_get_driver(void *object, const char *interface)
+{
+    QXilinxZynqA9Machine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in arm/xilinx-zynq-a9\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *xilinx_zynq_a9_get_device(void *obj, const char *device)
+{
+    QXilinxZynqA9Machine *machine = obj;
+    if (!g_strcmp0(device, "generic-sdhci")) {
+        return &machine->sdhci.obj;
+    }
+
+    fprintf(stderr, "%s not present in arm/xilinx-zynq-a9\n", device);
+    g_assert_not_reached();
+}
+
+static void xilinx_zynq_a9_destructor(QOSGraphObject *obj)
+{
+    QXilinxZynqA9Machine *machine = (QXilinxZynqA9Machine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *qos_create_machine_arm_xilinx_zynq_a9(QTestState *qts)
+{
+    QXilinxZynqA9Machine *machine = g_new0(QXilinxZynqA9Machine, 1);
+
+    alloc_init(&machine->alloc, 0,
+               XILINX_ZYNQ_A9_RAM_ADDR + (1 << 20),
+               XILINX_ZYNQ_A9_RAM_ADDR + XILINX_ZYNQ_A9_RAM_SIZE,
+               ARM_PAGE_SIZE);
+
+    machine->obj.get_device = xilinx_zynq_a9_get_device;
+    machine->obj.get_driver = xilinx_zynq_a9_get_driver;
+    machine->obj.destructor = xilinx_zynq_a9_destructor;
+    /* Datasheet: UG585 (v1.12.1) */
+    qos_init_sdhci_mm(&machine->sdhci, qts, 0xe0100000, &(QSDHCIProperties) {
+        .version = 2,
+        .baseclock = 0,
+        .capab.sdma = true,
+        .capab.reg = 0x69ec0080,
+    });
+    return &machine->obj;
+}
+
+static void xilinx_zynq_a9_register_nodes(void)
+{
+    qos_node_create_machine("arm/xilinx-zynq-a9",
+                            qos_create_machine_arm_xilinx_zynq_a9);
+    qos_node_contains("arm/xilinx-zynq-a9", "generic-sdhci", NULL);
+}
+
+libqos_init(xilinx_zynq_a9_register_nodes);
diff --git a/tests/libqos/e1000e.c b/tests/libqos/e1000e.c
new file mode 100644
index 0000000000..54d3898899
--- /dev/null
+++ b/tests/libqos/e1000e.c
@@ -0,0 +1,260 @@
+/*
+ * 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-common.h"
+#include "libqos/pci-pc.h"
+#include "qemu/sockets.h"
+#include "qemu/iov.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)
+{
+    uint32_t tail = e1000e_macreg_read(d, E1000E_TDT);
+    uint32_t len = e1000e_macreg_read(d, E1000E_TDLEN) / E1000E_TXD_LEN;
+
+    memwrite(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 */
+    memread(d->tx_ring + tail * E1000E_TXD_LEN, descr, E1000E_TXD_LEN);
+}
+
+void e1000e_rx_ring_push(QE1000E *d, void *descr)
+{
+    uint32_t tail = e1000e_macreg_read(d, E1000E_RDT);
+    uint32_t len = e1000e_macreg_read(d, E1000E_RDLEN) / E1000E_RXD_LEN;
+
+    memwrite(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 */
+    memread(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;
+        }
+        clock_step(10000);
+    } while (g_get_monotonic_time() < end_time);
+
+    g_error("Timeout expired");
+}
+
+static void e1000e_pci_destructor(QOSGraphObject *obj)
+{
+    QE1000E_PCI *epci = (QE1000E_PCI *) obj;
+    qpci_iounmap(&epci->pci_dev, epci->mac_regs);
+    qpci_msix_disable(&epci->pci_dev);
+}
+
+static void e1000e_pci_start_hw(QOSGraphObject *obj)
+{
+    QE1000E_PCI *d = (QE1000E_PCI *) obj;
+    uint32_t val;
+
+    /* Enable the device */
+    qpci_device_enable(&d->pci_dev);
+
+    /* Reset the device */
+    val = e1000e_macreg_read(&d->e1000e, E1000E_CTRL);
+    e1000e_macreg_write(&d->e1000e, E1000E_CTRL, val | E1000E_CTRL_RESET);
+
+    /* Enable and configure MSI-X */
+    qpci_msix_enable(&d->pci_dev);
+    e1000e_macreg_write(&d->e1000e, E1000E_IVAR, E1000E_IVAR_TEST_CFG);
+
+    /* Check the device status - link and speed */
+    val = e1000e_macreg_read(&d->e1000e, E1000E_STATUS);
+    g_assert_cmphex(val & (E1000E_STATUS_LU | E1000E_STATUS_ASDV1000),
+        ==, E1000E_STATUS_LU | E1000E_STATUS_ASDV1000);
+
+    /* Initialize TX/RX logic */
+    e1000e_macreg_write(&d->e1000e, E1000E_RCTL, 0);
+    e1000e_macreg_write(&d->e1000e, E1000E_TCTL, 0);
+
+    /* Notify the device that the driver is ready */
+    val = e1000e_macreg_read(&d->e1000e, E1000E_CTRL_EXT);
+    e1000e_macreg_write(&d->e1000e, E1000E_CTRL_EXT,
+        val | E1000E_CTRL_EXT_DRV_LOAD | E1000E_CTRL_EXT_TXLSFLOW);
+
+    e1000e_macreg_write(&d->e1000e, E1000E_TDBAL,
+                           (uint32_t) d->e1000e.tx_ring);
+    e1000e_macreg_write(&d->e1000e, E1000E_TDBAH,
+                           (uint32_t) (d->e1000e.tx_ring >> 32));
+    e1000e_macreg_write(&d->e1000e, E1000E_TDLEN, E1000E_RING_LEN);
+    e1000e_macreg_write(&d->e1000e, E1000E_TDT, 0);
+    e1000e_macreg_write(&d->e1000e, E1000E_TDH, 0);
+
+    /* Enable transmit */
+    e1000e_macreg_write(&d->e1000e, E1000E_TCTL, E1000E_TCTL_EN);
+    e1000e_macreg_write(&d->e1000e, E1000E_RDBAL,
+                           (uint32_t)d->e1000e.rx_ring);
+    e1000e_macreg_write(&d->e1000e, E1000E_RDBAH,
+                           (uint32_t)(d->e1000e.rx_ring >> 32));
+    e1000e_macreg_write(&d->e1000e, E1000E_RDLEN, E1000E_RING_LEN);
+    e1000e_macreg_write(&d->e1000e, E1000E_RDT, 0);
+    e1000e_macreg_write(&d->e1000e, E1000E_RDH, 0);
+
+    /* Enable receive */
+    e1000e_macreg_write(&d->e1000e, E1000E_RFCTL, E1000E_RFCTL_EXTEN);
+    e1000e_macreg_write(&d->e1000e, E1000E_RCTL, E1000E_RCTL_EN  |
+                                        E1000E_RCTL_UPE |
+                                        E1000E_RCTL_MPE);
+
+    /* Enable all interrupts */
+    e1000e_macreg_write(&d->e1000e, E1000E_IMS, 0xFFFFFFFF);
+
+}
+
+static void *e1000e_pci_get_driver(void *obj, const char *interface)
+{
+    QE1000E_PCI *epci = obj;
+    if (!g_strcmp0(interface, "e1000e-if")) {
+        return &epci->e1000e;
+    }
+
+    /* implicit contains */
+    if (!g_strcmp0(interface, "pci-device")) {
+        return &epci->pci_dev;
+    }
+
+    fprintf(stderr, "%s not present in e1000e\n", interface);
+    g_assert_not_reached();
+}
+
+static void *e1000e_pci_create(void *pci_bus, QGuestAllocator *alloc,
+                               void *addr)
+{
+    QE1000E_PCI *d = g_new0(QE1000E_PCI, 1);
+    QPCIBus *bus = pci_bus;
+    QPCIAddress *address = addr;
+
+    qpci_device_foreach(bus, address->vendor_id, address->device_id,
+                        e1000e_foreach_callback, &d->pci_dev);
+
+    /* Map BAR0 (mac registers) */
+    d->mac_regs = qpci_iomap(&d->pci_dev, 0, NULL);
+
+    /* Allocate and setup TX ring */
+    d->e1000e.tx_ring = guest_alloc(alloc, E1000E_RING_LEN);
+    g_assert(d->e1000e.tx_ring != 0);
+
+    /* Allocate and setup RX ring */
+    d->e1000e.rx_ring = guest_alloc(alloc, E1000E_RING_LEN);
+    g_assert(d->e1000e.rx_ring != 0);
+
+    d->obj.get_driver = e1000e_pci_get_driver;
+    d->obj.start_hw = e1000e_pci_start_hw;
+    d->obj.destructor = e1000e_pci_destructor;
+
+    return &d->obj;
+}
+
+static void e1000e_register_nodes(void)
+{
+    QPCIAddress addr = {
+        .vendor_id = 0x8086,
+        .device_id = 0x10D3,
+    };
+
+    /* FIXME: every test using this node needs to setup a -netdev socket,id=hs0
+     * otherwise QEMU is not going to start */
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "netdev=hs0",
+    };
+    add_qpci_address(&opts, &addr);
+
+    qos_node_create_driver("e1000e", e1000e_pci_create);
+    qos_node_consumes("e1000e", "pci-bus", &opts);
+}
+
+libqos_init(e1000e_register_nodes);
diff --git a/tests/libqos/e1000e.h b/tests/libqos/e1000e.h
new file mode 100644
index 0000000000..9d37094f43
--- /dev/null
+++ b/tests/libqos/e1000e.h
@@ -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
+#define QGRAPH_E1000E
+
+#include "libqos/qgraph.h"
+#include "pci.h"
+
+#define E1000E_RX0_MSG_ID           (0)
+#define E1000E_TX0_MSG_ID           (1)
+#define E1000E_OTHER_MSG_ID         (2)
+
+#define E1000E_TDLEN    (0x3808)
+#define E1000E_TDT      (0x3818)
+#define E1000E_RDLEN    (0x2808)
+#define E1000E_RDT      (0x2818)
+
+typedef struct QE1000E QE1000E;
+typedef struct QE1000E_PCI QE1000E_PCI;
+
+struct QE1000E {
+    uint64_t tx_ring;
+    uint64_t rx_ring;
+};
+
+struct QE1000E_PCI {
+    QOSGraphObject obj;
+    QPCIDevice pci_dev;
+    QPCIBar mac_regs;
+    QE1000E e1000e;
+};
+
+void e1000e_wait_isr(QE1000E *d, uint16_t msg_id);
+void e1000e_tx_ring_push(QE1000E *d, void *descr);
+void e1000e_rx_ring_push(QE1000E *d, void *descr);
+
+#endif
diff --git a/tests/libqos/libqos-pc.c b/tests/libqos/libqos-pc.c
index a9c1aceaa7..d04abc548b 100644
--- a/tests/libqos/libqos-pc.c
+++ b/tests/libqos/libqos-pc.c
@@ -4,9 +4,8 @@
 #include "libqos/pci-pc.h"
 
 static QOSOps qos_ops = {
-    .init_allocator = pc_alloc_init_flags,
-    .uninit_allocator = pc_alloc_uninit,
-    .qpci_init = qpci_init_pc,
+    .alloc_init = pc_alloc_init,
+    .qpci_new = qpci_new_pc,
     .qpci_free = qpci_free_pc,
     .shutdown = qtest_pc_shutdown,
 };
diff --git a/tests/libqos/libqos-spapr.c b/tests/libqos/libqos-spapr.c
index a37791e5d0..8766d543ce 100644
--- a/tests/libqos/libqos-spapr.c
+++ b/tests/libqos/libqos-spapr.c
@@ -4,9 +4,8 @@
 #include "libqos/pci-spapr.h"
 
 static QOSOps qos_ops = {
-    .init_allocator = spapr_alloc_init_flags,
-    .uninit_allocator = spapr_alloc_uninit,
-    .qpci_init = qpci_init_spapr,
+    .alloc_init = spapr_alloc_init,
+    .qpci_new = qpci_new_spapr,
     .qpci_free = qpci_free_spapr,
     .shutdown = qtest_spapr_shutdown,
 };
diff --git a/tests/libqos/libqos.c b/tests/libqos/libqos.c
index c514187344..636a111a6f 100644
--- a/tests/libqos/libqos.c
+++ b/tests/libqos/libqos.c
@@ -24,8 +24,8 @@ QOSState *qtest_vboot(QOSOps *ops, const char *cmdline_fmt, va_list ap)
     qs->qts = qtest_init(cmdline);
     qs->ops = ops;
     if (ops) {
-        qs->alloc = ops->init_allocator(qs->qts, ALLOC_NO_FLAGS);
-        qs->pcibus = ops->qpci_init(qs->qts, qs->alloc);
+        ops->alloc_init(&qs->alloc, qs->qts, ALLOC_NO_FLAGS);
+        qs->pcibus = ops->qpci_new(qs->qts, &qs->alloc);
     }
 
     g_free(cmdline);
@@ -58,11 +58,8 @@ void qtest_common_shutdown(QOSState *qs)
             qs->ops->qpci_free(qs->pcibus);
             qs->pcibus = NULL;
         }
-        if (qs->alloc && qs->ops->uninit_allocator) {
-            qs->ops->uninit_allocator(qs->alloc);
-            qs->alloc = NULL;
-        }
     }
+    alloc_destroy(&qs->alloc);
     qtest_quit(qs->qts);
     g_free(qs);
 }
@@ -116,7 +113,7 @@ void migrate(QOSState *from, QOSState *to, const char *uri)
 
     /* If we were running, we can wait for an event. */
     if (running) {
-        migrate_allocator(from->alloc, to->alloc);
+        migrate_allocator(&from->alloc, &to->alloc);
         set_context(to);
         qtest_qmp_eventwait(to->qts, "RESUME");
         return;
@@ -146,7 +143,7 @@ void migrate(QOSState *from, QOSState *to, const char *uri)
         g_assert_not_reached();
     }
 
-    migrate_allocator(from->alloc, to->alloc);
+    migrate_allocator(&from->alloc, &to->alloc);
     set_context(to);
 }
 
diff --git a/tests/libqos/libqos.h b/tests/libqos/libqos.h
index 07d4b93d1d..149b0be8bc 100644
--- a/tests/libqos/libqos.h
+++ b/tests/libqos/libqos.h
@@ -3,21 +3,20 @@
 
 #include "libqtest.h"
 #include "libqos/pci.h"
-#include "libqos/malloc-pc.h"
+#include "libqos/malloc.h"
 
 typedef struct QOSState QOSState;
 
 typedef struct QOSOps {
-    QGuestAllocator *(*init_allocator)(QTestState *qts, QAllocOpts);
-    void (*uninit_allocator)(QGuestAllocator *);
-    QPCIBus *(*qpci_init)(QTestState *qts, QGuestAllocator *alloc);
+    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;
+    QGuestAllocator alloc;
     QPCIBus *pcibus;
     QOSOps *ops;
 };
@@ -36,12 +35,12 @@ 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);
+    return guest_alloc(&q->alloc, bytes);
 }
 
 static inline void qfree(QOSState *q, uint64_t addr)
 {
-    guest_free(q->alloc, addr);
+    guest_free(&q->alloc, addr);
 }
 
 #endif
diff --git a/tests/libqos/malloc-generic.c b/tests/libqos/malloc-generic.c
deleted file mode 100644
index 33ce90b925..0000000000
--- a/tests/libqos/malloc-generic.c
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Basic libqos generic malloc support
- *
- * 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 "libqos/malloc-generic.h"
-#include "libqos/malloc.h"
-
-/*
- * Mostly for valgrind happiness, but it does offer
- * a chokepoint for debugging guest memory leaks, too.
- */
-void generic_alloc_uninit(QGuestAllocator *allocator)
-{
-    alloc_uninit(allocator);
-}
-
-QGuestAllocator *generic_alloc_init_flags(uint64_t base_addr, uint64_t size,
-                                        uint32_t page_size, QAllocOpts flags)
-{
-    QGuestAllocator *s;
-    uint64_t start = base_addr + (1 << 20); /* Start at 1MB */
-
-    s = alloc_init_flags(flags, start, start + size);
-    alloc_set_page_size(s, page_size);
-
-    return s;
-}
-
-inline QGuestAllocator *generic_alloc_init(uint64_t base_addr, uint64_t size,
-                                                            uint32_t page_size)
-{
-    return generic_alloc_init_flags(base_addr, size, page_size, ALLOC_NO_FLAGS);
-}
diff --git a/tests/libqos/malloc-generic.h b/tests/libqos/malloc-generic.h
deleted file mode 100644
index 90104ecec9..0000000000
--- a/tests/libqos/malloc-generic.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Basic libqos generic malloc support
- *
- * 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_MALLOC_GENERIC_H
-#define LIBQOS_MALLOC_GENERIC_H
-
-#include "libqos/malloc.h"
-
-QGuestAllocator *generic_alloc_init(uint64_t base_addr, uint64_t size,
-                                                            uint32_t page_size);
-QGuestAllocator *generic_alloc_init_flags(uint64_t base_addr, uint64_t size,
-                                        uint32_t page_size, QAllocOpts flags);
-void generic_alloc_uninit(QGuestAllocator *allocator);
-
-#endif
diff --git a/tests/libqos/malloc-pc.c b/tests/libqos/malloc-pc.c
index b83cb8f0af..949a99361d 100644
--- a/tests/libqos/malloc-pc.c
+++ b/tests/libqos/malloc-pc.c
@@ -20,32 +20,14 @@
 
 #define PAGE_SIZE (4096)
 
-/*
- * Mostly for valgrind happiness, but it does offer
- * a chokepoint for debugging guest memory leaks, too.
- */
-void pc_alloc_uninit(QGuestAllocator *allocator)
-{
-    alloc_uninit(allocator);
-}
-
-QGuestAllocator *pc_alloc_init_flags(QTestState *qts, QAllocOpts flags)
+void pc_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags)
 {
-    QGuestAllocator *s;
     uint64_t ram_size;
     QFWCFG *fw_cfg = pc_fw_cfg_init(qts);
 
     ram_size = qfw_cfg_get_u64(fw_cfg, FW_CFG_RAM_SIZE);
-    s = alloc_init_flags(flags, 1 << 20, MIN(ram_size, 0xE0000000));
-    alloc_set_page_size(s, PAGE_SIZE);
+    alloc_init(s, flags, 1 << 20, MIN(ram_size, 0xE0000000), PAGE_SIZE);
 
     /* clean-up */
     g_free(fw_cfg);
-
-    return s;
-}
-
-inline QGuestAllocator *pc_alloc_init(QTestState *qts)
-{
-    return pc_alloc_init_flags(qts, ALLOC_NO_FLAGS);
 }
diff --git a/tests/libqos/malloc-pc.h b/tests/libqos/malloc-pc.h
index 10f3da6cf2..21e75ae004 100644
--- a/tests/libqos/malloc-pc.h
+++ b/tests/libqos/malloc-pc.h
@@ -15,8 +15,6 @@
 
 #include "libqos/malloc.h"
 
-QGuestAllocator *pc_alloc_init(QTestState *qts);
-QGuestAllocator *pc_alloc_init_flags(QTestState *qts, QAllocOpts flags);
-void pc_alloc_uninit(QGuestAllocator *allocator);
+void pc_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags);
 
 #endif
diff --git a/tests/libqos/malloc-spapr.c b/tests/libqos/malloc-spapr.c
index 1c359cea6c..2a6b7e3776 100644
--- a/tests/libqos/malloc-spapr.c
+++ b/tests/libqos/malloc-spapr.c
@@ -17,22 +17,7 @@
  */
 #define SPAPR_MIN_SIZE 0x10000000
 
-void spapr_alloc_uninit(QGuestAllocator *allocator)
+void spapr_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags)
 {
-    alloc_uninit(allocator);
-}
-
-QGuestAllocator *spapr_alloc_init_flags(QTestState *qts, QAllocOpts flags)
-{
-    QGuestAllocator *s;
-
-    s = alloc_init_flags(flags, 1 << 20, SPAPR_MIN_SIZE);
-    alloc_set_page_size(s, PAGE_SIZE);
-
-    return s;
-}
-
-QGuestAllocator *spapr_alloc_init(void)
-{
-    return spapr_alloc_init_flags(NULL, ALLOC_NO_FLAGS);
+    alloc_init(s, flags, 1 << 20, SPAPR_MIN_SIZE, PAGE_SIZE);
 }
diff --git a/tests/libqos/malloc-spapr.h b/tests/libqos/malloc-spapr.h
index 52a9346a26..e5fe9bfc4b 100644
--- a/tests/libqos/malloc-spapr.h
+++ b/tests/libqos/malloc-spapr.h
@@ -10,8 +10,6 @@
 
 #include "libqos/malloc.h"
 
-QGuestAllocator *spapr_alloc_init(void);
-QGuestAllocator *spapr_alloc_init_flags(QTestState *qts, QAllocOpts flags);
-void spapr_alloc_uninit(QGuestAllocator *allocator);
+void spapr_alloc_init(QGuestAllocator *s, QTestState *qts, QAllocOpts flags);
 
 #endif
diff --git a/tests/libqos/malloc.c b/tests/libqos/malloc.c
index f7bae47a08..615422a5c4 100644
--- a/tests/libqos/malloc.c
+++ b/tests/libqos/malloc.c
@@ -15,24 +15,12 @@
 #include "qemu-common.h"
 #include "qemu/host-utils.h"
 
-typedef QTAILQ_HEAD(MemList, MemBlock) MemList;
-
 typedef struct MemBlock {
     QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
     uint64_t size;
     uint64_t addr;
 } MemBlock;
 
-struct QGuestAllocator {
-    QAllocOpts opts;
-    uint64_t start;
-    uint64_t end;
-    uint32_t page_size;
-
-    MemList *used;
-    MemList *free;
-};
-
 #define DEFAULT_PAGE_SIZE 4096
 
 static void mlist_delete(MemList *list, MemBlock *node)
@@ -225,7 +213,7 @@ static void mlist_free(QGuestAllocator *s, uint64_t addr)
  * Mostly for valgrind happiness, but it does offer
  * a chokepoint for debugging guest memory leaks, too.
  */
-void alloc_uninit(QGuestAllocator *allocator)
+void alloc_destroy(QGuestAllocator *allocator)
 {
     MemBlock *node;
     MemBlock *tmp;
@@ -261,7 +249,6 @@ void alloc_uninit(QGuestAllocator *allocator)
 
     g_free(allocator->used);
     g_free(allocator->free);
-    g_free(allocator);
 }
 
 uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
@@ -297,11 +284,13 @@ void guest_free(QGuestAllocator *allocator, uint64_t addr)
     }
 }
 
-QGuestAllocator *alloc_init(uint64_t start, uint64_t end)
+void alloc_init(QGuestAllocator *s, QAllocOpts opts,
+                uint64_t start, uint64_t end,
+                size_t page_size)
 {
-    QGuestAllocator *s = g_malloc0(sizeof(*s));
     MemBlock *node;
 
+    s->opts = opts;
     s->start = start;
     s->end = end;
 
@@ -313,26 +302,7 @@ QGuestAllocator *alloc_init(uint64_t start, uint64_t end)
     node = mlist_new(s->start, s->end - s->start);
     QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME);
 
-    s->page_size = DEFAULT_PAGE_SIZE;
-
-    return s;
-}
-
-QGuestAllocator *alloc_init_flags(QAllocOpts opts,
-                                  uint64_t start, uint64_t end)
-{
-    QGuestAllocator *s = alloc_init(start, end);
-    s->opts = opts;
-    return s;
-}
-
-void alloc_set_page_size(QGuestAllocator *allocator, size_t page_size)
-{
-    /* Can't alter the page_size for an allocator in-use */
-    g_assert(QTAILQ_EMPTY(allocator->used));
-
-    g_assert(is_power_of_2(page_size));
-    allocator->page_size = page_size;
+    s->page_size = page_size;
 }
 
 void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts)
diff --git a/tests/libqos/malloc.h b/tests/libqos/malloc.h
index 828fddabdb..4d1a2e2bef 100644
--- a/tests/libqos/malloc.h
+++ b/tests/libqos/malloc.h
@@ -23,19 +23,28 @@ typedef enum {
     ALLOC_PARANOID    = 0x04
 } QAllocOpts;
 
-typedef struct QGuestAllocator QGuestAllocator;
+typedef QTAILQ_HEAD(MemList, MemBlock) MemList;
 
-void alloc_uninit(QGuestAllocator *allocator);
+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);
 
-QGuestAllocator *alloc_init(uint64_t start, uint64_t end);
-QGuestAllocator *alloc_init_flags(QAllocOpts flags,
-                                  uint64_t start, uint64_t end);
-void alloc_set_page_size(QGuestAllocator *allocator, size_t page_size);
 void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts);
 
+void alloc_init(QGuestAllocator *alloc, QAllocOpts flags,
+                uint64_t start, uint64_t end,
+                size_t page_size);
+void alloc_destroy(QGuestAllocator *allocator);
+
 #endif
diff --git a/tests/libqos/pci-pc.c b/tests/libqos/pci-pc.c
index a4fc02b5d8..4ab16facf2 100644
--- a/tests/libqos/pci-pc.c
+++ b/tests/libqos/pci-pc.c
@@ -18,15 +18,9 @@
 
 #include "qemu-common.h"
 
-
 #define ACPI_PCIHP_ADDR         0xae00
 #define PCI_EJ_BASE             0x0008
 
-typedef struct QPCIBusPC
-{
-    QPCIBus bus;
-} QPCIBusPC;
-
 static uint8_t qpci_pc_pio_readb(QPCIBus *bus, uint32_t addr)
 {
     return qtest_inb(bus->qts, addr);
@@ -116,44 +110,68 @@ static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint3
     qtest_outl(bus->qts, 0xcfc, value);
 }
 
-QPCIBus *qpci_init_pc(QTestState *qts, QGuestAllocator *alloc)
+static void *qpci_pc_get_driver(void *obj, const char *interface)
 {
-    QPCIBusPC *ret = g_new0(QPCIBusPC, 1);
+    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);
 
-    ret->bus.pio_readb = qpci_pc_pio_readb;
-    ret->bus.pio_readw = qpci_pc_pio_readw;
-    ret->bus.pio_readl = qpci_pc_pio_readl;
-    ret->bus.pio_readq = qpci_pc_pio_readq;
+    /* 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;
 
-    ret->bus.pio_writeb = qpci_pc_pio_writeb;
-    ret->bus.pio_writew = qpci_pc_pio_writew;
-    ret->bus.pio_writel = qpci_pc_pio_writel;
-    ret->bus.pio_writeq = qpci_pc_pio_writeq;
+    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;
 
-    ret->bus.memread = qpci_pc_memread;
-    ret->bus.memwrite = qpci_pc_memwrite;
+    qpci->bus.memread = qpci_pc_memread;
+    qpci->bus.memwrite = qpci_pc_memwrite;
 
-    ret->bus.config_readb = qpci_pc_config_readb;
-    ret->bus.config_readw = qpci_pc_config_readw;
-    ret->bus.config_readl = qpci_pc_config_readl;
+    qpci->bus.config_readb = qpci_pc_config_readb;
+    qpci->bus.config_readw = qpci_pc_config_readw;
+    qpci->bus.config_readl = qpci_pc_config_readl;
 
-    ret->bus.config_writeb = qpci_pc_config_writeb;
-    ret->bus.config_writew = qpci_pc_config_writew;
-    ret->bus.config_writel = qpci_pc_config_writel;
+    qpci->bus.config_writeb = qpci_pc_config_writeb;
+    qpci->bus.config_writew = qpci_pc_config_writew;
+    qpci->bus.config_writel = qpci_pc_config_writel;
 
-    ret->bus.qts = qts;
-    ret->bus.pio_alloc_ptr = 0xc000;
-    ret->bus.mmio_alloc_ptr = 0xE0000000;
-    ret->bus.mmio_limit = 0x100000000ULL;
+    qpci->bus.qts = qts;
+    qpci->bus.pio_alloc_ptr = 0xc000;
+    qpci->bus.mmio_alloc_ptr = 0xE0000000;
+    qpci->bus.mmio_limit = 0x100000000ULL;
 
-    return &ret->bus;
+    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 = container_of(bus, QPCIBusPC, bus);
+    QPCIBusPC *s;
+
+    if (!bus) {
+        return;
+    }
+    s = container_of(bus, QPCIBusPC, bus);
 
     g_free(s);
 }
@@ -172,3 +190,11 @@ void qpci_unplug_acpi_device_test(const char *id, uint8_t slot)
 
     qmp_eventwait("DEVICE_DELETED");
 }
+
+static void qpci_pc_register_nodes(void)
+{
+    qos_node_create_driver("pci-bus-pc", NULL);
+    qos_node_produces("pci-bus-pc", "pci-bus");
+}
+
+libqos_init(qpci_pc_register_nodes);
diff --git a/tests/libqos/pci-pc.h b/tests/libqos/pci-pc.h
index 491eeac756..4690005232 100644
--- a/tests/libqos/pci-pc.h
+++ b/tests/libqos/pci-pc.h
@@ -15,8 +15,35 @@
 
 #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);
 
-QPCIBus *qpci_init_pc(QTestState *qts, QGuestAllocator *alloc);
 void     qpci_free_pc(QPCIBus *bus);
 
 #endif
diff --git a/tests/libqos/pci-spapr.c b/tests/libqos/pci-spapr.c
index 4c29889b0b..6925925997 100644
--- a/tests/libqos/pci-spapr.c
+++ b/tests/libqos/pci-spapr.c
@@ -9,33 +9,13 @@
 #include "libqtest.h"
 #include "libqos/pci-spapr.h"
 #include "libqos/rtas.h"
+#include "libqos/qgraph.h"
 
 #include "hw/pci/pci_regs.h"
 
 #include "qemu-common.h"
 #include "qemu/host-utils.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 {
-    QPCIBus bus;
-    QGuestAllocator *alloc;
-
-    uint64_t buid;
-
-    uint64_t pio_cpu_base;
-    QPCIWindow pio;
-
-    uint64_t mmio32_cpu_base;
-    QPCIWindow mmio32;
-} QPCIBusSPAPR;
-
 /*
  * PCI devices are always little-endian
  * SPAPR by default is big-endian
@@ -160,60 +140,93 @@ static void qpci_spapr_config_writel(QPCIBus *bus, int devfn, uint8_t offset,
 #define SPAPR_PCI_MMIO32_WIN_SIZE    0x80000000 /* 2 GiB */
 #define SPAPR_PCI_IO_WIN_SIZE        0x10000
 
-QPCIBus *qpci_init_spapr(QTestState *qts, QGuestAllocator *alloc)
+static void *qpci_spapr_get_driver(void *obj, const char *interface)
 {
-    QPCIBusSPAPR *ret = g_new0(QPCIBusSPAPR, 1);
+    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);
 
-    ret->alloc = alloc;
+    /* tests cannot use spapr, needs to be fixed first */
+    qpci->bus.has_buggy_msi = TRUE;
+
+    qpci->alloc = alloc;
 
-    ret->bus.pio_readb = qpci_spapr_pio_readb;
-    ret->bus.pio_readw = qpci_spapr_pio_readw;
-    ret->bus.pio_readl = qpci_spapr_pio_readl;
-    ret->bus.pio_readq = qpci_spapr_pio_readq;
+    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;
 
-    ret->bus.pio_writeb = qpci_spapr_pio_writeb;
-    ret->bus.pio_writew = qpci_spapr_pio_writew;
-    ret->bus.pio_writel = qpci_spapr_pio_writel;
-    ret->bus.pio_writeq = qpci_spapr_pio_writeq;
+    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;
 
-    ret->bus.memread = qpci_spapr_memread;
-    ret->bus.memwrite = qpci_spapr_memwrite;
+    qpci->bus.memread = qpci_spapr_memread;
+    qpci->bus.memwrite = qpci_spapr_memwrite;
 
-    ret->bus.config_readb = qpci_spapr_config_readb;
-    ret->bus.config_readw = qpci_spapr_config_readw;
-    ret->bus.config_readl = qpci_spapr_config_readl;
+    qpci->bus.config_readb = qpci_spapr_config_readb;
+    qpci->bus.config_readw = qpci_spapr_config_readw;
+    qpci->bus.config_readl = qpci_spapr_config_readl;
 
-    ret->bus.config_writeb = qpci_spapr_config_writeb;
-    ret->bus.config_writew = qpci_spapr_config_writew;
-    ret->bus.config_writel = qpci_spapr_config_writel;
+    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 */
-    ret->buid = 0x800000020000000ULL;
+    qpci->buid = 0x800000020000000ULL;
 
-    ret->pio_cpu_base = SPAPR_PCI_BASE;
-    ret->pio.pci_base = 0;
-    ret->pio.size = SPAPR_PCI_IO_WIN_SIZE;
+    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 */
-    ret->mmio32_cpu_base = SPAPR_PCI_BASE;
-    ret->mmio32.pci_base = SPAPR_PCI_MMIO32_WIN_SIZE;
-    ret->mmio32.size = SPAPR_PCI_MMIO32_WIN_SIZE;
+    qpci->mmio32_cpu_base = SPAPR_PCI_BASE;
+    qpci->mmio32.pci_base = SPAPR_PCI_MMIO32_WIN_SIZE;
+    qpci->mmio32.size = SPAPR_PCI_MMIO32_WIN_SIZE;
 
-    ret->bus.qts = qts;
-    ret->bus.pio_alloc_ptr = 0xc000;
-    ret->bus.mmio_alloc_ptr = ret->mmio32.pci_base;
-    ret->bus.mmio_limit = ret->mmio32.pci_base + ret->mmio32.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;
 
-    return &ret->bus;
+    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 = container_of(bus, QPCIBusSPAPR, bus);
+    QPCIBusSPAPR *s;
+
+    if (!bus) {
+        return;
+    }
+    s = container_of(bus, QPCIBusSPAPR, bus);
 
     g_free(s);
 }
+
+static void qpci_spapr_register_nodes(void)
+{
+    qos_node_create_driver("pci-bus-spapr", NULL);
+    qos_node_produces("pci-bus-spapr", "pci-bus");
+}
+
+libqos_init(qpci_spapr_register_nodes);
diff --git a/tests/libqos/pci-spapr.h b/tests/libqos/pci-spapr.h
index 387686dfc8..d9e25631c6 100644
--- a/tests/libqos/pci-spapr.h
+++ b/tests/libqos/pci-spapr.h
@@ -10,8 +10,32 @@
 
 #include "libqos/malloc.h"
 #include "libqos/pci.h"
+#include "libqos/qgraph.h"
 
-QPCIBus *qpci_init_spapr(QTestState *qts, QGuestAllocator *alloc);
+/* From include/hw/pci-host/spapr.h */
+
+typedef struct QPCIWindow {
+    uint64_t pci_base;    /* window address in PCI space */
+    uint64_t size;        /* window size */
+} QPCIWindow;
+
+typedef struct QPCIBusSPAPR {
+    QOSGraphObject obj;
+    QPCIBus bus;
+    QGuestAllocator *alloc;
+
+    uint64_t buid;
+
+    uint64_t pio_cpu_base;
+    QPCIWindow pio;
+
+    uint64_t mmio32_cpu_base;
+    QPCIWindow mmio32;
+} QPCIBusSPAPR;
+
+void qpci_init_spapr(QPCIBusSPAPR *ret, QTestState *qts,
+                     QGuestAllocator *alloc);
+QPCIBus *qpci_new_spapr(QTestState *qts, QGuestAllocator *alloc);
 void     qpci_free_spapr(QPCIBus *bus);
 
 #endif
diff --git a/tests/libqos/pci.c b/tests/libqos/pci.c
index e8c342c257..662ee7a517 100644
--- a/tests/libqos/pci.c
+++ b/tests/libqos/pci.c
@@ -15,6 +15,7 @@
 
 #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),
@@ -50,13 +51,34 @@ void qpci_device_foreach(QPCIBus *bus, int vendor_id, int device_id,
     }
 }
 
+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));
-    dev->bus = bus;
-    dev->devfn = devfn;
+    qpci_device_set(dev, bus, devfn);
 
     if (qpci_config_readw(dev, PCI_VENDOR_ID) == 0xFFFF) {
         g_free(dev);
@@ -66,6 +88,17 @@ QPCIDevice *qpci_device_find(QPCIBus *bus, int devfn)
     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;
@@ -395,3 +428,12 @@ QPCIBar qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr)
     QPCIBar bar = { .addr = addr };
     return bar;
 }
+
+void add_qpci_address(QOSGraphEdgeOptions *opts, QPCIAddress *addr)
+{
+    g_assert(addr);
+    g_assert(opts);
+
+    opts->arg = addr;
+    opts->size_arg = sizeof(QPCIAddress);
+}
diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h
index 0b7e936174..8e1d292a7d 100644
--- a/tests/libqos/pci.h
+++ b/tests/libqos/pci.h
@@ -14,6 +14,7 @@
 #define LIBQOS_PCI_H
 
 #include "libqtest.h"
+#include "libqos/qgraph.h"
 
 #define QPCI_PIO_LIMIT    0x10000
 
@@ -22,6 +23,7 @@
 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);
@@ -51,6 +53,8 @@ struct QPCIBus {
     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 {
@@ -66,10 +70,20 @@ struct QPCIDevice
     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);
@@ -110,4 +124,6 @@ void qpci_iounmap(QPCIDevice *dev, QPCIBar addr);
 QPCIBar qpci_legacy_iomap(QPCIDevice *dev, uint16_t addr);
 
 void qpci_unplug_acpi_device_test(const char *id, uint8_t slot);
+
+void add_qpci_address(QOSGraphEdgeOptions *opts, QPCIAddress *addr);
 #endif
diff --git a/tests/libqos/ppc64_pseries-machine.c b/tests/libqos/ppc64_pseries-machine.c
new file mode 100644
index 0000000000..2f3640010d
--- /dev/null
+++ b/tests/libqos/ppc64_pseries-machine.c
@@ -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 "libqos/qgraph.h"
+#include "pci-spapr.h"
+#include "libqos/malloc-spapr.h"
+
+typedef struct QSPAPR_pci_host QSPAPR_pci_host;
+typedef struct Qppc64_pseriesMachine Qppc64_pseriesMachine;
+
+struct QSPAPR_pci_host {
+    QOSGraphObject obj;
+    QPCIBusSPAPR pci;
+};
+
+struct Qppc64_pseriesMachine {
+    QOSGraphObject obj;
+    QGuestAllocator alloc;
+    QSPAPR_pci_host bridge;
+};
+
+/* QSPAPR_pci_host */
+
+static QOSGraphObject *QSPAPR_host_get_device(void *obj, const char *device)
+{
+    QSPAPR_pci_host *host = obj;
+    if (!g_strcmp0(device, "pci-bus-spapr")) {
+        return &host->pci.obj;
+    }
+    fprintf(stderr, "%s not present in QSPAPR_pci_host\n", device);
+    g_assert_not_reached();
+}
+
+static void qos_create_QSPAPR_host(QSPAPR_pci_host *host,
+                                   QTestState *qts,
+                                   QGuestAllocator *alloc)
+{
+    host->obj.get_device = QSPAPR_host_get_device;
+    qpci_init_spapr(&host->pci, qts, alloc);
+}
+
+/* ppc64/pseries machine */
+
+static void spapr_destructor(QOSGraphObject *obj)
+{
+    Qppc64_pseriesMachine *machine = (Qppc64_pseriesMachine *) obj;
+    alloc_destroy(&machine->alloc);
+}
+
+static void *spapr_get_driver(void *object, const char *interface)
+{
+    Qppc64_pseriesMachine *machine = object;
+    if (!g_strcmp0(interface, "memory")) {
+        return &machine->alloc;
+    }
+
+    fprintf(stderr, "%s not present in ppc64/pseries\n", interface);
+    g_assert_not_reached();
+}
+
+static QOSGraphObject *spapr_get_device(void *obj, const char *device)
+{
+    Qppc64_pseriesMachine *machine = obj;
+    if (!g_strcmp0(device, "spapr-pci-host-bridge")) {
+        return &machine->bridge.obj;
+    }
+
+    fprintf(stderr, "%s not present in ppc64/pseries\n", device);
+    g_assert_not_reached();
+}
+
+static void *qos_create_machine_spapr(QTestState *qts)
+{
+    Qppc64_pseriesMachine *machine = g_new0(Qppc64_pseriesMachine, 1);
+    machine->obj.get_device = spapr_get_device;
+    machine->obj.get_driver = spapr_get_driver;
+    machine->obj.destructor = spapr_destructor;
+    spapr_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS);
+
+    qos_create_QSPAPR_host(&machine->bridge, qts, &machine->alloc);
+
+    return &machine->obj;
+}
+
+static void spapr_machine_register_nodes(void)
+{
+    qos_node_create_machine("ppc64/pseries", qos_create_machine_spapr);
+    qos_node_create_driver("spapr-pci-host-bridge", NULL);
+    qos_node_contains("ppc64/pseries", "spapr-pci-host-bridge", NULL);
+    qos_node_contains("spapr-pci-host-bridge", "pci-bus-spapr", NULL);
+}
+
+libqos_init(spapr_machine_register_nodes);
+
diff --git a/tests/libqos/qgraph.c b/tests/libqos/qgraph.c
new file mode 100644
index 0000000000..122efc1b7b
--- /dev/null
+++ b/tests/libqos/qgraph.c
@@ -0,0 +1,753 @@
+/*
+ * 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);
+
+    if (!list) {
+        list = g_new0(QOSGraphEdgeList, 1);
+        key = g_strdup(source);
+        g_hash_table_insert(edge_table, key, list);
+    }
+
+    if (!opts) {
+        opts = &(QOSGraphEdgeOptions) { };
+    }
+
+    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);;
+
+    if (!opts) {
+        opts = &(QOSGraphTestOptions) { };
+    }
+    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,
+                       ...)
+{
+    va_list va;
+    va_start(va, contained);
+    QOSGraphEdgeOptions *opts;
+
+    do {
+        opts = va_arg(va, QOSGraphEdgeOptions *);
+        add_edge(container, contained, QEDGE_CONTAINS, opts);
+    } 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;
+    }
+}
diff --git a/tests/libqos/qgraph.h b/tests/libqos/qgraph.h
new file mode 100644
index 0000000000..ef0c73837a
--- /dev/null
+++ b/tests/libqos/qgraph.h
@@ -0,0 +1,575 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <gmodule.h>
+#include <glib.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 an edge of type QEDGE_CONTAINS and
+ * adds it to the edge list mapped to @container in the
+ * edge hash table.
+ *
+ * This edge will have @container as source and @contained as destination.
+ *
+ * It also has the possibility to add optional NULL-terminated
+ * @opts parameters (see %QOSGraphEdgeOptions)
+ *
+ * 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, ...);
+
+/**
+ * qos_node_produces(): creates an edge of type QEDGE_PRODUCES and
+ * adds it to the edge list mapped to @producer in the
+ * edge hash table.
+ *
+ * This edge will have @producer as source and @interface as destination.
+ */
+void qos_node_produces(const char *producer, const char *interface);
+
+/**
+ * qos_node_consumes():  creates an edge of type QEDGE_CONSUMED_BY and
+ * adds it to the edge list mapped to @interface in the
+ * edge hash table.
+ *
+ * This edge will have @interface as source and @consumer as destination.
+ * It also has the possibility to add an optional @opts
+ * (see %QOSGraphEdgeOptions)
+ */
+void qos_node_consumes(const char *consumer, const char *interface,
+                       QOSGraphEdgeOptions *opts);
+
+/**
+ * qos_invalidate_command_line(): invalidates current command line, so that
+ * qgraph framework cannot try to cache the current command line and
+ * forces QEMU to restart.
+ */
+void qos_invalidate_command_line(void);
+
+/**
+ * qos_get_current_command_line(): return the command line required by the
+ * machine and driver objects.  This is the same string that was passed to
+ * the test's "before" callback, if any.
+ */
+const char *qos_get_current_command_line(void);
+
+/**
+ * qos_allocate_objects():
+ * @qts: The #QTestState that will be referred to by the machine object.
+ * @alloc: Where to store the allocator for the machine object, or %NULL.
+ *
+ * Allocate driver objects for the current test
+ * path, but relative to the QTestState @qts.
+ *
+ * Returns a test object just like the one that was passed to
+ * the test function, but relative to @qts.
+ */
+void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc);
+
+/**
+ * qos_object_destroy(): calls the destructor for @obj
+ */
+void qos_object_destroy(QOSGraphObject *obj);
+
+/**
+ * qos_object_queue_destroy(): queue the destructor for @obj so that it is
+ * called at the end of the test
+ */
+void qos_object_queue_destroy(QOSGraphObject *obj);
+
+/**
+ * qos_object_start_hw(): calls the start_hw function for @obj
+ */
+void qos_object_start_hw(QOSGraphObject *obj);
+
+/**
+ * qos_machine_new(): instantiate a new machine node
+ * @node: A machine node to be instantiated
+ * @qts: The #QTestState that will be referred to by the machine object.
+ *
+ * Returns a machine object.
+ */
+QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts);
+
+/**
+ * qos_machine_new(): instantiate a new driver node
+ * @node: A driver node to be instantiated
+ * @parent: A #QOSGraphObject to be consumed by the new driver node
+ * @alloc: An allocator to be used by the new driver node.
+ * @arg: The argument for the consumed-by edge to @node.
+ *
+ * Calls the constructor for the driver object.
+ */
+QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent,
+                               QGuestAllocator *alloc, void *arg);
+
+
+#endif
diff --git a/tests/libqos/qgraph_internal.h b/tests/libqos/qgraph_internal.h
new file mode 100644
index 0000000000..2ef748baf6
--- /dev/null
+++ b/tests/libqos/qgraph_internal.h
@@ -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_EXTRA_H
+#define QGRAPH_EXTRA_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
diff --git a/tests/libqos/sdhci.c b/tests/libqos/sdhci.c
new file mode 100644
index 0000000000..22c33de453
--- /dev/null
+++ b/tests/libqos/sdhci.c
@@ -0,0 +1,163 @@
+/*
+ * 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 "sdhci.h"
+#include "hw/pci/pci.h"
+
+static void set_qsdhci_fields(QSDHCI *s, uint8_t version, uint8_t baseclock,
+                              bool sdma, uint64_t reg)
+{
+    s->props.version = version;
+    s->props.baseclock = baseclock;
+    s->props.capab.sdma = sdma;
+    s->props.capab.reg = reg;
+}
+
+/* Memory mapped implementation of QSDHCI */
+
+static uint16_t sdhci_mm_readw(QSDHCI *s, uint32_t reg)
+{
+    QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci);
+    return qtest_readw(smm->qts, smm->addr + reg);
+}
+
+static uint64_t sdhci_mm_readq(QSDHCI *s, uint32_t reg)
+{
+    QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci);
+    return qtest_readq(smm->qts, smm->addr + reg);
+}
+
+static void sdhci_mm_writeq(QSDHCI *s, uint32_t reg, uint64_t val)
+{
+    QSDHCI_MemoryMapped *smm = container_of(s, QSDHCI_MemoryMapped, sdhci);
+    qtest_writeq(smm->qts, smm->addr + reg, val);
+}
+
+static void *sdhci_mm_get_driver(void *obj, const char *interface)
+{
+    QSDHCI_MemoryMapped *smm = obj;
+    if (!g_strcmp0(interface, "sdhci")) {
+        return &smm->sdhci;
+    }
+    fprintf(stderr, "%s not present in generic-sdhci\n", interface);
+    g_assert_not_reached();
+}
+
+void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts,
+                       uint32_t addr, QSDHCIProperties *common)
+{
+    sdhci->obj.get_driver = sdhci_mm_get_driver;
+    sdhci->sdhci.readw = sdhci_mm_readw;
+    sdhci->sdhci.readq = sdhci_mm_readq;
+    sdhci->sdhci.writeq = sdhci_mm_writeq;
+    memcpy(&sdhci->sdhci.props, common, sizeof(QSDHCIProperties));
+    sdhci->addr = addr;
+    sdhci->qts = qts;
+}
+
+/* PCI implementation of QSDHCI */
+
+static uint16_t sdhci_pci_readw(QSDHCI *s, uint32_t reg)
+{
+    QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci);
+    return qpci_io_readw(&spci->dev, spci->mem_bar, reg);
+}
+
+static uint64_t sdhci_pci_readq(QSDHCI *s, uint32_t reg)
+{
+    QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci);
+    return qpci_io_readq(&spci->dev, spci->mem_bar, reg);
+}
+
+static void sdhci_pci_writeq(QSDHCI *s, uint32_t reg, uint64_t val)
+{
+    QSDHCI_PCI *spci = container_of(s, QSDHCI_PCI, sdhci);
+    return qpci_io_writeq(&spci->dev, spci->mem_bar, reg, val);
+}
+
+static void *sdhci_pci_get_driver(void *object, const char *interface)
+{
+    QSDHCI_PCI *spci = object;
+    if (!g_strcmp0(interface, "sdhci")) {
+        return &spci->sdhci;
+    }
+
+    fprintf(stderr, "%s not present in sdhci-pci\n", interface);
+    g_assert_not_reached();
+}
+
+static void sdhci_pci_start_hw(QOSGraphObject *obj)
+{
+    QSDHCI_PCI *spci = (QSDHCI_PCI *)obj;
+    qpci_device_enable(&spci->dev);
+}
+
+static void sdhci_destructor(QOSGraphObject *obj)
+{
+    QSDHCI_PCI *spci = (QSDHCI_PCI *)obj;
+    qpci_iounmap(&spci->dev, spci->mem_bar);
+}
+
+static void *sdhci_pci_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+    QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1);
+    QPCIBus *bus = pci_bus;
+    uint64_t barsize;
+
+    qpci_device_init(&spci->dev, bus, addr);
+    spci->mem_bar = qpci_iomap(&spci->dev, 0, &barsize);
+    spci->sdhci.readw = sdhci_pci_readw;
+    spci->sdhci.readq = sdhci_pci_readq;
+    spci->sdhci.writeq = sdhci_pci_writeq;
+    set_qsdhci_fields(&spci->sdhci, 2, 0, 1, 0x057834b4);
+
+    spci->obj.get_driver = sdhci_pci_get_driver;
+    spci->obj.start_hw = sdhci_pci_start_hw;
+    spci->obj.destructor = sdhci_destructor;
+    return &spci->obj;
+}
+
+static void qsdhci_register_nodes(void)
+{
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+        .vendor_id = PCI_VENDOR_ID_REDHAT,
+        .device_id = PCI_DEVICE_ID_REDHAT_SDHCI,
+    };
+
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "addr=04.0",
+    };
+
+    /* generic-sdhci */
+    qos_node_create_driver("generic-sdhci", NULL);
+    qos_node_produces("generic-sdhci", "sdhci");
+
+    /* sdhci-pci */
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("sdhci-pci", sdhci_pci_create);
+    qos_node_produces("sdhci-pci", "sdhci");
+    qos_node_consumes("sdhci-pci", "pci-bus", &opts);
+
+}
+
+libqos_init(qsdhci_register_nodes);
diff --git a/tests/libqos/sdhci.h b/tests/libqos/sdhci.h
new file mode 100644
index 0000000000..032d815c38
--- /dev/null
+++ b/tests/libqos/sdhci.h
@@ -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
+#define QGRAPH_QSDHCI
+
+#include "libqos/qgraph.h"
+#include "pci.h"
+
+typedef struct QSDHCI QSDHCI;
+typedef struct QSDHCI_MemoryMapped QSDHCI_MemoryMapped;
+typedef struct QSDHCI_PCI  QSDHCI_PCI;
+typedef struct QSDHCIProperties QSDHCIProperties;
+
+/* Properties common to all QSDHCI devices */
+struct QSDHCIProperties {
+    uint8_t version;
+    uint8_t baseclock;
+    struct {
+        bool sdma;
+        uint64_t reg;
+    } capab;
+};
+
+struct QSDHCI {
+    uint16_t (*readw)(QSDHCI *s, uint32_t reg);
+    uint64_t (*readq)(QSDHCI *s, uint32_t reg);
+    void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val);
+    QSDHCIProperties props;
+};
+
+/* Memory Mapped implementation of QSDHCI */
+struct QSDHCI_MemoryMapped {
+    QOSGraphObject obj;
+    QTestState *qts;
+    QSDHCI sdhci;
+    uint64_t addr;
+};
+
+/* PCI implementation of QSDHCI */
+struct QSDHCI_PCI {
+    QOSGraphObject obj;
+    QPCIDevice dev;
+    QSDHCI sdhci;
+    QPCIBar mem_bar;
+};
+
+/**
+ * qos_init_sdhci_mm(): external constructor used by all drivers/machines
+ * that "contain" a #QSDHCI_MemoryMapped driver
+ */
+void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts,
+                       uint32_t addr, QSDHCIProperties *common);
+
+#endif
diff --git a/tests/libqos/tpci200.c b/tests/libqos/tpci200.c
new file mode 100644
index 0000000000..98dc532933
--- /dev/null
+++ b/tests/libqos/tpci200.c
@@ -0,0 +1,65 @@
+/*
+ * 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 "libqos/qgraph.h"
+#include "libqos/pci.h"
+
+typedef struct QTpci200 QTpci200;
+typedef struct QIpack QIpack;
+
+struct QIpack {
+
+};
+struct QTpci200 {
+    QOSGraphObject obj;
+    QPCIDevice dev;
+    QIpack ipack;
+};
+
+/* tpci200 */
+static void *tpci200_get_driver(void *obj, const char *interface)
+{
+    QTpci200 *tpci200 = obj;
+    if (!g_strcmp0(interface, "ipack")) {
+        return &tpci200->ipack;
+    }
+    if (!g_strcmp0(interface, "pci-device")) {
+        return &tpci200->dev;
+    }
+
+    fprintf(stderr, "%s not present in tpci200\n", interface);
+    g_assert_not_reached();
+}
+
+static void *tpci200_create(void *pci_bus, QGuestAllocator *alloc, void *addr)
+{
+    QTpci200 *tpci200 = g_new0(QTpci200, 1);
+    QPCIBus *bus = pci_bus;
+
+    qpci_device_init(&tpci200->dev, bus, addr);
+    tpci200->obj.get_driver = tpci200_get_driver;
+    return &tpci200->obj;
+}
+
+static void tpci200_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "addr=04.0,id=ipack0",
+    };
+    add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) });
+
+    qos_node_create_driver("tpci200", tpci200_create);
+    qos_node_consumes("tpci200", "pci-bus", &opts);
+    qos_node_produces("tpci200", "ipack");
+    qos_node_produces("tpci200", "pci-device");
+}
+
+libqos_init(tpci200_register_nodes);
diff --git a/tests/libqos/virtio-9p.c b/tests/libqos/virtio-9p.c
new file mode 100644
index 0000000000..a378b56fa9
--- /dev/null
+++ b/tests/libqos/virtio-9p.c
@@ -0,0 +1,173 @@
+/*
+ * 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 "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)
+{
+    interface->vq = qvirtqueue_setup(interface->vdev, alloc, 0);
+    qvirtio_set_driver_ok(interface->vdev);
+}
+
+/* virtio-9p-device */
+static void virtio_9p_device_destructor(QOSGraphObject *obj)
+{
+    QVirtio9PDevice *v_9p = (QVirtio9PDevice *) obj;
+    QVirtio9P *v9p = &v_9p->v9p;
+
+    virtio_9p_cleanup(v9p);
+}
+
+static void virtio_9p_device_start_hw(QOSGraphObject *obj)
+{
+    QVirtio9PDevice *v_9p = (QVirtio9PDevice *) obj;
+    QVirtio9P *v9p = &v_9p->v9p;
+
+    virtio_9p_setup(v9p);
+}
+
+static void *virtio_9p_get_driver(QVirtio9P *v_9p,
+                                         const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-9p")) {
+        return v_9p;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_9p->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-9p-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *virtio_9p_device_get_driver(void *object, const char *interface)
+{
+    QVirtio9PDevice *v_9p = object;
+    return virtio_9p_get_driver(&v_9p->v9p, interface);
+}
+
+static void *virtio_9p_device_create(void *virtio_dev,
+                                     QGuestAllocator *t_alloc,
+                                     void *addr)
+{
+    QVirtio9PDevice *virtio_device = g_new0(QVirtio9PDevice, 1);
+    QVirtio9P *interface = &virtio_device->v9p;
+
+    interface->vdev = virtio_dev;
+    alloc = t_alloc;
+
+    virtio_device->obj.destructor = virtio_9p_device_destructor;
+    virtio_device->obj.get_driver = virtio_9p_device_get_driver;
+    virtio_device->obj.start_hw = virtio_9p_device_start_hw;
+
+    return &virtio_device->obj;
+}
+
+/* virtio-9p-pci */
+static void virtio_9p_pci_destructor(QOSGraphObject *obj)
+{
+    QVirtio9PPCI *v9_pci = (QVirtio9PPCI *) obj;
+    QVirtio9P *interface = &v9_pci->v9p;
+    QOSGraphObject *pci_vobj =  &v9_pci->pci_vdev.obj;
+
+    virtio_9p_cleanup(interface);
+    qvirtio_pci_destructor(pci_vobj);
+}
+
+static void virtio_9p_pci_start_hw(QOSGraphObject *obj)
+{
+    QVirtio9PPCI *v9_pci = (QVirtio9PPCI *) obj;
+    QVirtio9P *interface = &v9_pci->v9p;
+    QOSGraphObject *pci_vobj =  &v9_pci->pci_vdev.obj;
+
+    qvirtio_pci_start_hw(pci_vobj);
+    virtio_9p_setup(interface);
+}
+
+static void *virtio_9p_pci_get_driver(void *object, const char *interface)
+{
+    QVirtio9PPCI *v_9p = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_9p->pci_vdev.pdev;
+    }
+    return virtio_9p_get_driver(&v_9p->v9p, interface);
+}
+
+static void *virtio_9p_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                  void *addr)
+{
+    QVirtio9PPCI *v9_pci = g_new0(QVirtio9PPCI, 1);
+    QVirtio9P *interface = &v9_pci->v9p;
+    QOSGraphObject *obj = &v9_pci->pci_vdev.obj;
+
+    virtio_pci_init(&v9_pci->pci_vdev, pci_bus, addr);
+    interface->vdev = &v9_pci->pci_vdev.vdev;
+    alloc = t_alloc;
+
+    g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_9P);
+
+    obj->destructor = virtio_9p_pci_destructor;
+    obj->start_hw = virtio_9p_pci_start_hw;
+    obj->get_driver = virtio_9p_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_9p_register_nodes(void)
+{
+    const char *str_simple = "fsdev=fsdev0,mount_tag=" MOUNT_TAG;
+    const char *str_addr = "fsdev=fsdev0,addr=04.0,mount_tag=" MOUNT_TAG;
+
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    QOSGraphEdgeOptions opts = {
+        .before_cmd_line = "-fsdev synth,id=fsdev0",
+    };
+
+    /* virtio-9p-device */
+    opts.extra_device_opts = str_simple,
+    qos_node_create_driver("virtio-9p-device", virtio_9p_device_create);
+    qos_node_consumes("virtio-9p-device", "virtio-bus", &opts);
+    qos_node_produces("virtio-9p-device", "virtio");
+    qos_node_produces("virtio-9p-device", "virtio-9p");
+
+    /* virtio-9p-pci */
+    opts.extra_device_opts = str_addr;
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-9p-pci", virtio_9p_pci_create);
+    qos_node_consumes("virtio-9p-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-9p-pci", "pci-device");
+    qos_node_produces("virtio-9p-pci", "virtio");
+    qos_node_produces("virtio-9p-pci", "virtio-9p");
+
+}
+
+libqos_init(virtio_9p_register_nodes);
diff --git a/tests/libqos/virtio-9p.h b/tests/libqos/virtio-9p.h
new file mode 100644
index 0000000000..dba22772b5
--- /dev/null
+++ b/tests/libqos/virtio-9p.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "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;
+};
diff --git a/tests/libqos/virtio-balloon.c b/tests/libqos/virtio-balloon.c
new file mode 100644
index 0000000000..7e6e9e9de5
--- /dev/null
+++ b/tests/libqos/virtio-balloon.c
@@ -0,0 +1,113 @@
+/*
+ * 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 "libqos/virtio-balloon.h"
+
+/* virtio-balloon-device */
+static void *qvirtio_balloon_get_driver(QVirtioBalloon *v_balloon,
+                                        const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-balloon")) {
+        return v_balloon;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_balloon->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-balloon-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *qvirtio_balloon_device_get_driver(void *object,
+                                               const char *interface)
+{
+    QVirtioBalloonDevice *v_balloon = object;
+    return qvirtio_balloon_get_driver(&v_balloon->balloon, interface);
+}
+
+static void *virtio_balloon_device_create(void *virtio_dev,
+                                          QGuestAllocator *t_alloc,
+                                          void *addr)
+{
+    QVirtioBalloonDevice *virtio_bdevice = g_new0(QVirtioBalloonDevice, 1);
+    QVirtioBalloon *interface = &virtio_bdevice->balloon;
+
+    interface->vdev = virtio_dev;
+
+    virtio_bdevice->obj.get_driver = qvirtio_balloon_device_get_driver;
+
+    return &virtio_bdevice->obj;
+}
+
+/* virtio-balloon-pci */
+static void *qvirtio_balloon_pci_get_driver(void *object,
+                                            const char *interface)
+{
+    QVirtioBalloonPCI *v_balloon = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_balloon->pci_vdev.pdev;
+    }
+    return qvirtio_balloon_get_driver(&v_balloon->balloon, interface);
+}
+
+static void *virtio_balloon_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                  void *addr)
+{
+    QVirtioBalloonPCI *virtio_bpci = g_new0(QVirtioBalloonPCI, 1);
+    QVirtioBalloon *interface = &virtio_bpci->balloon;
+    QOSGraphObject *obj = &virtio_bpci->pci_vdev.obj;
+
+
+    virtio_pci_init(&virtio_bpci->pci_vdev, pci_bus, addr);
+    interface->vdev = &virtio_bpci->pci_vdev.vdev;
+
+    obj->get_driver = qvirtio_balloon_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_balloon_register_nodes(void)
+{
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "addr=04.0",
+    };
+
+    /* virtio-balloon-device */
+    qos_node_create_driver("virtio-balloon-device",
+                            virtio_balloon_device_create);
+    qos_node_consumes("virtio-balloon-device", "virtio-bus", NULL);
+    qos_node_produces("virtio-balloon-device", "virtio");
+    qos_node_produces("virtio-balloon-device", "virtio-balloon");
+
+    /* virtio-balloon-pci */
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-balloon-pci", virtio_balloon_pci_create);
+    qos_node_consumes("virtio-balloon-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-balloon-pci", "pci-device");
+    qos_node_produces("virtio-balloon-pci", "virtio");
+    qos_node_produces("virtio-balloon-pci", "virtio-balloon");
+}
+
+libqos_init(virtio_balloon_register_nodes);
diff --git a/tests/libqos/virtio-balloon.h b/tests/libqos/virtio-balloon.h
new file mode 100644
index 0000000000..e8066c42bb
--- /dev/null
+++ b/tests/libqos/virtio-balloon.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "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;
+};
diff --git a/tests/libqos/virtio-blk.c b/tests/libqos/virtio-blk.c
new file mode 100644
index 0000000000..c17bdf4217
--- /dev/null
+++ b/tests/libqos/virtio-blk.c
@@ -0,0 +1,124 @@
+/*
+ * 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 "standard-headers/linux/virtio_blk.h"
+#include "libqos/qgraph.h"
+#include "libqos/virtio-blk.h"
+
+#define PCI_SLOT                0x04
+#define PCI_FN                  0x00
+
+/* virtio-blk-device */
+static void *qvirtio_blk_get_driver(QVirtioBlk *v_blk,
+                                    const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-blk")) {
+        return v_blk;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_blk->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-blk-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *qvirtio_blk_device_get_driver(void *object,
+                                           const char *interface)
+{
+    QVirtioBlkDevice *v_blk = object;
+    return qvirtio_blk_get_driver(&v_blk->blk, interface);
+}
+
+static void *virtio_blk_device_create(void *virtio_dev,
+                                      QGuestAllocator *t_alloc,
+                                      void *addr)
+{
+    QVirtioBlkDevice *virtio_blk = g_new0(QVirtioBlkDevice, 1);
+    QVirtioBlk *interface = &virtio_blk->blk;
+
+    interface->vdev = virtio_dev;
+
+    virtio_blk->obj.get_driver = qvirtio_blk_device_get_driver;
+
+    return &virtio_blk->obj;
+}
+
+/* virtio-blk-pci */
+static void *qvirtio_blk_pci_get_driver(void *object, const char *interface)
+{
+    QVirtioBlkPCI *v_blk = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_blk->pci_vdev.pdev;
+    }
+    return qvirtio_blk_get_driver(&v_blk->blk, interface);
+}
+
+static void *virtio_blk_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                      void *addr)
+{
+    QVirtioBlkPCI *virtio_blk = g_new0(QVirtioBlkPCI, 1);
+    QVirtioBlk *interface = &virtio_blk->blk;
+    QOSGraphObject *obj = &virtio_blk->pci_vdev.obj;
+
+    virtio_pci_init(&virtio_blk->pci_vdev, pci_bus, addr);
+    interface->vdev = &virtio_blk->pci_vdev.vdev;
+
+    g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_BLOCK);
+
+    obj->get_driver = qvirtio_blk_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_blk_register_nodes(void)
+{
+    /* FIXME: every test using these two nodes needs to setup a
+     * -drive,id=drive0 otherwise QEMU is not going to start.
+     * Therefore, we do not include "produces" edge for virtio
+     * and pci-device yet.
+    */
+
+    char *arg = g_strdup_printf("id=drv0,drive=drive0,addr=%x.%x",
+                                PCI_SLOT, PCI_FN);
+
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(PCI_SLOT, PCI_FN),
+    };
+
+    QOSGraphEdgeOptions opts = { };
+
+    /* virtio-blk-device */
+    opts.extra_device_opts = "drive=drive0";
+    qos_node_create_driver("virtio-blk-device", virtio_blk_device_create);
+    qos_node_consumes("virtio-blk-device", "virtio-bus", &opts);
+    qos_node_produces("virtio-blk-device", "virtio-blk");
+
+    /* virtio-blk-pci */
+    opts.extra_device_opts = arg;
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-blk-pci", virtio_blk_pci_create);
+    qos_node_consumes("virtio-blk-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-blk-pci", "virtio-blk");
+
+    g_free(arg);
+}
+
+libqos_init(virtio_blk_register_nodes);
diff --git a/tests/libqos/virtio-blk.h b/tests/libqos/virtio-blk.h
new file mode 100644
index 0000000000..dc258496ba
--- /dev/null
+++ b/tests/libqos/virtio-blk.h
@@ -0,0 +1,40 @@
+/*
+ * 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 "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;
+};
diff --git a/tests/libqos/virtio-mmio.c b/tests/libqos/virtio-mmio.c
index 7aa8383338..3678c07ef0 100644
--- a/tests/libqos/virtio-mmio.c
+++ b/tests/libqos/virtio-mmio.c
@@ -12,74 +12,74 @@
 #include "libqos/virtio.h"
 #include "libqos/virtio-mmio.h"
 #include "libqos/malloc.h"
-#include "libqos/malloc-generic.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 = (QVirtioMMIODevice *)d;
-    return readb(dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + 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 = (QVirtioMMIODevice *)d;
-    return readw(dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + 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 = (QVirtioMMIODevice *)d;
-    return readl(dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + 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 = (QVirtioMMIODevice *)d;
-    return readq(dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off);
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
+    return qtest_readq(dev->qts, dev->addr + QVIRTIO_MMIO_DEVICE_SPECIFIC + off);
 }
 
 static uint32_t qvirtio_mmio_get_features(QVirtioDevice *d)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
-    writel(dev->addr + QVIRTIO_MMIO_HOST_FEATURES_SEL, 0);
-    return readl(dev->addr + QVIRTIO_MMIO_HOST_FEATURES);
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
+    qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES_SEL, 0);
+    return qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_HOST_FEATURES);
 }
 
 static void qvirtio_mmio_set_features(QVirtioDevice *d, uint32_t features)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
     dev->features = features;
-    writel(dev->addr + QVIRTIO_MMIO_GUEST_FEATURES_SEL, 0);
-    writel(dev->addr + QVIRTIO_MMIO_GUEST_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);
 }
 
 static uint32_t qvirtio_mmio_get_guest_features(QVirtioDevice *d)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
     return dev->features;
 }
 
 static uint8_t qvirtio_mmio_get_status(QVirtioDevice *d)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
-    return (uint8_t)readl(dev->addr + QVIRTIO_MMIO_DEVICE_STATUS);
+    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 = (QVirtioMMIODevice *)d;
-    writel(dev->addr + QVIRTIO_MMIO_DEVICE_STATUS, (uint32_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 = (QVirtioMMIODevice *)d;
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
     uint32_t isr;
 
-    isr = readl(dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 1;
+    isr = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 1;
     if (isr != 0) {
-        writel(dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 1);
+        qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 1);
         return true;
     }
 
@@ -88,12 +88,12 @@ static bool qvirtio_mmio_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq)
 
 static bool qvirtio_mmio_get_config_isr_status(QVirtioDevice *d)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
     uint32_t isr;
 
-    isr = readl(dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 2;
+    isr = qtest_readl(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_STATUS) & 2;
     if (isr != 0) {
-        writel(dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 2);
+        qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_INTERRUPT_ACK, 2);
         return true;
     }
 
@@ -102,34 +102,34 @@ static bool qvirtio_mmio_get_config_isr_status(QVirtioDevice *d)
 
 static void qvirtio_mmio_queue_select(QVirtioDevice *d, uint16_t index)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
-    writel(dev->addr + QVIRTIO_MMIO_QUEUE_SEL, (uint32_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(readl(dev->addr + QVIRTIO_MMIO_QUEUE_PFN), ==, 0);
+    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 = (QVirtioMMIODevice *)d;
-    return (uint16_t)readl(dev->addr + QVIRTIO_MMIO_QUEUE_NUM_MAX);
+    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, uint32_t pfn)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
-    writel(dev->addr + QVIRTIO_MMIO_QUEUE_PFN, pfn);
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
+    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 = (QVirtioMMIODevice *)d;
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
     QVirtQueue *vq;
     uint64_t addr;
 
     vq = g_malloc0(sizeof(*vq));
     qvirtio_mmio_queue_select(d, index);
-    writel(dev->addr + QVIRTIO_MMIO_QUEUE_ALIGN, dev->page_size);
+    qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_ALIGN, dev->page_size);
 
     vq->index = index;
     vq->size = qvirtio_mmio_get_queue_size(d);
@@ -139,7 +139,7 @@ static QVirtQueue *qvirtio_mmio_virtqueue_setup(QVirtioDevice *d,
     vq->indirect = (dev->features & (1u << VIRTIO_RING_F_INDIRECT_DESC)) != 0;
     vq->event = (dev->features & (1u << VIRTIO_RING_F_EVENT_IDX)) != 0;
 
-    writel(dev->addr + QVIRTIO_MMIO_QUEUE_NUM, vq->size);
+    qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NUM, vq->size);
 
     /* Check different than 0 */
     g_assert_cmpint(vq->size, !=, 0);
@@ -163,8 +163,8 @@ static void qvirtio_mmio_virtqueue_cleanup(QVirtQueue *vq,
 
 static void qvirtio_mmio_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq)
 {
-    QVirtioMMIODevice *dev = (QVirtioMMIODevice *)d;
-    writel(dev->addr + QVIRTIO_MMIO_QUEUE_NOTIFY, vq->index);
+    QVirtioMMIODevice *dev = container_of(d, QVirtioMMIODevice, vdev);
+    qtest_writel(dev->qts, dev->addr + QVIRTIO_MMIO_QUEUE_NOTIFY, vq->index);
 }
 
 const QVirtioBus qvirtio_mmio = {
@@ -187,21 +187,45 @@ const QVirtioBus qvirtio_mmio = {
     .virtqueue_kick = qvirtio_mmio_virtqueue_kick,
 };
 
-QVirtioMMIODevice *qvirtio_mmio_init_device(uint64_t addr, uint32_t page_size)
+static void *qvirtio_mmio_get_driver(void *obj, const char *interface)
 {
-    QVirtioMMIODevice *dev;
-    uint32_t magic;
-    dev = g_malloc0(sizeof(*dev));
+    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);
+}
 
-    magic = readl(addr + QVIRTIO_MMIO_MAGIC_VALUE);
+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->qts = qts;
     dev->addr = addr;
     dev->page_size = page_size;
-    dev->vdev.device_type = readl(addr + QVIRTIO_MMIO_DEVICE_ID);
+    dev->vdev.device_type = qtest_readl(qts, addr + QVIRTIO_MMIO_DEVICE_ID);
     dev->vdev.bus = &qvirtio_mmio;
 
-    writel(addr + QVIRTIO_MMIO_GUEST_PAGE_SIZE, page_size);
+    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;
+}
 
-    return dev;
+static void virtio_mmio_register_nodes(void)
+{
+    qos_node_create_driver("virtio-mmio", NULL);
+    qos_node_produces("virtio-mmio", "virtio-bus");
 }
+
+libqos_init(virtio_mmio_register_nodes);
diff --git a/tests/libqos/virtio-mmio.h b/tests/libqos/virtio-mmio.h
index e3e52b9ce1..17a17141c3 100644
--- a/tests/libqos/virtio-mmio.h
+++ b/tests/libqos/virtio-mmio.h
@@ -11,6 +11,7 @@
 #define LIBQOS_VIRTIO_MMIO_H
 
 #include "libqos/virtio.h"
+#include "libqos/qgraph.h"
 
 #define QVIRTIO_MMIO_MAGIC_VALUE        0x000
 #define QVIRTIO_MMIO_VERSION            0x004
@@ -33,7 +34,9 @@
 #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 */
@@ -41,6 +44,7 @@ typedef struct QVirtioMMIODevice {
 
 extern const QVirtioBus qvirtio_mmio;
 
-QVirtioMMIODevice *qvirtio_mmio_init_device(uint64_t addr, uint32_t page_size);
+void qvirtio_mmio_init_device(QVirtioMMIODevice *dev, QTestState *qts,
+                              uint64_t addr, uint32_t page_size);
 
 #endif
diff --git a/tests/libqos/virtio-net.c b/tests/libqos/virtio-net.c
new file mode 100644
index 0000000000..61c56170e9
--- /dev/null
+++ b/tests/libqos/virtio-net.c
@@ -0,0 +1,195 @@
+/*
+ * 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 "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 |
+                  (1u << VIRTIO_RING_F_INDIRECT_DESC) |
+                  (1u << VIRTIO_RING_F_EVENT_IDX));
+    qvirtio_set_features(vdev, features);
+
+    if (features & (1u << VIRTIO_NET_F_MQ)) {
+        interface->n_queues = qvirtio_config_readw(vdev, 8) * 2;
+    } else {
+        interface->n_queues = 2;
+    }
+
+    interface->queues = g_new(QVirtQueue *, interface->n_queues);
+    for (i = 0; i < interface->n_queues; i++) {
+        interface->queues[i] = qvirtqueue_setup(vdev, alloc, i);
+    }
+    qvirtio_set_driver_ok(vdev);
+}
+
+/* virtio-net-device */
+static void qvirtio_net_device_destructor(QOSGraphObject *obj)
+{
+    QVirtioNetDevice *v_net = (QVirtioNetDevice *) obj;
+    virtio_net_cleanup(&v_net->net);
+}
+
+static void qvirtio_net_device_start_hw(QOSGraphObject *obj)
+{
+    QVirtioNetDevice *v_net = (QVirtioNetDevice *) obj;
+    QVirtioNet *interface = &v_net->net;
+
+    virtio_net_setup(interface);
+}
+
+static void *qvirtio_net_get_driver(QVirtioNet *v_net,
+                                    const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-net")) {
+        return v_net;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_net->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-net-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *qvirtio_net_device_get_driver(void *object,
+                                           const char *interface)
+{
+    QVirtioNetDevice *v_net = object;
+    return qvirtio_net_get_driver(&v_net->net, interface);
+}
+
+static void *virtio_net_device_create(void *virtio_dev,
+                                          QGuestAllocator *t_alloc,
+                                          void *addr)
+{
+    QVirtioNetDevice *virtio_ndevice = g_new0(QVirtioNetDevice, 1);
+    QVirtioNet *interface = &virtio_ndevice->net;
+
+    interface->vdev = virtio_dev;
+    alloc = t_alloc;
+
+    virtio_ndevice->obj.destructor = qvirtio_net_device_destructor;
+    virtio_ndevice->obj.get_driver = qvirtio_net_device_get_driver;
+    virtio_ndevice->obj.start_hw = qvirtio_net_device_start_hw;
+
+    return &virtio_ndevice->obj;
+}
+
+/* virtio-net-pci */
+static void qvirtio_net_pci_destructor(QOSGraphObject *obj)
+{
+    QVirtioNetPCI *v_net = (QVirtioNetPCI *) obj;
+    QVirtioNet *interface = &v_net->net;
+    QOSGraphObject *pci_vobj =  &v_net->pci_vdev.obj;
+
+    virtio_net_cleanup(interface);
+    qvirtio_pci_destructor(pci_vobj);
+}
+
+static void qvirtio_net_pci_start_hw(QOSGraphObject *obj)
+{
+    QVirtioNetPCI *v_net = (QVirtioNetPCI *) obj;
+    QVirtioNet *interface = &v_net->net;
+    QOSGraphObject *pci_vobj =  &v_net->pci_vdev.obj;
+
+    qvirtio_pci_start_hw(pci_vobj);
+    virtio_net_setup(interface);
+}
+
+static void *qvirtio_net_pci_get_driver(void *object,
+                                            const char *interface)
+{
+    QVirtioNetPCI *v_net = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_net->pci_vdev.pdev;
+    }
+    return qvirtio_net_get_driver(&v_net->net, interface);
+}
+
+static void *virtio_net_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                  void *addr)
+{
+    QVirtioNetPCI *virtio_bpci = g_new0(QVirtioNetPCI, 1);
+    QVirtioNet *interface = &virtio_bpci->net;
+    QOSGraphObject *obj = &virtio_bpci->pci_vdev.obj;
+
+    virtio_pci_init(&virtio_bpci->pci_vdev, pci_bus, addr);
+    interface->vdev = &virtio_bpci->pci_vdev.vdev;
+    alloc = t_alloc;
+
+    g_assert_cmphex(interface->vdev->device_type, ==, VIRTIO_ID_NET);
+
+    obj->destructor = qvirtio_net_pci_destructor;
+    obj->start_hw = qvirtio_net_pci_start_hw;
+    obj->get_driver = qvirtio_net_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_net_register_nodes(void)
+{
+    /* FIXME: every test using these nodes needs to setup a
+     * -netdev socket,id=hs0 otherwise QEMU is not going to start.
+     * Therefore, we do not include "produces" edge for virtio
+     * and pci-device yet.
+     */
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    QOSGraphEdgeOptions opts = { };
+
+    /* virtio-net-device */
+    opts.extra_device_opts = "netdev=hs0";
+    qos_node_create_driver("virtio-net-device",
+                            virtio_net_device_create);
+    qos_node_consumes("virtio-net-device", "virtio-bus", &opts);
+    qos_node_produces("virtio-net-device", "virtio-net");
+
+    /* virtio-net-pci */
+    opts.extra_device_opts = "netdev=hs0,addr=04.0";
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-net-pci", virtio_net_pci_create);
+    qos_node_consumes("virtio-net-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-net-pci", "virtio-net");
+}
+
+libqos_init(virtio_net_register_nodes);
diff --git a/tests/libqos/virtio-net.h b/tests/libqos/virtio-net.h
new file mode 100644
index 0000000000..28238a1b20
--- /dev/null
+++ b/tests/libqos/virtio-net.h
@@ -0,0 +1,41 @@
+/*
+ * 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 "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;
+    QVirtQueue **queues;
+};
+
+struct QVirtioNetPCI {
+    QVirtioPCIDevice pci_vdev;
+    QVirtioNet net;
+};
+
+struct QVirtioNetDevice {
+    QOSGraphObject obj;
+    QVirtioNet net;
+};
diff --git a/tests/libqos/virtio-pci.c b/tests/libqos/virtio-pci.c
index 550dede0a2..993d347830 100644
--- a/tests/libqos/virtio-pci.c
+++ b/tests/libqos/virtio-pci.c
@@ -15,68 +15,39 @@
 #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"
 
-typedef struct QVirtioPCIForeachData {
-    void (*func)(QVirtioDevice *d, void *data);
-    uint16_t device_type;
-    bool has_slot;
-    int slot;
-    void *user_data;
-} QVirtioPCIForeachData;
-
-void qvirtio_pci_device_free(QVirtioPCIDevice *dev)
-{
-    g_free(dev->pdev);
-    g_free(dev);
-}
-
-static QVirtioPCIDevice *qpcidevice_to_qvirtiodevice(QPCIDevice *pdev)
-{
-    QVirtioPCIDevice *vpcidev;
-    vpcidev = g_malloc0(sizeof(*vpcidev));
-
-    if (pdev) {
-        vpcidev->pdev = pdev;
-        vpcidev->vdev.device_type =
-                            qpci_config_readw(vpcidev->pdev, PCI_SUBSYSTEM_ID);
-    }
-
-    vpcidev->config_msix_entry = -1;
-
-    return vpcidev;
-}
+/* 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.
+ */
 
-static void qvirtio_pci_foreach_callback(
-                        QPCIDevice *dev, int devfn, void *data)
+static inline bool qvirtio_pci_is_big_endian(QVirtioPCIDevice *dev)
 {
-    QVirtioPCIForeachData *d = data;
-    QVirtioPCIDevice *vpcidev = qpcidevice_to_qvirtiodevice(dev);
-
-    if (vpcidev->vdev.device_type == d->device_type &&
-        (!d->has_slot || vpcidev->pdev->devfn == d->slot << 3)) {
-        d->func(&vpcidev->vdev, d->user_data);
-    } else {
-        qvirtio_pci_device_free(vpcidev);
-    }
-}
+    QPCIBus *bus = dev->pdev->bus;
 
-static void qvirtio_pci_assign_device(QVirtioDevice *d, void *data)
-{
-    QVirtioPCIDevice **vpcidev = data;
-    assert(!*vpcidev);
-    *vpcidev = (QVirtioPCIDevice *)d;
+    /* FIXME: virtio 1.0 is always little-endian */
+    return qtest_big_endian(bus->qts);
 }
 
 #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 = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     return qpci_io_readb(dev->pdev, dev->bar, CONFIG_BASE(dev) + off);
 }
 
@@ -85,12 +56,12 @@ static uint8_t qvirtio_pci_config_readb(QVirtioDevice *d, uint64_t off)
  * so with a big-endian guest the order has been reversed,
  * reverse it again
  * virtio-1.0 is always little-endian, like PCI, but this
- * case will be managed inside qvirtio_is_big_endian()
+ * case will be managed inside qvirtio_pci_is_big_endian()
  */
 
 static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, uint64_t off)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     uint16_t value;
 
     value = qpci_io_readw(dev->pdev, dev->bar, CONFIG_BASE(dev) + off);
@@ -102,7 +73,7 @@ static uint16_t qvirtio_pci_config_readw(QVirtioDevice *d, uint64_t off)
 
 static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, uint64_t off)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     uint32_t value;
 
     value = qpci_io_readl(dev->pdev, dev->bar, CONFIG_BASE(dev) + off);
@@ -114,7 +85,7 @@ static uint32_t qvirtio_pci_config_readl(QVirtioDevice *d, uint64_t off)
 
 static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, uint64_t off)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     uint64_t val;
 
     val = qpci_io_readq(dev->pdev, dev->bar, CONFIG_BASE(dev) + off);
@@ -127,37 +98,37 @@ static uint64_t qvirtio_pci_config_readq(QVirtioDevice *d, uint64_t off)
 
 static uint32_t qvirtio_pci_get_features(QVirtioDevice *d)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)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, uint32_t features)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_GUEST_FEATURES, features);
 }
 
 static uint32_t qvirtio_pci_get_guest_features(QVirtioDevice *d)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)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 = (QVirtioPCIDevice *)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 = (QVirtioPCIDevice *)d;
+    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 = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     QVirtQueuePCI *vqpci = (QVirtQueuePCI *)vq;
     uint32_t data;
 
@@ -182,7 +153,7 @@ static bool qvirtio_pci_get_queue_isr_status(QVirtioDevice *d, QVirtQueue *vq)
 
 static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     uint32_t data;
 
     if (dev->pdev->msix_enabled) {
@@ -206,19 +177,19 @@ static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d)
 
 static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    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 = (QVirtioPCIDevice *)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, uint32_t pfn)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     qpci_io_writel(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_PFN, pfn);
 }
 
@@ -270,7 +241,7 @@ static void qvirtio_pci_virtqueue_cleanup(QVirtQueue *vq,
 
 static void qvirtio_pci_virtqueue_kick(QVirtioDevice *d, QVirtQueue *vq)
 {
-    QVirtioPCIDevice *dev = (QVirtioPCIDevice *)d;
+    QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev);
     qpci_io_writew(dev->pdev, dev->bar, VIRTIO_PCI_QUEUE_NOTIFY, vq->index);
 }
 
@@ -294,47 +265,6 @@ const QVirtioBus qvirtio_pci = {
     .virtqueue_kick = qvirtio_pci_virtqueue_kick,
 };
 
-static void qvirtio_pci_foreach(QPCIBus *bus, uint16_t device_type,
-                bool has_slot, int slot,
-                void (*func)(QVirtioDevice *d, void *data), void *data)
-{
-    QVirtioPCIForeachData d = { .func = func,
-                                .device_type = device_type,
-                                .has_slot = has_slot,
-                                .slot = slot,
-                                .user_data = data };
-
-    qpci_device_foreach(bus, PCI_VENDOR_ID_REDHAT_QUMRANET, -1,
-                        qvirtio_pci_foreach_callback, &d);
-}
-
-QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type)
-{
-    QVirtioPCIDevice *dev = NULL;
-
-    qvirtio_pci_foreach(bus, device_type, false, 0,
-                        qvirtio_pci_assign_device, &dev);
-
-    if (dev) {
-        dev->vdev.bus = &qvirtio_pci;
-    }
-
-    return dev;
-}
-
-QVirtioPCIDevice *qvirtio_pci_device_find_slot(QPCIBus *bus,
-                                               uint16_t device_type, int slot)
-{
-    QVirtioPCIDevice *dev = NULL;
-
-    qvirtio_pci_foreach(bus, device_type, true, slot,
-                        qvirtio_pci_assign_device, &dev);
-
-    dev->vdev.bus = &qvirtio_pci;
-
-    return dev;
-}
-
 void qvirtio_pci_device_enable(QVirtioPCIDevice *d)
 {
     qpci_device_enable(d->pdev);
@@ -416,3 +346,54 @@ void qvirtio_pci_set_msix_configuration_vector(QVirtioPCIDevice *d,
     vector = qpci_io_readw(d->pdev, d->bar, VIRTIO_MSI_CONFIG_VECTOR);
     g_assert_cmphex(vector, !=, VIRTIO_MSI_NO_VECTOR);
 }
+
+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_from_pcidev(QVirtioPCIDevice *dev, QPCIDevice *pci_dev)
+{
+    dev->pdev = pci_dev;
+    dev->vdev.device_type = qpci_config_readw(pci_dev, PCI_SUBSYSTEM_ID);
+
+    dev->config_msix_entry = -1;
+
+    dev->vdev.bus = &qvirtio_pci;
+    dev->vdev.big_endian = qvirtio_pci_is_big_endian(dev);
+
+    /* each virtio-xxx-pci device should override at least this function */
+    dev->obj.get_driver = NULL;
+    dev->obj.start_hw = qvirtio_pci_start_hw;
+    dev->obj.destructor = qvirtio_pci_destructor;
+}
+
+void virtio_pci_init(QVirtioPCIDevice *dev, QPCIBus *bus, QPCIAddress * addr)
+{
+    QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn);
+    g_assert_nonnull(pci_dev);
+    qvirtio_pci_init_from_pcidev(dev, pci_dev);
+}
+
+QVirtioPCIDevice *virtio_pci_new(QPCIBus *bus, QPCIAddress * addr)
+{
+    QVirtioPCIDevice *dev;
+    QPCIDevice *pci_dev = qpci_device_find(bus, addr->devfn);
+    if (!pci_dev) {
+        return NULL;
+    }
+
+    dev = g_new0(QVirtioPCIDevice, 1);
+    qvirtio_pci_init_from_pcidev(dev, pci_dev);
+    dev->obj.free = g_free;
+    return dev;
+}
diff --git a/tests/libqos/virtio-pci.h b/tests/libqos/virtio-pci.h
index 6ef19094cb..728b4715f1 100644
--- a/tests/libqos/virtio-pci.h
+++ b/tests/libqos/virtio-pci.h
@@ -12,8 +12,10 @@
 
 #include "libqos/virtio.h"
 #include "libqos/pci.h"
+#include "libqos/qgraph.h"
 
 typedef struct QVirtioPCIDevice {
+    QOSGraphObject obj;
     QVirtioDevice vdev;
     QPCIDevice *pdev;
     QPCIBar bar;
@@ -31,10 +33,18 @@ typedef struct QVirtQueuePCI {
 
 extern const QVirtioBus qvirtio_pci;
 
-QVirtioPCIDevice *qvirtio_pci_device_find(QPCIBus *bus, uint16_t device_type);
-QVirtioPCIDevice *qvirtio_pci_device_find_slot(QPCIBus *bus,
-                                               uint16_t device_type, int slot);
-void qvirtio_pci_device_free(QVirtioPCIDevice *dev);
+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);
diff --git a/tests/libqos/virtio-rng.c b/tests/libqos/virtio-rng.c
new file mode 100644
index 0000000000..a1d2c7671c
--- /dev/null
+++ b/tests/libqos/virtio-rng.c
@@ -0,0 +1,110 @@
+/*
+ * 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 "libqos/virtio-rng.h"
+
+/* virtio-rng-device */
+static void *qvirtio_rng_get_driver(QVirtioRng *v_rng,
+                                    const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-rng")) {
+        return v_rng;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_rng->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-rng-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *qvirtio_rng_device_get_driver(void *object,
+                                           const char *interface)
+{
+    QVirtioRngDevice *v_rng = object;
+    return qvirtio_rng_get_driver(&v_rng->rng, interface);
+}
+
+static void *virtio_rng_device_create(void *virtio_dev,
+                                      QGuestAllocator *t_alloc,
+                                      void *addr)
+{
+    QVirtioRngDevice *virtio_rdevice = g_new0(QVirtioRngDevice, 1);
+    QVirtioRng *interface = &virtio_rdevice->rng;
+
+    interface->vdev = virtio_dev;
+
+    virtio_rdevice->obj.get_driver = qvirtio_rng_device_get_driver;
+
+    return &virtio_rdevice->obj;
+}
+
+/* virtio-rng-pci */
+static void *qvirtio_rng_pci_get_driver(void *object, const char *interface)
+{
+    QVirtioRngPCI *v_rng = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_rng->pci_vdev.pdev;
+    }
+    return qvirtio_rng_get_driver(&v_rng->rng, interface);
+}
+
+static void *virtio_rng_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                   void *addr)
+{
+    QVirtioRngPCI *virtio_rpci = g_new0(QVirtioRngPCI, 1);
+    QVirtioRng *interface = &virtio_rpci->rng;
+    QOSGraphObject *obj = &virtio_rpci->pci_vdev.obj;
+
+    virtio_pci_init(&virtio_rpci->pci_vdev, pci_bus, addr);
+    interface->vdev = &virtio_rpci->pci_vdev.vdev;
+
+    obj->get_driver = qvirtio_rng_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_rng_register_nodes(void)
+{
+    QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "addr=04.0",
+    };
+
+    /* virtio-rng-device */
+    qos_node_create_driver("virtio-rng-device", virtio_rng_device_create);
+    qos_node_consumes("virtio-rng-device", "virtio-bus", NULL);
+    qos_node_produces("virtio-rng-device", "virtio");
+    qos_node_produces("virtio-rng-device", "virtio-rng");
+
+    /* virtio-rng-pci */
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-rng-pci", virtio_rng_pci_create);
+    qos_node_consumes("virtio-rng-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-rng-pci", "pci-device");
+    qos_node_produces("virtio-rng-pci", "virtio");
+    qos_node_produces("virtio-rng-pci", "virtio-rng");
+}
+
+libqos_init(virtio_rng_register_nodes);
diff --git a/tests/libqos/virtio-rng.h b/tests/libqos/virtio-rng.h
new file mode 100644
index 0000000000..fbba988875
--- /dev/null
+++ b/tests/libqos/virtio-rng.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "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;
+};
diff --git a/tests/libqos/virtio-scsi.c b/tests/libqos/virtio-scsi.c
new file mode 100644
index 0000000000..482684d0bc
--- /dev/null
+++ b/tests/libqos/virtio-scsi.c
@@ -0,0 +1,117 @@
+/*
+ * 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 "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://,format=raw",
+        .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0",
+    };
+
+    /* virtio-scsi-device */
+    opts.extra_device_opts = "id=vs0";
+    qos_node_create_driver("virtio-scsi-device",
+                            virtio_scsi_device_create);
+    qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts);
+    qos_node_produces("virtio-scsi-device", "virtio-scsi");
+
+    /* virtio-scsi-pci */
+    opts.extra_device_opts = "id=vs0,addr=04.0";
+    add_qpci_address(&opts, &addr);
+    qos_node_create_driver("virtio-scsi-pci", virtio_scsi_pci_create);
+    qos_node_consumes("virtio-scsi-pci", "pci-bus", &opts);
+    qos_node_produces("virtio-scsi-pci", "pci-device");
+    qos_node_produces("virtio-scsi-pci", "virtio-scsi");
+}
+
+libqos_init(virtio_scsi_register_nodes);
diff --git a/tests/libqos/virtio-scsi.h b/tests/libqos/virtio-scsi.h
new file mode 100644
index 0000000000..17a47beddc
--- /dev/null
+++ b/tests/libqos/virtio-scsi.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "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;
+};
diff --git a/tests/libqos/virtio-serial.c b/tests/libqos/virtio-serial.c
new file mode 100644
index 0000000000..91cedefb8d
--- /dev/null
+++ b/tests/libqos/virtio-serial.c
@@ -0,0 +1,110 @@
+/*
+ * 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 "libqos/virtio-serial.h"
+
+static void *qvirtio_serial_get_driver(QVirtioSerial *v_serial,
+                                       const char *interface)
+{
+    if (!g_strcmp0(interface, "virtio-serial")) {
+        return v_serial;
+    }
+    if (!g_strcmp0(interface, "virtio")) {
+        return v_serial->vdev;
+    }
+
+    fprintf(stderr, "%s not present in virtio-serial-device\n", interface);
+    g_assert_not_reached();
+}
+
+static void *qvirtio_serial_device_get_driver(void *object,
+                                              const char *interface)
+{
+    QVirtioSerialDevice *v_serial = object;
+    return qvirtio_serial_get_driver(&v_serial->serial, interface);
+}
+
+static void *virtio_serial_device_create(void *virtio_dev,
+                                         QGuestAllocator *t_alloc,
+                                         void *addr)
+{
+    QVirtioSerialDevice *virtio_device = g_new0(QVirtioSerialDevice, 1);
+    QVirtioSerial *interface = &virtio_device->serial;
+
+    interface->vdev = virtio_dev;
+
+    virtio_device->obj.get_driver = qvirtio_serial_device_get_driver;
+
+    return &virtio_device->obj;
+}
+
+/* virtio-serial-pci */
+static void *qvirtio_serial_pci_get_driver(void *object, const char *interface)
+{
+    QVirtioSerialPCI *v_serial = object;
+    if (!g_strcmp0(interface, "pci-device")) {
+        return v_serial->pci_vdev.pdev;
+    }
+    return qvirtio_serial_get_driver(&v_serial->serial, interface);
+}
+
+static void *virtio_serial_pci_create(void *pci_bus, QGuestAllocator *t_alloc,
+                                      void *addr)
+{
+    QVirtioSerialPCI *virtio_spci = g_new0(QVirtioSerialPCI, 1);
+    QVirtioSerial *interface = &virtio_spci->serial;
+    QOSGraphObject *obj = &virtio_spci->pci_vdev.obj;
+
+    virtio_pci_init(&virtio_spci->pci_vdev, pci_bus, addr);
+    interface->vdev = &virtio_spci->pci_vdev.vdev;
+
+    obj->get_driver = qvirtio_serial_pci_get_driver;
+
+    return obj;
+}
+
+static void virtio_serial_register_nodes(void)
+{
+   QPCIAddress addr = {
+        .devfn = QPCI_DEVFN(4, 0),
+    };
+
+    QOSGraphEdgeOptions edge_opts = { };
+
+    /* virtio-serial-device */
+    edge_opts.extra_device_opts = "id=vser0";
+    qos_node_create_driver("virtio-serial-device",
+                            virtio_serial_device_create);
+    qos_node_consumes("virtio-serial-device", "virtio-bus", &edge_opts);
+    qos_node_produces("virtio-serial-device", "virtio");
+    qos_node_produces("virtio-serial-device", "virtio-serial");
+
+    /* virtio-serial-pci */
+    edge_opts.extra_device_opts = "id=vser0,addr=04.0";
+    add_qpci_address(&edge_opts, &addr);
+    qos_node_create_driver("virtio-serial-pci", virtio_serial_pci_create);
+    qos_node_consumes("virtio-serial-pci", "pci-bus", &edge_opts);
+    qos_node_produces("virtio-serial-pci", "pci-device");
+    qos_node_produces("virtio-serial-pci", "virtio");
+    qos_node_produces("virtio-serial-pci", "virtio-serial");
+}
+
+libqos_init(virtio_serial_register_nodes);
diff --git a/tests/libqos/virtio-serial.h b/tests/libqos/virtio-serial.h
new file mode 100644
index 0000000000..b7e2a5d178
--- /dev/null
+++ b/tests/libqos/virtio-serial.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "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;
+};
diff --git a/tests/libqos/virtio.c b/tests/libqos/virtio.c
index 0dad5c19ac..5e8f39b4d3 100644
--- a/tests/libqos/virtio.c
+++ b/tests/libqos/virtio.c
@@ -40,6 +40,7 @@ uint32_t qvirtio_get_features(QVirtioDevice *d)
 
 void qvirtio_set_features(QVirtioDevice *d, uint32_t features)
 {
+    d->features = features;
     d->bus->set_features(d, features);
 }
 
@@ -349,19 +350,14 @@ void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx)
     writew(vq->avail + 4 + (2 * vq->size), idx);
 }
 
-/*
- * qvirtio_get_dev_type:
- * Returns: the preferred virtio bus/device type for the current architecture.
- */
-const char *qvirtio_get_dev_type(void)
+void qvirtio_start_device(QVirtioDevice *vdev)
 {
-    const char *arch = qtest_get_arch();
-
-    if (g_str_equal(arch, "arm") || g_str_equal(arch, "aarch64")) {
-        return "device";  /* for virtio-mmio */
-    } else if (g_str_equal(arch, "s390x")) {
-        return "ccw";
-    } else {
-        return "pci";
-    }
+    qvirtio_reset(vdev);
+    qvirtio_set_acknowledge(vdev);
+    qvirtio_set_driver(vdev);
+}
+
+bool qvirtio_is_big_endian(QVirtioDevice *d)
+{
+    return d->big_endian;
 }
diff --git a/tests/libqos/virtio.h b/tests/libqos/virtio.h
index 69b5b13840..51d2359ace 100644
--- a/tests/libqos/virtio.h
+++ b/tests/libqos/virtio.h
@@ -21,6 +21,8 @@ typedef struct QVirtioDevice {
     const QVirtioBus *bus;
     /* Device type */
     uint16_t device_type;
+    uint64_t features;
+    bool big_endian;
 } QVirtioDevice;
 
 typedef struct QVirtQueue {
@@ -90,12 +92,6 @@ struct QVirtioBus {
     void (*virtqueue_kick)(QVirtioDevice *d, QVirtQueue *vq);
 };
 
-static inline bool qvirtio_is_big_endian(QVirtioDevice *d)
-{
-    /* FIXME: virtio 1.0 is always little-endian */
-    return qtest_big_endian(global_qtest);
-}
-
 static inline uint32_t qvring_size(uint32_t num, uint32_t align)
 {
     return ((sizeof(struct vring_desc) * num + sizeof(uint16_t) * (3 + num)
@@ -109,6 +105,7 @@ uint32_t qvirtio_config_readl(QVirtioDevice *d, uint64_t addr);
 uint64_t qvirtio_config_readq(QVirtioDevice *d, uint64_t addr);
 uint32_t qvirtio_get_features(QVirtioDevice *d);
 void qvirtio_set_features(QVirtioDevice *d, uint32_t features);
+bool qvirtio_is_big_endian(QVirtioDevice *d);
 
 void qvirtio_reset(QVirtioDevice *d);
 void qvirtio_set_acknowledge(QVirtioDevice *d);
@@ -145,6 +142,6 @@ bool qvirtqueue_get_buf(QVirtQueue *vq, uint32_t *desc_idx, uint32_t *len);
 
 void qvirtqueue_set_used_event(QVirtQueue *vq, uint16_t idx);
 
-const char *qvirtio_get_dev_type(void);
+void qvirtio_start_device(QVirtioDevice *vdev);
 
 #endif
diff --git a/tests/libqos/x86_64_pc-machine.c b/tests/libqos/x86_64_pc-machine.c
new file mode 100644
index 0000000000..8bd0360ba9
--- /dev/null
+++ b/tests/libqos/x86_64_pc-machine.c
@@ -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 "libqos/qgraph.h"
+#include "pci-pc.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);