1 /* 2 * libqos driver framework 3 * 4 * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com> 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License version 2 as published by the Free Software Foundation. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, see <http://www.gnu.org/licenses/> 17 */ 18 19 #include "qemu/osdep.h" 20 #include "libqtest.h" 21 #include "qemu/queue.h" 22 #include "libqos/qgraph_internal.h" 23 #include "libqos/qgraph.h" 24 25 #define QGRAPH_PRINT_DEBUG 0 26 #define QOS_ROOT "" 27 typedef struct QOSStackElement QOSStackElement; 28 29 /* Graph Edge.*/ 30 struct QOSGraphEdge { 31 QOSEdgeType type; 32 char *dest; 33 void *arg; /* just for QEDGE_CONTAINS 34 * and QEDGE_CONSUMED_BY */ 35 char *extra_device_opts; /* added to -device option, "," is 36 * automatically added 37 */ 38 char *before_cmd_line; /* added before node cmd_line */ 39 char *after_cmd_line; /* added after -device options */ 40 char *edge_name; /* used by QEDGE_CONTAINS */ 41 QSLIST_ENTRY(QOSGraphEdge) edge_list; 42 }; 43 44 typedef QSLIST_HEAD(, QOSGraphEdge) QOSGraphEdgeList; 45 46 /** 47 * Stack used to keep track of the discovered path when using 48 * the DFS algorithm 49 */ 50 struct QOSStackElement { 51 QOSGraphNode *node; 52 QOSStackElement *parent; 53 QOSGraphEdge *parent_edge; 54 int length; 55 }; 56 57 /* Each enty in these hash table will consist of <string, node/edge> pair. */ 58 static GHashTable *edge_table; 59 static GHashTable *node_table; 60 61 /* stack used by the DFS algorithm to store the path from machine to test */ 62 static QOSStackElement qos_node_stack[QOS_PATH_MAX_ELEMENT_SIZE]; 63 static int qos_node_tos; 64 65 /** 66 * add_edge(): creates an edge of type @type 67 * from @source to @dest node, and inserts it in the 68 * edges hash table 69 * 70 * Nodes @source and @dest do not necessarily need to exist. 71 * Possibility to add also options (see #QOSGraphEdgeOptions) 72 * edge->edge_name is used as identifier for get_device relationships, 73 * so by default is equal to @dest. 74 */ 75 static void add_edge(const char *source, const char *dest, 76 QOSEdgeType type, QOSGraphEdgeOptions *opts) 77 { 78 char *key; 79 QOSGraphEdgeList *list = g_hash_table_lookup(edge_table, source); 80 QOSGraphEdgeOptions def_opts = { }; 81 82 if (!list) { 83 list = g_new0(QOSGraphEdgeList, 1); 84 key = g_strdup(source); 85 g_hash_table_insert(edge_table, key, list); 86 } 87 88 if (!opts) { 89 opts = &def_opts; 90 } 91 92 QOSGraphEdge *edge = g_new0(QOSGraphEdge, 1); 93 edge->type = type; 94 edge->dest = g_strdup(dest); 95 edge->edge_name = g_strdup(opts->edge_name ?: dest); 96 edge->arg = g_memdup(opts->arg, opts->size_arg); 97 98 edge->before_cmd_line = 99 opts->before_cmd_line ? g_strconcat(" ", opts->before_cmd_line, NULL) : NULL; 100 edge->extra_device_opts = 101 opts->extra_device_opts ? g_strconcat(",", opts->extra_device_opts, NULL) : NULL; 102 edge->after_cmd_line = 103 opts->after_cmd_line ? g_strconcat(" ", opts->after_cmd_line, NULL) : NULL; 104 105 QSLIST_INSERT_HEAD(list, edge, edge_list); 106 } 107 108 /* destroy_edges(): frees all edges inside a given @list */ 109 static void destroy_edges(void *list) 110 { 111 QOSGraphEdge *temp; 112 QOSGraphEdgeList *elist = list; 113 114 while (!QSLIST_EMPTY(elist)) { 115 temp = QSLIST_FIRST(elist); 116 QSLIST_REMOVE_HEAD(elist, edge_list); 117 g_free(temp->dest); 118 g_free(temp->before_cmd_line); 119 g_free(temp->after_cmd_line); 120 g_free(temp->extra_device_opts); 121 g_free(temp->edge_name); 122 g_free(temp->arg); 123 g_free(temp); 124 } 125 g_free(elist); 126 } 127 128 /** 129 * create_node(): creates a node @name of type @type 130 * and inserts it to the nodes hash table. 131 * By default, node is not available. 132 */ 133 static QOSGraphNode *create_node(const char *name, QOSNodeType type) 134 { 135 if (g_hash_table_lookup(node_table, name)) { 136 g_printerr("Node %s already created\n", name); 137 abort(); 138 } 139 140 QOSGraphNode *node = g_new0(QOSGraphNode, 1); 141 node->type = type; 142 node->available = false; 143 node->name = g_strdup(name); 144 g_hash_table_insert(node_table, node->name, node); 145 return node; 146 } 147 148 /** 149 * destroy_node(): frees a node @val from the nodes hash table. 150 * Note that node->name is not free'd since it will represent the 151 * hash table key 152 */ 153 static void destroy_node(void *val) 154 { 155 QOSGraphNode *node = val; 156 g_free(node->command_line); 157 g_free(node); 158 } 159 160 /** 161 * destroy_string(): frees @key from the nodes hash table. 162 * Actually frees the node->name 163 */ 164 static void destroy_string(void *key) 165 { 166 g_free(key); 167 } 168 169 /** 170 * search_node(): search for a node @key in the nodes hash table 171 * Returns the QOSGraphNode if found, #NULL otherwise 172 */ 173 static QOSGraphNode *search_node(const char *key) 174 { 175 return g_hash_table_lookup(node_table, key); 176 } 177 178 /** 179 * get_edgelist(): returns the edge list (value) assigned to 180 * the @key in the edge hash table. 181 * This list will contain all edges with source equal to @key 182 * 183 * Returns: on success: the %QOSGraphEdgeList 184 * otherwise: abort() 185 */ 186 static QOSGraphEdgeList *get_edgelist(const char *key) 187 { 188 return g_hash_table_lookup(edge_table, key); 189 } 190 191 /** 192 * search_list_edges(): search for an edge with destination @dest 193 * in the given @edgelist. 194 * 195 * Returns: on success: the %QOSGraphEdge 196 * otherwise: #NULL 197 */ 198 static QOSGraphEdge *search_list_edges(QOSGraphEdgeList *edgelist, 199 const char *dest) 200 { 201 QOSGraphEdge *tmp, *next; 202 if (!edgelist) { 203 return NULL; 204 } 205 QSLIST_FOREACH_SAFE(tmp, edgelist, edge_list, next) { 206 if (g_strcmp0(tmp->dest, dest) == 0) { 207 break; 208 } 209 } 210 return tmp; 211 } 212 213 /** 214 * search_machine(): search for a machine @name in the node hash 215 * table. A machine is the child of the root node. 216 * This function forces the research in the childs of the root, 217 * to check the node is a proper machine 218 * 219 * Returns: on success: the %QOSGraphNode 220 * otherwise: #NULL 221 */ 222 static QOSGraphNode *search_machine(const char *name) 223 { 224 QOSGraphNode *n; 225 QOSGraphEdgeList *root_list = get_edgelist(QOS_ROOT); 226 QOSGraphEdge *e = search_list_edges(root_list, name); 227 if (!e) { 228 return NULL; 229 } 230 n = search_node(e->dest); 231 if (n->type == QNODE_MACHINE) { 232 return n; 233 } 234 return NULL; 235 } 236 237 /** 238 * create_interface(): checks if there is already 239 * a node @node in the node hash table, if not 240 * creates a node @node of type #QNODE_INTERFACE 241 * and inserts it. If there is one, check it's 242 * a #QNODE_INTERFACE and abort() if it's not. 243 */ 244 static void create_interface(const char *node) 245 { 246 QOSGraphNode *interface; 247 interface = search_node(node); 248 if (!interface) { 249 create_node(node, QNODE_INTERFACE); 250 } else if (interface->type != QNODE_INTERFACE) { 251 fprintf(stderr, "Error: Node %s is not an interface\n", node); 252 abort(); 253 } 254 } 255 256 /** 257 * build_machine_cmd_line(): builds the command line for the machine 258 * @node. The node name must be a valid qemu identifier, since it 259 * will be used to build the command line. 260 * 261 * It is also possible to pass an optional @args that will be 262 * concatenated to the command line. 263 * 264 * For machines, prepend -M to the machine name. ", @rgs" is added 265 * after the -M <machine> command. 266 */ 267 static void build_machine_cmd_line(QOSGraphNode *node, const char *args) 268 { 269 char *machine = qos_get_machine_type(node->name); 270 if (args) { 271 node->command_line = g_strconcat("-M ", machine, ",", args, NULL); 272 } else { 273 node->command_line = g_strconcat("-M ", machine, " ", NULL); 274 } 275 } 276 277 /** 278 * build_driver_cmd_line(): builds the command line for the driver 279 * @node. The node name must be a valid qemu identifier, since it 280 * will be used to build the command line. 281 * 282 * Driver do not need additional command line, since it will be 283 * provided by the edge options. 284 * 285 * For drivers, prepend -device to the node name. 286 */ 287 static void build_driver_cmd_line(QOSGraphNode *node) 288 { 289 node->command_line = g_strconcat(" -device ", node->name, NULL); 290 } 291 292 /* qos_print_cb(): callback prints all path found by the DFS algorithm. */ 293 static void qos_print_cb(QOSGraphNode *path, int length) 294 { 295 #if QGRAPH_PRINT_DEBUG 296 printf("%d elements\n", length); 297 298 if (!path) { 299 return; 300 } 301 302 while (path->path_edge) { 303 printf("%s ", path->name); 304 switch (path->path_edge->type) { 305 case QEDGE_PRODUCES: 306 printf("--PRODUCES--> "); 307 break; 308 case QEDGE_CONSUMED_BY: 309 printf("--CONSUMED_BY--> "); 310 break; 311 case QEDGE_CONTAINS: 312 printf("--CONTAINS--> "); 313 break; 314 } 315 path = search_node(path->path_edge->dest); 316 } 317 318 printf("%s\n\n", path->name); 319 #endif 320 } 321 322 /* qos_push(): push a node @el and edge @e in the qos_node_stack */ 323 static void qos_push(QOSGraphNode *el, QOSStackElement *parent, 324 QOSGraphEdge *e) 325 { 326 int len = 0; /* root is not counted */ 327 if (qos_node_tos == QOS_PATH_MAX_ELEMENT_SIZE) { 328 g_printerr("QOSStack: full stack, cannot push"); 329 abort(); 330 } 331 332 if (parent) { 333 len = parent->length + 1; 334 } 335 qos_node_stack[qos_node_tos++] = (QOSStackElement) { 336 .node = el, 337 .parent = parent, 338 .parent_edge = e, 339 .length = len, 340 }; 341 } 342 343 /* qos_tos(): returns the top of stack, without popping */ 344 static QOSStackElement *qos_tos(void) 345 { 346 return &qos_node_stack[qos_node_tos - 1]; 347 } 348 349 /* qos_pop(): pops an element from the tos, setting it unvisited*/ 350 static QOSStackElement *qos_pop(void) 351 { 352 if (qos_node_tos == 0) { 353 g_printerr("QOSStack: empty stack, cannot pop"); 354 abort(); 355 } 356 QOSStackElement *e = qos_tos(); 357 e->node->visited = false; 358 qos_node_tos--; 359 return e; 360 } 361 362 /** 363 * qos_reverse_path(): reverses the found path, going from 364 * test-to-machine to machine-to-test 365 */ 366 static QOSGraphNode *qos_reverse_path(QOSStackElement *el) 367 { 368 if (!el) { 369 return NULL; 370 } 371 372 el->node->path_edge = NULL; 373 374 while (el->parent) { 375 el->parent->node->path_edge = el->parent_edge; 376 el = el->parent; 377 } 378 379 return el->node; 380 } 381 382 /** 383 * qos_traverse_graph(): graph-walking algorithm, using Depth First Search it 384 * starts from the root @machine and walks all possible path until it 385 * reaches a test node. 386 * At that point, it reverses the path found and invokes the @callback. 387 * 388 * Being Depth First Search, time complexity is O(|V| + |E|), while 389 * space is O(|V|). In this case, the maximum stack size is set by 390 * QOS_PATH_MAX_ELEMENT_SIZE. 391 */ 392 static void qos_traverse_graph(QOSGraphNode *root, QOSTestCallback callback) 393 { 394 QOSGraphNode *v, *dest_node, *path; 395 QOSStackElement *s_el; 396 QOSGraphEdge *e, *next; 397 QOSGraphEdgeList *list; 398 399 qos_push(root, NULL, NULL); 400 401 while (qos_node_tos > 0) { 402 s_el = qos_tos(); 403 v = s_el->node; 404 if (v->visited) { 405 qos_pop(); 406 continue; 407 } 408 v->visited = true; 409 list = get_edgelist(v->name); 410 if (!list) { 411 qos_pop(); 412 if (v->type == QNODE_TEST) { 413 v->visited = false; 414 path = qos_reverse_path(s_el); 415 callback(path, s_el->length); 416 } 417 } else { 418 QSLIST_FOREACH_SAFE(e, list, edge_list, next) { 419 dest_node = search_node(e->dest); 420 421 if (!dest_node) { 422 fprintf(stderr, "node %s in %s -> %s does not exist\n", 423 e->dest, v->name, e->dest); 424 abort(); 425 } 426 427 if (!dest_node->visited && dest_node->available) { 428 qos_push(dest_node, s_el, e); 429 } 430 } 431 } 432 } 433 } 434 435 /* QGRAPH API*/ 436 437 QOSGraphNode *qos_graph_get_node(const char *key) 438 { 439 return search_node(key); 440 } 441 442 bool qos_graph_has_node(const char *node) 443 { 444 QOSGraphNode *n = search_node(node); 445 return n != NULL; 446 } 447 448 QOSNodeType qos_graph_get_node_type(const char *node) 449 { 450 QOSGraphNode *n = search_node(node); 451 if (n) { 452 return n->type; 453 } 454 return -1; 455 } 456 457 bool qos_graph_get_node_availability(const char *node) 458 { 459 QOSGraphNode *n = search_node(node); 460 if (n) { 461 return n->available; 462 } 463 return false; 464 } 465 466 QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest) 467 { 468 QOSGraphEdgeList *list = get_edgelist(node); 469 return search_list_edges(list, dest); 470 } 471 472 QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge) 473 { 474 if (!edge) { 475 return -1; 476 } 477 return edge->type;; 478 } 479 480 char *qos_graph_edge_get_dest(QOSGraphEdge *edge) 481 { 482 if (!edge) { 483 return NULL; 484 } 485 return edge->dest; 486 } 487 488 void *qos_graph_edge_get_arg(QOSGraphEdge *edge) 489 { 490 if (!edge) { 491 return NULL; 492 } 493 return edge->arg; 494 } 495 496 char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge) 497 { 498 if (!edge) { 499 return NULL; 500 } 501 return edge->after_cmd_line; 502 } 503 504 char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge) 505 { 506 if (!edge) { 507 return NULL; 508 } 509 return edge->before_cmd_line; 510 } 511 512 char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge) 513 { 514 if (!edge) { 515 return NULL; 516 } 517 return edge->extra_device_opts; 518 } 519 520 char *qos_graph_edge_get_name(QOSGraphEdge *edge) 521 { 522 if (!edge) { 523 return NULL; 524 } 525 return edge->edge_name; 526 } 527 528 bool qos_graph_has_edge(const char *start, const char *dest) 529 { 530 QOSGraphEdgeList *list = get_edgelist(start); 531 QOSGraphEdge *e = search_list_edges(list, dest); 532 return e != NULL; 533 } 534 535 QOSGraphNode *qos_graph_get_machine(const char *node) 536 { 537 return search_machine(node); 538 } 539 540 bool qos_graph_has_machine(const char *node) 541 { 542 QOSGraphNode *m = search_machine(node); 543 return m != NULL; 544 } 545 546 void qos_print_graph(void) 547 { 548 qos_graph_foreach_test_path(qos_print_cb); 549 } 550 551 void qos_graph_init(void) 552 { 553 if (!node_table) { 554 node_table = g_hash_table_new_full(g_str_hash, g_str_equal, 555 destroy_string, destroy_node); 556 create_node(QOS_ROOT, QNODE_DRIVER); 557 } 558 559 if (!edge_table) { 560 edge_table = g_hash_table_new_full(g_str_hash, g_str_equal, 561 destroy_string, destroy_edges); 562 } 563 } 564 565 void qos_graph_destroy(void) 566 { 567 if (node_table) { 568 g_hash_table_destroy(node_table); 569 } 570 571 if (edge_table) { 572 g_hash_table_destroy(edge_table); 573 } 574 575 node_table = NULL; 576 edge_table = NULL; 577 } 578 579 void qos_node_destroy(void *key) 580 { 581 g_hash_table_remove(node_table, key); 582 } 583 584 void qos_edge_destroy(void *key) 585 { 586 g_hash_table_remove(edge_table, key); 587 } 588 589 void qos_add_test(const char *name, const char *interface, 590 QOSTestFunc test_func, QOSGraphTestOptions *opts) 591 { 592 QOSGraphNode *node; 593 char *test_name = g_strdup_printf("%s-tests/%s", interface, name);; 594 QOSGraphTestOptions def_opts = { }; 595 596 if (!opts) { 597 opts = &def_opts; 598 } 599 node = create_node(test_name, QNODE_TEST); 600 node->u.test.function = test_func; 601 node->u.test.arg = opts->arg; 602 assert(!opts->edge.arg); 603 assert(!opts->edge.size_arg); 604 605 node->u.test.before = opts->before; 606 node->u.test.subprocess = opts->subprocess; 607 node->available = true; 608 add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge); 609 g_free(test_name); 610 } 611 612 void qos_node_create_machine(const char *name, QOSCreateMachineFunc function) 613 { 614 qos_node_create_machine_args(name, function, NULL); 615 } 616 617 void qos_node_create_machine_args(const char *name, 618 QOSCreateMachineFunc function, 619 const char *opts) 620 { 621 QOSGraphNode *node = create_node(name, QNODE_MACHINE); 622 build_machine_cmd_line(node, opts); 623 node->u.machine.constructor = function; 624 add_edge(QOS_ROOT, name, QEDGE_CONTAINS, NULL); 625 } 626 627 void qos_node_create_driver(const char *name, QOSCreateDriverFunc function) 628 { 629 QOSGraphNode *node = create_node(name, QNODE_DRIVER); 630 build_driver_cmd_line(node); 631 node->u.driver.constructor = function; 632 } 633 634 void qos_node_contains(const char *container, const char *contained, 635 QOSGraphEdgeOptions *opts, ...) 636 { 637 va_list va; 638 639 if (opts == NULL) { 640 add_edge(container, contained, QEDGE_CONTAINS, NULL); 641 return; 642 } 643 644 va_start(va, opts); 645 do { 646 add_edge(container, contained, QEDGE_CONTAINS, opts); 647 opts = va_arg(va, QOSGraphEdgeOptions *); 648 } while (opts != NULL); 649 650 va_end(va); 651 } 652 653 void qos_node_produces(const char *producer, const char *interface) 654 { 655 create_interface(interface); 656 add_edge(producer, interface, QEDGE_PRODUCES, NULL); 657 } 658 659 void qos_node_consumes(const char *consumer, const char *interface, 660 QOSGraphEdgeOptions *opts) 661 { 662 create_interface(interface); 663 add_edge(interface, consumer, QEDGE_CONSUMED_BY, opts); 664 } 665 666 void qos_graph_node_set_availability(const char *node, bool av) 667 { 668 QOSGraphEdgeList *elist; 669 QOSGraphNode *n = search_node(node); 670 QOSGraphEdge *e, *next; 671 if (!n) { 672 return; 673 } 674 n->available = av; 675 elist = get_edgelist(node); 676 if (!elist) { 677 return; 678 } 679 QSLIST_FOREACH_SAFE(e, elist, edge_list, next) { 680 if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) { 681 qos_graph_node_set_availability(e->dest, av); 682 } 683 } 684 } 685 686 void qos_graph_foreach_test_path(QOSTestCallback fn) 687 { 688 QOSGraphNode *root = qos_graph_get_node(QOS_ROOT); 689 qos_traverse_graph(root, fn); 690 } 691 692 QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts) 693 { 694 QOSGraphObject *obj; 695 696 g_assert(node->type == QNODE_MACHINE); 697 obj = node->u.machine.constructor(qts); 698 obj->free = g_free; 699 return obj; 700 } 701 702 QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, 703 QGuestAllocator *alloc, void *arg) 704 { 705 QOSGraphObject *obj; 706 707 g_assert(node->type == QNODE_DRIVER); 708 obj = node->u.driver.constructor(parent, alloc, arg); 709 obj->free = g_free; 710 return obj; 711 } 712 713 void qos_object_destroy(QOSGraphObject *obj) 714 { 715 if (!obj) { 716 return; 717 } 718 if (obj->destructor) { 719 obj->destructor(obj); 720 } 721 if (obj->free) { 722 obj->free(obj); 723 } 724 } 725 726 void qos_object_queue_destroy(QOSGraphObject *obj) 727 { 728 g_test_queue_destroy((GDestroyNotify) qos_object_destroy, obj); 729 } 730 731 void qos_object_start_hw(QOSGraphObject *obj) 732 { 733 if (obj->start_hw) { 734 obj->start_hw(obj); 735 } 736 } 737 738 char *qos_get_machine_type(char *name) 739 { 740 while (*name != '\0' && *name != '/') { 741 name++; 742 } 743 744 if (!*name || !name[1]) { 745 fprintf(stderr, "Machine name has to be of the form <arch>/<machine>\n"); 746 abort(); 747 } 748 749 return name + 1; 750 } 751 752 void qos_delete_cmd_line(const char *name) 753 { 754 QOSGraphNode *node = search_node(name); 755 if (node) { 756 g_free(node->command_line); 757 node->command_line = NULL; 758 } 759 } 760