1 /*- 2 * BSD LICENSE 3 * 4 * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * * Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * * Neither the name of Intel Corporation nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <stdint.h> 35 #include <sys/queue.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <stdio.h> 39 #include <assert.h> 40 #include <errno.h> 41 #include <signal.h> 42 #include <stdarg.h> 43 #include <inttypes.h> 44 #include <getopt.h> 45 46 #include <rte_common.h> 47 #include <rte_log.h> 48 #include <rte_memory.h> 49 #include <rte_memcpy.h> 50 #include <rte_memzone.h> 51 #include <rte_eal.h> 52 #include <rte_per_lcore.h> 53 #include <rte_launch.h> 54 #include <rte_atomic.h> 55 #include <rte_cycles.h> 56 #include <rte_prefetch.h> 57 #include <rte_lcore.h> 58 #include <rte_per_lcore.h> 59 #include <rte_branch_prediction.h> 60 #include <rte_interrupts.h> 61 #include <rte_pci.h> 62 #include <rte_random.h> 63 #include <rte_debug.h> 64 #include <rte_ether.h> 65 #include <rte_ethdev.h> 66 #include <rte_log.h> 67 #include <rte_mempool.h> 68 #include <rte_mbuf.h> 69 #include <rte_memcpy.h> 70 71 #define MAX_QUEUES 1024 72 /* 73 * 1024 queues require to meet the needs of a large number of vmdq_pools. 74 * (RX/TX_queue_nb * RX/TX_ring_descriptors_nb) per port. 75 */ 76 #define NUM_MBUFS_PER_PORT (MAX_QUEUES * RTE_MAX(RTE_TEST_RX_DESC_DEFAULT, \ 77 RTE_TEST_TX_DESC_DEFAULT)) 78 #define MBUF_CACHE_SIZE 64 79 80 #define MAX_PKT_BURST 32 81 82 /* 83 * Configurable number of RX/TX ring descriptors 84 */ 85 #define RTE_TEST_RX_DESC_DEFAULT 128 86 #define RTE_TEST_TX_DESC_DEFAULT 512 87 88 #define INVALID_PORT_ID 0xFF 89 90 /* mask of enabled ports */ 91 static uint32_t enabled_port_mask; 92 93 /* number of pools (if user does not specify any, 8 by default */ 94 static uint32_t num_queues = 8; 95 static uint32_t num_pools = 8; 96 97 /* empty vmdq configuration structure. Filled in programatically */ 98 static const struct rte_eth_conf vmdq_conf_default = { 99 .rxmode = { 100 .mq_mode = ETH_MQ_RX_VMDQ_ONLY, 101 .split_hdr_size = 0, 102 .header_split = 0, /**< Header Split disabled */ 103 .hw_ip_checksum = 0, /**< IP checksum offload disabled */ 104 .hw_vlan_filter = 0, /**< VLAN filtering disabled */ 105 .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ 106 }, 107 108 .txmode = { 109 .mq_mode = ETH_MQ_TX_NONE, 110 }, 111 .rx_adv_conf = { 112 /* 113 * should be overridden separately in code with 114 * appropriate values 115 */ 116 .vmdq_rx_conf = { 117 .nb_queue_pools = ETH_8_POOLS, 118 .enable_default_pool = 0, 119 .default_pool = 0, 120 .nb_pool_maps = 0, 121 .pool_map = {{0, 0},}, 122 }, 123 }, 124 }; 125 126 static unsigned lcore_ids[RTE_MAX_LCORE]; 127 static uint8_t ports[RTE_MAX_ETHPORTS]; 128 static unsigned num_ports; /**< The number of ports specified in command line */ 129 130 /* array used for printing out statistics */ 131 volatile unsigned long rxPackets[MAX_QUEUES] = {0}; 132 133 const uint16_t vlan_tags[] = { 134 0, 1, 2, 3, 4, 5, 6, 7, 135 8, 9, 10, 11, 12, 13, 14, 15, 136 16, 17, 18, 19, 20, 21, 22, 23, 137 24, 25, 26, 27, 28, 29, 30, 31, 138 32, 33, 34, 35, 36, 37, 38, 39, 139 40, 41, 42, 43, 44, 45, 46, 47, 140 48, 49, 50, 51, 52, 53, 54, 55, 141 56, 57, 58, 59, 60, 61, 62, 63, 142 }; 143 const uint16_t num_vlans = RTE_DIM(vlan_tags); 144 static uint16_t num_pf_queues, num_vmdq_queues; 145 static uint16_t vmdq_pool_base, vmdq_queue_base; 146 /* pool mac addr template, pool mac addr is like: 52 54 00 12 port# pool# */ 147 static struct ether_addr pool_addr_template = { 148 .addr_bytes = {0x52, 0x54, 0x00, 0x12, 0x00, 0x00} 149 }; 150 151 /* ethernet addresses of ports */ 152 static struct ether_addr vmdq_ports_eth_addr[RTE_MAX_ETHPORTS]; 153 154 #define MAX_QUEUE_NUM_10G 128 155 #define MAX_QUEUE_NUM_1G 8 156 #define MAX_POOL_MAP_NUM_10G 64 157 #define MAX_POOL_MAP_NUM_1G 32 158 #define MAX_POOL_NUM_10G 64 159 #define MAX_POOL_NUM_1G 8 160 /* 161 * Builds up the correct configuration for vmdq based on the vlan tags array 162 * given above, and determine the queue number and pool map number according to 163 * valid pool number 164 */ 165 static inline int 166 get_eth_conf(struct rte_eth_conf *eth_conf, uint32_t num_pools) 167 { 168 struct rte_eth_vmdq_rx_conf conf; 169 unsigned i; 170 171 conf.nb_queue_pools = (enum rte_eth_nb_pools)num_pools; 172 conf.nb_pool_maps = num_pools; 173 conf.enable_default_pool = 0; 174 conf.default_pool = 0; /* set explicit value, even if not used */ 175 176 for (i = 0; i < conf.nb_pool_maps; i++) { 177 conf.pool_map[i].vlan_id = vlan_tags[i]; 178 conf.pool_map[i].pools = (1UL << (i % num_pools)); 179 } 180 181 (void)(rte_memcpy(eth_conf, &vmdq_conf_default, sizeof(*eth_conf))); 182 (void)(rte_memcpy(ð_conf->rx_adv_conf.vmdq_rx_conf, &conf, 183 sizeof(eth_conf->rx_adv_conf.vmdq_rx_conf))); 184 return 0; 185 } 186 187 /* 188 * Initialises a given port using global settings and with the rx buffers 189 * coming from the mbuf_pool passed as parameter 190 */ 191 static inline int 192 port_init(uint8_t port, struct rte_mempool *mbuf_pool) 193 { 194 struct rte_eth_dev_info dev_info; 195 struct rte_eth_rxconf *rxconf; 196 struct rte_eth_conf port_conf; 197 uint16_t rxRings, txRings; 198 uint16_t rxRingSize = RTE_TEST_RX_DESC_DEFAULT; 199 uint16_t txRingSize = RTE_TEST_TX_DESC_DEFAULT; 200 int retval; 201 uint16_t q; 202 uint16_t queues_per_pool; 203 uint32_t max_nb_pools; 204 205 /* 206 * The max pool number from dev_info will be used to validate the pool 207 * number specified in cmd line 208 */ 209 rte_eth_dev_info_get(port, &dev_info); 210 max_nb_pools = (uint32_t)dev_info.max_vmdq_pools; 211 /* 212 * We allow to process part of VMDQ pools specified by num_pools in 213 * command line. 214 */ 215 if (num_pools > max_nb_pools) { 216 printf("num_pools %d >max_nb_pools %d\n", 217 num_pools, max_nb_pools); 218 return -1; 219 } 220 retval = get_eth_conf(&port_conf, max_nb_pools); 221 if (retval < 0) 222 return retval; 223 224 /* 225 * NIC queues are divided into pf queues and vmdq queues. 226 */ 227 /* There is assumption here all ports have the same configuration! */ 228 num_pf_queues = dev_info.max_rx_queues - dev_info.vmdq_queue_num; 229 queues_per_pool = dev_info.vmdq_queue_num / dev_info.max_vmdq_pools; 230 num_vmdq_queues = num_pools * queues_per_pool; 231 num_queues = num_pf_queues + num_vmdq_queues; 232 vmdq_queue_base = dev_info.vmdq_queue_base; 233 vmdq_pool_base = dev_info.vmdq_pool_base; 234 235 printf("pf queue num: %u, configured vmdq pool num: %u," 236 " each vmdq pool has %u queues\n", 237 num_pf_queues, num_pools, queues_per_pool); 238 printf("vmdq queue base: %d pool base %d\n", 239 vmdq_queue_base, vmdq_pool_base); 240 if (port >= rte_eth_dev_count()) 241 return -1; 242 243 /* 244 * Though in this example, we only receive packets from the first queue 245 * of each pool and send packets through first rte_lcore_count() tx 246 * queues of vmdq queues, all queues including pf queues are setup. 247 * This is because VMDQ queues doesn't always start from zero, and the 248 * PMD layer doesn't support selectively initialising part of rx/tx 249 * queues. 250 */ 251 rxRings = (uint16_t)dev_info.max_rx_queues; 252 txRings = (uint16_t)dev_info.max_tx_queues; 253 retval = rte_eth_dev_configure(port, rxRings, txRings, &port_conf); 254 if (retval != 0) 255 return retval; 256 257 retval = rte_eth_dev_adjust_nb_rx_tx_desc(port, &rxRingSize, 258 &txRingSize); 259 if (retval != 0) 260 return retval; 261 if (RTE_MAX(rxRingSize, txRingSize) > RTE_MAX(RTE_TEST_RX_DESC_DEFAULT, 262 RTE_TEST_TX_DESC_DEFAULT)) { 263 printf("Mbuf pool has an insufficient size for port %u.\n", 264 port); 265 return -1; 266 } 267 268 rte_eth_dev_info_get(port, &dev_info); 269 rxconf = &dev_info.default_rxconf; 270 rxconf->rx_drop_en = 1; 271 for (q = 0; q < rxRings; q++) { 272 retval = rte_eth_rx_queue_setup(port, q, rxRingSize, 273 rte_eth_dev_socket_id(port), 274 rxconf, 275 mbuf_pool); 276 if (retval < 0) { 277 printf("initialise rx queue %d failed\n", q); 278 return retval; 279 } 280 } 281 282 for (q = 0; q < txRings; q++) { 283 retval = rte_eth_tx_queue_setup(port, q, txRingSize, 284 rte_eth_dev_socket_id(port), 285 NULL); 286 if (retval < 0) { 287 printf("initialise tx queue %d failed\n", q); 288 return retval; 289 } 290 } 291 292 retval = rte_eth_dev_start(port); 293 if (retval < 0) { 294 printf("port %d start failed\n", port); 295 return retval; 296 } 297 298 rte_eth_macaddr_get(port, &vmdq_ports_eth_addr[port]); 299 printf("Port %u MAC: %02"PRIx8" %02"PRIx8" %02"PRIx8 300 " %02"PRIx8" %02"PRIx8" %02"PRIx8"\n", 301 (unsigned)port, 302 vmdq_ports_eth_addr[port].addr_bytes[0], 303 vmdq_ports_eth_addr[port].addr_bytes[1], 304 vmdq_ports_eth_addr[port].addr_bytes[2], 305 vmdq_ports_eth_addr[port].addr_bytes[3], 306 vmdq_ports_eth_addr[port].addr_bytes[4], 307 vmdq_ports_eth_addr[port].addr_bytes[5]); 308 309 /* 310 * Set mac for each pool. 311 * There is no default mac for the pools in i40. 312 * Removes this after i40e fixes this issue. 313 */ 314 for (q = 0; q < num_pools; q++) { 315 struct ether_addr mac; 316 mac = pool_addr_template; 317 mac.addr_bytes[4] = port; 318 mac.addr_bytes[5] = q; 319 printf("Port %u vmdq pool %u set mac %02x:%02x:%02x:%02x:%02x:%02x\n", 320 port, q, 321 mac.addr_bytes[0], mac.addr_bytes[1], 322 mac.addr_bytes[2], mac.addr_bytes[3], 323 mac.addr_bytes[4], mac.addr_bytes[5]); 324 retval = rte_eth_dev_mac_addr_add(port, &mac, 325 q + vmdq_pool_base); 326 if (retval) { 327 printf("mac addr add failed at pool %d\n", q); 328 return retval; 329 } 330 } 331 332 return 0; 333 } 334 335 /* Check num_pools parameter and set it if OK*/ 336 static int 337 vmdq_parse_num_pools(const char *q_arg) 338 { 339 char *end = NULL; 340 int n; 341 342 /* parse number string */ 343 n = strtol(q_arg, &end, 10); 344 if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) 345 return -1; 346 347 if (num_pools > num_vlans) { 348 printf("num_pools %d > num_vlans %d\n", num_pools, num_vlans); 349 return -1; 350 } 351 352 num_pools = n; 353 354 return 0; 355 } 356 357 358 static int 359 parse_portmask(const char *portmask) 360 { 361 char *end = NULL; 362 unsigned long pm; 363 364 /* parse hexadecimal string */ 365 pm = strtoul(portmask, &end, 16); 366 if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0')) 367 return -1; 368 369 if (pm == 0) 370 return -1; 371 372 return pm; 373 } 374 375 /* Display usage */ 376 static void 377 vmdq_usage(const char *prgname) 378 { 379 printf("%s [EAL options] -- -p PORTMASK]\n" 380 " --nb-pools NP: number of pools\n", 381 prgname); 382 } 383 384 /* Parse the argument (num_pools) given in the command line of the application */ 385 static int 386 vmdq_parse_args(int argc, char **argv) 387 { 388 int opt; 389 int option_index; 390 unsigned i; 391 const char *prgname = argv[0]; 392 static struct option long_option[] = { 393 {"nb-pools", required_argument, NULL, 0}, 394 {NULL, 0, 0, 0} 395 }; 396 397 /* Parse command line */ 398 while ((opt = getopt_long(argc, argv, "p:", long_option, 399 &option_index)) != EOF) { 400 switch (opt) { 401 /* portmask */ 402 case 'p': 403 enabled_port_mask = parse_portmask(optarg); 404 if (enabled_port_mask == 0) { 405 printf("invalid portmask\n"); 406 vmdq_usage(prgname); 407 return -1; 408 } 409 break; 410 case 0: 411 if (vmdq_parse_num_pools(optarg) == -1) { 412 printf("invalid number of pools\n"); 413 vmdq_usage(prgname); 414 return -1; 415 } 416 break; 417 418 default: 419 vmdq_usage(prgname); 420 return -1; 421 } 422 } 423 424 for (i = 0; i < RTE_MAX_ETHPORTS; i++) { 425 if (enabled_port_mask & (1 << i)) 426 ports[num_ports++] = (uint8_t)i; 427 } 428 429 if (num_ports < 2 || num_ports % 2) { 430 printf("Current enabled port number is %u," 431 "but it should be even and at least 2\n", num_ports); 432 return -1; 433 } 434 435 return 0; 436 } 437 438 static void 439 update_mac_address(struct rte_mbuf *m, unsigned dst_port) 440 { 441 struct ether_hdr *eth; 442 void *tmp; 443 444 eth = rte_pktmbuf_mtod(m, struct ether_hdr *); 445 446 /* 02:00:00:00:00:xx */ 447 tmp = ð->d_addr.addr_bytes[0]; 448 *((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dst_port << 40); 449 450 /* src addr */ 451 ether_addr_copy(&vmdq_ports_eth_addr[dst_port], ð->s_addr); 452 } 453 454 /* When we receive a HUP signal, print out our stats */ 455 static void 456 sighup_handler(int signum) 457 { 458 unsigned q; 459 for (q = 0; q < num_queues; q++) { 460 if (q % (num_queues/num_pools) == 0) 461 printf("\nPool %u: ", q/(num_queues/num_pools)); 462 printf("%lu ", rxPackets[q]); 463 } 464 printf("\nFinished handling signal %d\n", signum); 465 } 466 467 /* 468 * Main thread that does the work, reading from INPUT_PORT 469 * and writing to OUTPUT_PORT 470 */ 471 static int 472 lcore_main(__attribute__((__unused__)) void *dummy) 473 { 474 const uint16_t lcore_id = (uint16_t)rte_lcore_id(); 475 const uint16_t num_cores = (uint16_t)rte_lcore_count(); 476 uint16_t core_id = 0; 477 uint16_t startQueue, endQueue; 478 uint16_t q, i, p; 479 const uint16_t remainder = (uint16_t)(num_vmdq_queues % num_cores); 480 481 for (i = 0; i < num_cores; i++) 482 if (lcore_ids[i] == lcore_id) { 483 core_id = i; 484 break; 485 } 486 487 if (remainder != 0) { 488 if (core_id < remainder) { 489 startQueue = (uint16_t)(core_id * 490 (num_vmdq_queues / num_cores + 1)); 491 endQueue = (uint16_t)(startQueue + 492 (num_vmdq_queues / num_cores) + 1); 493 } else { 494 startQueue = (uint16_t)(core_id * 495 (num_vmdq_queues / num_cores) + 496 remainder); 497 endQueue = (uint16_t)(startQueue + 498 (num_vmdq_queues / num_cores)); 499 } 500 } else { 501 startQueue = (uint16_t)(core_id * 502 (num_vmdq_queues / num_cores)); 503 endQueue = (uint16_t)(startQueue + 504 (num_vmdq_queues / num_cores)); 505 } 506 507 /* vmdq queue idx doesn't always start from zero.*/ 508 startQueue += vmdq_queue_base; 509 endQueue += vmdq_queue_base; 510 printf("core %u(lcore %u) reading queues %i-%i\n", (unsigned)core_id, 511 (unsigned)lcore_id, startQueue, endQueue - 1); 512 513 if (startQueue == endQueue) { 514 printf("lcore %u has nothing to do\n", lcore_id); 515 return 0; 516 } 517 518 for (;;) { 519 struct rte_mbuf *buf[MAX_PKT_BURST]; 520 const uint16_t buf_size = sizeof(buf) / sizeof(buf[0]); 521 522 for (p = 0; p < num_ports; p++) { 523 const uint8_t sport = ports[p]; 524 /* 0 <-> 1, 2 <-> 3 etc */ 525 const uint8_t dport = ports[p ^ 1]; 526 if ((sport == INVALID_PORT_ID) || (dport == INVALID_PORT_ID)) 527 continue; 528 529 for (q = startQueue; q < endQueue; q++) { 530 const uint16_t rxCount = rte_eth_rx_burst(sport, 531 q, buf, buf_size); 532 533 if (unlikely(rxCount == 0)) 534 continue; 535 536 rxPackets[q] += rxCount; 537 538 for (i = 0; i < rxCount; i++) 539 update_mac_address(buf[i], dport); 540 541 const uint16_t txCount = rte_eth_tx_burst(dport, 542 vmdq_queue_base + core_id, 543 buf, 544 rxCount); 545 546 if (txCount != rxCount) { 547 for (i = txCount; i < rxCount; i++) 548 rte_pktmbuf_free(buf[i]); 549 } 550 } 551 } 552 } 553 } 554 555 /* 556 * Update the global var NUM_PORTS and array PORTS according to system ports number 557 * and return valid ports number 558 */ 559 static unsigned check_ports_num(unsigned nb_ports) 560 { 561 unsigned valid_num_ports = num_ports; 562 unsigned portid; 563 564 if (num_ports > nb_ports) { 565 printf("\nSpecified port number(%u) exceeds total system port number(%u)\n", 566 num_ports, nb_ports); 567 num_ports = nb_ports; 568 } 569 570 for (portid = 0; portid < num_ports; portid++) { 571 if (ports[portid] >= nb_ports) { 572 printf("\nSpecified port ID(%u) exceeds max system port ID(%u)\n", 573 ports[portid], (nb_ports - 1)); 574 ports[portid] = INVALID_PORT_ID; 575 valid_num_ports--; 576 } 577 } 578 return valid_num_ports; 579 } 580 581 /* Main function, does initialisation and calls the per-lcore functions */ 582 int 583 main(int argc, char *argv[]) 584 { 585 struct rte_mempool *mbuf_pool; 586 unsigned lcore_id, core_id = 0; 587 int ret; 588 unsigned nb_ports, valid_num_ports; 589 uint8_t portid; 590 591 signal(SIGHUP, sighup_handler); 592 593 /* init EAL */ 594 ret = rte_eal_init(argc, argv); 595 if (ret < 0) 596 rte_exit(EXIT_FAILURE, "Error with EAL initialization\n"); 597 argc -= ret; 598 argv += ret; 599 600 /* parse app arguments */ 601 ret = vmdq_parse_args(argc, argv); 602 if (ret < 0) 603 rte_exit(EXIT_FAILURE, "Invalid VMDQ argument\n"); 604 605 for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) 606 if (rte_lcore_is_enabled(lcore_id)) 607 lcore_ids[core_id++] = lcore_id; 608 609 if (rte_lcore_count() > RTE_MAX_LCORE) 610 rte_exit(EXIT_FAILURE, "Not enough cores\n"); 611 612 nb_ports = rte_eth_dev_count(); 613 614 /* 615 * Update the global var NUM_PORTS and global array PORTS 616 * and get value of var VALID_NUM_PORTS according to system ports number 617 */ 618 valid_num_ports = check_ports_num(nb_ports); 619 620 if (valid_num_ports < 2 || valid_num_ports % 2) { 621 printf("Current valid ports number is %u\n", valid_num_ports); 622 rte_exit(EXIT_FAILURE, "Error with valid ports number is not even or less than 2\n"); 623 } 624 625 mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", 626 NUM_MBUFS_PER_PORT * nb_ports, MBUF_CACHE_SIZE, 627 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); 628 if (mbuf_pool == NULL) 629 rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); 630 631 /* initialize all ports */ 632 for (portid = 0; portid < nb_ports; portid++) { 633 /* skip ports that are not enabled */ 634 if ((enabled_port_mask & (1 << portid)) == 0) { 635 printf("\nSkipping disabled port %d\n", portid); 636 continue; 637 } 638 if (port_init(portid, mbuf_pool) != 0) 639 rte_exit(EXIT_FAILURE, "Cannot initialize network ports\n"); 640 } 641 642 /* call lcore_main() on every lcore */ 643 rte_eal_mp_remote_launch(lcore_main, NULL, CALL_MASTER); 644 RTE_LCORE_FOREACH_SLAVE(lcore_id) { 645 if (rte_eal_wait_lcore(lcore_id) < 0) 646 return -1; 647 } 648 649 return 0; 650 } 651