Merge remote-tracking branch 'remotes/berrange-gitlab/tags/dep-many-pull-request...
[qemu/ar7.git] / docs / devel / qgraph.rst
bloba9aff167adac15cc4a670d1e287eb8caebddff15
1 .. _qgraph:
3 ========================================
4 Qtest Driver Framework
5 ========================================
7 In order to test a specific driver, plain libqos tests need to
8 take care of booting QEMU with the right machine and devices.
9 This makes each test "hardcoded" for a specific configuration, reducing
10 the possible coverage that it can reach.
12 For example, the sdhci device is supported on both x86_64 and ARM boards,
13 therefore a generic sdhci test should test all machines and drivers that
14 support that device.
15 Using only libqos APIs, the test has to manually take care of
16 covering all the setups, and build the correct command line.
18 This also introduces backward compability issues: if a device/driver command
19 line name is changed, all tests that use that will not work
20 properly anymore and need to be adjusted.
22 The aim of qgraph is to create a graph of drivers, machines and tests such that
23 a test aimed to a certain driver does not have to care of
24 booting the right QEMU machine, pick the right device, build the command line
25 and so on. Instead, it only defines what type of device it is testing
26 (interface in qgraph terms) and the framework takes care of
27 covering all supported types of devices and machine architectures.
29 Following the above example, an interface would be ``sdhci``,
30 so the sdhci-test should only care of linking its qgraph node with
31 that interface. In this way, if the command line of a sdhci driver
32 is changed, only the respective qgraph driver node has to be adjusted.
34 The graph is composed by nodes that represent machines, drivers, tests
35 and edges that define the relationships between them (``CONSUMES``, ``PRODUCES``, and
36 ``CONTAINS``).
39 Nodes
40 ^^^^^^
42 A node can be of four types:
44 - **QNODE_MACHINE**:   for example ``arm/raspi2``
45 - **QNODE_DRIVER**:    for example ``generic-sdhci``
46 - **QNODE_INTERFACE**: for example ``sdhci`` (interface for all ``-sdhci``
47   drivers).
48   An interface is not explicitly created, it will be automatically
49   instantiated when a node consumes or produces it.
50   An interface is simply a struct that abstracts the various drivers
51   for the same type of device, and offers an API to the nodes that
52   use it ("consume" relation in qgraph terms) that is implemented/backed up by the drivers that implement it ("produce" relation in qgraph terms).
53 - **QNODE_TEST**:      for example ``sdhci-test``. A test consumes an interface
54   and tests the functions provided by it.
56 Notes for the nodes:
58 - QNODE_MACHINE: each machine struct must have a ``QGuestAllocator`` and
59   implement ``get_driver()`` to return the allocator mapped to the interface
60   "memory". The function can also return ``NULL`` if the allocator
61   is not set.
62 - QNODE_DRIVER:  driver names must be unique, and machines and nodes
63   planned to be "consumed" by other nodes must match QEMU
64   drivers name, otherwise they won't be discovered
66 Edges
67 ^^^^^^
69 An edge relation between two nodes (drivers or machines) `X` and `Y` can be:
71 - ``X CONSUMES Y``: `Y` can be plugged into `X`
72 - ``X PRODUCES Y``: `X` provides the interface `Y`
73 - ``X CONTAINS Y``: `Y` is part of `X` component
75 Execution steps
76 ^^^^^^^^^^^^^^^
78 The basic framework steps are the following:
80 - All nodes and edges are created in their respective
81   machine/driver/test files
82 - The framework starts QEMU and asks for a list of available devices
83   and machines (note that only machines and "consumed" nodes are mapped
84   1:1 with QEMU devices)
85 - The framework walks the graph starting from the available machines and
86   performs a Depth First Search for tests
87 - Once a test is found, the path is walked again and all drivers are
88   allocated accordingly and the final interface is passed to the test
89 - The test is executed
90 - Unused objects are cleaned and the path discovery is continued
92 Depending on the QEMU binary used, only some drivers/machines will be
93 available and only test that are reached by them will be executed.
95 Creating a new driver and its interface
96 """""""""""""""""""""""""""""""""""""""""
98 Here we continue the ``sdhci`` use case, with the following scenario:
100 - ``sdhci-test`` aims to test the ``read[q,w], writeq`` functions
101   offered by the ``sdhci`` drivers.
102 - The current ``sdhci`` device is supported by both ``x86_64/pc`` and ``ARM``
103   (in this example we focus on the ``arm-raspi2``) machines.
104 - QEMU offers 2 types of drivers: ``QSDHCI_MemoryMapped`` for ``ARM`` and
105   ``QSDHCI_PCI`` for ``x86_64/pc``. Both implement the
106   ``read[q,w], writeq`` functions.
108 In order to implement such scenario in qgraph, the test developer needs to:
110 - Create the ``x86_64/pc`` machine node. This machine uses the
111   ``pci-bus`` architecture so it ``contains`` a PCI driver,
112   ``pci-bus-pc``. The actual path is
114   ``x86_64/pc --contains--> 1440FX-pcihost --contains-->
115   pci-bus-pc --produces--> pci-bus``.
117   For the sake of this example,
118   we do not focus on the PCI interface implementation.
119 - Create the ``sdhci-pci`` driver node, representing ``QSDHCI_PCI``.
120   The driver uses the PCI bus (and its API),
121   so it must ``consume`` the ``pci-bus`` generic interface (which abstracts
122   all the pci drivers available)
124   ``sdhci-pci --consumes--> pci-bus``
125 - Create an ``arm/raspi2`` machine node. This machine ``contains``
126   a ``generic-sdhci`` memory mapped ``sdhci`` driver node, representing
127   ``QSDHCI_MemoryMapped``.
129   ``arm/raspi2 --contains--> generic-sdhci``
130 - Create the ``sdhci`` interface node. This interface offers the
131   functions that are shared by all ``sdhci`` devices.
132   The interface is produced by ``sdhci-pci`` and ``generic-sdhci``,
133   the available architecture-specific drivers.
135   ``sdhci-pci --produces--> sdhci``
137   ``generic-sdhci --produces--> sdhci``
138 - Create the ``sdhci-test`` test node. The test ``consumes`` the
139   ``sdhci`` interface, using its API. It doesn't need to look at
140   the supported machines or drivers.
142   ``sdhci-test --consumes--> sdhci``
144 ``arm-raspi2`` machine, simplified from
145 ``tests/qtest/libqos/arm-raspi2-machine.c``::
147     #include "qgraph.h"
149     struct QRaspi2Machine {
150         QOSGraphObject obj;
151         QGuestAllocator alloc;
152         QSDHCI_MemoryMapped sdhci;
153     };
155     static void *raspi2_get_driver(void *object, const char *interface)
156     {
157         QRaspi2Machine *machine = object;
158         if (!g_strcmp0(interface, "memory")) {
159             return &machine->alloc;
160         }
162         fprintf(stderr, "%s not present in arm/raspi2\n", interface);
163         g_assert_not_reached();
164     }
166     static QOSGraphObject *raspi2_get_device(void *obj,
167                                                 const char *device)
168     {
169         QRaspi2Machine *machine = obj;
170         if (!g_strcmp0(device, "generic-sdhci")) {
171             return &machine->sdhci.obj;
172         }
174         fprintf(stderr, "%s not present in arm/raspi2\n", device);
175         g_assert_not_reached();
176     }
178     static void *qos_create_machine_arm_raspi2(QTestState *qts)
179     {
180         QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1);
182         alloc_init(&machine->alloc, ...);
184         /* Get node(s) contained inside (CONTAINS) */
185         machine->obj.get_device = raspi2_get_device;
187         /* Get node(s) produced (PRODUCES) */
188         machine->obj.get_driver = raspi2_get_driver;
190         /* free the object */
191         machine->obj.destructor = raspi2_destructor;
192         qos_init_sdhci_mm(&machine->sdhci, ...);
193         return &machine->obj;
194     }
196     static void raspi2_register_nodes(void)
197     {
198         /* arm/raspi2 --contains--> generic-sdhci */
199         qos_node_create_machine("arm/raspi2",
200                                  qos_create_machine_arm_raspi2);
201         qos_node_contains("arm/raspi2", "generic-sdhci", NULL);
202     }
204     libqos_init(raspi2_register_nodes);
206 ``x86_64/pc`` machine, simplified from
207 ``tests/qtest/libqos/x86_64_pc-machine.c``::
209     #include "qgraph.h"
211     struct i440FX_pcihost {
212         QOSGraphObject obj;
213         QPCIBusPC pci;
214     };
216     struct QX86PCMachine {
217         QOSGraphObject obj;
218         QGuestAllocator alloc;
219         i440FX_pcihost bridge;
220     };
222     /* i440FX_pcihost */
224     static QOSGraphObject *i440FX_host_get_device(void *obj,
225                                                 const char *device)
226     {
227         i440FX_pcihost *host = obj;
228         if (!g_strcmp0(device, "pci-bus-pc")) {
229             return &host->pci.obj;
230         }
231         fprintf(stderr, "%s not present in i440FX-pcihost\n", device);
232         g_assert_not_reached();
233     }
235     /* x86_64/pc machine */
237     static void *pc_get_driver(void *object, const char *interface)
238     {
239         QX86PCMachine *machine = object;
240         if (!g_strcmp0(interface, "memory")) {
241             return &machine->alloc;
242         }
244         fprintf(stderr, "%s not present in x86_64/pc\n", interface);
245         g_assert_not_reached();
246     }
248     static QOSGraphObject *pc_get_device(void *obj, const char *device)
249     {
250         QX86PCMachine *machine = obj;
251         if (!g_strcmp0(device, "i440FX-pcihost")) {
252             return &machine->bridge.obj;
253         }
255         fprintf(stderr, "%s not present in x86_64/pc\n", device);
256         g_assert_not_reached();
257     }
259     static void *qos_create_machine_pc(QTestState *qts)
260     {
261         QX86PCMachine *machine = g_new0(QX86PCMachine, 1);
263         /* Get node(s) contained inside (CONTAINS) */
264         machine->obj.get_device = pc_get_device;
266         /* Get node(s) produced (PRODUCES) */
267         machine->obj.get_driver = pc_get_driver;
269         /* free the object */
270         machine->obj.destructor = pc_destructor;
271         pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS);
273         /* Get node(s) contained inside (CONTAINS) */
274         machine->bridge.obj.get_device = i440FX_host_get_device;
276         return &machine->obj;
277     }
279     static void pc_machine_register_nodes(void)
280     {
281         /* x86_64/pc --contains--> 1440FX-pcihost --contains-->
282          * pci-bus-pc [--produces--> pci-bus (in pci.h)] */
283         qos_node_create_machine("x86_64/pc", qos_create_machine_pc);
284         qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL);
286         /* contained drivers don't need a constructor,
287          * they will be init by the parent */
288         qos_node_create_driver("i440FX-pcihost", NULL);
289         qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL);
290     }
292     libqos_init(pc_machine_register_nodes);
294 ``sdhci`` taken from ``tests/qtest/libqos/sdhci.c``::
296     /* Interface node, offers the sdhci API */
297     struct QSDHCI {
298         uint16_t (*readw)(QSDHCI *s, uint32_t reg);
299         uint64_t (*readq)(QSDHCI *s, uint32_t reg);
300         void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val);
301         /* other fields */
302     };
304     /* Memory Mapped implementation of QSDHCI */
305     struct QSDHCI_MemoryMapped {
306         QOSGraphObject obj;
307         QSDHCI sdhci;
308         /* other driver-specific fields */
309     };
311     /* PCI implementation of QSDHCI */
312     struct QSDHCI_PCI {
313         QOSGraphObject obj;
314         QSDHCI sdhci;
315         /* other driver-specific fields */
316     };
318     /* Memory mapped implementation of QSDHCI */
320     static void *sdhci_mm_get_driver(void *obj, const char *interface)
321     {
322         QSDHCI_MemoryMapped *smm = obj;
323         if (!g_strcmp0(interface, "sdhci")) {
324             return &smm->sdhci;
325         }
326         fprintf(stderr, "%s not present in generic-sdhci\n", interface);
327         g_assert_not_reached();
328     }
330     void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts,
331                         uint32_t addr, QSDHCIProperties *common)
332     {
333         /* Get node contained inside (CONTAINS) */
334         sdhci->obj.get_driver = sdhci_mm_get_driver;
336         /* SDHCI interface API */
337         sdhci->sdhci.readw = sdhci_mm_readw;
338         sdhci->sdhci.readq = sdhci_mm_readq;
339         sdhci->sdhci.writeq = sdhci_mm_writeq;
340         sdhci->qts = qts;
341     }
343     /* PCI implementation of QSDHCI */
345     static void *sdhci_pci_get_driver(void *object,
346                                       const char *interface)
347     {
348         QSDHCI_PCI *spci = object;
349         if (!g_strcmp0(interface, "sdhci")) {
350             return &spci->sdhci;
351         }
353         fprintf(stderr, "%s not present in sdhci-pci\n", interface);
354         g_assert_not_reached();
355     }
357     static void *sdhci_pci_create(void *pci_bus,
358                                   QGuestAllocator *alloc,
359                                   void *addr)
360     {
361         QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1);
362         QPCIBus *bus = pci_bus;
363         uint64_t barsize;
365         qpci_device_init(&spci->dev, bus, addr);
367         /* SDHCI interface API */
368         spci->sdhci.readw = sdhci_pci_readw;
369         spci->sdhci.readq = sdhci_pci_readq;
370         spci->sdhci.writeq = sdhci_pci_writeq;
372         /* Get node(s) produced (PRODUCES) */
373         spci->obj.get_driver = sdhci_pci_get_driver;
375         spci->obj.start_hw = sdhci_pci_start_hw;
376         spci->obj.destructor = sdhci_destructor;
377         return &spci->obj;
378     }
380     static void qsdhci_register_nodes(void)
381     {
382         QOSGraphEdgeOptions opts = {
383             .extra_device_opts = "addr=04.0",
384         };
386         /* generic-sdhci */
387         /* generic-sdhci --produces--> sdhci */
388         qos_node_create_driver("generic-sdhci", NULL);
389         qos_node_produces("generic-sdhci", "sdhci");
391         /* sdhci-pci */
392         /* sdhci-pci --produces--> sdhci
393          * sdhci-pci --consumes--> pci-bus */
394         qos_node_create_driver("sdhci-pci", sdhci_pci_create);
395         qos_node_produces("sdhci-pci", "sdhci");
396         qos_node_consumes("sdhci-pci", "pci-bus", &opts);
397     }
399     libqos_init(qsdhci_register_nodes);
401 In the above example, all possible types of relations are created::
403   x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
404                                                             |
405                sdhci-pci --consumes--> pci-bus <--produces--+
406                   |
407                   +--produces--+
408                                |
409                                v
410                              sdhci
411                                ^
412                                |
413                                +--produces-- +
414                                              |
415                arm/raspi2 --contains--> generic-sdhci
417 or inverting the consumes edge in consumed_by::
419   x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
420                                                             |
421             sdhci-pci <--consumed by-- pci-bus <--produces--+
422                 |
423                 +--produces--+
424                              |
425                              v
426                             sdhci
427                              ^
428                              |
429                              +--produces-- +
430                                            |
431             arm/raspi2 --contains--> generic-sdhci
433 Adding a new test
434 """""""""""""""""
436 Given the above setup, adding a new test is very simple.
437 ``sdhci-test``, taken from ``tests/qtest/sdhci-test.c``::
439     static void check_capab_sdma(QSDHCI *s, bool supported)
440     {
441         uint64_t capab, capab_sdma;
443         capab = s->readq(s, SDHC_CAPAB);
444         capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
445         g_assert_cmpuint(capab_sdma, ==, supported);
446     }
448     static void test_registers(void *obj, void *data,
449                                 QGuestAllocator *alloc)
450     {
451         QSDHCI *s = obj;
453         /* example test */
454         check_capab_sdma(s, s->props.capab.sdma);
455     }
457     static void register_sdhci_test(void)
458     {
459         /* sdhci-test --consumes--> sdhci */
460         qos_add_test("registers", "sdhci", test_registers, NULL);
461     }
463     libqos_init(register_sdhci_test);
465 Here a new test is created, consuming ``sdhci`` interface node
466 and creating a valid path from both machines to a test.
467 Final graph will be like this::
469   x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
470                                                             |
471                sdhci-pci --consumes--> pci-bus <--produces--+
472                   |
473                   +--produces--+
474                                |
475                                v
476                              sdhci <--consumes-- sdhci-test
477                                ^
478                                |
479                                +--produces-- +
480                                              |
481                arm/raspi2 --contains--> generic-sdhci
483 or inverting the consumes edge in consumed_by::
485   x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
486                                                             |
487             sdhci-pci <--consumed by-- pci-bus <--produces--+
488                 |
489                 +--produces--+
490                              |
491                              v
492                             sdhci --consumed by--> sdhci-test
493                              ^
494                              |
495                              +--produces-- +
496                                            |
497             arm/raspi2 --contains--> generic-sdhci
499 Assuming there the binary is
500 ``QTEST_QEMU_BINARY=./qemu-system-x86_64``
501 a valid test path will be:
502 ``/x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test``
504 and for the binary ``QTEST_QEMU_BINARY=./qemu-system-arm``:
506 ``/arm/raspi2/generic-sdhci/sdhci/sdhci-test``
508 Additional examples are also in ``test-qgraph.c``
510 Command line:
511 """"""""""""""
513 Command line is built by using node names and optional arguments
514 passed by the user when building the edges.
516 There are three types of command line arguments:
518 - ``in node``      : created from the node name. For example, machines will
519   have ``-M <machine>`` to its command line, while devices
520   ``-device <device>``. It is automatically done by the framework.
521 - ``after node``   : added as additional argument to the node name.
522   This argument is added optionally when creating edges,
523   by setting the parameter ``after_cmd_line`` and
524   ``extra_edge_opts`` in ``QOSGraphEdgeOptions``.
525   The framework automatically adds
526   a comma before ``extra_edge_opts``,
527   because it is going to add attributes
528   after the destination node pointed by
529   the edge containing these options, and automatically
530   adds a space before ``after_cmd_line``, because it
531   adds an additional device, not an attribute.
532 - ``before node``  : added as additional argument to the node name.
533   This argument is added optionally when creating edges,
534   by setting the parameter ``before_cmd_line`` in
535   ``QOSGraphEdgeOptions``. This attribute
536   is going to add attributes before the destination node
537   pointed by the edge containing these options. It is
538   helpful to commands that are not node-representable,
539   such as ``-fdsev`` or ``-netdev``.
541 While adding command line in edges is always used, not all nodes names are
542 used in every path walk: this is because the contained or produced ones
543 are already added by QEMU, so only nodes that "consumes" will be used to
544 build the command line. Also, nodes that will have ``{ "abstract" : true }``
545 as QMP attribute will loose their command line, since they are not proper
546 devices to be added in QEMU.
548 Example::
550     QOSGraphEdgeOptions opts = {
551         .before_cmd_line = "-drive id=drv0,if=none,file=null-co://,"
552                            "file.read-zeroes=on,format=raw",
553         .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0",
555         opts.extra_device_opts = "id=vs0";
556     };
558     qos_node_create_driver("virtio-scsi-device",
559                             virtio_scsi_device_create);
560     qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts);
562 Will produce the following command line:
563 ``-drive id=drv0,if=none,file=null-co://, -device virtio-scsi-device,id=vs0 -device scsi-hd,bus=vs0.0,drive=drv0``
565 Qgraph API reference
566 ^^^^^^^^^^^^^^^^^^^^
568 .. kernel-doc:: tests/qtest/libqos/qgraph.h