summaryrefslogtreecommitdiff
path: root/tests/libqos/qgraph.h
blob: ef0c73837a60b3f5b427a255d63d937e17aaa117 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
/*
 * libqos driver framework
 *
 * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>
 */

#ifndef QGRAPH_H
#define QGRAPH_H

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <gmodule.h>
#include <glib.h>
#include "qemu/module.h"
#include "malloc.h"

/* maximum path length */
#define QOS_PATH_MAX_ELEMENT_SIZE 50

typedef struct QOSGraphObject QOSGraphObject;
typedef struct QOSGraphNode QOSGraphNode;
typedef struct QOSGraphEdge QOSGraphEdge;
typedef struct QOSGraphNodeOptions QOSGraphNodeOptions;
typedef struct QOSGraphEdgeOptions QOSGraphEdgeOptions;
typedef struct QOSGraphTestOptions QOSGraphTestOptions;

/* Constructor for drivers, machines and test */
typedef void *(*QOSCreateDriverFunc) (void *parent, QGuestAllocator *alloc,
                                      void *addr);
typedef void *(*QOSCreateMachineFunc) (QTestState *qts);
typedef void (*QOSTestFunc) (void *parent, void *arg, QGuestAllocator *alloc);

/* QOSGraphObject functions */
typedef void *(*QOSGetDriver) (void *object, const char *interface);
typedef QOSGraphObject *(*QOSGetDevice) (void *object, const char *name);
typedef void (*QOSDestructorFunc) (QOSGraphObject *object);
typedef void (*QOSStartFunct) (QOSGraphObject *object);

/* Test options functions */
typedef void *(*QOSBeforeTest) (GString *cmd_line, void *arg);

