Data Plane Development Kit Logo
21.08.0
  • Getting Started Guide for Linux
  • Getting Started Guide for FreeBSD
  • Getting Started Guide for Windows
  • Sample Applications User Guides
    • 1. Introduction to the DPDK Sample Applications
    • 2. Compiling the Sample Applications
    • 3. Command Line Sample Application
    • 4. Ethtool Sample Application
    • 5. Hello World Sample Application
    • 6. Basic Forwarding Sample Application
    • 7. RX/TX Callbacks Sample Application
    • 8. Flow Classify Sample Application
    • 9. Basic RTE Flow Filtering Sample Application
    • 10. IP Fragmentation Sample Application
    • 11. IPv4 Multicast Sample Application
    • 12. IP Reassembly Sample Application
    • 13. Kernel NIC Interface Sample Application
    • 14. Keep Alive Sample Application
    • 15. Packet copying using Intel® QuickData Technology
    • 16. L2 Forwarding with Crypto Sample Application
    • 17. L2 Forwarding Sample Application (in Real and Virtualized Environments) with core load statistics.
    • 18. L2 Forwarding Sample Application (in Real and Virtualized Environments)
    • 19. L2 Forwarding Eventdev Sample Application
    • 20. L2 Forwarding Sample Application with Cache Allocation Technology (CAT)
    • 21. L3 Forwarding Sample Application
    • 22. L3 Forwarding Graph Sample Application
    • 23. L3 Forwarding with Power Management Sample Application
    • 24. L3 Forwarding with Access Control Sample Application
    • 25. Link Status Interrupt Sample Application
      • 25.1. Overview
      • 25.2. Compiling the Application
      • 25.3. Running the Application
      • 25.4. Explanation
        • 25.4.1. Command Line Arguments
        • 25.4.2. Mbuf Pool Initialization
        • 25.4.3. Driver Initialization
        • 25.4.4. Interrupt Callback Registration
        • 25.4.5. RX Queue Initialization
        • 25.4.6. TX Queue Initialization
        • 25.4.7. Receive, Process and Transmit Packets
    • 26. Server-Node EFD Sample Application
    • 27. Service Cores Sample Application
    • 28. Multi-process Sample Application
    • 29. QoS Metering Sample Application
    • 30. QoS Scheduler Sample Application
    • 31. Timer Sample Application
    • 32. Packet Ordering Application
    • 33. VMDQ and DCB Forwarding Sample Application
    • 34. VMDq Forwarding Sample Application
    • 35. Vhost Sample Application
    • 36. Vhost_blk Sample Application
    • 37. Vhost_Crypto Sample Application
    • 38. Vdpa Sample Application
    • 39. Internet Protocol (IP) Pipeline Application
    • 40. Test Pipeline Application
    • 41. Pipeline Application
    • 42. Eventdev Pipeline Sample Application
    • 43. Distributor Sample Application
    • 44. Virtual Machine Power Management Application
    • 45. PTP Client Sample Application
    • 46. Performance Thread Sample Application
    • 47. Federal Information Processing Standards (FIPS) CryptoDev Validation
    • 48. IPsec Security Gateway Sample Application
    • 49. Loop-back Sample Application using Baseband Device (bbdev)
    • 50. NTB Sample Application
  • Programmer’s Guide
  • HowTo Guides
  • DPDK Tools User Guides
  • Testpmd Application User Guide
  • Network Interface Controller Drivers
  • Baseband Device Drivers
  • Crypto Device Drivers
  • Compression Device Drivers
  • vDPA Device Drivers
  • REGEX Device Drivers
  • Event Device Drivers
  • Rawdev Drivers
  • Mempool Device Driver
  • Platform Specific Guides
  • Contributor’s Guidelines
  • Release Notes
  • FAQ
Data Plane Development Kit
  • Docs »
  • Sample Applications User Guides »
  • 25. Link Status Interrupt Sample Application
  • View page source

25. Link Status Interrupt Sample Application

The Link Status Interrupt sample application is a simple example of packet processing using the Data Plane Development Kit (DPDK) that demonstrates how network link status changes for a network port can be captured and used by a DPDK application.

25.1. Overview

