Qtest Driver Framework

In order to test a specific driver, plain libqos tests need to take care of booting QEMU with the right machine and devices. This makes each test “hardcoded” for a specific configuration, reducing the possible coverage that it can reach.

For example, the sdhci device is supported on both x86_64 and ARM boards, therefore a generic sdhci test should test all machines and drivers that support that device. Using only libqos APIs, the test has to manually take care of covering all the setups, and build the correct command line.

This also introduces backward compatibility issues: if a device/driver command line name is changed, all tests that use that will not work properly anymore and need to be adjusted.

The aim of qgraph is to create a graph of drivers, machines and tests such that a test aimed to a certain driver does not have to care of booting the right QEMU machine, pick the right device, build the command line and so on. Instead, it only defines what type of device it is testing (interface in qgraph terms) and the framework takes care of covering all supported types of devices and machine architectures.

Following the above example, an interface would be sdhci, so the sdhci-test should only care of linking its qgraph node with that interface. In this way, if the command line of a sdhci driver is changed, only the respective qgraph driver node has to be adjusted.

QGraph concepts

The graph is composed by nodes that represent machines, drivers, tests and edges that define the relationships between them (CONSUMES, PRODUCES, and CONTAINS).

Nodes

A node can be of four types:

  • QNODE_MACHINE: for example arm/raspi2b

  • 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 automatically instantiated when a node consumes or produces it. An interface is simply a struct that abstracts the various drivers for the same type of device, and offers an API to the nodes that use it (“consume” relation in qgraph terms) that is implemented/backed up by the drivers that implement it (“produce” relation in qgraph terms).

  • QNODE_TEST: for example sdhci-test. A test consumes an interface and tests the functions provided by it.

Notes for the nodes:

  • QNODE_MACHINE: each machine struct must have a QGuestAllocator and implement get_driver() to return the allocator mapped to the interface “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

Edges

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

Execution steps

The 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.

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 = {
    .before_cmd_line = "-drive id=drv0,if=none,file=null-co://,"
                       "file.read-zeroes=on,format=raw",
    .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0",

    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);

Will produce the following command line: -drive id=drv0,if=none,file=null-co://, -device virtio-scsi-device,id=vs0 -device scsi-hd,bus=vs0.0,drive=drv0

Troubleshooting unavailable tests

If there is no path from an available machine to a test then that test will be unavailable and won’t execute. This can happen if a test or driver did not set up its qgraph node correctly. It can also happen if the necessary machine type or device is missing from the QEMU binary because it was compiled out or otherwise.

It is possible to troubleshoot unavailable tests by running:

$ QTEST_QEMU_BINARY=build/qemu-system-x86_64 build/tests/qtest/qos-test --verbose
# ALL QGRAPH EDGES: {
#   src='virtio-net'
#      |-> dest='virtio-net-tests/vhost-user/multiqueue' type=2 (node=0x559142109e30)
#      |-> dest='virtio-net-tests/vhost-user/migrate' type=2 (node=0x559142109d00)
#   src='virtio-net-pci'
#      |-> dest='virtio-net' type=1 (node=0x55914210d740)
#   src='pci-bus'
#      |-> dest='virtio-net-pci' type=2 (node=0x55914210d880)
#   src='pci-bus-pc'
#      |-> dest='pci-bus' type=1 (node=0x559142103f40)
#   src='i440FX-pcihost'
#      |-> dest='pci-bus-pc' type=0 (node=0x55914210ac70)
#   src='x86_64/pc'
#      |-> dest='i440FX-pcihost' type=0 (node=0x5591421117f0)
#   src=''
#      |-> dest='x86_64/pc' type=0 (node=0x559142111600)
#      |-> dest='arm/raspi2b' type=0 (node=0x559142110740)
...
# }
# ALL QGRAPH NODES: {
#   name='virtio-net-tests/announce-self' type=3 cmd_line='(null)' [available]
#   name='arm/raspi2b' type=0 cmd_line='-M raspi2b ' [UNAVAILABLE]
...
# }

The virtio-net-tests/announce-self test is listed as “available” in the “ALL QGRAPH NODES” output. This means the test will execute. We can follow the qgraph path in the “ALL QGRAPH EDGES” output as follows: ‘’ -> ‘x86_64/pc’ -> ‘i440FX-pcihost’ -> ‘pci-bus-pc’ -> ‘pci-bus’ -> ‘virtio-net-pci’ -> ‘virtio-net’. The root of the qgraph is ‘’ and the depth first search begins there.

