Signed-off-by: Stefan Weil <sw@weilnetz.de> Message-Id: <20211117210702.1393570-1-sw@weilnetz.de> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> [thuth: "what's" --> "what is" as suggested by philmd] Signed-off-by: Thomas Huth <thuth@redhat.com>
		
			
				
	
	
		
			629 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			629 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
.. _qgraph:
 | 
						|
 | 
						|
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
 | 
						|
--------------------
 | 
						|
 | 
						|
.. kernel-doc:: tests/qtest/libqos/qgraph.h
 |