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