1.. SPDX-License-Identifier: BSD-3-Clause 2 Copyright(c) 2015 Intel Corporation. 3 4Basic Forwarding Sample Application 5=================================== 6 7The Basic Forwarding sample application is a simple *skeleton* example of a 8forwarding application. 9 10It is intended as a demonstration of the basic components of a DPDK forwarding 11application. For more detailed implementations see the L2 and L3 forwarding 12sample applications. 13 14Compiling the Application 15------------------------- 16 17To compile the sample application see :doc:`compiling`. 18 19The application is located in the ``skeleton`` sub-directory. 20 21Running the Application 22----------------------- 23 24To run the example in a ``linux`` environment: 25 26.. code-block:: console 27 28 ./<build_dir>/examples/dpdk-skeleton -l 1 -n 4 29 30Refer to *DPDK Getting Started Guide* for general information on running 31applications and the Environment Abstraction Layer (EAL) options. 32 33 34Explanation 35----------- 36 37The following sections provide an explanation of the main components of the 38code. 39 40All DPDK library functions used in the sample code are prefixed with ``rte_`` 41and are explained in detail in the *DPDK API Documentation*. 42 43 44The Main Function 45~~~~~~~~~~~~~~~~~ 46 47The ``main()`` function performs the initialization and calls the execution 48threads for each lcore. 49 50The first task is to initialize the Environment Abstraction Layer (EAL). The 51``argc`` and ``argv`` arguments are provided to the ``rte_eal_init()`` 52function. The value returned is the number of parsed arguments: 53 54.. code-block:: c 55 56 int ret = rte_eal_init(argc, argv); 57 if (ret < 0) 58 rte_exit(EXIT_FAILURE, "Error with EAL initialization\n"); 59 60 61The ``main()`` also allocates a mempool to hold the mbufs (Message Buffers) 62used by the application: 63 64.. code-block:: c 65 66 mbuf_pool = rte_mempool_create("MBUF_POOL", 67 NUM_MBUFS * nb_ports, 68 MBUF_SIZE, 69 MBUF_CACHE_SIZE, 70 sizeof(struct rte_pktmbuf_pool_private), 71 rte_pktmbuf_pool_init, NULL, 72 rte_pktmbuf_init, NULL, 73 rte_socket_id(), 74 0); 75 76Mbufs are the packet buffer structure used by DPDK. They are explained in 77detail in the "Mbuf Library" section of the *DPDK Programmer's Guide*. 78 79The ``main()`` function also initializes all the ports using the user defined 80``port_init()`` function which is explained in the next section: 81 82.. code-block:: c 83 84 RTE_ETH_FOREACH_DEV(portid) { 85 if (port_init(portid, mbuf_pool) != 0) { 86 rte_exit(EXIT_FAILURE, 87 "Cannot init port %" PRIu8 "\n", portid); 88 } 89 } 90 91 92Once the initialization is complete, the application is ready to launch a 93function on an lcore. In this example ``lcore_main()`` is called on a single 94lcore. 95 96 97.. code-block:: c 98 99 lcore_main(); 100 101The ``lcore_main()`` function is explained below. 102 103 104 105The Port Initialization Function 106~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 108The main functional part of the port initialization used in the Basic 109Forwarding application is shown below: 110 111.. code-block:: c 112 113 static inline int 114 port_init(uint16_t port, struct rte_mempool *mbuf_pool) 115 { 116 struct rte_eth_conf port_conf = port_conf_default; 117 const uint16_t rx_rings = 1, tx_rings = 1; 118 struct rte_ether_addr addr; 119 int retval; 120 uint16_t q; 121 122 if (!rte_eth_dev_is_valid_port(port)) 123 return -1; 124 125 /* Configure the Ethernet device. */ 126 retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf); 127 if (retval != 0) 128 return retval; 129 130 /* Allocate and set up 1 RX queue per Ethernet port. */ 131 for (q = 0; q < rx_rings; q++) { 132 retval = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE, 133 rte_eth_dev_socket_id(port), NULL, mbuf_pool); 134 if (retval < 0) 135 return retval; 136 } 137 138 /* Allocate and set up 1 TX queue per Ethernet port. */ 139 for (q = 0; q < tx_rings; q++) { 140 retval = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE, 141 rte_eth_dev_socket_id(port), NULL); 142 if (retval < 0) 143 return retval; 144 } 145 146 /* Start the Ethernet port. */ 147 retval = rte_eth_dev_start(port); 148 if (retval < 0) 149 return retval; 150 151 /* Enable RX in promiscuous mode for the Ethernet device. */ 152 retval = rte_eth_promiscuous_enable(port); 153 if (retval != 0) 154 return retval; 155 156 return 0; 157 } 158 159The Ethernet ports are configured with default settings using the 160``rte_eth_dev_configure()`` function and the ``port_conf_default`` struct: 161 162.. code-block:: c 163 164 static const struct rte_eth_conf port_conf_default = { 165 .rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN } 166 }; 167 168For this example the ports are set up with 1 RX and 1 TX queue using the 169``rte_eth_rx_queue_setup()`` and ``rte_eth_tx_queue_setup()`` functions. 170 171The Ethernet port is then started: 172 173.. code-block:: c 174 175 retval = rte_eth_dev_start(port); 176 177 178Finally the RX port is set in promiscuous mode: 179 180.. code-block:: c 181 182 retval = rte_eth_promiscuous_enable(port); 183 184 185The Lcores Main 186~~~~~~~~~~~~~~~ 187 188As we saw above the ``main()`` function calls an application function on the 189available lcores. For the Basic Forwarding application the lcore function 190looks like the following: 191 192.. code-block:: c 193 194 static __rte_noreturn void 195 lcore_main(void) 196 { 197 uint16_t port; 198 199 /* 200 * Check that the port is on the same NUMA node as the polling thread 201 * for best performance. 202 */ 203 RTE_ETH_FOREACH_DEV(port) 204 if (rte_eth_dev_socket_id(port) > 0 && 205 rte_eth_dev_socket_id(port) != 206 (int)rte_socket_id()) 207 printf("WARNING, port %u is on remote NUMA node to " 208 "polling thread.\n\tPerformance will " 209 "not be optimal.\n", port); 210 211 printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n", 212 rte_lcore_id()); 213 214 /* Run until the application is quit or killed. */ 215 for (;;) { 216 /* 217 * Receive packets on a port and forward them on the paired 218 * port. The mapping is 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, etc. 219 */ 220 RTE_ETH_FOREACH_DEV(port) { 221 222 /* Get burst of RX packets, from first port of pair. */ 223 struct rte_mbuf *bufs[BURST_SIZE]; 224 const uint16_t nb_rx = rte_eth_rx_burst(port, 0, 225 bufs, BURST_SIZE); 226 227 if (unlikely(nb_rx == 0)) 228 continue; 229 230 /* Send burst of TX packets, to second port of pair. */ 231 const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0, 232 bufs, nb_rx); 233 234 /* Free any unsent packets. */ 235 if (unlikely(nb_tx < nb_rx)) { 236 uint16_t buf; 237 for (buf = nb_tx; buf < nb_rx; buf++) 238 rte_pktmbuf_free(bufs[buf]); 239 } 240 } 241 } 242 } 243 244 245The main work of the application is done within the loop: 246 247.. code-block:: c 248 249 for (;;) { 250 RTE_ETH_FOREACH_DEV(port) { 251 252 /* Get burst of RX packets, from first port of pair. */ 253 struct rte_mbuf *bufs[BURST_SIZE]; 254 const uint16_t nb_rx = rte_eth_rx_burst(port, 0, 255 bufs, BURST_SIZE); 256 257 if (unlikely(nb_rx == 0)) 258 continue; 259 260 /* Send burst of TX packets, to second port of pair. */ 261 const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0, 262 bufs, nb_rx); 263 264 /* Free any unsent packets. */ 265 if (unlikely(nb_tx < nb_rx)) { 266 uint16_t buf; 267 for (buf = nb_tx; buf < nb_rx; buf++) 268 rte_pktmbuf_free(bufs[buf]); 269 } 270 } 271 } 272 273Packets are received in bursts on the RX ports and transmitted in bursts on 274the TX ports. The ports are grouped in pairs with a simple mapping scheme 275using the an XOR on the port number:: 276 277 0 -> 1 278 1 -> 0 279 280 2 -> 3 281 3 -> 2 282 283 etc. 284 285The ``rte_eth_tx_burst()`` function frees the memory buffers of packets that 286are transmitted. If packets fail to transmit, ``(nb_tx < nb_rx)``, then they 287must be freed explicitly using ``rte_pktmbuf_free()``. 288 289The forwarding loop can be interrupted and the application closed using 290``Ctrl-C``. 291