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 ``linuxapp`` environment: 25 26.. code-block:: console 27 28 ./build/basicfwd -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 for (portid = 0; portid < nb_ports; 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 ether_addr addr; 119 int retval; 120 uint16_t q; 121 122 if (port >= rte_eth_dev_count()) 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 rte_eth_promiscuous_enable(port); 153 154 return 0; 155 } 156 157The Ethernet ports are configured with default settings using the 158``rte_eth_dev_configure()`` function and the ``port_conf_default`` struct: 159 160.. code-block:: c 161 162 static const struct rte_eth_conf port_conf_default = { 163 .rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN } 164 }; 165 166For this example the ports are set up with 1 RX and 1 TX queue using the 167``rte_eth_rx_queue_setup()`` and ``rte_eth_tx_queue_setup()`` functions. 168 169The Ethernet port is then started: 170 171.. code-block:: c 172 173 retval = rte_eth_dev_start(port); 174 175 176Finally the RX port is set in promiscuous mode: 177 178.. code-block:: c 179 180 rte_eth_promiscuous_enable(port); 181 182 183The Lcores Main 184~~~~~~~~~~~~~~~ 185 186As we saw above the ``main()`` function calls an application function on the 187available lcores. For the Basic Forwarding application the lcore function 188looks like the following: 189 190.. code-block:: c 191 192 static __attribute__((noreturn)) void 193 lcore_main(void) 194 { 195 const uint16_t nb_ports = rte_eth_dev_count(); 196 uint16_t port; 197 198 /* 199 * Check that the port is on the same NUMA node as the polling thread 200 * for best performance. 201 */ 202 for (port = 0; port < nb_ports; port++) 203 if (rte_eth_dev_socket_id(port) > 0 && 204 rte_eth_dev_socket_id(port) != 205 (int)rte_socket_id()) 206 printf("WARNING, port %u is on remote NUMA node to " 207 "polling thread.\n\tPerformance will " 208 "not be optimal.\n", port); 209 210 printf("\nCore %u forwarding packets. [Ctrl+C to quit]\n", 211 rte_lcore_id()); 212 213 /* Run until the application is quit or killed. */ 214 for (;;) { 215 /* 216 * Receive packets on a port and forward them on the paired 217 * port. The mapping is 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, etc. 218 */ 219 for (port = 0; port < nb_ports; port++) { 220 221 /* Get burst of RX packets, from first port of pair. */ 222 struct rte_mbuf *bufs[BURST_SIZE]; 223 const uint16_t nb_rx = rte_eth_rx_burst(port, 0, 224 bufs, BURST_SIZE); 225 226 if (unlikely(nb_rx == 0)) 227 continue; 228 229 /* Send burst of TX packets, to second port of pair. */ 230 const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0, 231 bufs, nb_rx); 232 233 /* Free any unsent packets. */ 234 if (unlikely(nb_tx < nb_rx)) { 235 uint16_t buf; 236 for (buf = nb_tx; buf < nb_rx; buf++) 237 rte_pktmbuf_free(bufs[buf]); 238 } 239 } 240 } 241 } 242 243 244The main work of the application is done within the loop: 245 246.. code-block:: c 247 248 for (;;) { 249 for (port = 0; port < nb_ports; port++) { 250 251 /* Get burst of RX packets, from first port of pair. */ 252 struct rte_mbuf *bufs[BURST_SIZE]; 253 const uint16_t nb_rx = rte_eth_rx_burst(port, 0, 254 bufs, BURST_SIZE); 255 256 if (unlikely(nb_rx == 0)) 257 continue; 258 259 /* Send burst of TX packets, to second port of pair. */ 260 const uint16_t nb_tx = rte_eth_tx_burst(port ^ 1, 0, 261 bufs, nb_rx); 262 263 /* Free any unsent packets. */ 264 if (unlikely(nb_tx < nb_rx)) { 265 uint16_t buf; 266 for (buf = nb_tx; buf < nb_rx; buf++) 267 rte_pktmbuf_free(bufs[buf]); 268 } 269 } 270 } 271 272Packets are received in bursts on the RX ports and transmitted in bursts on 273the TX ports. The ports are grouped in pairs with a simple mapping scheme 274using the an XOR on the port number:: 275 276 0 -> 1 277 1 -> 0 278 279 2 -> 3 280 3 -> 2 281 282 etc. 283 284The ``rte_eth_tx_burst()`` function frees the memory buffers of packets that 285are transmitted. If packets fail to transmit, ``(nb_tx < nb_rx)``, then they 286must be freed explicitly using ``rte_pktmbuf_free()``. 287 288The forwarding loop can be interrupted and the application closed using 289``Ctrl-C``. 290