| /* |
| * 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.1 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 <gmodule.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 "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 "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=./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 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 one or more edges of type QEDGE_CONTAINS |
| * and adds them to the edge list mapped to @container in the |
| * edge hash table. |
| * |
| * The edges will have @container as source and @contained as destination. |
| * |
| * If @opts is NULL, a single edge will be added with no options. |
| * If @opts is non-NULL, the arguments after @contained represent a |
| * NULL-terminated list of %QOSGraphEdgeOptions structs, and an |
| * edge will be added for each of them. |
| * |
| * 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, |
| QOSGraphEdgeOptions *opts, ...); |
| |
| /** |
| * 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 |