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_tailq.h> 52 #include <rte_eal.h> 53 #include <rte_per_lcore.h> 54 #include <rte_launch.h> 55 #include <rte_atomic.h> 56 #include <rte_cycles.h> 57 #include <rte_prefetch.h> 58 #include <rte_lcore.h> 59 #include <rte_per_lcore.h> 60 #include <rte_branch_prediction.h> 61 #include <rte_interrupts.h> 62 #include <rte_pci.h> 63 #include <rte_random.h> 64 #include <rte_debug.h> 65 #include <rte_ether.h> 66 #include <rte_ethdev.h> 67 #include <rte_ring.h> 68 #include <rte_log.h> 69 #include <rte_mempool.h> 70 #include <rte_mbuf.h> 71 #include <rte_memcpy.h> 72 73 #include "main.h" 74 75 #define MAX_QUEUES 128 76 /* 77 * For 10 GbE, 128 queues require roughly 78 * 128*512 (RX/TX_queue_nb * RX/TX_ring_descriptors_nb) per port. 79 */ 80 #define NUM_MBUFS_PER_PORT (128*512) 81 #define MBUF_CACHE_SIZE 64 82 #define MBUF_SIZE (2048 + sizeof(struct rte_mbuf) + RTE_PKTMBUF_HEADROOM) 83 84 /* 85 * RX and TX Prefetch, Host, and Write-back threshold values should be 86 * carefully set for optimal performance. Consult the network 87 * controller's datasheet and supporting DPDK documentation for guidance 88 * on how these parameters should be set. 89 */ 90 #define RX_PTHRESH 8 /**< Default values of RX prefetch threshold reg. */ 91 #define RX_HTHRESH 8 /**< Default values of RX host threshold reg. */ 92 #define RX_WTHRESH 4 /**< Default values of RX write-back threshold reg. */ 93 94 /* 95 * These default values are optimized for use with the Intel(R) 82599 10 GbE 96 * Controller and the DPDK ixgbe PMD. Consider using other values for other 97 * network controllers and/or network drivers. 98 */ 99 #define TX_PTHRESH 36 /**< Default values of TX prefetch threshold reg. */ 100 #define TX_HTHRESH 0 /**< Default values of TX host threshold reg. */ 101 #define TX_WTHRESH 0 /**< Default values of TX write-back threshold reg. */ 102 103 #define MAX_PKT_BURST 32 104 105 /* 106 * Configurable number of RX/TX ring descriptors 107 */ 108 #define RTE_TEST_RX_DESC_DEFAULT 128 109 #define RTE_TEST_TX_DESC_DEFAULT 512 110 111 #define INVALID_PORT_ID 0xFF 112 113 /* mask of enabled ports */ 114 static uint32_t enabled_port_mask = 0; 115 116 /* number of pools (if user does not specify any, 8 by default */ 117 static uint32_t num_queues = 8; 118 static uint32_t num_pools = 8; 119 120 /* 121 * RX and TX Prefetch, Host, and Write-back threshold values should be 122 * carefully set for optimal performance. Consult the network 123 * controller's datasheet and supporting DPDK documentation for guidance 124 * on how these parameters should be set. 125 */ 126 /* Default configuration for rx and tx thresholds etc. */ 127 static const struct rte_eth_rxconf rx_conf_default = { 128 .rx_thresh = { 129 .pthresh = RX_PTHRESH, 130 .hthresh = RX_HTHRESH, 131 .wthresh = RX_WTHRESH, 132 }, 133 }; 134 135 /* 136 * These default values are optimized for use with the Intel(R) 82599 10 GbE 137 * Controller and the DPDK ixgbe/igb PMD. Consider using other values for other 138 * network controllers and/or network drivers. 139 */ 140 static const struct rte_eth_txconf tx_conf_default = { 141 .tx_thresh = { 142 .pthresh = TX_PTHRESH, 143 .hthresh = TX_HTHRESH, 144 .wthresh = TX_WTHRESH, 145 }, 146 .tx_free_thresh = 0, /* Use PMD default values */ 147 .tx_rs_thresh = 0, /* Use PMD default values */ 148 }; 149 150 /* empty vmdq configuration structure. Filled in programatically */ 151 static const struct rte_eth_conf vmdq_conf_default = { 152 .rxmode = { 153 .mq_mode = ETH_MQ_RX_VMDQ_ONLY, 154 .split_hdr_size = 0, 155 .header_split = 0, /**< Header Split disabled */ 156 .hw_ip_checksum = 0, /**< IP checksum offload disabled */ 157 .hw_vlan_filter = 0, /**< VLAN filtering disabled */ 158 .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ 159 }, 160 161 .txmode = { 162 .mq_mode = ETH_MQ_TX_NONE, 163 }, 164 .rx_adv_conf = { 165 /* 166 * should be overridden separately in code with 167 * appropriate values 168 */ 169 .vmdq_rx_conf = { 170 .nb_queue_pools = ETH_8_POOLS, 171 .enable_default_pool = 0, 172 .default_pool = 0, 173 .nb_pool_maps = 0, 174 .pool_map = {{0, 0},}, 175 }, 176 }, 177 }; 178 179 static unsigned lcore_ids[RTE_MAX_LCORE]; 180 static uint8_t ports[RTE_MAX_ETHPORTS]; 181 static unsigned num_ports = 0; /**< The number of ports specified in command line */ 182 183 /* array used for printing out statistics */ 184 volatile unsigned long rxPackets[ MAX_QUEUES ] = {0}; 185 186 const uint16_t vlan_tags[] = { 187 0, 1, 2, 3, 4, 5, 6, 7, 188 8, 9, 10, 11, 12, 13, 14, 15, 189 16, 17, 18, 19, 20, 21, 22, 23, 190 24, 25, 26, 27, 28, 29, 30, 31, 191 32, 33, 34, 35, 36, 37, 38, 39, 192 40, 41, 42, 43, 44, 45, 46, 47, 193 48, 49, 50, 51, 52, 53, 54, 55, 194 56, 57, 58, 59, 60, 61, 62, 63, 195 }; 196 197 /* ethernet addresses of ports */ 198 static struct ether_addr vmdq_ports_eth_addr[RTE_MAX_ETHPORTS]; 199 200 #define MAX_QUEUE_NUM_10G 128 201 #define MAX_QUEUE_NUM_1G 8 202 #define MAX_POOL_MAP_NUM_10G 64 203 #define MAX_POOL_MAP_NUM_1G 32 204 #define MAX_POOL_NUM_10G 64 205 #define MAX_POOL_NUM_1G 8 206 /* Builds up the correct configuration for vmdq based on the vlan tags array 207 * given above, and determine the queue number and pool map number according to valid pool number */ 208 static inline int 209 get_eth_conf(struct rte_eth_conf *eth_conf, uint32_t num_pools) 210 { 211 struct rte_eth_vmdq_rx_conf conf; 212 unsigned i; 213 214 conf.nb_queue_pools = (enum rte_eth_nb_pools)num_pools; 215 conf.enable_default_pool = 0; 216 conf.default_pool = 0; /* set explicit value, even if not used */ 217 switch (num_pools) { 218 /* For 10G NIC like 82599, 128 is valid for queue number */ 219 case MAX_POOL_NUM_10G: 220 num_queues = MAX_QUEUE_NUM_10G; 221 conf.nb_pool_maps = MAX_POOL_MAP_NUM_10G; 222 break; 223 /* For 1G NIC like i350, 82580 and 82576, 8 is valid for queue number */ 224 case MAX_POOL_NUM_1G: 225 num_queues = MAX_QUEUE_NUM_1G; 226 conf.nb_pool_maps = MAX_POOL_MAP_NUM_1G; 227 break; 228 default: 229 return -1; 230 } 231 232 for (i = 0; i < conf.nb_pool_maps; i++){ 233 conf.pool_map[i].vlan_id = vlan_tags[ i ]; 234 conf.pool_map[i].pools = (1UL << (i % num_pools)); 235 } 236 237 (void)(rte_memcpy(eth_conf, &vmdq_conf_default, sizeof(*eth_conf))); 238 (void)(rte_memcpy(ð_conf->rx_adv_conf.vmdq_rx_conf, &conf, 239 sizeof(eth_conf->rx_adv_conf.vmdq_rx_conf))); 240 return 0; 241 } 242 243 /* 244 * Validate the pool number accrording to the max pool number gotten form dev_info 245 * If the pool number is invalid, give the error message and return -1 246 */ 247 static inline int 248 validate_num_pools(uint32_t max_nb_pools) 249 { 250 if (num_pools > max_nb_pools) { 251 printf("invalid number of pools\n"); 252 return -1; 253 } 254 255 switch (max_nb_pools) { 256 /* For 10G NIC like 82599, 64 is valid for pool number */ 257 case MAX_POOL_NUM_10G: 258 if (num_pools != MAX_POOL_NUM_10G) { 259 printf("invalid number of pools\n"); 260 return -1; 261 } 262 break; 263 /* For 1G NIC like i350, 82580 and 82576, 8 is valid for pool number */ 264 case MAX_POOL_NUM_1G: 265 if (num_pools != MAX_POOL_NUM_1G) { 266 printf("invalid number of pools\n"); 267 return -1; 268 } 269 break; 270 default: 271 return -1; 272 } 273 274 return 0; 275 } 276 277 /* 278 * Initialises a given port using global settings and with the rx buffers 279 * coming from the mbuf_pool passed as parameter 280 */ 281 static inline int 282 port_init(uint8_t port, struct rte_mempool *mbuf_pool) 283 { 284 struct rte_eth_dev_info dev_info; 285 struct rte_eth_conf port_conf; 286 uint16_t rxRings, txRings = (uint16_t)rte_lcore_count(); 287 const uint16_t rxRingSize = RTE_TEST_RX_DESC_DEFAULT, txRingSize = RTE_TEST_TX_DESC_DEFAULT; 288 int retval; 289 uint16_t q; 290 uint32_t max_nb_pools; 291 292 /* The max pool number from dev_info will be used to validate the pool number specified in cmd line */ 293 rte_eth_dev_info_get (port, &dev_info); 294 max_nb_pools = (uint32_t)dev_info.max_vmdq_pools; 295 retval = validate_num_pools(max_nb_pools); 296 if (retval < 0) 297 return retval; 298 299 retval = get_eth_conf(&port_conf, num_pools); 300 if (retval < 0) 301 return retval; 302 303 if (port >= rte_eth_dev_count()) return -1; 304 305 rxRings = (uint16_t)num_queues, 306 retval = rte_eth_dev_configure(port, rxRings, txRings, &port_conf); 307 if (retval != 0) 308 return retval; 309 310 for (q = 0; q < rxRings; q ++) { 311 retval = rte_eth_rx_queue_setup(port, q, rxRingSize, 312 rte_eth_dev_socket_id(port), &rx_conf_default, 313 mbuf_pool); 314 if (retval < 0) 315 return retval; 316 } 317 318 for (q = 0; q < txRings; q ++) { 319 retval = rte_eth_tx_queue_setup(port, q, txRingSize, 320 rte_eth_dev_socket_id(port), &tx_conf_default); 321 if (retval < 0) 322 return retval; 323 } 324 325 retval = rte_eth_dev_start(port); 326 if (retval < 0) 327 return retval; 328 329 rte_eth_macaddr_get(port, &vmdq_ports_eth_addr[port]); 330 printf("Port %u MAC: %02"PRIx8" %02"PRIx8" %02"PRIx8 331 " %02"PRIx8" %02"PRIx8" %02"PRIx8"\n", 332 (unsigned)port, 333 vmdq_ports_eth_addr[port].addr_bytes[0], 334 vmdq_ports_eth_addr[port].addr_bytes[1], 335 vmdq_ports_eth_addr[port].addr_bytes[2], 336 vmdq_ports_eth_addr[port].addr_bytes[3], 337 vmdq_ports_eth_addr[port].addr_bytes[4], 338 vmdq_ports_eth_addr[port].addr_bytes[5]); 339 340 return 0; 341 } 342 343 /* Check num_pools parameter and set it if OK*/ 344 static int 345 vmdq_parse_num_pools(const char *q_arg) 346 { 347 char *end = NULL; 348 int n; 349 350 /* parse number string */ 351 n = strtol(q_arg, &end, 10); 352 if ((q_arg[0] == '\0') || (end == NULL) || (*end != '\0')) 353 return -1; 354 355 num_pools = n; 356 357 return 0; 358 } 359 360 361 static int 362 parse_portmask(const char *portmask) 363 { 364 char *end = NULL; 365 unsigned long pm; 366 367 /* parse hexadecimal string */ 368 pm = strtoul(portmask, &end, 16); 369 if ((portmask[0] == '\0') || (end == NULL) || (*end != '\0')) 370 return -1; 371 372 if (pm == 0) 373 return -1; 374 375 return pm; 376 } 377 378 /* Display usage */ 379 static void 380 vmdq_usage(const char *prgname) 381 { 382 printf("%s [EAL options] -- -p PORTMASK]\n" 383 " --nb-pools NP: number of pools\n", 384 prgname); 385 } 386 387 /* Parse the argument (num_pools) given in the command line of the application */ 388 static int 389 vmdq_parse_args(int argc, char **argv) 390 { 391 int opt; 392 int option_index; 393 unsigned i; 394 const char *prgname = argv[0]; 395 static struct option long_option[] = { 396 {"nb-pools", required_argument, NULL, 0}, 397 {NULL, 0, 0, 0} 398 }; 399 400 /* Parse command line */ 401 while ((opt = getopt_long(argc, argv, "p:",long_option,&option_index)) != EOF) { 402 switch (opt) { 403 /* portmask */ 404 case 'p': 405 enabled_port_mask = parse_portmask(optarg); 406 if (enabled_port_mask == 0) { 407 printf("invalid portmask\n"); 408 vmdq_usage(prgname); 409 return -1; 410 } 411 break; 412 case 0: 413 if (vmdq_parse_num_pools(optarg) == -1){ 414 printf("invalid number of pools\n"); 415 vmdq_usage(prgname); 416 return -1; 417 } 418 break; 419 420 default: 421 vmdq_usage(prgname); 422 return -1; 423 } 424 } 425 426 for(i = 0; i < RTE_MAX_ETHPORTS; i++) { 427 if (enabled_port_mask & (1 << i)) 428 ports[num_ports++] = (uint8_t)i; 429 } 430 431 if (num_ports < 2 || num_ports % 2) { 432 printf("Current enabled port number is %u," 433 "but it should be even and at least 2\n",num_ports); 434 return -1; 435 } 436 437 return 0; 438 } 439 440 static void 441 update_mac_address(struct rte_mbuf *m, unsigned dst_port) 442 { 443 struct ether_hdr *eth; 444 void *tmp; 445 446 eth = rte_pktmbuf_mtod(m, struct ether_hdr *); 447 448 /* 02:00:00:00:00:xx */ 449 tmp = ð->d_addr.addr_bytes[0]; 450 *((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dst_port << 40); 451 452 /* src addr */ 453 ether_addr_copy(&vmdq_ports_eth_addr[dst_port], ð->s_addr); 454 } 455 456 #ifndef RTE_EXEC_ENV_BAREMETAL 457 /* When we receive a HUP signal, print out our stats */ 458 static void 459 sighup_handler(int signum) 460 { 461 unsigned q; 462 for (q = 0; q < num_queues; q ++) { 463 if (q % (num_queues/num_pools) == 0) 464 printf("\nPool %u: ", q/(num_queues/num_pools)); 465 printf("%lu ", rxPackets[ q ]); 466 } 467 printf("\nFinished handling signal %d\n", signum); 468 } 469 #endif 470 471 /* 472 * Main thread that does the work, reading from INPUT_PORT 473 * and writing to OUTPUT_PORT 474 */ 475 static __attribute__((noreturn)) int 476 lcore_main(__attribute__((__unused__)) void* dummy) 477 { 478 const uint16_t lcore_id = (uint16_t)rte_lcore_id(); 479 const uint16_t num_cores = (uint16_t)rte_lcore_count(); 480 uint16_t core_id = 0; 481 uint16_t startQueue, endQueue; 482 uint16_t q, i, p; 483 const uint16_t remainder = (uint16_t)(num_queues % num_cores); 484 485 for (i = 0; i < num_cores; i ++) 486 if (lcore_ids[i] == lcore_id) { 487 core_id = i; 488 break; 489 } 490 491 if (remainder != 0) { 492 if (core_id < remainder) { 493 startQueue = (uint16_t)(core_id * (num_queues/num_cores + 1)); 494 endQueue = (uint16_t)(startQueue + (num_queues/num_cores) + 1); 495 } else { 496 startQueue = (uint16_t)(core_id * (num_queues/num_cores) + remainder); 497 endQueue = (uint16_t)(startQueue + (num_queues/num_cores)); 498 } 499 } else { 500 startQueue = (uint16_t)(core_id * (num_queues/num_cores)); 501 endQueue = (uint16_t)(startQueue + (num_queues/num_cores)); 502 } 503 504 printf("core %u(lcore %u) reading queues %i-%i\n", (unsigned)core_id, 505 (unsigned)lcore_id, startQueue, endQueue - 1); 506 507 for (;;) { 508 struct rte_mbuf *buf[MAX_PKT_BURST]; 509 const uint16_t buf_size = sizeof(buf) / sizeof(buf[0]); 510 511 for (p = 0; p < num_ports; p++) { 512 const uint8_t sport = ports[p]; 513 const uint8_t dport = ports[p ^ 1]; /* 0 <-> 1, 2 <-> 3 etc */ 514 515 if ((sport == INVALID_PORT_ID) || (dport == INVALID_PORT_ID)) 516 continue; 517 518 for (q = startQueue; q < endQueue; q++) { 519 const uint16_t rxCount = rte_eth_rx_burst(sport, 520 q, buf, buf_size); 521 522 if (unlikely(rxCount == 0)) 523 continue; 524 525 rxPackets[q] += rxCount; 526 527 for (i = 0; i < rxCount; i++) 528 update_mac_address(buf[i], dport); 529 530 const uint16_t txCount = rte_eth_tx_burst(dport, 531 lcore_id, buf, rxCount); 532 533 if (txCount != rxCount) { 534 for (i = txCount; i < rxCount; i++) 535 rte_pktmbuf_free(buf[i]); 536 } 537 } 538 } 539 } 540 } 541 542 /* 543 * Update the global var NUM_PORTS and array PORTS according to system ports number 544 * and return valid ports number 545 */ 546 static unsigned check_ports_num(unsigned nb_ports) 547 { 548 unsigned valid_num_ports = num_ports; 549 unsigned portid; 550 551 if (num_ports > nb_ports) { 552 printf("\nSpecified port number(%u) exceeds total system port number(%u)\n", 553 num_ports, nb_ports); 554 num_ports = nb_ports; 555 } 556 557 for (portid = 0; portid < num_ports; portid ++) { 558 if (ports[portid] >= nb_ports) { 559 printf("\nSpecified port ID(%u) exceeds max system port ID(%u)\n", 560 ports[portid], (nb_ports - 1)); 561 ports[portid] = INVALID_PORT_ID; 562 valid_num_ports --; 563 } 564 } 565 return valid_num_ports; 566 } 567 568 /* Main function, does initialisation and calls the per-lcore functions */ 569 int 570 MAIN(int argc, char *argv[]) 571 { 572 struct rte_mempool *mbuf_pool; 573 unsigned lcore_id, core_id = 0; 574 int ret; 575 unsigned nb_ports, valid_num_ports; 576 uint8_t portid; 577 578 #ifndef RTE_EXEC_ENV_BAREMETAL 579 signal(SIGHUP, sighup_handler); 580 #endif 581 582 /* init EAL */ 583 ret = rte_eal_init(argc, argv); 584 if (ret < 0) 585 rte_exit(EXIT_FAILURE, "Error with EAL initialization\n"); 586 argc -= ret; 587 argv += ret; 588 589 /* parse app arguments */ 590 ret = vmdq_parse_args(argc, argv); 591 if (ret < 0) 592 rte_exit(EXIT_FAILURE, "Invalid VMDQ argument\n"); 593 594 if (rte_pmd_init_all() != 0 || rte_eal_pci_probe() != 0) 595 rte_exit(EXIT_FAILURE, "Error with NIC driver initialization\n"); 596 597 for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id ++) 598 if (rte_lcore_is_enabled(lcore_id)) 599 lcore_ids[core_id ++] = lcore_id; 600 601 if (rte_lcore_count() > RTE_MAX_LCORE) 602 rte_exit(EXIT_FAILURE,"Not enough cores\n"); 603 604 nb_ports = rte_eth_dev_count(); 605 if (nb_ports > RTE_MAX_ETHPORTS) 606 nb_ports = RTE_MAX_ETHPORTS; 607 608 /* 609 * Update the global var NUM_PORTS and global array PORTS 610 * and get value of var VALID_NUM_PORTS according to system ports number 611 */ 612 valid_num_ports = check_ports_num(nb_ports); 613 614 if (valid_num_ports < 2 || valid_num_ports % 2) { 615 printf("Current valid ports number is %u\n", valid_num_ports); 616 rte_exit(EXIT_FAILURE, "Error with valid ports number is not even or less than 2\n"); 617 } 618 619 mbuf_pool = rte_mempool_create("MBUF_POOL", NUM_MBUFS_PER_PORT * nb_ports, 620 MBUF_SIZE, MBUF_CACHE_SIZE, 621 sizeof(struct rte_pktmbuf_pool_private), 622 rte_pktmbuf_pool_init, NULL, 623 rte_pktmbuf_init, NULL, 624 rte_socket_id(), 0); 625 if (mbuf_pool == NULL) 626 rte_exit(EXIT_FAILURE, "Cannot create mbuf pool\n"); 627 628 /* initialize all ports */ 629 for (portid = 0; portid < nb_ports; portid++) { 630 /* skip ports that are not enabled */ 631 if ((enabled_port_mask & (1 << portid)) == 0) { 632 printf("\nSkipping disabled port %d\n", portid); 633 continue; 634 } 635 if (port_init(portid, mbuf_pool) != 0) 636 rte_exit(EXIT_FAILURE, "Cannot initialize network ports\n"); 637 } 638 639 /* call lcore_main() on every lcore */ 640 rte_eal_mp_remote_launch(lcore_main, NULL, CALL_MASTER); 641 RTE_LCORE_FOREACH_SLAVE(lcore_id) { 642 if (rte_eal_wait_lcore(lcore_id) < 0) 643 return -1; 644 } 645 646 return 0; 647 } 648