The arm/raspi2b machine node is listed as “UNAVAILABLE”. Although it is reachable from the root via ‘’ -> ‘arm/raspi2b’ the node is unavailable because the QEMU binary did not list it when queried by the framework. This is expected because we used the qemu-system-x86_64 binary which does not support ARM machine types.

If a test is unexpectedly listed as “UNAVAILABLE”, first check that the “ALL QGRAPH EDGES” output reports edge connectivity from the root (‘’) to the test. If there is no connectivity then the qgraph nodes were not set up correctly and the driver or test code is incorrect. If there is connectivity, check the availability of each node in the path in the “ALL QGRAPH NODES” output. The first unavailable node in the path is the reason why the test is unavailable. Typically this is because the QEMU binary lacks support for the necessary machine type or device.

Creating a new driver and its interface

Here we continue the sdhci use case, with the following scenario:

  • sdhci-test aims to test the read[q,w], writeq functions offered by the sdhci drivers.

  • The current sdhci device is supported by both x86_64/pc and ARM (in this example we focus on the arm-raspi2b) machines.

  • QEMU offers 2 types of drivers: QSDHCI_MemoryMapped for ARM and QSDHCI_PCI for x86_64/pc. Both implement the read[q,w], writeq functions.

In order to implement such scenario in qgraph, the test developer needs to:

  • Create the x86_64/pc machine node. This machine uses the pci-bus architecture so it contains a PCI driver, pci-bus-pc. The actual path is

    x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc --produces--> pci-bus.

    For the sake of this example, we do not focus on the PCI interface implementation.

  • Create the sdhci-pci driver node, representing QSDHCI_PCI. The driver uses the PCI bus (and its API), so it must consume the pci-bus generic interface (which abstracts all the pci drivers available)

    sdhci-pci --consumes--> pci-bus

  • Create an arm/raspi2b machine node. This machine contains a generic-sdhci memory mapped sdhci driver node, representing QSDHCI_MemoryMapped.

    arm/raspi2b --contains--> generic-sdhci

  • Create the sdhci interface node. This interface offers the functions that are shared by all sdhci devices. The interface is produced by sdhci-pci and generic-sdhci, the available architecture-specific drivers.

    sdhci-pci --produces--> sdhci

    generic-sdhci --produces--> sdhci

  • Create the sdhci-test test node. The test consumes the sdhci interface, using its API. It doesn’t need to look at the supported machines or drivers.

    sdhci-test --consumes--> sdhci

arm-raspi2b machine, simplified from tests/qtest/libqos/arm-raspi2-machine.c:

#include "qgraph.h"

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/raspi2b\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/raspi2b\n", device);
    g_assert_not_reached();
}

static void *qos_create_machine_arm_raspi2(QTestState *qts)
{
    QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1);

    alloc_init(&machine->alloc, ...);

    /* Get node(s) contained inside (CONTAINS) */
    machine->obj.get_device = raspi2_get_device;

    /* Get node(s) produced (PRODUCES) */
    machine->obj.get_driver = raspi2_get_driver;

    /* free the object */
    machine->obj.destructor = raspi2_destructor;
    qos_init_sdhci_mm(&machine->sdhci, ...);
    return &machine->obj;
}

static void raspi2_register_nodes(void)
{
    /* arm/raspi2b --contains--> generic-sdhci */
    qos_node_create_machine("arm/raspi2b",
                             qos_create_machine_arm_raspi2);
    qos_node_contains("arm/raspi2b", "generic-sdhci", NULL);
}

libqos_init(raspi2_register_nodes);

x86_64/pc machine, simplified from tests/qtest/libqos/x86_64_pc-machine.c:

#include "qgraph.h"

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();
}

/* x86_64/pc machine */

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);

    /* Get node(s) contained inside (CONTAINS) */
    machine->obj.get_device = pc_get_device;

    /* Get node(s) produced (PRODUCES) */
    machine->obj.get_driver = pc_get_driver;

    /* free the object */
    machine->obj.destructor = pc_destructor;
    pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS);

    /* Get node(s) contained inside (CONTAINS) */
    machine->bridge.obj.get_device = i440FX_host_get_device;

    return &machine->obj;
}

