2021-03-08 10:32:40 +03:00
|
|
|
.. _qgraph:
|
|
|
|
|
|
|
|
========================================
|
|
|
|
Qtest Driver Framework
|
|
|
|
========================================
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
In order to test a specific driver, plain libqos tests need to
|
|
|
|
take care of booting QEMU with the right machine and devices.
|
|
|
|
This makes each test "hardcoded" for a specific configuration, reducing
|
|
|
|
the possible coverage that it can reach.
|
|
|
|
|
|
|
|
For example, the sdhci device is supported on both x86_64 and ARM boards,
|
|
|
|
therefore a generic sdhci test should test all machines and drivers that
|
|
|
|
support that device.
|
|
|
|
Using only libqos APIs, the test has to manually take care of
|
|
|
|
covering all the setups, and build the correct command line.
|
|
|
|
|
|
|
|
This also introduces backward compability issues: if a device/driver command
|
|
|
|
line name is changed, all tests that use that will not work
|
|
|
|
properly anymore and need to be adjusted.
|
|
|
|
|
|
|
|
The aim of qgraph is to create a graph of drivers, machines and tests such that
|
|
|
|
a test aimed to a certain driver does not have to care of
|
|
|
|
booting the right QEMU machine, pick the right device, build the command line
|
|
|
|
and so on. Instead, it only defines what type of device it is testing
|
|
|
|
(interface in qgraph terms) and the framework takes care of
|
|
|
|
covering all supported types of devices and machine architectures.
|
|
|
|
|
|
|
|
Following the above example, an interface would be ``sdhci``,
|
|
|
|
so the sdhci-test should only care of linking its qgraph node with
|
|
|
|
that interface. In this way, if the command line of a sdhci driver
|
|
|
|
is changed, only the respective qgraph driver node has to be adjusted.
|
|
|
|
|
|
|
|
The graph is composed by nodes that represent machines, drivers, tests
|
|
|
|
and edges that define the relationships between them (``CONSUMES``, ``PRODUCES``, and
|
|
|
|
``CONTAINS``).
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
Nodes
|
|
|
|
^^^^^^
|
|
|
|
|
|
|
|
A node can be of four types:
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
- **QNODE_MACHINE**: for example ``arm/raspi2b``
|
2021-03-08 10:32:40 +03:00
|
|
|
- **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.
|
2021-03-01 12:24:32 +03:00
|
|
|
An interface is simply a struct that abstracts the various drivers
|
|
|
|
for the same type of device, and offers an API to the nodes that
|
|
|
|
use it ("consume" relation in qgraph terms) that is implemented/backed up by the drivers that implement it ("produce" relation in qgraph terms).
|
|
|
|
- **QNODE_TEST**: for example ``sdhci-test``. A test consumes an interface
|
|
|
|
and tests the functions provided by it.
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
^^^^^^
|
|
|
|
|
2021-07-26 17:23:33 +03:00
|
|
|
An edge relation between two nodes (drivers or machines) ``X`` and ``Y`` can be:
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-07-26 17:23:33 +03:00
|
|
|
- ``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
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2021-04-12 17:34:37 +03:00
|
|
|
Troubleshooting unavailable tests
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
If there is no path from an available machine to a test then that test will be
|
|
|
|
unavailable and won't execute. This can happen if a test or driver did not set
|
|
|
|
up its qgraph node correctly. It can also happen if the necessary machine type
|
|
|
|
or device is missing from the QEMU binary because it was compiled out or
|
|
|
|
otherwise.
|
|
|
|
|
|
|
|
It is possible to troubleshoot unavailable tests by running::
|
|
|
|
|
|
|
|
$ QTEST_QEMU_BINARY=build/qemu-system-x86_64 build/tests/qtest/qos-test --verbose
|
|
|
|
# ALL QGRAPH EDGES: {
|
|
|
|
# src='virtio-net'
|
|
|
|
# |-> dest='virtio-net-tests/vhost-user/multiqueue' type=2 (node=0x559142109e30)
|
|
|
|
# |-> dest='virtio-net-tests/vhost-user/migrate' type=2 (node=0x559142109d00)
|
|
|
|
# src='virtio-net-pci'
|
|
|
|
# |-> dest='virtio-net' type=1 (node=0x55914210d740)
|
|
|
|
# src='pci-bus'
|
|
|
|
# |-> dest='virtio-net-pci' type=2 (node=0x55914210d880)
|
|
|
|
# src='pci-bus-pc'
|
|
|
|
# |-> dest='pci-bus' type=1 (node=0x559142103f40)
|
|
|
|
# src='i440FX-pcihost'
|
|
|
|
# |-> dest='pci-bus-pc' type=0 (node=0x55914210ac70)
|
|
|
|
# src='x86_64/pc'
|
|
|
|
# |-> dest='i440FX-pcihost' type=0 (node=0x5591421117f0)
|
|
|
|
# src=''
|
|
|
|
# |-> dest='x86_64/pc' type=0 (node=0x559142111600)
|
2021-08-27 09:08:14 +03:00
|
|
|
# |-> dest='arm/raspi2b' type=0 (node=0x559142110740)
|
2021-04-12 17:34:37 +03:00
|
|
|
...
|
|
|
|
# }
|
|
|
|
# ALL QGRAPH NODES: {
|
|
|
|
# name='virtio-net-tests/announce-self' type=3 cmd_line='(null)' [available]
|
2021-08-27 09:08:14 +03:00
|
|
|
# name='arm/raspi2b' type=0 cmd_line='-M raspi2b ' [UNAVAILABLE]
|
2021-04-12 17:34:37 +03:00
|
|
|
...
|
|
|
|
# }
|
|
|
|
|
|
|
|
The ``virtio-net-tests/announce-self`` test is listed as "available" in the
|
|
|
|
"ALL QGRAPH NODES" output. This means the test will execute. We can follow the
|
|
|
|
qgraph path in the "ALL QGRAPH EDGES" output as follows: '' -> 'x86_64/pc' ->
|
|
|
|
'i440FX-pcihost' -> 'pci-bus-pc' -> 'pci-bus' -> 'virtio-net-pci' ->
|
|
|
|
'virtio-net'. The root of the qgraph is '' and the depth first search begins
|
|
|
|
there.
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
The ``arm/raspi2b`` machine node is listed as "UNAVAILABLE". Although it is
|
|
|
|
reachable from the root via '' -> 'arm/raspi2b' the node is unavailable because
|
2021-04-12 17:34:37 +03:00
|
|
|
the QEMU binary did not list it when queried by the framework. This is expected
|
|
|
|
because we used the ``qemu-system-x86_64`` binary which does not support ARM
|
|
|
|
machine types.
|
|
|
|
|
|
|
|
If a test is unexpectedly listed as "UNAVAILABLE", first check that the "ALL
|
|
|
|
QGRAPH EDGES" output reports edge connectivity from the root ('') to the test.
|
|
|
|
If there is no connectivity then the qgraph nodes were not set up correctly and
|
|
|
|
the driver or test code is incorrect. If there is connectivity, check the
|
|
|
|
availability of each node in the path in the "ALL QGRAPH NODES" output. The
|
|
|
|
first unavailable node in the path is the reason why the test is unavailable.
|
|
|
|
Typically this is because the QEMU binary lacks support for the necessary
|
|
|
|
machine type or device.
|
|
|
|
|
2021-03-08 10:32:40 +03:00
|
|
|
Creating a new driver and its interface
|
|
|
|
"""""""""""""""""""""""""""""""""""""""""
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
Here we continue the ``sdhci`` use case, with the following scenario:
|
|
|
|
|
|
|
|
- ``sdhci-test`` aims to test the ``read[q,w], writeq`` functions
|
|
|
|
offered by the ``sdhci`` drivers.
|
|
|
|
- The current ``sdhci`` device is supported by both ``x86_64/pc`` and ``ARM``
|
2021-08-27 09:08:14 +03:00
|
|
|
(in this example we focus on the ``arm-raspi2b``) machines.
|
2021-03-01 12:24:32 +03:00
|
|
|
- QEMU offers 2 types of drivers: ``QSDHCI_MemoryMapped`` for ``ARM`` and
|
|
|
|
``QSDHCI_PCI`` for ``x86_64/pc``. Both implement the
|
|
|
|
``read[q,w], writeq`` functions.
|
|
|
|
|
|
|
|
In order to implement such scenario in qgraph, the test developer needs to:
|
|
|
|
|
|
|
|
- Create the ``x86_64/pc`` machine node. This machine uses the
|
|
|
|
``pci-bus`` architecture so it ``contains`` a PCI driver,
|
|
|
|
``pci-bus-pc``. The actual path is
|
|
|
|
|
|
|
|
``x86_64/pc --contains--> 1440FX-pcihost --contains-->
|
|
|
|
pci-bus-pc --produces--> pci-bus``.
|
|
|
|
|
|
|
|
For the sake of this example,
|
|
|
|
we do not focus on the PCI interface implementation.
|
|
|
|
- Create the ``sdhci-pci`` driver node, representing ``QSDHCI_PCI``.
|
|
|
|
The driver uses the PCI bus (and its API),
|
|
|
|
so it must ``consume`` the ``pci-bus`` generic interface (which abstracts
|
|
|
|
all the pci drivers available)
|
|
|
|
|
|
|
|
``sdhci-pci --consumes--> pci-bus``
|
2021-08-27 09:08:14 +03:00
|
|
|
- Create an ``arm/raspi2b`` machine node. This machine ``contains``
|
2021-03-01 12:24:32 +03:00
|
|
|
a ``generic-sdhci`` memory mapped ``sdhci`` driver node, representing
|
|
|
|
``QSDHCI_MemoryMapped``.
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
``arm/raspi2b --contains--> generic-sdhci``
|
2021-03-01 12:24:32 +03:00
|
|
|
- Create the ``sdhci`` interface node. This interface offers the
|
|
|
|
functions that are shared by all ``sdhci`` devices.
|
|
|
|
The interface is produced by ``sdhci-pci`` and ``generic-sdhci``,
|
|
|
|
the available architecture-specific drivers.
|
|
|
|
|
|
|
|
``sdhci-pci --produces--> sdhci``
|
|
|
|
|
|
|
|
``generic-sdhci --produces--> sdhci``
|
|
|
|
- Create the ``sdhci-test`` test node. The test ``consumes`` the
|
|
|
|
``sdhci`` interface, using its API. It doesn't need to look at
|
|
|
|
the supported machines or drivers.
|
|
|
|
|
|
|
|
``sdhci-test --consumes--> sdhci``
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
``arm-raspi2b`` machine, simplified from
|
2021-03-01 12:24:32 +03:00
|
|
|
``tests/qtest/libqos/arm-raspi2-machine.c``::
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
#include "qgraph.h"
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
struct QRaspi2Machine {
|
2021-03-08 10:32:40 +03:00
|
|
|
QOSGraphObject obj;
|
2021-03-01 12:24:32 +03:00
|
|
|
QGuestAllocator alloc;
|
|
|
|
QSDHCI_MemoryMapped sdhci;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void *raspi2_get_driver(void *object, const char *interface)
|
|
|
|
{
|
|
|
|
QRaspi2Machine *machine = object;
|
|
|
|
if (!g_strcmp0(interface, "memory")) {
|
|
|
|
return &machine->alloc;
|
|
|
|
}
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
fprintf(stderr, "%s not present in arm/raspi2b\n", interface);
|
2021-03-01 12:24:32 +03:00
|
|
|
g_assert_not_reached();
|
|
|
|
}
|
|
|
|
|
|
|
|
static QOSGraphObject *raspi2_get_device(void *obj,
|
|
|
|
const char *device)
|
|
|
|
{
|
|
|
|
QRaspi2Machine *machine = obj;
|
|
|
|
if (!g_strcmp0(device, "generic-sdhci")) {
|
|
|
|
return &machine->sdhci.obj;
|
|
|
|
}
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
fprintf(stderr, "%s not present in arm/raspi2b\n", device);
|
2021-03-01 12:24:32 +03:00
|
|
|
g_assert_not_reached();
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static void *qos_create_machine_arm_raspi2(QTestState *qts)
|
2021-03-08 10:32:40 +03:00
|
|
|
{
|
2021-03-01 12:24:32 +03:00
|
|
|
QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1);
|
|
|
|
|
|
|
|
alloc_init(&machine->alloc, ...);
|
|
|
|
|
|
|
|
/* Get node(s) contained inside (CONTAINS) */
|
|
|
|
machine->obj.get_device = raspi2_get_device;
|
|
|
|
|
|
|
|
/* Get node(s) produced (PRODUCES) */
|
|
|
|
machine->obj.get_driver = raspi2_get_driver;
|
|
|
|
|
|
|
|
/* free the object */
|
|
|
|
machine->obj.destructor = raspi2_destructor;
|
|
|
|
qos_init_sdhci_mm(&machine->sdhci, ...);
|
|
|
|
return &machine->obj;
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static void raspi2_register_nodes(void)
|
|
|
|
{
|
2021-08-27 09:08:14 +03:00
|
|
|
/* arm/raspi2b --contains--> generic-sdhci */
|
|
|
|
qos_node_create_machine("arm/raspi2b",
|
2021-03-01 12:24:32 +03:00
|
|
|
qos_create_machine_arm_raspi2);
|
2021-08-27 09:08:14 +03:00
|
|
|
qos_node_contains("arm/raspi2b", "generic-sdhci", NULL);
|
2021-03-01 12:24:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
libqos_init(raspi2_register_nodes);
|
|
|
|
|
|
|
|
``x86_64/pc`` machine, simplified from
|
|
|
|
``tests/qtest/libqos/x86_64_pc-machine.c``::
|
|
|
|
|
|
|
|
#include "qgraph.h"
|
|
|
|
|
|
|
|
struct i440FX_pcihost {
|
|
|
|
QOSGraphObject obj;
|
|
|
|
QPCIBusPC pci;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct QX86PCMachine {
|
|
|
|
QOSGraphObject obj;
|
|
|
|
QGuestAllocator alloc;
|
|
|
|
i440FX_pcihost bridge;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* i440FX_pcihost */
|
|
|
|
|
|
|
|
static QOSGraphObject *i440FX_host_get_device(void *obj,
|
|
|
|
const char *device)
|
|
|
|
{
|
|
|
|
i440FX_pcihost *host = obj;
|
|
|
|
if (!g_strcmp0(device, "pci-bus-pc")) {
|
|
|
|
return &host->pci.obj;
|
|
|
|
}
|
|
|
|
fprintf(stderr, "%s not present in i440FX-pcihost\n", device);
|
|
|
|
g_assert_not_reached();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* x86_64/pc machine */
|
|
|
|
|
|
|
|
static void *pc_get_driver(void *object, const char *interface)
|
|
|
|
{
|
|
|
|
QX86PCMachine *machine = object;
|
|
|
|
if (!g_strcmp0(interface, "memory")) {
|
|
|
|
return &machine->alloc;
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
2021-03-01 12:24:32 +03:00
|
|
|
|
|
|
|
fprintf(stderr, "%s not present in x86_64/pc\n", interface);
|
|
|
|
g_assert_not_reached();
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static QOSGraphObject *pc_get_device(void *obj, const char *device)
|
|
|
|
{
|
|
|
|
QX86PCMachine *machine = obj;
|
|
|
|
if (!g_strcmp0(device, "i440FX-pcihost")) {
|
|
|
|
return &machine->bridge.obj;
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
2021-03-01 12:24:32 +03:00
|
|
|
|
|
|
|
fprintf(stderr, "%s not present in x86_64/pc\n", device);
|
|
|
|
g_assert_not_reached();
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static void *qos_create_machine_pc(QTestState *qts)
|
2021-03-08 10:32:40 +03:00
|
|
|
{
|
2021-03-01 12:24:32 +03:00
|
|
|
QX86PCMachine *machine = g_new0(QX86PCMachine, 1);
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
/* Get node(s) contained inside (CONTAINS) */
|
|
|
|
machine->obj.get_device = pc_get_device;
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
/* Get node(s) produced (PRODUCES) */
|
|
|
|
machine->obj.get_driver = pc_get_driver;
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
/* free the object */
|
|
|
|
machine->obj.destructor = pc_destructor;
|
|
|
|
pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS);
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
/* Get node(s) contained inside (CONTAINS) */
|
|
|
|
machine->bridge.obj.get_device = i440FX_host_get_device;
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
return &machine->obj;
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static void pc_machine_register_nodes(void)
|
2021-03-08 10:32:40 +03:00
|
|
|
{
|
2021-03-01 12:24:32 +03:00
|
|
|
/* x86_64/pc --contains--> 1440FX-pcihost --contains-->
|
|
|
|
* pci-bus-pc [--produces--> pci-bus (in pci.h)] */
|
|
|
|
qos_node_create_machine("x86_64/pc", qos_create_machine_pc);
|
|
|
|
qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL);
|
|
|
|
|
|
|
|
/* contained drivers don't need a constructor,
|
|
|
|
* they will be init by the parent */
|
|
|
|
qos_node_create_driver("i440FX-pcihost", NULL);
|
|
|
|
qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL);
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
libqos_init(pc_machine_register_nodes);
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
``sdhci`` taken from ``tests/qtest/libqos/sdhci.c``::
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
/* Interface node, offers the sdhci API */
|
|
|
|
struct QSDHCI {
|
|
|
|
uint16_t (*readw)(QSDHCI *s, uint32_t reg);
|
|
|
|
uint64_t (*readq)(QSDHCI *s, uint32_t reg);
|
|
|
|
void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val);
|
|
|
|
/* other fields */
|
|
|
|
};
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
/* Memory Mapped implementation of QSDHCI */
|
|
|
|
struct QSDHCI_MemoryMapped {
|
|
|
|
QOSGraphObject obj;
|
|
|
|
QSDHCI sdhci;
|
|
|
|
/* other driver-specific fields */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* PCI implementation of QSDHCI */
|
|
|
|
struct QSDHCI_PCI {
|
|
|
|
QOSGraphObject obj;
|
|
|
|
QSDHCI sdhci;
|
|
|
|
/* other driver-specific fields */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Memory mapped implementation of QSDHCI */
|
|
|
|
|
|
|
|
static void *sdhci_mm_get_driver(void *obj, const char *interface)
|
|
|
|
{
|
|
|
|
QSDHCI_MemoryMapped *smm = obj;
|
|
|
|
if (!g_strcmp0(interface, "sdhci")) {
|
|
|
|
return &smm->sdhci;
|
|
|
|
}
|
|
|
|
fprintf(stderr, "%s not present in generic-sdhci\n", interface);
|
|
|
|
g_assert_not_reached();
|
|
|
|
}
|
|
|
|
|
|
|
|
void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts,
|
|
|
|
uint32_t addr, QSDHCIProperties *common)
|
|
|
|
{
|
|
|
|
/* Get node contained inside (CONTAINS) */
|
|
|
|
sdhci->obj.get_driver = sdhci_mm_get_driver;
|
|
|
|
|
|
|
|
/* SDHCI interface API */
|
|
|
|
sdhci->sdhci.readw = sdhci_mm_readw;
|
|
|
|
sdhci->sdhci.readq = sdhci_mm_readq;
|
|
|
|
sdhci->sdhci.writeq = sdhci_mm_writeq;
|
|
|
|
sdhci->qts = qts;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* PCI implementation of QSDHCI */
|
|
|
|
|
|
|
|
static void *sdhci_pci_get_driver(void *object,
|
|
|
|
const char *interface)
|
|
|
|
{
|
|
|
|
QSDHCI_PCI *spci = object;
|
|
|
|
if (!g_strcmp0(interface, "sdhci")) {
|
|
|
|
return &spci->sdhci;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s not present in sdhci-pci\n", interface);
|
|
|
|
g_assert_not_reached();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *sdhci_pci_create(void *pci_bus,
|
|
|
|
QGuestAllocator *alloc,
|
|
|
|
void *addr)
|
|
|
|
{
|
|
|
|
QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1);
|
|
|
|
QPCIBus *bus = pci_bus;
|
|
|
|
uint64_t barsize;
|
|
|
|
|
|
|
|
qpci_device_init(&spci->dev, bus, addr);
|
|
|
|
|
|
|
|
/* SDHCI interface API */
|
|
|
|
spci->sdhci.readw = sdhci_pci_readw;
|
|
|
|
spci->sdhci.readq = sdhci_pci_readq;
|
|
|
|
spci->sdhci.writeq = sdhci_pci_writeq;
|
|
|
|
|
|
|
|
/* Get node(s) produced (PRODUCES) */
|
|
|
|
spci->obj.get_driver = sdhci_pci_get_driver;
|
|
|
|
|
|
|
|
spci->obj.start_hw = sdhci_pci_start_hw;
|
|
|
|
spci->obj.destructor = sdhci_destructor;
|
|
|
|
return &spci->obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qsdhci_register_nodes(void)
|
|
|
|
{
|
|
|
|
QOSGraphEdgeOptions opts = {
|
|
|
|
.extra_device_opts = "addr=04.0",
|
|
|
|
};
|
|
|
|
|
|
|
|
/* generic-sdhci */
|
|
|
|
/* generic-sdhci --produces--> sdhci */
|
|
|
|
qos_node_create_driver("generic-sdhci", NULL);
|
|
|
|
qos_node_produces("generic-sdhci", "sdhci");
|
|
|
|
|
|
|
|
/* sdhci-pci */
|
|
|
|
/* sdhci-pci --produces--> sdhci
|
|
|
|
* sdhci-pci --consumes--> pci-bus */
|
|
|
|
qos_node_create_driver("sdhci-pci", sdhci_pci_create);
|
|
|
|
qos_node_produces("sdhci-pci", "sdhci");
|
|
|
|
qos_node_consumes("sdhci-pci", "pci-bus", &opts);
|
|
|
|
}
|
|
|
|
|
|
|
|
libqos_init(qsdhci_register_nodes);
|
|
|
|
|
|
|
|
In the above example, all possible types of relations are created::
|
|
|
|
|
|
|
|
x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
sdhci-pci --consumes--> pci-bus <--produces--+
|
|
|
|
|
|
|
|
|
+--produces--+
|
|
|
|
|
|
|
|
|
v
|
|
|
|
sdhci
|
|
|
|
^
|
|
|
|
|
|
|
|
|
+--produces-- +
|
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
arm/raspi2b --contains--> generic-sdhci
|
2021-03-01 12:24:32 +03:00
|
|
|
|
|
|
|
or inverting the consumes edge in consumed_by::
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
|
|
|
|
|
|
|
|
|
sdhci-pci <--consumed by-- pci-bus <--produces--+
|
|
|
|
|
|
|
|
|
+--produces--+
|
|
|
|
|
|
|
|
|
v
|
|
|
|
sdhci
|
|
|
|
^
|
|
|
|
|
|
|
|
|
+--produces-- +
|
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
arm/raspi2b --contains--> generic-sdhci
|
2021-03-01 12:24:32 +03:00
|
|
|
|
|
|
|
Adding a new test
|
2021-03-08 10:32:40 +03:00
|
|
|
"""""""""""""""""
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
Given the above setup, adding a new test is very simple.
|
|
|
|
``sdhci-test``, taken from ``tests/qtest/sdhci-test.c``::
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static void check_capab_sdma(QSDHCI *s, bool supported)
|
|
|
|
{
|
|
|
|
uint64_t capab, capab_sdma;
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
capab = s->readq(s, SDHC_CAPAB);
|
|
|
|
capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA);
|
|
|
|
g_assert_cmpuint(capab_sdma, ==, supported);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_registers(void *obj, void *data,
|
|
|
|
QGuestAllocator *alloc)
|
2021-03-08 10:32:40 +03:00
|
|
|
{
|
2021-03-01 12:24:32 +03:00
|
|
|
QSDHCI *s = obj;
|
|
|
|
|
|
|
|
/* example test */
|
|
|
|
check_capab_sdma(s, s->props.capab.sdma);
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
static void register_sdhci_test(void)
|
2021-03-08 10:32:40 +03:00
|
|
|
{
|
2021-03-01 12:24:32 +03:00
|
|
|
/* sdhci-test --consumes--> sdhci */
|
|
|
|
qos_add_test("registers", "sdhci", test_registers, NULL);
|
2021-03-08 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
libqos_init(register_sdhci_test);
|
2021-03-08 10:32:40 +03:00
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
Here a new test is created, consuming ``sdhci`` interface node
|
|
|
|
and creating a valid path from both machines to a test.
|
2021-03-08 10:32:40 +03:00
|
|
|
Final graph will be like this::
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
|
|
|
|
|
|
|
|
|
sdhci-pci --consumes--> pci-bus <--produces--+
|
|
|
|
|
|
|
|
|
+--produces--+
|
|
|
|
|
|
|
|
|
v
|
|
|
|
sdhci <--consumes-- sdhci-test
|
|
|
|
^
|
|
|
|
|
|
|
|
|
+--produces-- +
|
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
arm/raspi2b --contains--> generic-sdhci
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
or inverting the consumes edge in consumed_by::
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
2021-03-01 12:24:32 +03:00
|
|
|
sdhci-pci <--consumed by-- pci-bus <--produces--+
|
|
|
|
|
|
|
|
|
+--produces--+
|
|
|
|
|
|
|
|
|
v
|
|
|
|
sdhci --consumed by--> sdhci-test
|
|
|
|
^
|
|
|
|
|
|
|
|
|
+--produces-- +
|
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
arm/raspi2b --contains--> generic-sdhci
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
Assuming there the binary is
|
|
|
|
``QTEST_QEMU_BINARY=./qemu-system-x86_64``
|
|
|
|
a valid test path will be:
|
2021-03-01 12:24:32 +03:00
|
|
|
``/x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test``
|
|
|
|
|
|
|
|
and for the binary ``QTEST_QEMU_BINARY=./qemu-system-arm``:
|
|
|
|
|
2021-08-27 09:08:14 +03:00
|
|
|
``/arm/raspi2b/generic-sdhci/sdhci/sdhci-test``
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
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,
|
2021-03-01 12:24:32 +03:00
|
|
|
by setting the parameter ``after_cmd_line`` and
|
|
|
|
``extra_edge_opts`` in ``QOSGraphEdgeOptions``.
|
2021-03-08 10:32:40 +03:00
|
|
|
The framework automatically adds
|
2021-03-01 12:24:32 +03:00
|
|
|
a comma before ``extra_edge_opts``,
|
2021-03-08 10:32:40 +03:00
|
|
|
because it is going to add attributes
|
|
|
|
after the destination node pointed by
|
|
|
|
the edge containing these options, and automatically
|
2021-03-01 12:24:32 +03:00
|
|
|
adds a space before ``after_cmd_line``, because it
|
2021-03-08 10:32:40 +03:00
|
|
|
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,
|
2021-03-01 12:24:32 +03:00
|
|
|
by setting the parameter ``before_cmd_line`` in
|
|
|
|
``QOSGraphEdgeOptions``. This attribute
|
2021-03-08 10:32:40 +03:00
|
|
|
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 = {
|
2021-03-01 12:24:32 +03:00
|
|
|
.before_cmd_line = "-drive id=drv0,if=none,file=null-co://,"
|
|
|
|
"file.read-zeroes=on,format=raw",
|
|
|
|
.after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0",
|
|
|
|
|
|
|
|
opts.extra_device_opts = "id=vs0";
|
2021-03-08 10:32:40 +03:00
|
|
|
};
|
2021-03-01 12:24:32 +03:00
|
|
|
|
|
|
|
qos_node_create_driver("virtio-scsi-device",
|
|
|
|
virtio_scsi_device_create);
|
|
|
|
qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts);
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
Will produce the following command line:
|
2021-03-01 12:24:32 +03:00
|
|
|
``-drive id=drv0,if=none,file=null-co://, -device virtio-scsi-device,id=vs0 -device scsi-hd,bus=vs0.0,drive=drv0``
|
2021-03-08 10:32:40 +03:00
|
|
|
|
|
|
|
Qgraph API reference
|
|
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
.. kernel-doc:: tests/qtest/libqos/qgraph.h
|