1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (C) 2017 Intel Corporation. 3 * All rights reserved. 4 */ 5 6 #include "spdk/stdinc.h" 7 8 #include "env_internal.h" 9 10 #include "spdk/version.h" 11 #include "spdk/env_dpdk.h" 12 #include "spdk/log.h" 13 #include "spdk/config.h" 14 15 #include <openssl/ssl.h> 16 #include <openssl/err.h> 17 18 #include <rte_config.h> 19 #include <rte_eal.h> 20 #include <rte_errno.h> 21 #include <rte_vfio.h> 22 23 #define SPDK_ENV_DPDK_DEFAULT_NAME "spdk" 24 #define SPDK_ENV_DPDK_DEFAULT_SHM_ID -1 25 #define SPDK_ENV_DPDK_DEFAULT_MEM_SIZE -1 26 #define SPDK_ENV_DPDK_DEFAULT_MAIN_CORE -1 27 #define SPDK_ENV_DPDK_DEFAULT_MEM_CHANNEL -1 28 #define SPDK_ENV_DPDK_DEFAULT_CORE_MASK "0x1" 29 #define SPDK_ENV_DPDK_DEFAULT_BASE_VIRTADDR 0x200000000000 30 31 #define DPDK_ALLOW_PARAM "--allow" 32 #define DPDK_BLOCK_PARAM "--block" 33 #define DPDK_MAIN_CORE_PARAM "--main-lcore" 34 35 static char **g_eal_cmdline; 36 static int g_eal_cmdline_argcount; 37 static bool g_external_init = true; 38 39 static char * 40 _sprintf_alloc(const char *format, ...) 41 { 42 va_list args; 43 va_list args_copy; 44 char *buf; 45 size_t bufsize; 46 int rc; 47 48 va_start(args, format); 49 50 /* Try with a small buffer first. */ 51 bufsize = 32; 52 53 /* Limit maximum buffer size to something reasonable so we don't loop forever. */ 54 while (bufsize <= 1024 * 1024) { 55 buf = malloc(bufsize); 56 if (buf == NULL) { 57 va_end(args); 58 return NULL; 59 } 60 61 va_copy(args_copy, args); 62 rc = vsnprintf(buf, bufsize, format, args_copy); 63 va_end(args_copy); 64 65 /* 66 * If vsnprintf() returned a count within our current buffer size, we are done. 67 * The count does not include the \0 terminator, so rc == bufsize is not OK. 68 */ 69 if (rc >= 0 && (size_t)rc < bufsize) { 70 va_end(args); 71 return buf; 72 } 73 74 /* 75 * vsnprintf() should return the required space, but some libc versions do not 76 * implement this correctly, so just double the buffer size and try again. 77 * 78 * We don't need the data in buf, so rather than realloc(), use free() and malloc() 79 * again to avoid a copy. 80 */ 81 free(buf); 82 bufsize *= 2; 83 } 84 85 va_end(args); 86 return NULL; 87 } 88 89 void 90 spdk_env_opts_init(struct spdk_env_opts *opts) 91 { 92 if (!opts) { 93 return; 94 } 95 96 memset(opts, 0, sizeof(*opts)); 97 98 opts->name = SPDK_ENV_DPDK_DEFAULT_NAME; 99 opts->core_mask = SPDK_ENV_DPDK_DEFAULT_CORE_MASK; 100 opts->shm_id = SPDK_ENV_DPDK_DEFAULT_SHM_ID; 101 opts->mem_size = SPDK_ENV_DPDK_DEFAULT_MEM_SIZE; 102 opts->main_core = SPDK_ENV_DPDK_DEFAULT_MAIN_CORE; 103 opts->mem_channel = SPDK_ENV_DPDK_DEFAULT_MEM_CHANNEL; 104 opts->base_virtaddr = SPDK_ENV_DPDK_DEFAULT_BASE_VIRTADDR; 105 } 106 107 static void 108 free_args(char **args, int argcount) 109 { 110 int i; 111 112 if (args == NULL) { 113 return; 114 } 115 116 for (i = 0; i < argcount; i++) { 117 free(args[i]); 118 } 119 120 if (argcount) { 121 free(args); 122 } 123 } 124 125 static char ** 126 push_arg(char *args[], int *argcount, char *arg) 127 { 128 char **tmp; 129 130 if (arg == NULL) { 131 SPDK_ERRLOG("%s: NULL arg supplied\n", __func__); 132 free_args(args, *argcount); 133 return NULL; 134 } 135 136 tmp = realloc(args, sizeof(char *) * (*argcount + 1)); 137 if (tmp == NULL) { 138 free(arg); 139 free_args(args, *argcount); 140 return NULL; 141 } 142 143 tmp[*argcount] = arg; 144 (*argcount)++; 145 146 return tmp; 147 } 148 149 #if defined(__linux__) && defined(__x86_64__) 150 151 /* TODO: Can likely get this value from rlimits in the future */ 152 #define SPDK_IOMMU_VA_REQUIRED_WIDTH 48 153 #define VTD_CAP_MGAW_SHIFT 16 154 #define VTD_CAP_MGAW_MASK (0x3F << VTD_CAP_MGAW_SHIFT) 155 #define RD_AMD_CAP_VASIZE_SHIFT 15 156 #define RD_AMD_CAP_VASIZE_MASK (0x7F << RD_AMD_CAP_VASIZE_SHIFT) 157 158 static int 159 get_iommu_width(void) 160 { 161 int width = 0; 162 glob_t glob_results = {}; 163 164 /* Break * and / into separate strings to appease check_format.sh comment style check. */ 165 glob("/sys/devices/virtual/iommu/dmar*" "/intel-iommu/cap", 0, NULL, &glob_results); 166 glob("/sys/class/iommu/ivhd*" "/amd-iommu/cap", GLOB_APPEND, NULL, &glob_results); 167 168 for (size_t i = 0; i < glob_results.gl_pathc; i++) { 169 const char *filename = glob_results.gl_pathv[0]; 170 FILE *file = fopen(filename, "r"); 171 uint64_t cap_reg = 0; 172 173 if (file == NULL) { 174 continue; 175 } 176 177 if (fscanf(file, "%" PRIx64, &cap_reg) == 1) { 178 if (strstr(filename, "intel-iommu") != NULL) { 179 /* We have an Intel IOMMU */ 180 int mgaw = ((cap_reg & VTD_CAP_MGAW_MASK) >> VTD_CAP_MGAW_SHIFT) + 1; 181 182 if (width == 0 || (mgaw > 0 && mgaw < width)) { 183 width = mgaw; 184 } 185 } else if (strstr(filename, "amd-iommu") != NULL) { 186 /* We have an AMD IOMMU */ 187 int mgaw = ((cap_reg & RD_AMD_CAP_VASIZE_MASK) >> RD_AMD_CAP_VASIZE_SHIFT) + 1; 188 189 if (width == 0 || (mgaw > 0 && mgaw < width)) { 190 width = mgaw; 191 } 192 } 193 } 194 195 fclose(file); 196 } 197 198 globfree(&glob_results); 199 return width; 200 } 201 202 #endif 203 204 static int 205 build_eal_cmdline(const struct spdk_env_opts *opts) 206 { 207 int argcount = 0; 208 char **args; 209 bool no_huge; 210 211 args = NULL; 212 no_huge = opts->no_huge || (opts->env_context && strstr(opts->env_context, "--no-huge") != NULL); 213 214 /* set the program name */ 215 args = push_arg(args, &argcount, _sprintf_alloc("%s", opts->name)); 216 if (args == NULL) { 217 return -1; 218 } 219 220 /* disable shared configuration files when in single process mode. This allows for cleaner shutdown */ 221 if (opts->shm_id < 0) { 222 args = push_arg(args, &argcount, _sprintf_alloc("%s", "--no-shconf")); 223 if (args == NULL) { 224 return -1; 225 } 226 } 227 228 /* Either lcore_map or core_mask must be set. If both, or none specified, fail */ 229 if ((opts->core_mask == NULL) == (opts->lcore_map == NULL)) { 230 if (opts->core_mask && opts->lcore_map) { 231 fprintf(stderr, 232 "Both, lcore map and core mask are provided, while only one can be set\n"); 233 } else { 234 fprintf(stderr, "Core mask or lcore map must be specified\n"); 235 } 236 free_args(args, argcount); 237 return -1; 238 } 239 240 if (opts->lcore_map) { 241 /* If lcore list is set, generate --lcores parameter */ 242 args = push_arg(args, &argcount, _sprintf_alloc("--lcores=%s", opts->lcore_map)); 243 } else if (opts->core_mask[0] == '-') { 244 /* 245 * Set the coremask: 246 * 247 * - if it starts with '-', we presume it's literal EAL arguments such 248 * as --lcores. 249 * 250 * - if it starts with '[', we presume it's a core list to use with the 251 * -l option. 252 * 253 * - otherwise, it's a CPU mask of the form "0xff.." as expected by the 254 * -c option. 255 */ 256 args = push_arg(args, &argcount, _sprintf_alloc("%s", opts->core_mask)); 257 } else if (opts->core_mask[0] == '[') { 258 char *l_arg = _sprintf_alloc("-l %s", opts->core_mask + 1); 259 260 if (l_arg != NULL) { 261 int len = strlen(l_arg); 262 263 if (l_arg[len - 1] == ']') { 264 l_arg[len - 1] = '\0'; 265 } 266 } 267 args = push_arg(args, &argcount, l_arg); 268 } else { 269 args = push_arg(args, &argcount, _sprintf_alloc("-c %s", opts->core_mask)); 270 } 271 272 if (args == NULL) { 273 return -1; 274 } 275 276 /* set the memory channel number */ 277 if (opts->mem_channel > 0) { 278 args = push_arg(args, &argcount, _sprintf_alloc("-n %d", opts->mem_channel)); 279 if (args == NULL) { 280 return -1; 281 } 282 } 283 284 /* set the memory size */ 285 if (opts->mem_size >= 0) { 286 args = push_arg(args, &argcount, _sprintf_alloc("-m %d", opts->mem_size)); 287 if (args == NULL) { 288 return -1; 289 } 290 } 291 292 /* set no huge pages */ 293 if (opts->no_huge) { 294 mem_disable_huge_pages(); 295 } 296 297 /* set the main core */ 298 if (opts->main_core > 0) { 299 args = push_arg(args, &argcount, _sprintf_alloc("%s=%d", 300 DPDK_MAIN_CORE_PARAM, opts->main_core)); 301 if (args == NULL) { 302 return -1; 303 } 304 } 305 306 /* set no pci if enabled */ 307 if (opts->no_pci) { 308 args = push_arg(args, &argcount, _sprintf_alloc("--no-pci")); 309 if (args == NULL) { 310 return -1; 311 } 312 } 313 314 if (no_huge) { 315 if (opts->hugepage_single_segments || opts->unlink_hugepage || opts->hugedir) { 316 fprintf(stderr, "--no-huge invalid with other hugepage options\n"); 317 free_args(args, argcount); 318 return -1; 319 } 320 321 if (opts->mem_size < 0) { 322 fprintf(stderr, 323 "Disabling hugepages requires specifying how much memory " 324 "will be allocated using -s parameter\n"); 325 free_args(args, argcount); 326 return -1; 327 } 328 329 /* iova-mode=pa is incompatible with no_huge */ 330 if (opts->iova_mode && 331 (strcmp(opts->iova_mode, "pa") == 0)) { 332 fprintf(stderr, "iova-mode=pa is incompatible with specified " 333 "no-huge parameter\n"); 334 free_args(args, argcount); 335 return -1; 336 } 337 338 args = push_arg(args, &argcount, _sprintf_alloc("--no-huge")); 339 args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=va")); 340 341 } else { 342 /* create just one hugetlbfs file */ 343 if (opts->hugepage_single_segments) { 344 args = push_arg(args, &argcount, _sprintf_alloc("--single-file-segments")); 345 if (args == NULL) { 346 return -1; 347 } 348 } 349 350 /* unlink hugepages after initialization */ 351 /* Note: Automatically unlink hugepage when shm_id < 0, since it means we're not using 352 * multi-process so we don't need the hugepage links anymore. But we need to make sure 353 * we don't specify --huge-unlink implicitly if --single-file-segments was specified since 354 * DPDK doesn't support that. 355 */ 356 if (opts->unlink_hugepage || 357 (opts->shm_id < 0 && !opts->hugepage_single_segments)) { 358 args = push_arg(args, &argcount, _sprintf_alloc("--huge-unlink")); 359 if (args == NULL) { 360 return -1; 361 } 362 } 363 364 /* use a specific hugetlbfs mount */ 365 if (opts->hugedir) { 366 args = push_arg(args, &argcount, _sprintf_alloc("--huge-dir=%s", opts->hugedir)); 367 if (args == NULL) { 368 return -1; 369 } 370 } 371 } 372 373 if (opts->num_pci_addr) { 374 size_t i; 375 char bdf[32]; 376 struct spdk_pci_addr *pci_addr = 377 opts->pci_blocked ? opts->pci_blocked : opts->pci_allowed; 378 379 for (i = 0; i < opts->num_pci_addr; i++) { 380 spdk_pci_addr_fmt(bdf, 32, &pci_addr[i]); 381 args = push_arg(args, &argcount, _sprintf_alloc("%s=%s", 382 (opts->pci_blocked ? DPDK_BLOCK_PARAM : DPDK_ALLOW_PARAM), 383 bdf)); 384 if (args == NULL) { 385 return -1; 386 } 387 } 388 } 389 390 /* Disable DPDK telemetry information by default, can be modified with env_context. 391 * Prevents creation of dpdk_telemetry socket and additional pthread for it. 392 */ 393 args = push_arg(args, &argcount, _sprintf_alloc("--no-telemetry")); 394 if (args == NULL) { 395 return -1; 396 } 397 398 /* Lower default EAL loglevel to RTE_LOG_NOTICE - normal, but significant messages. 399 * This can be overridden by specifying the same option in opts->env_context 400 */ 401 args = push_arg(args, &argcount, strdup("--log-level=lib.eal:6")); 402 if (args == NULL) { 403 return -1; 404 } 405 406 /* Lower default CRYPTO loglevel to RTE_LOG_WARNING to avoid a ton of init msgs. 407 * This can be overridden by specifying the same option in opts->env_context 408 */ 409 args = push_arg(args, &argcount, strdup("--log-level=lib.cryptodev:5")); 410 if (args == NULL) { 411 return -1; 412 } 413 414 /* Lower default POWER loglevel to RTE_LOG_WARNING to avoid a ton of init msgs. 415 * This can be overridden by specifying the same option in opts->env_context 416 */ 417 args = push_arg(args, &argcount, strdup("--log-level=lib.power:5")); 418 if (args == NULL) { 419 return -1; 420 } 421 422 /* `user1` log type is used by rte_vhost, which prints an INFO log for each received 423 * vhost user message. We don't want that. The same log type is also used by a couple 424 * of other DPDK libs, but none of which we make use right now. If necessary, this can 425 * be overridden via opts->env_context. 426 */ 427 args = push_arg(args, &argcount, strdup("--log-level=user1:6")); 428 if (args == NULL) { 429 return -1; 430 } 431 432 #ifdef __linux__ 433 434 if (opts->iova_mode) { 435 /* iova-mode=pa is incompatible with no_huge */ 436 args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=%s", opts->iova_mode)); 437 if (args == NULL) { 438 return -1; 439 } 440 } else { 441 /* When using vfio with enable_unsafe_noiommu_mode=Y, we need iova-mode=pa, 442 * but DPDK guesses it should be iova-mode=va. Add a check and force 443 * iova-mode=pa here. */ 444 if (!no_huge && rte_vfio_noiommu_is_enabled()) { 445 args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=pa")); 446 if (args == NULL) { 447 return -1; 448 } 449 } 450 451 #if defined(__x86_64__) 452 /* DPDK by default guesses that it should be using iova-mode=va so that it can 453 * support running as an unprivileged user. However, some systems (especially 454 * virtual machines) don't have an IOMMU capable of handling the full virtual 455 * address space and DPDK doesn't currently catch that. Add a check in SPDK 456 * and force iova-mode=pa here. */ 457 if (!no_huge && get_iommu_width() < SPDK_IOMMU_VA_REQUIRED_WIDTH) { 458 args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=pa")); 459 if (args == NULL) { 460 return -1; 461 } 462 } 463 #elif defined(__PPC64__) 464 /* On Linux + PowerPC, DPDK doesn't support VA mode at all. Unfortunately, it doesn't correctly 465 * auto-detect at the moment, so we'll just force it here. */ 466 args = push_arg(args, &argcount, _sprintf_alloc("--iova-mode=pa")); 467 if (args == NULL) { 468 return -1; 469 } 470 #endif 471 } 472 473 474 /* Set the base virtual address - it must be an address that is not in the 475 * ASAN shadow region, otherwise ASAN-enabled builds will ignore the 476 * mmap hint. 477 * 478 * Ref: https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm 479 */ 480 args = push_arg(args, &argcount, _sprintf_alloc("--base-virtaddr=0x%" PRIx64, opts->base_virtaddr)); 481 if (args == NULL) { 482 return -1; 483 } 484 485 /* --match-allocation prevents DPDK from merging or splitting system memory allocations under the hood. 486 * This is critical for RDMA when attempting to use an rte_mempool based buffer pool. If DPDK merges two 487 * physically or IOVA contiguous memory regions, then when we go to allocate a buffer pool, it can split 488 * the memory for a buffer over two allocations meaning the buffer will be split over a memory region. 489 */ 490 491 /* --no-huge is incompatible with --match-allocations 492 * Ref: https://doc.dpdk.org/guides/prog_guide/env_abstraction_layer.html#hugepage-allocation-matching 493 */ 494 if (!no_huge && 495 (!opts->env_context || strstr(opts->env_context, "--legacy-mem") == NULL)) { 496 args = push_arg(args, &argcount, _sprintf_alloc("%s", "--match-allocations")); 497 if (args == NULL) { 498 return -1; 499 } 500 } 501 502 if (opts->shm_id < 0) { 503 args = push_arg(args, &argcount, _sprintf_alloc("--file-prefix=spdk_pid%d", 504 getpid())); 505 if (args == NULL) { 506 return -1; 507 } 508 } else { 509 args = push_arg(args, &argcount, _sprintf_alloc("--file-prefix=spdk%d", 510 opts->shm_id)); 511 if (args == NULL) { 512 return -1; 513 } 514 515 /* set the process type */ 516 args = push_arg(args, &argcount, _sprintf_alloc("--proc-type=auto")); 517 if (args == NULL) { 518 return -1; 519 } 520 } 521 522 /* --vfio-vf-token used for VF initialized by vfio_pci driver. */ 523 if (opts->vf_token) { 524 args = push_arg(args, &argcount, _sprintf_alloc("--vfio-vf-token=%s", 525 opts->vf_token)); 526 if (args == NULL) { 527 return -1; 528 } 529 } 530 #endif 531 532 if (opts->env_context) { 533 char *ptr = strdup(opts->env_context); 534 char *tok = strtok(ptr, " \t"); 535 536 /* DPDK expects each argument as a separate string in the argv 537 * array, so we need to tokenize here in case the caller 538 * passed multiple arguments in the env_context string. 539 */ 540 while (tok != NULL) { 541 args = push_arg(args, &argcount, strdup(tok)); 542 tok = strtok(NULL, " \t"); 543 } 544 545 free(ptr); 546 } 547 548 g_eal_cmdline = args; 549 g_eal_cmdline_argcount = argcount; 550 return argcount; 551 } 552 553 int 554 spdk_env_dpdk_post_init(bool legacy_mem) 555 { 556 int rc; 557 558 rc = pci_env_init(); 559 if (rc < 0) { 560 SPDK_ERRLOG("pci_env_init() failed\n"); 561 return rc; 562 } 563 564 rc = mem_map_init(legacy_mem); 565 if (rc < 0) { 566 SPDK_ERRLOG("Failed to allocate mem_map\n"); 567 return rc; 568 } 569 570 rc = vtophys_init(); 571 if (rc < 0) { 572 SPDK_ERRLOG("Failed to initialize vtophys\n"); 573 return rc; 574 } 575 576 return 0; 577 } 578 579 void 580 spdk_env_dpdk_post_fini(void) 581 { 582 pci_env_fini(); 583 584 free_args(g_eal_cmdline, g_eal_cmdline_argcount); 585 g_eal_cmdline = NULL; 586 g_eal_cmdline_argcount = 0; 587 } 588 589 int 590 spdk_env_init(const struct spdk_env_opts *opts) 591 { 592 char **dpdk_args = NULL; 593 char *args_print = NULL, *args_tmp = NULL; 594 OPENSSL_INIT_SETTINGS *settings; 595 int i, rc; 596 int orig_optind; 597 bool legacy_mem; 598 599 /* If SPDK env has been initialized before, then only pci env requires 600 * reinitialization. 601 */ 602 if (g_external_init == false) { 603 if (opts != NULL) { 604 fprintf(stderr, "Invalid arguments to reinitialize SPDK env\n"); 605 return -EINVAL; 606 } 607 608 printf("Starting %s / %s reinitialization...\n", SPDK_VERSION_STRING, rte_version()); 609 pci_env_reinit(); 610 611 return 0; 612 } 613 614 if (opts == NULL) { 615 fprintf(stderr, "NULL arguments to initialize DPDK\n"); 616 return -EINVAL; 617 } 618 619 settings = OPENSSL_INIT_new(); 620 if (!settings) { 621 fprintf(stderr, "Failed to create openssl settings object\n"); 622 ERR_print_errors_fp(stderr); 623 return -ENOMEM; 624 } 625 626 #if OPENSSL_VERSION_NUMBER >= 0x30000000 /* OPENSSL 3.0.0 */ 627 OPENSSL_INIT_set_config_file_flags(settings, 0); 628 #endif 629 rc = OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, settings); 630 if (rc != 1) { 631 fprintf(stderr, "Failed to initialize OpenSSL\n"); 632 ERR_print_errors_fp(stderr); 633 return -EINVAL; 634 } 635 OPENSSL_INIT_free(settings); 636 637 rc = build_eal_cmdline(opts); 638 if (rc < 0) { 639 SPDK_ERRLOG("Invalid arguments to initialize DPDK\n"); 640 return -EINVAL; 641 } 642 643 SPDK_PRINTF("Starting %s / %s initialization...\n", SPDK_VERSION_STRING, rte_version()); 644 645 args_print = _sprintf_alloc("[ DPDK EAL parameters: "); 646 if (args_print == NULL) { 647 return -ENOMEM; 648 } 649 for (i = 0; i < g_eal_cmdline_argcount; i++) { 650 args_tmp = args_print; 651 args_print = _sprintf_alloc("%s%s ", args_tmp, g_eal_cmdline[i]); 652 if (args_print == NULL) { 653 free(args_tmp); 654 return -ENOMEM; 655 } 656 free(args_tmp); 657 } 658 SPDK_PRINTF("%s]\n", args_print); 659 free(args_print); 660 661 /* DPDK rearranges the array we pass to it, so make a copy 662 * before passing so we can still free the individual strings 663 * correctly. 664 */ 665 dpdk_args = calloc(g_eal_cmdline_argcount, sizeof(char *)); 666 if (dpdk_args == NULL) { 667 SPDK_ERRLOG("Failed to allocate dpdk_args\n"); 668 return -ENOMEM; 669 } 670 memcpy(dpdk_args, g_eal_cmdline, sizeof(char *) * g_eal_cmdline_argcount); 671 672 fflush(stdout); 673 orig_optind = optind; 674 optind = 1; 675 rc = rte_eal_init(g_eal_cmdline_argcount, dpdk_args); 676 optind = orig_optind; 677 678 free(dpdk_args); 679 680 if (rc < 0) { 681 if (rte_errno == EALREADY) { 682 SPDK_ERRLOG("DPDK already initialized\n"); 683 } else { 684 SPDK_ERRLOG("Failed to initialize DPDK\n"); 685 } 686 return -rte_errno; 687 } 688 689 legacy_mem = false; 690 if (opts->env_context && strstr(opts->env_context, "--legacy-mem") != NULL) { 691 legacy_mem = true; 692 } 693 694 rc = spdk_env_dpdk_post_init(legacy_mem); 695 if (rc == 0) { 696 g_external_init = false; 697 } 698 699 return rc; 700 } 701 702 /* We use priority 101 which is the highest priority level available 703 * to applications (the toolchains reserve 1 to 100 for internal usage). 704 * This ensures this destructor runs last, after any other destructors 705 * that might still need the environment up and running. 706 */ 707 __attribute__((destructor(101))) static void 708 dpdk_cleanup(void) 709 { 710 /* Only call rte_eal_cleanup if the SPDK env library called rte_eal_init. */ 711 if (!g_external_init) { 712 rte_eal_cleanup(); 713 } 714 } 715 716 void 717 spdk_env_fini(void) 718 { 719 spdk_env_dpdk_post_fini(); 720 } 721 722 bool 723 spdk_env_dpdk_external_init(void) 724 { 725 return g_external_init; 726 } 727