mirror of
				https://github.com/qemu/qemu.git
				synced 2025-10-31 04:06:46 +00:00 
			
		
		
		
	 b980c1aec6
			
		
	
	
		b980c1aec6
		
	
	
	
	
		
			
			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
 |