/**
 * SECTION: qgraph.h
 * @title: Qtest Driver Framework
 * @short_description: interfaces to organize drivers and tests
 *                     as nodes in a graph
 *
 * This Qgraph API provides all basic functions to create a graph
 * and instantiate nodes representing machines, drivers and tests
 * representing their relations with CONSUMES, PRODUCES, and CONTAINS
 * edges.
 *
 * The idea is to have a framework where each test asks for a specific
 * driver, and the framework takes care of allocating the proper devices
 * required and passing the correct command line arguments to QEMU.
 *
 * A node can be of four types:
 * - QNODE_MACHINE:   for example "arm/raspi2"
 * - 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 auto-
 *                     matically instantiated when a node consumes or produces
 *                     it.
 * - QNODE_TEST:      for example "sdhci-test", consumes an interface and tests
 *                    the functions provided
 *
 * Notes for the nodes:
 * - QNODE_MACHINE: each machine struct must have a QGuestAllocator and
 *                  implement get_driver to return the allocator passing
 *                  "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
 *
 * 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
 *
 * 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.
 *
 * <example>
 *   <title>Creating new driver an its interface</title>
 *   <programlisting>
 #include "libqos/qgraph.h"

 struct My_driver {
     QOSGraphObject obj;
     Node_produced prod;
     Node_contained cont;
 }

 static void my_destructor(QOSGraphObject *obj)
 {
    g_free(obj);
 }

 static void my_get_driver(void *object, const char *interface) {
    My_driver *dev = object;
    if (!g_strcmp0(interface, "my_interface")) {
        return &dev->prod;
    }
    abort();
 }

 static void my_get_device(void *object, const char *device) {
    My_driver *dev = object;
    if (!g_strcmp0(device, "my_driver_contained")) {
        return &dev->cont;
    }
    abort();
 }

 static void *my_driver_constructor(void *node_consumed,
                                    QOSGraphObject *alloc)
 {
    My_driver dev = g_new(My_driver, 1);
    // get the node pointed by the produce edge
    dev->obj.get_driver = my_get_driver;
    // get the node pointed by the contains
    dev->obj.get_device = my_get_device;
    // free the object
    dev->obj.destructor = my_destructor;
    do_something_with_node_consumed(node_consumed);
    // set all fields of contained device
    init_contained_device(&dev->cont);
    return &dev->obj;
 }

 static void register_my_driver(void)
 {
     qos_node_create_driver("my_driver", my_driver_constructor);
     // contained drivers don't need a constructor,
     // they will be init by the parent.
     qos_node_create_driver("my_driver_contained", NULL);

    // For the sake of this example, assume machine x86_64/pc contains
    // "other_node".
    // This relation, along with the machine and "other_node" creation,
    // should be defined in the x86_64_pc-machine.c file.
    // "my_driver" will then consume "other_node"
    qos_node_contains("my_driver", "my_driver_contained");
    qos_node_produces("my_driver", "my_interface");
    qos_node_consumes("my_driver", "other_node");
 }
 *   </programlisting>
 * </example>
 *
 * In the above example, all possible types of relations are created:
 * node "my_driver" consumes, contains and produces other nodes.
 * more specifically:
 * x86_64/pc -->contains--> other_node <--consumes-- my_driver
 *                                                       |
 *                      my_driver_contained <--contains--+
 *                                                       |
 *                             my_interface <--produces--+
 *
 * or inverting the consumes edge in consumed_by:
 *
 * x86_64/pc -->contains--> other_node --consumed_by--> my_driver
 *                                                           |
 *                          my_driver_contained <--contains--+
 *                                                           |
 *                                 my_interface <--produces--+
 *
 * <example>
 *   <title>Creating new test</title>
 *   <programlisting>
 * #include "libqos/qgraph.h"
 *
 * static void my_test_function(void *obj, void *data)
 * {
 *    Node_produced *interface_to_test = obj;
 *    // test interface_to_test
 * }
 *
 * static void register_my_test(void)
 * {
 *    qos_add_test("my_interface", "my_test", my_test_function);
 * }
 *
 * libqos_init(register_my_test);
 *
 *   </programlisting>
 * </example>
 *
 * Here a new test is created, consuming "my_interface" node
 * and creating a valid path from a machine to a test.
 * Final graph will be like this:
 * x86_64/pc -->contains--> other_node <--consumes-- my_driver
 *                                                        |
 *                       my_driver_contained <--contains--+
 *                                                        |
 *        my_test --consumes--> my_interface <--produces--+
 *
 * or inverting the consumes edge in consumed_by:
 *
 * x86_64/pc -->contains--> other_node --consumed_by--> my_driver
 *                                                           |
 *                          my_driver_contained <--contains--+
 *                                                           |
 *        my_test <--consumed_by-- my_interface <--produces--+
 *
 * Assuming there the binary is
 * QTEST_QEMU_BINARY=x86_64-softmmu/qemu-system-x86_64
 * a valid test path will be:
 * "/x86_64/pc/other_node/my_driver/my_interface/my_test".
 *
 * Additional examples are also in libqos/test-qgraph.c
 *
 * 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 = {
     .arg = NULL,
     .size_arg = 0,
     .after_cmd_line = "-device other",
     .before_cmd_line = "-netdev something",
     .extra_edge_opts = "addr=04.0",
 };
 QOSGraphNode * node = qos_node_create_driver("my_node", constructor);
 qos_node_consumes_args("my_node", "interface", &opts);
 *
 * Will produce the following command line:
 * "-netdev something -device my_node,addr=04.0 -device other"
 */

/**
 * Edge options to be passed to the contains/consumes *_args function.
 */
struct QOSGraphEdgeOptions {
    void *arg;                    /*
                                   * optional arg that will be used by
                                   * dest edge
                                   */
    uint32_t size_arg;            /*
                                   * optional arg size that will be used by
                                   * dest edge
                                   */
    const char *extra_device_opts;/*
                                   *optional additional command line for dest
                                   * edge, used to add additional attributes
                                   * *after* the node command line, the
                                   * framework automatically prepends ","
                                   * to this argument.
                                   */
    const char *before_cmd_line;  /*
                                   * optional additional command line for dest
                                   * edge, used to add additional attributes
                                   * *before* the node command line, usually
                                   * other non-node represented commands,
                                   * like "-fdsev synt"
                                   */
    const char *after_cmd_line;   /*
                                   * optional extra command line to be added
                                   * after the device command. This option
                                   * is used to add other devices
                                   * command line that depend on current node.
                                   * Automatically prepends " " to this
                                   * argument
                                   */
    const char *edge_name;        /*
                                   * optional edge to differentiate multiple
                                   * devices with same node name
                                   */
};