The Link Status Interrupt sample application registers a user space callback for the link status interrupt of each port and performs L2 forwarding for each packet that is received on an RX_PORT. The following operations are performed:

  • RX_PORT and TX_PORT are paired with available ports one-by-one according to the core mask
  • The source MAC address is replaced by the TX_PORT MAC address
  • The destination MAC address is replaced by 02:00:00:00:00:TX_PORT_ID

This application can be used to demonstrate the usage of link status interrupt and its user space callbacks and the behavior of L2 forwarding each time the link status changes.

25.2. Compiling the Application

To compile the sample application see Compiling the Sample Applications.

The application is located in the link_status_interrupt sub-directory.

25.3. Running the Application

The application requires a number of command line options:

./<build_dir>/examples/dpdk-link_status_interrupt [EAL options] -- -p PORTMASK [-q NQ][-T PERIOD]

where,

  • -p PORTMASK: A hexadecimal bitmask of the ports to configure
  • -q NQ: A number of queues (=ports) per lcore (default is 1)
  • -T PERIOD: statistics will be refreshed each PERIOD seconds (0 to disable, 10 default)

To run the application in a linux environment with 4 lcores, 4 memory channels, 16 ports and 8 RX queues per lcore, issue the command:

$ ./<build_dir>/examples/dpdk-link_status_interrupt -l 0-3 -n 4-- -q 8 -p ffff

Refer to the DPDK Getting Started Guide for general information on running applications and the Environment Abstraction Layer (EAL) options.

25.4. Explanation

The following sections provide some explanation of the code.

25.4.1. Command Line Arguments

The Link Status Interrupt sample application takes specific parameters, in addition to Environment Abstraction Layer (EAL) arguments (see Section Running the Application).

Command line parsing is done in the same way as it is done in the L2 Forwarding Sample Application. See Command Line Arguments for more information.

25.4.2. Mbuf Pool Initialization

Mbuf pool initialization is done in the same way as it is done in the L2 Forwarding Sample Application. See Mbuf Pool Initialization for more information.

25.4.3. Driver Initialization

The main part of the code in the main() function relates to the initialization of the driver. To fully understand this code, it is recommended to study the chapters that related to the Poll Mode Driver in the DPDK Programmer’s Guide and the DPDK API Reference.

for (portid = 0; portid < nb_ports; portid++) {
	/* skip ports that are not enabled */
	if ((lsi_enabled_port_mask & (1 << portid)) == 0)
		continue;

	/* save the destination port id */
	if (nb_ports_in_mask % 2) {
		lsi_dst_ports[portid] = portid_last;
		lsi_dst_ports[portid_last] = portid;
	}
	else
		portid_last = portid;

	nb_ports_in_mask++;
}

The next step is to configure the RX and TX queues. For each port, there is only one RX queue (only one lcore is able to poll a given port). The number of TX queues depends on the number of available lcores. The rte_eth_dev_configure() function is used to configure the number of queues for a port:

ret = rte_eth_dev_configure(portid, 1, 1, &local_port_conf);
if (ret < 0)
	rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%u\n",
		  ret, (unsigned) portid);

The global configuration is stored in a static structure:

static struct rte_eth_conf port_conf = {
	.rxmode = {
		.split_hdr_size = 0,
	},
	.txmode = {
		.mq_mode = ETH_MQ_TX_NONE,
	},
	.intr_conf = {
		.lsc = 1, /**< lsc interrupt feature enabled */
	},
};

Configuring lsc to 0 (the default) disables the generation of any link status change interrupts in kernel space and no user space interrupt event is received. The public interface rte_eth_link_get() accesses the NIC registers directly to update the link status. Configuring lsc to non-zero enables the generation of link status change interrupts in kernel space when a link status change is present and calls the user space callbacks registered by the application. The public interface rte_eth_link_get() just reads the link status in a global structure that would be updated in the interrupt host thread only.

25.4.4. Interrupt Callback Registration

The application can register one or more callbacks to a specific port and interrupt event. An example callback function that has been written as indicated below.