static void pc_machine_register_nodes(void)
{
    /* x86_64/pc --contains--> 1440FX-pcihost --contains-->
     * pci-bus-pc [--produces--> pci-bus (in pci.h)] */
    qos_node_create_machine("x86_64/pc", qos_create_machine_pc);
    qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL);

    /* contained drivers don't need a constructor,
     * they will be init by the parent */
    qos_node_create_driver("i440FX-pcihost", NULL);
    qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL);
}

libqos_init(pc_machine_register_nodes);

sdhci taken from tests/qtest/libqos/sdhci.c:

/* Interface node, offers the sdhci API */
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);
    /* other fields */
};

/* Memory Mapped implementation of QSDHCI */
struct QSDHCI_MemoryMapped {
    QOSGraphObject obj;
    QSDHCI sdhci;
    /* other driver-specific fields */
};

/* PCI implementation of QSDHCI */
struct QSDHCI_PCI {
    QOSGraphObject obj;
    QSDHCI sdhci;
    /* other driver-specific fields */
};

/* Memory mapped implementation of QSDHCI */

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)
{
    /* Get node contained inside (CONTAINS) */
    sdhci->obj.get_driver = sdhci_mm_get_driver;

    /* SDHCI interface API */
    sdhci->sdhci.readw = sdhci_mm_readw;
    sdhci->sdhci.readq = sdhci_mm_readq;
    sdhci->sdhci.writeq = sdhci_mm_writeq;
    sdhci->qts = qts;
}

/* PCI implementation of QSDHCI */

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_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);

    /* SDHCI interface API */
    spci->sdhci.readw = sdhci_pci_readw;
    spci->sdhci.readq = sdhci_pci_readq;
    spci->sdhci.writeq = sdhci_pci_writeq;

    /* Get node(s) produced (PRODUCES) */
    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)
{
    QOSGraphEdgeOptions opts = {
        .extra_device_opts = "addr=04.0",
    };

    /* generic-sdhci */
    /* generic-sdhci --produces--> sdhci */
    qos_node_create_driver("generic-sdhci", NULL);
    qos_node_produces("generic-sdhci", "sdhci");

    /* sdhci-pci */
    /* sdhci-pci --produces--> sdhci
     * sdhci-pci --consumes--> pci-bus */
    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);

In the above example, all possible types of relations are created:

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
             sdhci-pci --consumes--> pci-bus <--produces--+
                |
                +--produces--+
                             |
                             v
                           sdhci
                             ^
                             |
                             +--produces-- +
                                           |
             arm/raspi2b --contains--> generic-sdhci

or inverting the consumes edge in consumed_by:

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
          sdhci-pci <--consumed by-- pci-bus <--produces--+
              |
              +--produces--+
                           |
                           v
                          sdhci
                           ^
                           |
                           +--produces-- +
                                         |
          arm/raspi2b --contains--> generic-sdhci

Adding a new test

Given the above setup, adding a new test is very simple. sdhci-test, taken from tests/qtest/sdhci-test.c:

static void check_capab_sdma(QSDHCI *s, bool supported)
{
    uint64_t capab, capab_sdma;

    capab = s->readq(s, SDHC_CAPAB);
    capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
    g_assert_cmpuint(capab_sdma, ==, supported);
}

static void test_registers(void *obj, void *data,
                            QGuestAllocator *alloc)
{
    QSDHCI *s = obj;

    /* example test */
    check_capab_sdma(s, s->props.capab.sdma);
}

static void register_sdhci_test(void)
{
    /* sdhci-test --consumes--> sdhci */
    qos_add_test("registers", "sdhci", test_registers, NULL);
}

libqos_init(register_sdhci_test);

Here a new test is created, consuming sdhci interface node and creating a valid path from both machines to a test. Final graph will be like this:

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
             sdhci-pci --consumes--> pci-bus <--produces--+
                |
                +--produces--+
                             |
                             v
                           sdhci <--consumes-- sdhci-test
                             ^
                             |
                             +--produces-- +
                                           |
             arm/raspi2b --contains--> generic-sdhci

or inverting the consumes edge in consumed_by:

x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
                                                          |
          sdhci-pci <--consumed by-- pci-bus <--produces--+
              |
              +--produces--+
                           |
                           v
                          sdhci --consumed by--> sdhci-test
                           ^
                           |
                           +--produces-- +
                                         |
          arm/raspi2b --contains--> generic-sdhci

Assuming there the binary is QTEST_QEMU_BINARY=./qemu-system-x86_64 a valid test path will be: /x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test

and for the binary QTEST_QEMU_BINARY=./qemu-system-arm:

/arm/raspi2b/generic-sdhci/sdhci/sdhci-test

Additional examples are also in test-qgraph.c

Qgraph API reference

struct QOSGraphEdgeOptions

Edge options to be passed to the contains/consumes *_args function.

Definition

struct QOSGraphEdgeOptions {
  void *arg;
  uint32_t size_arg;
  const char *extra_device_opts;
  const char *before_cmd_line;
  const char *after_cmd_line;
  const char *edge_name;
};

Members

arg

optional arg that will be used by dest edge

size_arg

arg size that will be used by dest edge

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.

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”

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

edge_name

optional edge to differentiate multiple devices with same node name

struct QOSGraphTestOptions

Test options to be passed to the test functions.

Definition

struct QOSGraphTestOptions {
  QOSGraphEdgeOptions edge;
  void *arg;
  QOSBeforeTest before;
  bool subprocess;
};

Members

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.

arg

if before is non-NULL, pass arg there. Otherwise pass it to the test function.

before

executed before the test. Used to add additional parameters to the command line and modify the argument to the test function.

subprocess

run the test in a subprocess.

struct QOSGraphObject

Each driver, test or machine of this framework will have a QOSGraphObject as first field.

Definition

struct QOSGraphObject {
  QOSGetDriver get_driver;
  QOSGetDevice get_device;
  QOSStartFunct start_hw;
  QOSDestructorFunc destructor;
  GDestroyNotify free;
};

Members

get_driver

see get_device

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.

free

free the memory associated to the QOSGraphObject and its contained children

Description

This set of functions offered by QOSGraphObject are executed in different stages of the framework:

void qos_graph_init(void)

initialize the framework, creates two hash tables: one for the nodes and another for the edges.

Parameters

void

no arguments

void qos_graph_destroy(void)

deallocates all the hash tables, freeing all nodes and edges.

Parameters

void

no arguments

void qos_node_destroy(void *key)

removes and frees a node from the nodes hash table.

Parameters

void *key

Name of the node

void qos_edge_destroy(void *key)

removes and frees an edge from the edges hash table.

Parameters

void *key

Name of the node

void qos_add_test(const char *name, const char *interface, QOSTestFunc test_func, QOSGraphTestOptions *opts)

adds a test node name to the nodes hash table.

Parameters

const char *name

Name of the test

const char *interface

Name of the interface node it consumes

QOSTestFunc test_func

Actual test to perform

QOSGraphTestOptions *opts

Facultative options (see QOSGraphTestOptions)

Description

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 QOSGraphTestOptions).

For tests, opts->edge.arg and size_arg represent the arg to pass to test_func

void qos_node_create_machine(const char *name, QOSCreateMachineFunc function)

creates the machine name and adds it to the node hash table.

Parameters

const char *name

Name of the machine

QOSCreateMachineFunc function

Machine constructor

Description

This node will be of type QNODE_MACHINE and have function as constructor

void qos_node_create_machine_args(const char *name, QOSCreateMachineFunc function, const char *opts)

same as qos_node_create_machine, but with the possibility to add an optional “, opts” after -M machine command line.

Parameters

const char *name

Name of the machine

QOSCreateMachineFunc function

Machine constructor

const char *opts

Optional additional command line

void qos_node_create_driver(const char *name, QOSCreateDriverFunc function)

creates the driver name and adds it to the node hash table.

Parameters

const char *name

Name of the driver

QOSCreateDriverFunc function

Driver constructor

Description

This node will be of type QNODE_DRIVER and have function as constructor

void qos_node_create_driver_named(const char *name, const char *qemu_name, QOSCreateDriverFunc function)

behaves as qos_node_create_driver() with the extension of allowing to specify a different node name vs. associated QEMU device name.

Parameters

const char *name

Custom, unique name of the node to be created

const char *qemu_name