/**
 * Test options to be passed to the test functions.
 */
struct QOSGraphTestOptions {
    QOSGraphEdgeOptions edge;   /* edge arguments that will be used by test.
                                 * Note that test *does not* use edge_name,
                                 * and uses instead arg and size_arg as
                                 * data arg for its test function.
                                 */
    void *arg;                  /* passed to the .before function, or to the
                                 * test function if there is no .before
                                 * function
                                 */
    QOSBeforeTest before;       /* executed before the test. Can add
                                 * additional parameters to the command line
                                 * and modify the argument to the test function.
                                 */
    bool subprocess;            /* run the test in a subprocess */
};

/**
 * Each driver, test or machine of this framework will have a
 * QOSGraphObject as first field.
 *
 * This set of functions offered by QOSGraphObject are executed
 * in different stages of the framework:
 * - get_driver / get_device : Once a machine-to-test path has been
 * found, the framework traverses it again and allocates all the
 * nodes, using the provided constructor. To satisfy their relations,
 * i.e. for produces or contains, where a struct constructor needs
 * an external parameter represented by the previous node,
 * the framework will call get_device (for contains) or
 * get_driver (for produces), depending on the edge type, passing
 * them the name of the next node to be taken and getting from them
 * the corresponding pointer to the actual structure of the next node to
 * be used in the path.
 *
 * - start_hw: This function is executed after all the path objects
 * have been allocated, but before the test is run. It starts the hw, setting
 * the initial configurations (*_device_enable) and making it ready for the
 * test.
 *
 * - destructor: Opposite to the node constructor, destroys the object.
 * This function is called after the test has been executed, and performs
 * a complete cleanup of each node allocated field. In case no constructor
 * is provided, no destructor will be called.
 *
 */
struct QOSGraphObject {
    /* for produces edges, returns void * */
    QOSGetDriver get_driver;
    /* for contains edges, returns a QOSGraphObject * */
    QOSGetDevice get_device;
    /* start the hw, get ready for the test */
    QOSStartFunct start_hw;
    /* destroy this QOSGraphObject */
    QOSDestructorFunc destructor;
    /* free the memory associated to the QOSGraphObject and its contained
     * children */
    GDestroyNotify free;
};

/**
 * qos_graph_init(): initialize the framework, creates two hash
 * tables: one for the nodes and another for the edges.
 */
void qos_graph_init(void);

/**
 * qos_graph_destroy(): deallocates all the hash tables,
 * freeing all nodes and edges.
 */
void qos_graph_destroy(void);

/**
 * qos_node_destroy(): removes and frees a node from the,
 * nodes hash table.
 */
void qos_node_destroy(void *key);

/**
 * qos_edge_destroy(): removes and frees an edge from the,
 * edges hash table.
 */
void qos_edge_destroy(void *key);

/**
 * qos_add_test(): adds a test node @name to the nodes hash table.
 *
 * The test will consume a @interface node, and once the
 * graph walking algorithm has found it, the @test_func will be
 * executed. It also has the possibility to
 * add an optional @opts (see %QOSGraphNodeOptions).
 *
 * For tests, opts->edge.arg and size_arg represent the arg to pass
 * to @test_func
 */
void qos_add_test(const char *name, const char *interface,
                  QOSTestFunc test_func,
                  QOSGraphTestOptions *opts);

/**
 * qos_node_create_machine(): creates the machine @name and
 * adds it to the node hash table.
 *
 * This node will be of type QNODE_MACHINE and have @function
 * as constructor
 */
void qos_node_create_machine(const char *name, QOSCreateMachineFunc function);

/**
 * qos_node_create_machine_args(): same as qos_node_create_machine,
 * but with the possibility to add an optional ", @opts" after -M machine
 * command line.
 */
void qos_node_create_machine_args(const char *name,
                                  QOSCreateMachineFunc function,
                                  const char *opts);

/**
 * qos_node_create_driver(): creates the driver @name and
 * adds it to the node hash table.
 *
 * This node will be of type QNODE_DRIVER and have @function
 * as constructor
 */
