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