Actual (official) QEMU driver name the node shall be associated with

QOSCreateDriverFunc function

Driver constructor

Description

Use this function instead of qos_node_create_driver() if you need to create several instances of the same QEMU device. You are free to choose a custom node name, however the chosen node name must always be unique.

void qos_node_contains(const char *container, const char *contained, QOSGraphEdgeOptions *opts, ...)

creates one or more edges of type QEDGE_CONTAINS and adds them to the edge list mapped to container in the edge hash table.

Parameters

const char *container

Source node that “contains”

const char *contained

Destination node that “is contained”

QOSGraphEdgeOptions *opts

Facultative options (see QOSGraphEdgeOptions)

...

variable arguments

Description

The edges will have container as source and contained as destination.

If opts is NULL, a single edge will be added with no options. If opts is non-NULL, the arguments after contained represent a NULL-terminated list of QOSGraphEdgeOptions structs, and an edge will be added for each of them.

This function can be useful when there are multiple devices with the same node name contained in a machine/other node

For example, if arm/raspi2b contains 2 generic-sdhci devices, the right commands will be:

qos_node_create_machine("arm/raspi2b");
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/raspi2b", "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_produces(const char *producer, const char *interface)

creates an edge of type QEDGE_PRODUCES and adds it to the edge list mapped to producer in the edge hash table.

Parameters

const char *producer

Source node that “produces”

const char *interface

Interface node that “is produced”

Description

This edge will have producer as source and interface as destination.

void qos_node_consumes(const char *consumer, const char *interface, QOSGraphEdgeOptions *opts)

creates an edge of type QEDGE_CONSUMED_BY and adds it to the edge list mapped to interface in the edge hash table.

Parameters

const char *consumer

Node that “consumes”

const char *interface

Interface node that “is consumed by”

QOSGraphEdgeOptions *opts

Facultative options (see QOSGraphEdgeOptions)

Description

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_invalidate_command_line(void)

invalidates current command line, so that qgraph framework cannot try to cache the current command line and forces QEMU to restart.

Parameters

void

no arguments

const char *qos_get_current_command_line(void)

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.

Parameters

void

no arguments

void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc)

Parameters

QTestState *qts

The QTestState that will be referred to by the machine object.

QGuestAllocator **p_alloc

Where to store the allocator for the machine object, or NULL.

Description

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_object_destroy(QOSGraphObject *obj)

calls the destructor for obj

Parameters

QOSGraphObject *obj

A QOSGraphObject to destroy

void qos_object_queue_destroy(QOSGraphObject *obj)

queue the destructor for obj so that it is called at the end of the test

Parameters

QOSGraphObject *obj

A QOSGraphObject to destroy

void qos_object_start_hw(QOSGraphObject *obj)

calls the start_hw function for obj

Parameters

QOSGraphObject *obj

A QOSGraphObject containing the start_hw function

QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts)

instantiate a new machine node

Parameters

QOSGraphNode *node

Machine node to be instantiated

QTestState *qts

A QTestState that will be referred to by the machine object.

Description

Returns a machine object.

QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, QGuestAllocator *alloc, void *arg)

instantiate a new driver node

Parameters

QOSGraphNode *node

A driver node to be instantiated

QOSGraphObject *parent

A QOSGraphObject to be consumed by the new driver node

QGuestAllocator *alloc

An allocator to be used by the new driver node.

void *arg

The argument for the consumed-by edge to node.

Description

Calls the constructor for the driver object.

void qos_dump_graph(void)

prints all currently existing nodes and edges to stdout. Just for debugging purposes.

Parameters

void

no arguments

Description

All qtests add themselves to the overall qos graph by calling qgraph functions that add device nodes and edges between the individual graph nodes for tests. As the actual graph is assmbled at runtime by the qos subsystem, it is sometimes not obvious how the overall graph looks like. E.g. when writing new tests it may happen that those new tests are simply ignored by the qtest framework.

This function allows to identify problems in the created qgraph. Keep in mind: only tests with a path down from the actual test case node (leaf) up to the graph’s root node are actually executed by the qtest framework. And the qtest framework uses QMP to automatically check which QEMU drivers are actually currently available, and accordingly qos marks certain pathes as ‘unavailable’ in such cases (e.g. when QEMU was compiled without support for a certain feature).