void qos_node_create_driver(const char *name, QOSCreateDriverFunc function);

/**
 * qos_node_contains(): creates an edge of type QEDGE_CONTAINS and
 * adds it to the edge list mapped to @container in the
 * edge hash table.
 *
 * This edge will have @container as source and @contained as destination.
 *
 * It also has the possibility to add optional NULL-terminated
 * @opts parameters (see %QOSGraphEdgeOptions)
 *
 * This function can be useful when there are multiple devices
 * with the same node name contained in a machine/other node
 *
 * For example, if "arm/raspi2" contains 2 "generic-sdhci"
 * devices, the right commands will be:
 * qos_node_create_machine("arm/raspi2");
 * qos_node_create_driver("generic-sdhci", constructor);
 * //assume rest of the fields are set NULL
 * QOSGraphEdgeOptions op1 = { .edge_name = "emmc" };
 * QOSGraphEdgeOptions op2 = { .edge_name = "sdcard" };
 * qos_node_contains("arm/raspi2", "generic-sdhci", &op1, &op2, NULL);
 *
 * Of course this also requires that the @container's get_device function
 * should implement a case for "emmc" and "sdcard".
 *
 * For contains, op1.arg and op1.size_arg represent the arg to pass
 * to @contained constructor to properly initialize it.
 */
void qos_node_contains(const char *container, const char *contained, ...);

/**
 * qos_node_produces(): creates an edge of type QEDGE_PRODUCES and
 * adds it to the edge list mapped to @producer in the
 * edge hash table.
 *
 * This edge will have @producer as source and @interface as destination.
 */
void qos_node_produces(const char *producer, const char *interface);

/**
 * qos_node_consumes():  creates an edge of type QEDGE_CONSUMED_BY and
 * adds it to the edge list mapped to @interface in the
 * edge hash table.
 *
 * This edge will have @interface as source and @consumer as destination.
 * It also has the possibility to add an optional @opts
 * (see %QOSGraphEdgeOptions)
 */
void qos_node_consumes(const char *consumer, const char *interface,
                       QOSGraphEdgeOptions *opts);

/**
 * qos_invalidate_command_line(): invalidates current command line, so that
 * qgraph framework cannot try to cache the current command line and
 * forces QEMU to restart.
 */
void qos_invalidate_command_line(void);

/**
 * qos_get_current_command_line(): return the command line required by the
 * machine and driver objects.  This is the same string that was passed to
 * the test's "before" callback, if any.
 */
const char *qos_get_current_command_line(void);

/**
 * qos_allocate_objects():
 * @qts: The #QTestState that will be referred to by the machine object.
 * @alloc: Where to store the allocator for the machine object, or %NULL.
 *
 * Allocate driver objects for the current test
 * path, but relative to the QTestState @qts.
 *
 * Returns a test object just like the one that was passed to
 * the test function, but relative to @qts.
 */
void *qos_allocate_objects(QTestState *qts, QGuestAllocator **p_alloc);

/**
 * qos_object_destroy(): calls the destructor for @obj
 */
void qos_object_destroy(QOSGraphObject *obj);

/**
 * qos_object_queue_destroy(): queue the destructor for @obj so that it is
 * called at the end of the test
 */
void qos_object_queue_destroy(QOSGraphObject *obj);

/**
 * qos_object_start_hw(): calls the start_hw function for @obj
 */
void qos_object_start_hw(QOSGraphObject *obj);

/**
 * qos_machine_new(): instantiate a new machine node
 * @node: A machine node to be instantiated
 * @qts: The #QTestState that will be referred to by the machine object.
 *
 * Returns a machine object.
 */
QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts);

/**
 * qos_machine_new(): instantiate a new driver node
 * @node: A driver node to be instantiated
 * @parent: A #QOSGraphObject to be consumed by the new driver node
 * @alloc: An allocator to be used by the new driver node.
 * @arg: The argument for the consumed-by edge to @node.
 *
 * Calls the constructor for the driver object.
 */
QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent,
                               QGuestAllocator *alloc, void *arg);


#endif