xref: /openbmc/qemu/docs/devel/qgraph.rst (revision 6d9abb6d)
1.. _qgraph:
2
3========================================
4Qtest Driver Framework
5========================================
6
7In order to test a specific driver, plain libqos tests need to
8take care of booting QEMU with the right machine and devices.
9This makes each test "hardcoded" for a specific configuration, reducing
10the possible coverage that it can reach.
11
12For example, the sdhci device is supported on both x86_64 and ARM boards,
13therefore a generic sdhci test should test all machines and drivers that
14support that device.
15Using only libqos APIs, the test has to manually take care of
16covering all the setups, and build the correct command line.
17
18This also introduces backward compability issues: if a device/driver command
19line name is changed, all tests that use that will not work
20properly anymore and need to be adjusted.
21
22The aim of qgraph is to create a graph of drivers, machines and tests such that
23a test aimed to a certain driver does not have to care of
24booting the right QEMU machine, pick the right device, build the command line
25and so on. Instead, it only defines what type of device it is testing
26(interface in qgraph terms) and the framework takes care of
27covering all supported types of devices and machine architectures.
28
29Following the above example, an interface would be ``sdhci``,
30so the sdhci-test should only care of linking its qgraph node with
31that interface. In this way, if the command line of a sdhci driver
32is changed, only the respective qgraph driver node has to be adjusted.
33
34The graph is composed by nodes that represent machines, drivers, tests
35and edges that define the relationships between them (``CONSUMES``, ``PRODUCES``, and
36``CONTAINS``).
37
38
39Nodes
40^^^^^^
41
42A node can be of four types:
43
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.
55
56Notes for the nodes:
57
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
65
66Edges
67^^^^^^
68
69An edge relation between two nodes (drivers or machines) `X` and `Y` can be:
70
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
74
75Execution steps
76^^^^^^^^^^^^^^^
77
78The basic framework steps are the following:
79
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
91
92Depending on the QEMU binary used, only some drivers/machines will be
93available and only test that are reached by them will be executed.
94
95Creating a new driver and its interface
96"""""""""""""""""""""""""""""""""""""""""
97
98Here we continue the ``sdhci`` use case, with the following scenario:
99
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.
107
108In order to implement such scenario in qgraph, the test developer needs to:
109
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
113
114  ``x86_64/pc --contains--> 1440FX-pcihost --contains-->
115  pci-bus-pc --produces--> pci-bus``.
116
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)
123
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``.
128
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.
134
135  ``sdhci-pci --produces--> sdhci``
136
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.
141
142  ``sdhci-test --consumes--> sdhci``
143
144``arm-raspi2`` machine, simplified from
145``tests/qtest/libqos/arm-raspi2-machine.c``::
146
147    #include "qgraph.h"
148
149    struct QRaspi2Machine {
150        QOSGraphObject obj;
151        QGuestAllocator alloc;
152        QSDHCI_MemoryMapped sdhci;
153    };
154
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        }
161
162        fprintf(stderr, "%s not present in arm/raspi2\n", interface);
163        g_assert_not_reached();
164    }
165
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        }
173
174        fprintf(stderr, "%s not present in arm/raspi2\n", device);
175        g_assert_not_reached();
176    }
177
178    static void *qos_create_machine_arm_raspi2(QTestState *qts)
179    {
180        QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1);
181
182        alloc_init(&machine->alloc, ...);
183
184        /* Get node(s) contained inside (CONTAINS) */
185        machine->obj.get_device = raspi2_get_device;
186
187        /* Get node(s) produced (PRODUCES) */
188        machine->obj.get_driver = raspi2_get_driver;
189
190        /* free the object */
191        machine->obj.destructor = raspi2_destructor;
192        qos_init_sdhci_mm(&machine->sdhci, ...);
193        return &machine->obj;
194    }
195
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    }
203
204    libqos_init(raspi2_register_nodes);
205
206``x86_64/pc`` machine, simplified from
207``tests/qtest/libqos/x86_64_pc-machine.c``::
208
209    #include "qgraph.h"
210
211    struct i440FX_pcihost {
212        QOSGraphObject obj;
213        QPCIBusPC pci;
214    };
215
216    struct QX86PCMachine {
217        QOSGraphObject obj;
218        QGuestAllocator alloc;
219        i440FX_pcihost bridge;
220    };
221
222    /* i440FX_pcihost */
223
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    }
234
235    /* x86_64/pc machine */
236
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        }
243
244        fprintf(stderr, "%s not present in x86_64/pc\n", interface);
245        g_assert_not_reached();
246    }
247
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        }
254
255        fprintf(stderr, "%s not present in x86_64/pc\n", device);
256        g_assert_not_reached();
257    }
258
259    static void *qos_create_machine_pc(QTestState *qts)
260    {
261        QX86PCMachine *machine = g_new0(QX86PCMachine, 1);
262
263        /* Get node(s) contained inside (CONTAINS) */
264        machine->obj.get_device = pc_get_device;
265
266        /* Get node(s) produced (PRODUCES) */
267        machine->obj.get_driver = pc_get_driver;
268
269        /* free the object */
270        machine->obj.destructor = pc_destructor;
271        pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS);
272
273        /* Get node(s) contained inside (CONTAINS) */
274        machine->bridge.obj.get_device = i440FX_host_get_device;
275
276        return &machine->obj;
277    }
278
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);
285
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    }
291
292    libqos_init(pc_machine_register_nodes);
293
294``sdhci`` taken from ``tests/qtest/libqos/sdhci.c``::
295
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    };
303
304    /* Memory Mapped implementation of QSDHCI */
305    struct QSDHCI_MemoryMapped {
306        QOSGraphObject obj;
307        QSDHCI sdhci;
308        /* other driver-specific fields */
309    };
310
311    /* PCI implementation of QSDHCI */
312    struct QSDHCI_PCI {
313        QOSGraphObject obj;
314        QSDHCI sdhci;
315        /* other driver-specific fields */
316    };
317
318    /* Memory mapped implementation of QSDHCI */
319
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    }
329
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;
335
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    }
342
343    /* PCI implementation of QSDHCI */
344
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        }
352
353        fprintf(stderr, "%s not present in sdhci-pci\n", interface);
354        g_assert_not_reached();
355    }
356
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;
364
365        qpci_device_init(&spci->dev, bus, addr);
366
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;
371
372        /* Get node(s) produced (PRODUCES) */
373        spci->obj.get_driver = sdhci_pci_get_driver;
374
375        spci->obj.start_hw = sdhci_pci_start_hw;
376        spci->obj.destructor = sdhci_destructor;
377        return &spci->obj;
378    }
379
380    static void qsdhci_register_nodes(void)
381    {
382        QOSGraphEdgeOptions opts = {
383            .extra_device_opts = "addr=04.0",
384        };
385
386        /* generic-sdhci */
387        /* generic-sdhci --produces--> sdhci */
388        qos_node_create_driver("generic-sdhci", NULL);
389        qos_node_produces("generic-sdhci", "sdhci");
390
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    }
398
399    libqos_init(qsdhci_register_nodes);
400
401In the above example, all possible types of relations are created::
402
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
416
417or inverting the consumes edge in consumed_by::
418
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
432
433Adding a new test
434"""""""""""""""""
435
436Given the above setup, adding a new test is very simple.
437``sdhci-test``, taken from ``tests/qtest/sdhci-test.c``::
438
439    static void check_capab_sdma(QSDHCI *s, bool supported)
440    {
441        uint64_t capab, capab_sdma;
442
443        capab = s->readq(s, SDHC_CAPAB);
444        capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
445        g_assert_cmpuint(capab_sdma, ==, supported);
446    }
447
448    static void test_registers(void *obj, void *data,
449                                QGuestAllocator *alloc)
450    {
451        QSDHCI *s = obj;
452
453        /* example test */
454        check_capab_sdma(s, s->props.capab.sdma);
455    }
456
457    static void register_sdhci_test(void)
458    {
459        /* sdhci-test --consumes--> sdhci */
460        qos_add_test("registers", "sdhci", test_registers, NULL);
461    }
462
463    libqos_init(register_sdhci_test);
464
465Here a new test is created, consuming ``sdhci`` interface node
466and creating a valid path from both machines to a test.
467Final graph will be like this::
468
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
482
483or inverting the consumes edge in consumed_by::
484
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
498
499Assuming there the binary is
500``QTEST_QEMU_BINARY=./qemu-system-x86_64``
501a valid test path will be:
502``/x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test``
503
504and for the binary ``QTEST_QEMU_BINARY=./qemu-system-arm``:
505
506``/arm/raspi2/generic-sdhci/sdhci/sdhci-test``
507
508Additional examples are also in ``test-qgraph.c``
509
510Command line:
511""""""""""""""
512
513Command line is built by using node names and optional arguments
514passed by the user when building the edges.
515
516There are three types of command line arguments:
517
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``.
540
541While adding command line in edges is always used, not all nodes names are
542used in every path walk: this is because the contained or produced ones
543are already added by QEMU, so only nodes that "consumes" will be used to
544build the command line. Also, nodes that will have ``{ "abstract" : true }``
545as QMP attribute will loose their command line, since they are not proper
546devices to be added in QEMU.
547
548Example::
549
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",
554
555        opts.extra_device_opts = "id=vs0";
556    };
557
558    qos_node_create_driver("virtio-scsi-device",
559                            virtio_scsi_device_create);
560    qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts);
561
562Will 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``
564
565Qgraph API reference
566^^^^^^^^^^^^^^^^^^^^
567
568.. kernel-doc:: tests/qtest/libqos/qgraph.h
569