1.. _qgraph: 2 3======================================== 4Qtest Driver Framework 5======================================== 6 7In order to test a specific driver, plain libqos tests need to 8take care of booting QEMU with the right machine and devices. 9This makes each test "hardcoded" for a specific configuration, reducing 10the possible coverage that it can reach. 11 12For example, the sdhci device is supported on both x86_64 and ARM boards, 13therefore a generic sdhci test should test all machines and drivers that 14support that device. 15Using only libqos APIs, the test has to manually take care of 16covering all the setups, and build the correct command line. 17 18This also introduces backward compability issues: if a device/driver command 19line name is changed, all tests that use that will not work 20properly anymore and need to be adjusted. 21 22The aim of qgraph is to create a graph of drivers, machines and tests such that 23a test aimed to a certain driver does not have to care of 24booting the right QEMU machine, pick the right device, build the command line 25and so on. Instead, it only defines what type of device it is testing 26(interface in qgraph terms) and the framework takes care of 27covering all supported types of devices and machine architectures. 28 29Following the above example, an interface would be ``sdhci``, 30so the sdhci-test should only care of linking its qgraph node with 31that interface. In this way, if the command line of a sdhci driver 32is changed, only the respective qgraph driver node has to be adjusted. 33 34The graph is composed by nodes that represent machines, drivers, tests 35and edges that define the relationships between them (``CONSUMES``, ``PRODUCES``, and 36``CONTAINS``). 37 38 39Nodes 40^^^^^^ 41 42A node can be of four types: 43 44- **QNODE_MACHINE**: for example ``arm/raspi2`` 45- **QNODE_DRIVER**: for example ``generic-sdhci`` 46- **QNODE_INTERFACE**: for example ``sdhci`` (interface for all ``-sdhci`` 47 drivers). 48 An interface is not explicitly created, it will be automatically 49 instantiated when a node consumes or produces it. 50 An interface is simply a struct that abstracts the various drivers 51 for the same type of device, and offers an API to the nodes that 52 use it ("consume" relation in qgraph terms) that is implemented/backed up by the drivers that implement it ("produce" relation in qgraph terms). 53- **QNODE_TEST**: for example ``sdhci-test``. A test consumes an interface 54 and tests the functions provided by it. 55 56Notes for the nodes: 57 58- QNODE_MACHINE: each machine struct must have a ``QGuestAllocator`` and 59 implement ``get_driver()`` to return the allocator mapped to the interface 60 "memory". The function can also return ``NULL`` if the allocator 61 is not set. 62- QNODE_DRIVER: driver names must be unique, and machines and nodes 63 planned to be "consumed" by other nodes must match QEMU 64 drivers name, otherwise they won't be discovered 65 66Edges 67^^^^^^ 68 69An edge relation between two nodes (drivers or machines) `X` and `Y` can be: 70 71- ``X CONSUMES Y``: `Y` can be plugged into `X` 72- ``X PRODUCES Y``: `X` provides the interface `Y` 73- ``X CONTAINS Y``: `Y` is part of `X` component 74 75Execution steps 76^^^^^^^^^^^^^^^ 77 78The basic framework steps are the following: 79 80- All nodes and edges are created in their respective 81 machine/driver/test files 82- The framework starts QEMU and asks for a list of available devices 83 and machines (note that only machines and "consumed" nodes are mapped 84 1:1 with QEMU devices) 85- The framework walks the graph starting from the available machines and 86 performs a Depth First Search for tests 87- Once a test is found, the path is walked again and all drivers are 88 allocated accordingly and the final interface is passed to the test 89- The test is executed 90- Unused objects are cleaned and the path discovery is continued 91 92Depending on the QEMU binary used, only some drivers/machines will be 93available and only test that are reached by them will be executed. 94 95Creating a new driver and its interface 96""""""""""""""""""""""""""""""""""""""""" 97 98Here we continue the ``sdhci`` use case, with the following scenario: 99 100- ``sdhci-test`` aims to test the ``read[q,w], writeq`` functions 101 offered by the ``sdhci`` drivers. 102- The current ``sdhci`` device is supported by both ``x86_64/pc`` and ``ARM`` 103 (in this example we focus on the ``arm-raspi2``) machines. 104- QEMU offers 2 types of drivers: ``QSDHCI_MemoryMapped`` for ``ARM`` and 105 ``QSDHCI_PCI`` for ``x86_64/pc``. Both implement the 106 ``read[q,w], writeq`` functions. 107 108In order to implement such scenario in qgraph, the test developer needs to: 109 110- Create the ``x86_64/pc`` machine node. This machine uses the 111 ``pci-bus`` architecture so it ``contains`` a PCI driver, 112 ``pci-bus-pc``. The actual path is 113 114 ``x86_64/pc --contains--> 1440FX-pcihost --contains--> 115 pci-bus-pc --produces--> pci-bus``. 116 117 For the sake of this example, 118 we do not focus on the PCI interface implementation. 119- Create the ``sdhci-pci`` driver node, representing ``QSDHCI_PCI``. 120 The driver uses the PCI bus (and its API), 121 so it must ``consume`` the ``pci-bus`` generic interface (which abstracts 122 all the pci drivers available) 123 124 ``sdhci-pci --consumes--> pci-bus`` 125- Create an ``arm/raspi2`` machine node. This machine ``contains`` 126 a ``generic-sdhci`` memory mapped ``sdhci`` driver node, representing 127 ``QSDHCI_MemoryMapped``. 128 129 ``arm/raspi2 --contains--> generic-sdhci`` 130- Create the ``sdhci`` interface node. This interface offers the 131 functions that are shared by all ``sdhci`` devices. 132 The interface is produced by ``sdhci-pci`` and ``generic-sdhci``, 133 the available architecture-specific drivers. 134 135 ``sdhci-pci --produces--> sdhci`` 136 137 ``generic-sdhci --produces--> sdhci`` 138- Create the ``sdhci-test`` test node. The test ``consumes`` the 139 ``sdhci`` interface, using its API. It doesn't need to look at 140 the supported machines or drivers. 141 142 ``sdhci-test --consumes--> sdhci`` 143 144``arm-raspi2`` machine, simplified from 145``tests/qtest/libqos/arm-raspi2-machine.c``:: 146 147 #include "qgraph.h" 148 149 struct QRaspi2Machine { 150 QOSGraphObject obj; 151 QGuestAllocator alloc; 152 QSDHCI_MemoryMapped sdhci; 153 }; 154 155 static void *raspi2_get_driver(void *object, const char *interface) 156 { 157 QRaspi2Machine *machine = object; 158 if (!g_strcmp0(interface, "memory")) { 159 return &machine->alloc; 160 } 161 162 fprintf(stderr, "%s not present in arm/raspi2\n", interface); 163 g_assert_not_reached(); 164 } 165 166 static QOSGraphObject *raspi2_get_device(void *obj, 167 const char *device) 168 { 169 QRaspi2Machine *machine = obj; 170 if (!g_strcmp0(device, "generic-sdhci")) { 171 return &machine->sdhci.obj; 172 } 173 174 fprintf(stderr, "%s not present in arm/raspi2\n", device); 175 g_assert_not_reached(); 176 } 177 178 static void *qos_create_machine_arm_raspi2(QTestState *qts) 179 { 180 QRaspi2Machine *machine = g_new0(QRaspi2Machine, 1); 181 182 alloc_init(&machine->alloc, ...); 183 184 /* Get node(s) contained inside (CONTAINS) */ 185 machine->obj.get_device = raspi2_get_device; 186 187 /* Get node(s) produced (PRODUCES) */ 188 machine->obj.get_driver = raspi2_get_driver; 189 190 /* free the object */ 191 machine->obj.destructor = raspi2_destructor; 192 qos_init_sdhci_mm(&machine->sdhci, ...); 193 return &machine->obj; 194 } 195 196 static void raspi2_register_nodes(void) 197 { 198 /* arm/raspi2 --contains--> generic-sdhci */ 199 qos_node_create_machine("arm/raspi2", 200 qos_create_machine_arm_raspi2); 201 qos_node_contains("arm/raspi2", "generic-sdhci", NULL); 202 } 203 204 libqos_init(raspi2_register_nodes); 205 206``x86_64/pc`` machine, simplified from 207``tests/qtest/libqos/x86_64_pc-machine.c``:: 208 209 #include "qgraph.h" 210 211 struct i440FX_pcihost { 212 QOSGraphObject obj; 213 QPCIBusPC pci; 214 }; 215 216 struct QX86PCMachine { 217 QOSGraphObject obj; 218 QGuestAllocator alloc; 219 i440FX_pcihost bridge; 220 }; 221 222 /* i440FX_pcihost */ 223 224 static QOSGraphObject *i440FX_host_get_device(void *obj, 225 const char *device) 226 { 227 i440FX_pcihost *host = obj; 228 if (!g_strcmp0(device, "pci-bus-pc")) { 229 return &host->pci.obj; 230 } 231 fprintf(stderr, "%s not present in i440FX-pcihost\n", device); 232 g_assert_not_reached(); 233 } 234 235 /* x86_64/pc machine */ 236 237 static void *pc_get_driver(void *object, const char *interface) 238 { 239 QX86PCMachine *machine = object; 240 if (!g_strcmp0(interface, "memory")) { 241 return &machine->alloc; 242 } 243 244 fprintf(stderr, "%s not present in x86_64/pc\n", interface); 245 g_assert_not_reached(); 246 } 247 248 static QOSGraphObject *pc_get_device(void *obj, const char *device) 249 { 250 QX86PCMachine *machine = obj; 251 if (!g_strcmp0(device, "i440FX-pcihost")) { 252 return &machine->bridge.obj; 253 } 254 255 fprintf(stderr, "%s not present in x86_64/pc\n", device); 256 g_assert_not_reached(); 257 } 258 259 static void *qos_create_machine_pc(QTestState *qts) 260 { 261 QX86PCMachine *machine = g_new0(QX86PCMachine, 1); 262 263 /* Get node(s) contained inside (CONTAINS) */ 264 machine->obj.get_device = pc_get_device; 265 266 /* Get node(s) produced (PRODUCES) */ 267 machine->obj.get_driver = pc_get_driver; 268 269 /* free the object */ 270 machine->obj.destructor = pc_destructor; 271 pc_alloc_init(&machine->alloc, qts, ALLOC_NO_FLAGS); 272 273 /* Get node(s) contained inside (CONTAINS) */ 274 machine->bridge.obj.get_device = i440FX_host_get_device; 275 276 return &machine->obj; 277 } 278 279 static void pc_machine_register_nodes(void) 280 { 281 /* x86_64/pc --contains--> 1440FX-pcihost --contains--> 282 * pci-bus-pc [--produces--> pci-bus (in pci.h)] */ 283 qos_node_create_machine("x86_64/pc", qos_create_machine_pc); 284 qos_node_contains("x86_64/pc", "i440FX-pcihost", NULL); 285 286 /* contained drivers don't need a constructor, 287 * they will be init by the parent */ 288 qos_node_create_driver("i440FX-pcihost", NULL); 289 qos_node_contains("i440FX-pcihost", "pci-bus-pc", NULL); 290 } 291 292 libqos_init(pc_machine_register_nodes); 293 294``sdhci`` taken from ``tests/qtest/libqos/sdhci.c``:: 295 296 /* Interface node, offers the sdhci API */ 297 struct QSDHCI { 298 uint16_t (*readw)(QSDHCI *s, uint32_t reg); 299 uint64_t (*readq)(QSDHCI *s, uint32_t reg); 300 void (*writeq)(QSDHCI *s, uint32_t reg, uint64_t val); 301 /* other fields */ 302 }; 303 304 /* Memory Mapped implementation of QSDHCI */ 305 struct QSDHCI_MemoryMapped { 306 QOSGraphObject obj; 307 QSDHCI sdhci; 308 /* other driver-specific fields */ 309 }; 310 311 /* PCI implementation of QSDHCI */ 312 struct QSDHCI_PCI { 313 QOSGraphObject obj; 314 QSDHCI sdhci; 315 /* other driver-specific fields */ 316 }; 317 318 /* Memory mapped implementation of QSDHCI */ 319 320 static void *sdhci_mm_get_driver(void *obj, const char *interface) 321 { 322 QSDHCI_MemoryMapped *smm = obj; 323 if (!g_strcmp0(interface, "sdhci")) { 324 return &smm->sdhci; 325 } 326 fprintf(stderr, "%s not present in generic-sdhci\n", interface); 327 g_assert_not_reached(); 328 } 329 330 void qos_init_sdhci_mm(QSDHCI_MemoryMapped *sdhci, QTestState *qts, 331 uint32_t addr, QSDHCIProperties *common) 332 { 333 /* Get node contained inside (CONTAINS) */ 334 sdhci->obj.get_driver = sdhci_mm_get_driver; 335 336 /* SDHCI interface API */ 337 sdhci->sdhci.readw = sdhci_mm_readw; 338 sdhci->sdhci.readq = sdhci_mm_readq; 339 sdhci->sdhci.writeq = sdhci_mm_writeq; 340 sdhci->qts = qts; 341 } 342 343 /* PCI implementation of QSDHCI */ 344 345 static void *sdhci_pci_get_driver(void *object, 346 const char *interface) 347 { 348 QSDHCI_PCI *spci = object; 349 if (!g_strcmp0(interface, "sdhci")) { 350 return &spci->sdhci; 351 } 352 353 fprintf(stderr, "%s not present in sdhci-pci\n", interface); 354 g_assert_not_reached(); 355 } 356 357 static void *sdhci_pci_create(void *pci_bus, 358 QGuestAllocator *alloc, 359 void *addr) 360 { 361 QSDHCI_PCI *spci = g_new0(QSDHCI_PCI, 1); 362 QPCIBus *bus = pci_bus; 363 uint64_t barsize; 364 365 qpci_device_init(&spci->dev, bus, addr); 366 367 /* SDHCI interface API */ 368 spci->sdhci.readw = sdhci_pci_readw; 369 spci->sdhci.readq = sdhci_pci_readq; 370 spci->sdhci.writeq = sdhci_pci_writeq; 371 372 /* Get node(s) produced (PRODUCES) */ 373 spci->obj.get_driver = sdhci_pci_get_driver; 374 375 spci->obj.start_hw = sdhci_pci_start_hw; 376 spci->obj.destructor = sdhci_destructor; 377 return &spci->obj; 378 } 379 380 static void qsdhci_register_nodes(void) 381 { 382 QOSGraphEdgeOptions opts = { 383 .extra_device_opts = "addr=04.0", 384 }; 385 386 /* generic-sdhci */ 387 /* generic-sdhci --produces--> sdhci */ 388 qos_node_create_driver("generic-sdhci", NULL); 389 qos_node_produces("generic-sdhci", "sdhci"); 390 391 /* sdhci-pci */ 392 /* sdhci-pci --produces--> sdhci 393 * sdhci-pci --consumes--> pci-bus */ 394 qos_node_create_driver("sdhci-pci", sdhci_pci_create); 395 qos_node_produces("sdhci-pci", "sdhci"); 396 qos_node_consumes("sdhci-pci", "pci-bus", &opts); 397 } 398 399 libqos_init(qsdhci_register_nodes); 400 401In the above example, all possible types of relations are created:: 402 403 x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc 404 | 405 sdhci-pci --consumes--> pci-bus <--produces--+ 406 | 407 +--produces--+ 408 | 409 v 410 sdhci 411 ^ 412 | 413 +--produces-- + 414 | 415 arm/raspi2 --contains--> generic-sdhci 416 417or inverting the consumes edge in consumed_by:: 418 419 x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc 420 | 421 sdhci-pci <--consumed by-- pci-bus <--produces--+ 422 | 423 +--produces--+ 424 | 425 v 426 sdhci 427 ^ 428 | 429 +--produces-- + 430 | 431 arm/raspi2 --contains--> generic-sdhci 432 433Adding a new test 434""""""""""""""""" 435 436Given the above setup, adding a new test is very simple. 437``sdhci-test``, taken from ``tests/qtest/sdhci-test.c``:: 438 439 static void check_capab_sdma(QSDHCI *s, bool supported) 440 { 441 uint64_t capab, capab_sdma; 442 443 capab = s->readq(s, SDHC_CAPAB); 444 capab_sdma = FIELD_EX64(capab, SDHC_CAPAB, SDMA); 445 g_assert_cmpuint(capab_sdma, ==, supported); 446 } 447 448 static void test_registers(void *obj, void *data, 449 QGuestAllocator *alloc) 450 { 451 QSDHCI *s = obj; 452 453 /* example test */ 454 check_capab_sdma(s, s->props.capab.sdma); 455 } 456 457 static void register_sdhci_test(void) 458 { 459 /* sdhci-test --consumes--> sdhci */ 460 qos_add_test("registers", "sdhci", test_registers, NULL); 461 } 462 463 libqos_init(register_sdhci_test); 464 465Here a new test is created, consuming ``sdhci`` interface node 466and creating a valid path from both machines to a test. 467Final graph will be like this:: 468 469 x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc 470 | 471 sdhci-pci --consumes--> pci-bus <--produces--+ 472 | 473 +--produces--+ 474 | 475 v 476 sdhci <--consumes-- sdhci-test 477 ^ 478 | 479 +--produces-- + 480 | 481 arm/raspi2 --contains--> generic-sdhci 482 483or inverting the consumes edge in consumed_by:: 484 485 x86_64/pc --contains--> 1440FX-pcihost --contains--> pci-bus-pc 486 | 487 sdhci-pci <--consumed by-- pci-bus <--produces--+ 488 | 489 +--produces--+ 490 | 491 v 492 sdhci --consumed by--> sdhci-test 493 ^ 494 | 495 +--produces-- + 496 | 497 arm/raspi2 --contains--> generic-sdhci 498 499Assuming there the binary is 500``QTEST_QEMU_BINARY=./qemu-system-x86_64`` 501a valid test path will be: 502``/x86_64/pc/1440FX-pcihost/pci-bus-pc/pci-bus/sdhci-pc/sdhci/sdhci-test`` 503 504and for the binary ``QTEST_QEMU_BINARY=./qemu-system-arm``: 505 506``/arm/raspi2/generic-sdhci/sdhci/sdhci-test`` 507 508Additional examples are also in ``test-qgraph.c`` 509 510Command line: 511"""""""""""""" 512 513Command line is built by using node names and optional arguments 514passed by the user when building the edges. 515 516There are three types of command line arguments: 517 518- ``in node`` : created from the node name. For example, machines will 519 have ``-M <machine>`` to its command line, while devices 520 ``-device <device>``. It is automatically done by the framework. 521- ``after node`` : added as additional argument to the node name. 522 This argument is added optionally when creating edges, 523 by setting the parameter ``after_cmd_line`` and 524 ``extra_edge_opts`` in ``QOSGraphEdgeOptions``. 525 The framework automatically adds 526 a comma before ``extra_edge_opts``, 527 because it is going to add attributes 528 after the destination node pointed by 529 the edge containing these options, and automatically 530 adds a space before ``after_cmd_line``, because it 531 adds an additional device, not an attribute. 532- ``before node`` : added as additional argument to the node name. 533 This argument is added optionally when creating edges, 534 by setting the parameter ``before_cmd_line`` in 535 ``QOSGraphEdgeOptions``. This attribute 536 is going to add attributes before the destination node 537 pointed by the edge containing these options. It is 538 helpful to commands that are not node-representable, 539 such as ``-fdsev`` or ``-netdev``. 540 541While adding command line in edges is always used, not all nodes names are 542used in every path walk: this is because the contained or produced ones 543are already added by QEMU, so only nodes that "consumes" will be used to 544build the command line. Also, nodes that will have ``{ "abstract" : true }`` 545as QMP attribute will loose their command line, since they are not proper 546devices to be added in QEMU. 547 548Example:: 549 550 QOSGraphEdgeOptions opts = { 551 .before_cmd_line = "-drive id=drv0,if=none,file=null-co://," 552 "file.read-zeroes=on,format=raw", 553 .after_cmd_line = "-device scsi-hd,bus=vs0.0,drive=drv0", 554 555 opts.extra_device_opts = "id=vs0"; 556 }; 557 558 qos_node_create_driver("virtio-scsi-device", 559 virtio_scsi_device_create); 560 qos_node_consumes("virtio-scsi-device", "virtio-bus", &opts); 561 562Will produce the following command line: 563``-drive id=drv0,if=none,file=null-co://, -device virtio-scsi-device,id=vs0 -device scsi-hd,bus=vs0.0,drive=drv0`` 564 565Qgraph API reference 566^^^^^^^^^^^^^^^^^^^^ 567 568.. kernel-doc:: tests/qtest/libqos/qgraph.h 569