/* * libqos driver framework * * Copyright (c) 2018 Emanuele Giuseppe Esposito * * 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 */ #ifndef QGRAPH_H #define QGRAPH_H #include #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. * * * Creating new driver an its interface * #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"); } * * * * 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--+ * * * Creating new test * * #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); * * * * * 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 " to its command line, while devices * "-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); /** * Behaves as qos_node_create_driver() with the extension of allowing to * specify a different node name vs. associated QEMU device name. * * Use this function instead of qos_node_create_driver() if you need to create * several instances of the same QEMU device. You are free to choose a custom * node name, however the chosen node name must always be unique. * * @param name: custom, unique name of the node to be created * @param qemu_name: actual (official) QEMU driver name the node shall be * associated with * @param function: driver constructor */ void qos_node_create_driver_named(const char *name, const char *qemu_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); /** * Just for debugging purpose: prints all currently existing nodes and * edges to stdout. * * All qtests add themselves to the overall qos graph by calling qgraph * functions that add device nodes and edges between the individual graph * nodes for tests. As the actual graph is assmbled at runtime by the qos * subsystem, it is sometimes not obvious how the overall graph looks like. * E.g. when writing new tests it may happen that those new tests are simply * ignored by the qtest framework. * * This function allows to identify problems in the created qgraph. Keep in * mind: only tests with a path down from the actual test case node (leaf) up * to the graph's root node are actually executed by the qtest framework. And * the qtest framework uses QMP to automatically check which QEMU drivers are * actually currently available, and accordingly qos marks certain pathes as * 'unavailable' in such cases (e.g. when QEMU was compiled without support for * a certain feature). */ void qos_dump_graph(void); #endif