static int
lsi_event_callback(uint16_t port_id, enum rte_eth_event_type type, void *param,
		    void *ret_param)
{
	struct rte_eth_link link;
	int ret;
	char link_status_text[RTE_ETH_LINK_MAX_STR_LEN];

	RTE_SET_USED(param);
	RTE_SET_USED(ret_param);

	printf("\n\nIn registered callback...\n");
	printf("Event type: %s\n", type == RTE_ETH_EVENT_INTR_LSC ? "LSC interrupt" : "unknown event");
	ret = rte_eth_link_get_nowait(port_id, &link);
	if (ret < 0) {
		printf("Failed link get on port %d: %s\n",
		       port_id, rte_strerror(-ret));
		return ret;
	}
	rte_eth_link_to_str(link_status_text, sizeof(link_status_text), &link);
	printf("Port %d %s\n\n", port_id, link_status_text);

	return 0;
}

This function is called when a link status interrupt is present for the right port. The port_id indicates which port the interrupt applies to. The type parameter identifies the interrupt event type, which currently can be RTE_ETH_EVENT_INTR_LSC only, but other types can be added in the future. The param parameter is the address of the parameter for the callback. This function should be implemented with care since it will be called in the interrupt host thread, which is different from the main thread of its caller.

The application registers the lsi_event_callback and a NULL parameter to the link status interrupt event on each port:

rte_eth_dev_callback_register(portid,
	RTE_ETH_EVENT_INTR_LSC, lsi_event_callback, NULL);

This registration can be done only after calling the rte_eth_dev_configure() function and before calling any other function. If lsc is initialized with 0, the callback is never called since no interrupt event would ever be present.

25.4.5. RX Queue Initialization

The application uses one lcore to poll one or several ports, depending on the -q option, which specifies the number of queues per lcore.

For example, if the user specifies -q 4, the application is able to poll four ports with one lcore. If there are 16 ports on the target (and if the portmask argument is -p ffff), the application will need four lcores to poll all the ports.

ret = rte_eth_rx_queue_setup(portid, 0, nb_rxd,
			     rte_eth_dev_socket_id(portid),
			     &rxq_conf,
			     lsi_pktmbuf_pool);
if (ret < 0)
	rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup: err=%d, port=%u\n",
		  ret, (unsigned) portid);

The list of queues that must be polled for a given lcore is stored in a private structure called struct lcore_queue_conf.

struct lcore_queue_conf {
	unsigned n_rx_port;
	unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE];
	unsigned tx_queue_id;
} __rte_cache_aligned;
struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE];

The n_rx_port and rx_port_list[] fields are used in the main packet processing loop (see Receive, Process and Transmit Packets).

The global configuration for the RX queues is stored in a static structure:

struct lcore_queue_conf {
	unsigned n_rx_port;
	unsigned rx_port_list[MAX_RX_QUEUE_PER_LCORE];
	unsigned tx_queue_id;
} __rte_cache_aligned;
struct lcore_queue_conf lcore_queue_conf[RTE_MAX_LCORE];

25.4.6. TX Queue Initialization

Each lcore should be able to transmit on any port. For every port, a single TX queue is initialized.

fflush(stdout);
txq_conf = dev_info.default_txconf;
txq_conf.offloads = local_port_conf.txmode.offloads;
ret = rte_eth_tx_queue_setup(portid, 0, nb_txd,
		rte_eth_dev_socket_id(portid),
		&txq_conf);
if (ret < 0)
	rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup: err=%d,port=%u\n",
		  ret, (unsigned) portid);

The global configuration for TX queues is stored in a static structure:

static const struct rte_eth_txconf tx_conf = {
    .tx_thresh = {
        .pthresh = TX_PTHRESH,
        .hthresh = TX_HTHRESH,
        .wthresh = TX_WTHRESH,
    },
    .tx_free_thresh = RTE_TEST_TX_DESC_DEFAULT + 1, /* disable feature */
};

25.4.7. Receive, Process and Transmit Packets

In the lsi_main_loop() function, the main task is to read ingress packets from the RX queues. This is done using the following code:

for (i = 0; i < qconf->n_rx_port; i++) {

	portid = qconf->rx_port_list[i];
	nb_rx = rte_eth_rx_burst((uint8_t) portid, 0,
				 pkts_burst, MAX_PKT_BURST);

	port_statistics[portid].rx += nb_rx;

	for (j = 0; j < nb_rx; j++) {
		m = pkts_burst[j];
		rte_prefetch0(rte_pktmbuf_mtod(m, void *));
		lsi_simple_forward(m, portid);
	}
}

