diff options
Diffstat (limited to 'docs')
-rw-r--r-- | docs/devel/index.rst | 1 | ||||
-rw-r--r-- | docs/devel/qgraph.rst | 261 | ||||
-rw-r--r-- | docs/devel/qtest.rst | 8 |
3 files changed, 270 insertions, 0 deletions
diff --git a/docs/devel/index.rst b/docs/devel/index.rst index ae664da00c..7c424ea6d7 100644 --- a/docs/devel/index.rst +++ b/docs/devel/index.rst @@ -12,6 +12,7 @@ Contents: .. toctree:: :maxdepth: 2 + :includehidden: build-system style diff --git a/docs/devel/qgraph.rst b/docs/devel/qgraph.rst new file mode 100644 index 0000000000..62a45cbcbf --- /dev/null +++ b/docs/devel/qgraph.rst @@ -0,0 +1,261 @@ +.. _qgraph: + +======================================== +Qtest Driver Framework +======================================== + +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. + +Nodes +^^^^^^ + +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 automatically + 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 mapped to the interface + "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 + +Edges +^^^^^^ + +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 + +Execution steps +^^^^^^^^^^^^^^^ + +The 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 a new driver and its interface +""""""""""""""""""""""""""""""""""""""""" + +.. code:: + + #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 +""""""""""""""""" + +.. code:: + + #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 <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", + }; + QOSGraphNodeS *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`` + +Qgraph API reference +^^^^^^^^^^^^^^^^^^^^ + +.. kernel-doc:: tests/qtest/libqos/qgraph.h diff --git a/docs/devel/qtest.rst b/docs/devel/qtest.rst index 97c5a75626..c3dceb6c8a 100644 --- a/docs/devel/qtest.rst +++ b/docs/devel/qtest.rst @@ -2,6 +2,11 @@ QTest Device Emulation Testing Framework ======================================== +.. toctree:: + :hidden: + + qgraph + QTest is a device emulation testing framework. It can be very useful to test device models; it could also control certain aspects of QEMU (such as virtual clock stepping), with a special purpose "qtest" protocol. Refer to @@ -24,6 +29,9 @@ On top of libqtest, a higher level library, ``libqos``, was created to encapsulate common tasks of device drivers, such as memory management and communicating with system buses or devices. Many virtual device tests use libqos instead of directly calling into libqtest. +Libqos also offers the Qgraph API to increase each test coverage and +automate QEMU command line arguments and devices setup. +Refer to :ref:`qgraph` for Qgraph explanation and API. Steps to add a new QTest case are: |