Packets are read in a burst of size MAX_PKT_BURST. The rte_eth_rx_burst() function writes the mbuf pointers in a local table and returns the number of available mbufs in the table.

Then, each mbuf in the table is processed by the lsi_simple_forward() function. The processing is very simple: processes the TX port from the RX port and then replaces the source and destination MAC addresses.

Note

In the following code, the two lines for calculating the output port require some explanation. If portId is even, the first line does nothing (as portid & 1 will be 0), and the second line adds 1. If portId is odd, the first line subtracts one and the second line does nothing. Therefore, 0 goes to 1, and 1 to 0, 2 goes to 3 and 3 to 2, and so on.

static void
lsi_simple_forward(struct rte_mbuf *m, unsigned portid)
{
	struct rte_ether_hdr *eth;
	void *tmp;
	unsigned dst_port = lsi_dst_ports[portid];
	int sent;
	struct rte_eth_dev_tx_buffer *buffer;

	eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);

	/* 02:00:00:00:00:xx */
	tmp = &eth->d_addr.addr_bytes[0];
	*((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dst_port << 40);

	/* src addr */
	rte_ether_addr_copy(&lsi_ports_eth_addr[dst_port], &eth->s_addr);

	buffer = tx_buffer[dst_port];
	sent = rte_eth_tx_buffer(dst_port, 0, buffer, m);
	if (sent)
		port_statistics[dst_port].tx += sent;
}

Then, the packet is sent using the lsi_send_packet(m, dst_port) function. For this test application, the processing is exactly the same for all packets arriving on the same RX port. Therefore, it would have been possible to call the lsi_send_burst() function directly from the main loop to send all the received packets on the same TX port using the burst-oriented send function, which is more efficient.

However, in real-life applications (such as, L3 routing), packet N is not necessarily forwarded on the same port as packet N-1. The application is implemented to illustrate that so the same approach can be reused in a more complex application.

The lsi_send_packet() function stores the packet in a per-lcore and per-txport table. If the table is full, the whole packets table is transmitted using the lsi_send_burst() function:

static int
l2fwd_send_packet(struct rte_mbuf *m, uint16_t port)
{
	unsigned lcore_id, len;
	struct lcore_queue_conf *qconf;

	lcore_id = rte_lcore_id();

	qconf = &lcore_queue_conf[lcore_id];
	len = qconf->pkt_buf[port].len;
	qconf->pkt_buf[port].buffer[len] = m;
	len++;

	/* enough pkts to be sent */
	if (unlikely(len == MAX_PKT_BURST)) {
		l2fwd_send_burst(qconf, MAX_PKT_BURST, port);
		len = 0;
	}

	qconf->pkt_buf[port].len = len;
	return 0;
}

To ensure that no packets remain in the tables, each lcore does a draining of the TX queue in its main loop. This technique introduces some latency when there are not many packets to send. However, it improves performance:

cur_tsc = rte_rdtsc();

/*
 * TX burst queue drain
 */
diff_tsc = cur_tsc - prev_tsc;
if (unlikely(diff_tsc > drain_tsc)) {

	for (i = 0; i < qconf->n_rx_port; i++) {

		portid = lsi_dst_ports[qconf->rx_port_list[i]];
		buffer = tx_buffer[portid];

		sent = rte_eth_tx_buffer_flush(portid, 0, buffer);
		if (sent)
			port_statistics[portid].tx += sent;

	}

	/* if timer is enabled */
	if (timer_period > 0) {

		/* advance the timer */
		timer_tsc += diff_tsc;

		/* if timer has reached its timeout */
		if (unlikely(timer_tsc >= (uint64_t) timer_period)) {

			/* do this only on main core */
			if (lcore_id == rte_get_main_lcore()) {
				print_stats();
				/* reset the timer */
				timer_tsc = 0;
			}
		}
	}

	prev_tsc = cur_tsc;
}
Next Previous

Built with Sphinx using a theme provided by Read the Docs.