xref: /OK3568_Linux_fs/kernel/samples/bpf/xsk_fwd.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /* Copyright(c) 2020 Intel Corporation. */
3*4882a593Smuzhiyun 
4*4882a593Smuzhiyun #define _GNU_SOURCE
5*4882a593Smuzhiyun #include <poll.h>
6*4882a593Smuzhiyun #include <pthread.h>
7*4882a593Smuzhiyun #include <signal.h>
8*4882a593Smuzhiyun #include <sched.h>
9*4882a593Smuzhiyun #include <stdio.h>
10*4882a593Smuzhiyun #include <stdlib.h>
11*4882a593Smuzhiyun #include <string.h>
12*4882a593Smuzhiyun #include <sys/mman.h>
13*4882a593Smuzhiyun #include <sys/resource.h>
14*4882a593Smuzhiyun #include <sys/socket.h>
15*4882a593Smuzhiyun #include <sys/types.h>
16*4882a593Smuzhiyun #include <time.h>
17*4882a593Smuzhiyun #include <unistd.h>
18*4882a593Smuzhiyun #include <getopt.h>
19*4882a593Smuzhiyun #include <netinet/ether.h>
20*4882a593Smuzhiyun #include <net/if.h>
21*4882a593Smuzhiyun 
22*4882a593Smuzhiyun #include <linux/bpf.h>
23*4882a593Smuzhiyun #include <linux/if_link.h>
24*4882a593Smuzhiyun #include <linux/if_xdp.h>
25*4882a593Smuzhiyun 
26*4882a593Smuzhiyun #include <bpf/libbpf.h>
27*4882a593Smuzhiyun #include <bpf/xsk.h>
28*4882a593Smuzhiyun #include <bpf/bpf.h>
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
31*4882a593Smuzhiyun 
32*4882a593Smuzhiyun typedef __u64 u64;
33*4882a593Smuzhiyun typedef __u32 u32;
34*4882a593Smuzhiyun typedef __u16 u16;
35*4882a593Smuzhiyun typedef __u8  u8;
36*4882a593Smuzhiyun 
37*4882a593Smuzhiyun /* This program illustrates the packet forwarding between multiple AF_XDP
38*4882a593Smuzhiyun  * sockets in multi-threaded environment. All threads are sharing a common
39*4882a593Smuzhiyun  * buffer pool, with each socket having its own private buffer cache.
40*4882a593Smuzhiyun  *
41*4882a593Smuzhiyun  * Example 1: Single thread handling two sockets. The packets received by socket
42*4882a593Smuzhiyun  * A (interface IFA, queue QA) are forwarded to socket B (interface IFB, queue
43*4882a593Smuzhiyun  * QB), while the packets received by socket B are forwarded to socket A. The
44*4882a593Smuzhiyun  * thread is running on CPU core X:
45*4882a593Smuzhiyun  *
46*4882a593Smuzhiyun  *         ./xsk_fwd -i IFA -q QA -i IFB -q QB -c X
47*4882a593Smuzhiyun  *
48*4882a593Smuzhiyun  * Example 2: Two threads, each handling two sockets. The thread running on CPU
49*4882a593Smuzhiyun  * core X forwards all the packets received by socket A to socket B, and all the
50*4882a593Smuzhiyun  * packets received by socket B to socket A. The thread running on CPU core Y is
51*4882a593Smuzhiyun  * performing the same packet forwarding between sockets C and D:
52*4882a593Smuzhiyun  *
53*4882a593Smuzhiyun  *         ./xsk_fwd -i IFA -q QA -i IFB -q QB -i IFC -q QC -i IFD -q QD
54*4882a593Smuzhiyun  *         -c CX -c CY
55*4882a593Smuzhiyun  */
56*4882a593Smuzhiyun 
57*4882a593Smuzhiyun /*
58*4882a593Smuzhiyun  * Buffer pool and buffer cache
59*4882a593Smuzhiyun  *
60*4882a593Smuzhiyun  * For packet forwarding, the packet buffers are typically allocated from the
61*4882a593Smuzhiyun  * pool for packet reception and freed back to the pool for further reuse once
62*4882a593Smuzhiyun  * the packet transmission is completed.
63*4882a593Smuzhiyun  *
64*4882a593Smuzhiyun  * The buffer pool is shared between multiple threads. In order to minimize the
65*4882a593Smuzhiyun  * access latency to the shared buffer pool, each thread creates one (or
66*4882a593Smuzhiyun  * several) buffer caches, which, unlike the buffer pool, are private to the
67*4882a593Smuzhiyun  * thread that creates them and therefore cannot be shared with other threads.
68*4882a593Smuzhiyun  * The access to the shared pool is only needed either (A) when the cache gets
69*4882a593Smuzhiyun  * empty due to repeated buffer allocations and it needs to be replenished from
70*4882a593Smuzhiyun  * the pool, or (B) when the cache gets full due to repeated buffer free and it
71*4882a593Smuzhiyun  * needs to be flushed back to the pull.
72*4882a593Smuzhiyun  *
73*4882a593Smuzhiyun  * In a packet forwarding system, a packet received on any input port can
74*4882a593Smuzhiyun  * potentially be transmitted on any output port, depending on the forwarding
75*4882a593Smuzhiyun  * configuration. For AF_XDP sockets, for this to work with zero-copy of the
76*4882a593Smuzhiyun  * packet buffers when, it is required that the buffer pool memory fits into the
77*4882a593Smuzhiyun  * UMEM area shared by all the sockets.
78*4882a593Smuzhiyun  */
79*4882a593Smuzhiyun 
80*4882a593Smuzhiyun struct bpool_params {
81*4882a593Smuzhiyun 	u32 n_buffers;
82*4882a593Smuzhiyun 	u32 buffer_size;
83*4882a593Smuzhiyun 	int mmap_flags;
84*4882a593Smuzhiyun 
85*4882a593Smuzhiyun 	u32 n_users_max;
86*4882a593Smuzhiyun 	u32 n_buffers_per_slab;
87*4882a593Smuzhiyun };
88*4882a593Smuzhiyun 
89*4882a593Smuzhiyun /* This buffer pool implementation organizes the buffers into equally sized
90*4882a593Smuzhiyun  * slabs of *n_buffers_per_slab*. Initially, there are *n_slabs* slabs in the
91*4882a593Smuzhiyun  * pool that are completely filled with buffer pointers (full slabs).
92*4882a593Smuzhiyun  *
93*4882a593Smuzhiyun  * Each buffer cache has a slab for buffer allocation and a slab for buffer
94*4882a593Smuzhiyun  * free, with both of these slabs initially empty. When the cache's allocation
95*4882a593Smuzhiyun  * slab goes empty, it is swapped with one of the available full slabs from the
96*4882a593Smuzhiyun  * pool, if any is available. When the cache's free slab goes full, it is
97*4882a593Smuzhiyun  * swapped for one of the empty slabs from the pool, which is guaranteed to
98*4882a593Smuzhiyun  * succeed.
99*4882a593Smuzhiyun  *
100*4882a593Smuzhiyun  * Partially filled slabs never get traded between the cache and the pool
101*4882a593Smuzhiyun  * (except when the cache itself is destroyed), which enables fast operation
102*4882a593Smuzhiyun  * through pointer swapping.
103*4882a593Smuzhiyun  */
104*4882a593Smuzhiyun struct bpool {
105*4882a593Smuzhiyun 	struct bpool_params params;
106*4882a593Smuzhiyun 	pthread_mutex_t lock;
107*4882a593Smuzhiyun 	void *addr;
108*4882a593Smuzhiyun 
109*4882a593Smuzhiyun 	u64 **slabs;
110*4882a593Smuzhiyun 	u64 **slabs_reserved;
111*4882a593Smuzhiyun 	u64 *buffers;
112*4882a593Smuzhiyun 	u64 *buffers_reserved;
113*4882a593Smuzhiyun 
114*4882a593Smuzhiyun 	u64 n_slabs;
115*4882a593Smuzhiyun 	u64 n_slabs_reserved;
116*4882a593Smuzhiyun 	u64 n_buffers;
117*4882a593Smuzhiyun 
118*4882a593Smuzhiyun 	u64 n_slabs_available;
119*4882a593Smuzhiyun 	u64 n_slabs_reserved_available;
120*4882a593Smuzhiyun 
121*4882a593Smuzhiyun 	struct xsk_umem_config umem_cfg;
122*4882a593Smuzhiyun 	struct xsk_ring_prod umem_fq;
123*4882a593Smuzhiyun 	struct xsk_ring_cons umem_cq;
124*4882a593Smuzhiyun 	struct xsk_umem *umem;
125*4882a593Smuzhiyun };
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun static struct bpool *
bpool_init(struct bpool_params * params,struct xsk_umem_config * umem_cfg)128*4882a593Smuzhiyun bpool_init(struct bpool_params *params,
129*4882a593Smuzhiyun 	   struct xsk_umem_config *umem_cfg)
130*4882a593Smuzhiyun {
131*4882a593Smuzhiyun 	struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
132*4882a593Smuzhiyun 	u64 n_slabs, n_slabs_reserved, n_buffers, n_buffers_reserved;
133*4882a593Smuzhiyun 	u64 slabs_size, slabs_reserved_size;
134*4882a593Smuzhiyun 	u64 buffers_size, buffers_reserved_size;
135*4882a593Smuzhiyun 	u64 total_size, i;
136*4882a593Smuzhiyun 	struct bpool *bp;
137*4882a593Smuzhiyun 	u8 *p;
138*4882a593Smuzhiyun 	int status;
139*4882a593Smuzhiyun 
140*4882a593Smuzhiyun 	/* mmap prep. */
141*4882a593Smuzhiyun 	if (setrlimit(RLIMIT_MEMLOCK, &r))
142*4882a593Smuzhiyun 		return NULL;
143*4882a593Smuzhiyun 
144*4882a593Smuzhiyun 	/* bpool internals dimensioning. */
145*4882a593Smuzhiyun 	n_slabs = (params->n_buffers + params->n_buffers_per_slab - 1) /
146*4882a593Smuzhiyun 		params->n_buffers_per_slab;
147*4882a593Smuzhiyun 	n_slabs_reserved = params->n_users_max * 2;
148*4882a593Smuzhiyun 	n_buffers = n_slabs * params->n_buffers_per_slab;
149*4882a593Smuzhiyun 	n_buffers_reserved = n_slabs_reserved * params->n_buffers_per_slab;
150*4882a593Smuzhiyun 
151*4882a593Smuzhiyun 	slabs_size = n_slabs * sizeof(u64 *);
152*4882a593Smuzhiyun 	slabs_reserved_size = n_slabs_reserved * sizeof(u64 *);
153*4882a593Smuzhiyun 	buffers_size = n_buffers * sizeof(u64);
154*4882a593Smuzhiyun 	buffers_reserved_size = n_buffers_reserved * sizeof(u64);
155*4882a593Smuzhiyun 
156*4882a593Smuzhiyun 	total_size = sizeof(struct bpool) +
157*4882a593Smuzhiyun 		slabs_size + slabs_reserved_size +
158*4882a593Smuzhiyun 		buffers_size + buffers_reserved_size;
159*4882a593Smuzhiyun 
160*4882a593Smuzhiyun 	/* bpool memory allocation. */
161*4882a593Smuzhiyun 	p = calloc(total_size, sizeof(u8));
162*4882a593Smuzhiyun 	if (!p)
163*4882a593Smuzhiyun 		return NULL;
164*4882a593Smuzhiyun 
165*4882a593Smuzhiyun 	/* bpool memory initialization. */
166*4882a593Smuzhiyun 	bp = (struct bpool *)p;
167*4882a593Smuzhiyun 	memcpy(&bp->params, params, sizeof(*params));
168*4882a593Smuzhiyun 	bp->params.n_buffers = n_buffers;
169*4882a593Smuzhiyun 
170*4882a593Smuzhiyun 	bp->slabs = (u64 **)&p[sizeof(struct bpool)];
171*4882a593Smuzhiyun 	bp->slabs_reserved = (u64 **)&p[sizeof(struct bpool) +
172*4882a593Smuzhiyun 		slabs_size];
173*4882a593Smuzhiyun 	bp->buffers = (u64 *)&p[sizeof(struct bpool) +
174*4882a593Smuzhiyun 		slabs_size + slabs_reserved_size];
175*4882a593Smuzhiyun 	bp->buffers_reserved = (u64 *)&p[sizeof(struct bpool) +
176*4882a593Smuzhiyun 		slabs_size + slabs_reserved_size + buffers_size];
177*4882a593Smuzhiyun 
178*4882a593Smuzhiyun 	bp->n_slabs = n_slabs;
179*4882a593Smuzhiyun 	bp->n_slabs_reserved = n_slabs_reserved;
180*4882a593Smuzhiyun 	bp->n_buffers = n_buffers;
181*4882a593Smuzhiyun 
182*4882a593Smuzhiyun 	for (i = 0; i < n_slabs; i++)
183*4882a593Smuzhiyun 		bp->slabs[i] = &bp->buffers[i * params->n_buffers_per_slab];
184*4882a593Smuzhiyun 	bp->n_slabs_available = n_slabs;
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun 	for (i = 0; i < n_slabs_reserved; i++)
187*4882a593Smuzhiyun 		bp->slabs_reserved[i] = &bp->buffers_reserved[i *
188*4882a593Smuzhiyun 			params->n_buffers_per_slab];
189*4882a593Smuzhiyun 	bp->n_slabs_reserved_available = n_slabs_reserved;
190*4882a593Smuzhiyun 
191*4882a593Smuzhiyun 	for (i = 0; i < n_buffers; i++)
192*4882a593Smuzhiyun 		bp->buffers[i] = i * params->buffer_size;
193*4882a593Smuzhiyun 
194*4882a593Smuzhiyun 	/* lock. */
195*4882a593Smuzhiyun 	status = pthread_mutex_init(&bp->lock, NULL);
196*4882a593Smuzhiyun 	if (status) {
197*4882a593Smuzhiyun 		free(p);
198*4882a593Smuzhiyun 		return NULL;
199*4882a593Smuzhiyun 	}
200*4882a593Smuzhiyun 
201*4882a593Smuzhiyun 	/* mmap. */
202*4882a593Smuzhiyun 	bp->addr = mmap(NULL,
203*4882a593Smuzhiyun 			n_buffers * params->buffer_size,
204*4882a593Smuzhiyun 			PROT_READ | PROT_WRITE,
205*4882a593Smuzhiyun 			MAP_PRIVATE | MAP_ANONYMOUS | params->mmap_flags,
206*4882a593Smuzhiyun 			-1,
207*4882a593Smuzhiyun 			0);
208*4882a593Smuzhiyun 	if (bp->addr == MAP_FAILED) {
209*4882a593Smuzhiyun 		pthread_mutex_destroy(&bp->lock);
210*4882a593Smuzhiyun 		free(p);
211*4882a593Smuzhiyun 		return NULL;
212*4882a593Smuzhiyun 	}
213*4882a593Smuzhiyun 
214*4882a593Smuzhiyun 	/* umem. */
215*4882a593Smuzhiyun 	status = xsk_umem__create(&bp->umem,
216*4882a593Smuzhiyun 				  bp->addr,
217*4882a593Smuzhiyun 				  bp->params.n_buffers * bp->params.buffer_size,
218*4882a593Smuzhiyun 				  &bp->umem_fq,
219*4882a593Smuzhiyun 				  &bp->umem_cq,
220*4882a593Smuzhiyun 				  umem_cfg);
221*4882a593Smuzhiyun 	if (status) {
222*4882a593Smuzhiyun 		munmap(bp->addr, bp->params.n_buffers * bp->params.buffer_size);
223*4882a593Smuzhiyun 		pthread_mutex_destroy(&bp->lock);
224*4882a593Smuzhiyun 		free(p);
225*4882a593Smuzhiyun 		return NULL;
226*4882a593Smuzhiyun 	}
227*4882a593Smuzhiyun 	memcpy(&bp->umem_cfg, umem_cfg, sizeof(*umem_cfg));
228*4882a593Smuzhiyun 
229*4882a593Smuzhiyun 	return bp;
230*4882a593Smuzhiyun }
231*4882a593Smuzhiyun 
232*4882a593Smuzhiyun static void
bpool_free(struct bpool * bp)233*4882a593Smuzhiyun bpool_free(struct bpool *bp)
234*4882a593Smuzhiyun {
235*4882a593Smuzhiyun 	if (!bp)
236*4882a593Smuzhiyun 		return;
237*4882a593Smuzhiyun 
238*4882a593Smuzhiyun 	xsk_umem__delete(bp->umem);
239*4882a593Smuzhiyun 	munmap(bp->addr, bp->params.n_buffers * bp->params.buffer_size);
240*4882a593Smuzhiyun 	pthread_mutex_destroy(&bp->lock);
241*4882a593Smuzhiyun 	free(bp);
242*4882a593Smuzhiyun }
243*4882a593Smuzhiyun 
244*4882a593Smuzhiyun struct bcache {
245*4882a593Smuzhiyun 	struct bpool *bp;
246*4882a593Smuzhiyun 
247*4882a593Smuzhiyun 	u64 *slab_cons;
248*4882a593Smuzhiyun 	u64 *slab_prod;
249*4882a593Smuzhiyun 
250*4882a593Smuzhiyun 	u64 n_buffers_cons;
251*4882a593Smuzhiyun 	u64 n_buffers_prod;
252*4882a593Smuzhiyun };
253*4882a593Smuzhiyun 
254*4882a593Smuzhiyun static u32
bcache_slab_size(struct bcache * bc)255*4882a593Smuzhiyun bcache_slab_size(struct bcache *bc)
256*4882a593Smuzhiyun {
257*4882a593Smuzhiyun 	struct bpool *bp = bc->bp;
258*4882a593Smuzhiyun 
259*4882a593Smuzhiyun 	return bp->params.n_buffers_per_slab;
260*4882a593Smuzhiyun }
261*4882a593Smuzhiyun 
262*4882a593Smuzhiyun static struct bcache *
bcache_init(struct bpool * bp)263*4882a593Smuzhiyun bcache_init(struct bpool *bp)
264*4882a593Smuzhiyun {
265*4882a593Smuzhiyun 	struct bcache *bc;
266*4882a593Smuzhiyun 
267*4882a593Smuzhiyun 	bc = calloc(1, sizeof(struct bcache));
268*4882a593Smuzhiyun 	if (!bc)
269*4882a593Smuzhiyun 		return NULL;
270*4882a593Smuzhiyun 
271*4882a593Smuzhiyun 	bc->bp = bp;
272*4882a593Smuzhiyun 	bc->n_buffers_cons = 0;
273*4882a593Smuzhiyun 	bc->n_buffers_prod = 0;
274*4882a593Smuzhiyun 
275*4882a593Smuzhiyun 	pthread_mutex_lock(&bp->lock);
276*4882a593Smuzhiyun 	if (bp->n_slabs_reserved_available == 0) {
277*4882a593Smuzhiyun 		pthread_mutex_unlock(&bp->lock);
278*4882a593Smuzhiyun 		free(bc);
279*4882a593Smuzhiyun 		return NULL;
280*4882a593Smuzhiyun 	}
281*4882a593Smuzhiyun 
282*4882a593Smuzhiyun 	bc->slab_cons = bp->slabs_reserved[bp->n_slabs_reserved_available - 1];
283*4882a593Smuzhiyun 	bc->slab_prod = bp->slabs_reserved[bp->n_slabs_reserved_available - 2];
284*4882a593Smuzhiyun 	bp->n_slabs_reserved_available -= 2;
285*4882a593Smuzhiyun 	pthread_mutex_unlock(&bp->lock);
286*4882a593Smuzhiyun 
287*4882a593Smuzhiyun 	return bc;
288*4882a593Smuzhiyun }
289*4882a593Smuzhiyun 
290*4882a593Smuzhiyun static void
bcache_free(struct bcache * bc)291*4882a593Smuzhiyun bcache_free(struct bcache *bc)
292*4882a593Smuzhiyun {
293*4882a593Smuzhiyun 	struct bpool *bp;
294*4882a593Smuzhiyun 
295*4882a593Smuzhiyun 	if (!bc)
296*4882a593Smuzhiyun 		return;
297*4882a593Smuzhiyun 
298*4882a593Smuzhiyun 	/* In order to keep this example simple, the case of freeing any
299*4882a593Smuzhiyun 	 * existing buffers from the cache back to the pool is ignored.
300*4882a593Smuzhiyun 	 */
301*4882a593Smuzhiyun 
302*4882a593Smuzhiyun 	bp = bc->bp;
303*4882a593Smuzhiyun 	pthread_mutex_lock(&bp->lock);
304*4882a593Smuzhiyun 	bp->slabs_reserved[bp->n_slabs_reserved_available] = bc->slab_prod;
305*4882a593Smuzhiyun 	bp->slabs_reserved[bp->n_slabs_reserved_available + 1] = bc->slab_cons;
306*4882a593Smuzhiyun 	bp->n_slabs_reserved_available += 2;
307*4882a593Smuzhiyun 	pthread_mutex_unlock(&bp->lock);
308*4882a593Smuzhiyun 
309*4882a593Smuzhiyun 	free(bc);
310*4882a593Smuzhiyun }
311*4882a593Smuzhiyun 
312*4882a593Smuzhiyun /* To work correctly, the implementation requires that the *n_buffers* input
313*4882a593Smuzhiyun  * argument is never greater than the buffer pool's *n_buffers_per_slab*. This
314*4882a593Smuzhiyun  * is typically the case, with one exception taking place when large number of
315*4882a593Smuzhiyun  * buffers are allocated at init time (e.g. for the UMEM fill queue setup).
316*4882a593Smuzhiyun  */
317*4882a593Smuzhiyun static inline u32
bcache_cons_check(struct bcache * bc,u32 n_buffers)318*4882a593Smuzhiyun bcache_cons_check(struct bcache *bc, u32 n_buffers)
319*4882a593Smuzhiyun {
320*4882a593Smuzhiyun 	struct bpool *bp = bc->bp;
321*4882a593Smuzhiyun 	u64 n_buffers_per_slab = bp->params.n_buffers_per_slab;
322*4882a593Smuzhiyun 	u64 n_buffers_cons = bc->n_buffers_cons;
323*4882a593Smuzhiyun 	u64 n_slabs_available;
324*4882a593Smuzhiyun 	u64 *slab_full;
325*4882a593Smuzhiyun 
326*4882a593Smuzhiyun 	/*
327*4882a593Smuzhiyun 	 * Consumer slab is not empty: Use what's available locally. Do not
328*4882a593Smuzhiyun 	 * look for more buffers from the pool when the ask can only be
329*4882a593Smuzhiyun 	 * partially satisfied.
330*4882a593Smuzhiyun 	 */
331*4882a593Smuzhiyun 	if (n_buffers_cons)
332*4882a593Smuzhiyun 		return (n_buffers_cons < n_buffers) ?
333*4882a593Smuzhiyun 			n_buffers_cons :
334*4882a593Smuzhiyun 			n_buffers;
335*4882a593Smuzhiyun 
336*4882a593Smuzhiyun 	/*
337*4882a593Smuzhiyun 	 * Consumer slab is empty: look to trade the current consumer slab
338*4882a593Smuzhiyun 	 * (full) for a full slab from the pool, if any is available.
339*4882a593Smuzhiyun 	 */
340*4882a593Smuzhiyun 	pthread_mutex_lock(&bp->lock);
341*4882a593Smuzhiyun 	n_slabs_available = bp->n_slabs_available;
342*4882a593Smuzhiyun 	if (!n_slabs_available) {
343*4882a593Smuzhiyun 		pthread_mutex_unlock(&bp->lock);
344*4882a593Smuzhiyun 		return 0;
345*4882a593Smuzhiyun 	}
346*4882a593Smuzhiyun 
347*4882a593Smuzhiyun 	n_slabs_available--;
348*4882a593Smuzhiyun 	slab_full = bp->slabs[n_slabs_available];
349*4882a593Smuzhiyun 	bp->slabs[n_slabs_available] = bc->slab_cons;
350*4882a593Smuzhiyun 	bp->n_slabs_available = n_slabs_available;
351*4882a593Smuzhiyun 	pthread_mutex_unlock(&bp->lock);
352*4882a593Smuzhiyun 
353*4882a593Smuzhiyun 	bc->slab_cons = slab_full;
354*4882a593Smuzhiyun 	bc->n_buffers_cons = n_buffers_per_slab;
355*4882a593Smuzhiyun 	return n_buffers;
356*4882a593Smuzhiyun }
357*4882a593Smuzhiyun 
358*4882a593Smuzhiyun static inline u64
bcache_cons(struct bcache * bc)359*4882a593Smuzhiyun bcache_cons(struct bcache *bc)
360*4882a593Smuzhiyun {
361*4882a593Smuzhiyun 	u64 n_buffers_cons = bc->n_buffers_cons - 1;
362*4882a593Smuzhiyun 	u64 buffer;
363*4882a593Smuzhiyun 
364*4882a593Smuzhiyun 	buffer = bc->slab_cons[n_buffers_cons];
365*4882a593Smuzhiyun 	bc->n_buffers_cons = n_buffers_cons;
366*4882a593Smuzhiyun 	return buffer;
367*4882a593Smuzhiyun }
368*4882a593Smuzhiyun 
369*4882a593Smuzhiyun static inline void
bcache_prod(struct bcache * bc,u64 buffer)370*4882a593Smuzhiyun bcache_prod(struct bcache *bc, u64 buffer)
371*4882a593Smuzhiyun {
372*4882a593Smuzhiyun 	struct bpool *bp = bc->bp;
373*4882a593Smuzhiyun 	u64 n_buffers_per_slab = bp->params.n_buffers_per_slab;
374*4882a593Smuzhiyun 	u64 n_buffers_prod = bc->n_buffers_prod;
375*4882a593Smuzhiyun 	u64 n_slabs_available;
376*4882a593Smuzhiyun 	u64 *slab_empty;
377*4882a593Smuzhiyun 
378*4882a593Smuzhiyun 	/*
379*4882a593Smuzhiyun 	 * Producer slab is not yet full: store the current buffer to it.
380*4882a593Smuzhiyun 	 */
381*4882a593Smuzhiyun 	if (n_buffers_prod < n_buffers_per_slab) {
382*4882a593Smuzhiyun 		bc->slab_prod[n_buffers_prod] = buffer;
383*4882a593Smuzhiyun 		bc->n_buffers_prod = n_buffers_prod + 1;
384*4882a593Smuzhiyun 		return;
385*4882a593Smuzhiyun 	}
386*4882a593Smuzhiyun 
387*4882a593Smuzhiyun 	/*
388*4882a593Smuzhiyun 	 * Producer slab is full: trade the cache's current producer slab
389*4882a593Smuzhiyun 	 * (full) for an empty slab from the pool, then store the current
390*4882a593Smuzhiyun 	 * buffer to the new producer slab. As one full slab exists in the
391*4882a593Smuzhiyun 	 * cache, it is guaranteed that there is at least one empty slab
392*4882a593Smuzhiyun 	 * available in the pool.
393*4882a593Smuzhiyun 	 */
394*4882a593Smuzhiyun 	pthread_mutex_lock(&bp->lock);
395*4882a593Smuzhiyun 	n_slabs_available = bp->n_slabs_available;
396*4882a593Smuzhiyun 	slab_empty = bp->slabs[n_slabs_available];
397*4882a593Smuzhiyun 	bp->slabs[n_slabs_available] = bc->slab_prod;
398*4882a593Smuzhiyun 	bp->n_slabs_available = n_slabs_available + 1;
399*4882a593Smuzhiyun 	pthread_mutex_unlock(&bp->lock);
400*4882a593Smuzhiyun 
401*4882a593Smuzhiyun 	slab_empty[0] = buffer;
402*4882a593Smuzhiyun 	bc->slab_prod = slab_empty;
403*4882a593Smuzhiyun 	bc->n_buffers_prod = 1;
404*4882a593Smuzhiyun }
405*4882a593Smuzhiyun 
406*4882a593Smuzhiyun /*
407*4882a593Smuzhiyun  * Port
408*4882a593Smuzhiyun  *
409*4882a593Smuzhiyun  * Each of the forwarding ports sits on top of an AF_XDP socket. In order for
410*4882a593Smuzhiyun  * packet forwarding to happen with no packet buffer copy, all the sockets need
411*4882a593Smuzhiyun  * to share the same UMEM area, which is used as the buffer pool memory.
412*4882a593Smuzhiyun  */
413*4882a593Smuzhiyun #ifndef MAX_BURST_RX
414*4882a593Smuzhiyun #define MAX_BURST_RX 64
415*4882a593Smuzhiyun #endif
416*4882a593Smuzhiyun 
417*4882a593Smuzhiyun #ifndef MAX_BURST_TX
418*4882a593Smuzhiyun #define MAX_BURST_TX 64
419*4882a593Smuzhiyun #endif
420*4882a593Smuzhiyun 
421*4882a593Smuzhiyun struct burst_rx {
422*4882a593Smuzhiyun 	u64 addr[MAX_BURST_RX];
423*4882a593Smuzhiyun 	u32 len[MAX_BURST_RX];
424*4882a593Smuzhiyun };
425*4882a593Smuzhiyun 
426*4882a593Smuzhiyun struct burst_tx {
427*4882a593Smuzhiyun 	u64 addr[MAX_BURST_TX];
428*4882a593Smuzhiyun 	u32 len[MAX_BURST_TX];
429*4882a593Smuzhiyun 	u32 n_pkts;
430*4882a593Smuzhiyun };
431*4882a593Smuzhiyun 
432*4882a593Smuzhiyun struct port_params {
433*4882a593Smuzhiyun 	struct xsk_socket_config xsk_cfg;
434*4882a593Smuzhiyun 	struct bpool *bp;
435*4882a593Smuzhiyun 	const char *iface;
436*4882a593Smuzhiyun 	u32 iface_queue;
437*4882a593Smuzhiyun };
438*4882a593Smuzhiyun 
439*4882a593Smuzhiyun struct port {
440*4882a593Smuzhiyun 	struct port_params params;
441*4882a593Smuzhiyun 
442*4882a593Smuzhiyun 	struct bcache *bc;
443*4882a593Smuzhiyun 
444*4882a593Smuzhiyun 	struct xsk_ring_cons rxq;
445*4882a593Smuzhiyun 	struct xsk_ring_prod txq;
446*4882a593Smuzhiyun 	struct xsk_ring_prod umem_fq;
447*4882a593Smuzhiyun 	struct xsk_ring_cons umem_cq;
448*4882a593Smuzhiyun 	struct xsk_socket *xsk;
449*4882a593Smuzhiyun 	int umem_fq_initialized;
450*4882a593Smuzhiyun 
451*4882a593Smuzhiyun 	u64 n_pkts_rx;
452*4882a593Smuzhiyun 	u64 n_pkts_tx;
453*4882a593Smuzhiyun };
454*4882a593Smuzhiyun 
455*4882a593Smuzhiyun static void
port_free(struct port * p)456*4882a593Smuzhiyun port_free(struct port *p)
457*4882a593Smuzhiyun {
458*4882a593Smuzhiyun 	if (!p)
459*4882a593Smuzhiyun 		return;
460*4882a593Smuzhiyun 
461*4882a593Smuzhiyun 	/* To keep this example simple, the code to free the buffers from the
462*4882a593Smuzhiyun 	 * socket's receive and transmit queues, as well as from the UMEM fill
463*4882a593Smuzhiyun 	 * and completion queues, is not included.
464*4882a593Smuzhiyun 	 */
465*4882a593Smuzhiyun 
466*4882a593Smuzhiyun 	if (p->xsk)
467*4882a593Smuzhiyun 		xsk_socket__delete(p->xsk);
468*4882a593Smuzhiyun 
469*4882a593Smuzhiyun 	bcache_free(p->bc);
470*4882a593Smuzhiyun 
471*4882a593Smuzhiyun 	free(p);
472*4882a593Smuzhiyun }
473*4882a593Smuzhiyun 
474*4882a593Smuzhiyun static struct port *
port_init(struct port_params * params)475*4882a593Smuzhiyun port_init(struct port_params *params)
476*4882a593Smuzhiyun {
477*4882a593Smuzhiyun 	struct port *p;
478*4882a593Smuzhiyun 	u32 umem_fq_size, pos = 0;
479*4882a593Smuzhiyun 	int status, i;
480*4882a593Smuzhiyun 
481*4882a593Smuzhiyun 	/* Memory allocation and initialization. */
482*4882a593Smuzhiyun 	p = calloc(sizeof(struct port), 1);
483*4882a593Smuzhiyun 	if (!p)
484*4882a593Smuzhiyun 		return NULL;
485*4882a593Smuzhiyun 
486*4882a593Smuzhiyun 	memcpy(&p->params, params, sizeof(p->params));
487*4882a593Smuzhiyun 	umem_fq_size = params->bp->umem_cfg.fill_size;
488*4882a593Smuzhiyun 
489*4882a593Smuzhiyun 	/* bcache. */
490*4882a593Smuzhiyun 	p->bc = bcache_init(params->bp);
491*4882a593Smuzhiyun 	if (!p->bc ||
492*4882a593Smuzhiyun 	    (bcache_slab_size(p->bc) < umem_fq_size) ||
493*4882a593Smuzhiyun 	    (bcache_cons_check(p->bc, umem_fq_size) < umem_fq_size)) {
494*4882a593Smuzhiyun 		port_free(p);
495*4882a593Smuzhiyun 		return NULL;
496*4882a593Smuzhiyun 	}
497*4882a593Smuzhiyun 
498*4882a593Smuzhiyun 	/* xsk socket. */
499*4882a593Smuzhiyun 	status = xsk_socket__create_shared(&p->xsk,
500*4882a593Smuzhiyun 					   params->iface,
501*4882a593Smuzhiyun 					   params->iface_queue,
502*4882a593Smuzhiyun 					   params->bp->umem,
503*4882a593Smuzhiyun 					   &p->rxq,
504*4882a593Smuzhiyun 					   &p->txq,
505*4882a593Smuzhiyun 					   &p->umem_fq,
506*4882a593Smuzhiyun 					   &p->umem_cq,
507*4882a593Smuzhiyun 					   &params->xsk_cfg);
508*4882a593Smuzhiyun 	if (status) {
509*4882a593Smuzhiyun 		port_free(p);
510*4882a593Smuzhiyun 		return NULL;
511*4882a593Smuzhiyun 	}
512*4882a593Smuzhiyun 
513*4882a593Smuzhiyun 	/* umem fq. */
514*4882a593Smuzhiyun 	xsk_ring_prod__reserve(&p->umem_fq, umem_fq_size, &pos);
515*4882a593Smuzhiyun 
516*4882a593Smuzhiyun 	for (i = 0; i < umem_fq_size; i++)
517*4882a593Smuzhiyun 		*xsk_ring_prod__fill_addr(&p->umem_fq, pos + i) =
518*4882a593Smuzhiyun 			bcache_cons(p->bc);
519*4882a593Smuzhiyun 
520*4882a593Smuzhiyun 	xsk_ring_prod__submit(&p->umem_fq, umem_fq_size);
521*4882a593Smuzhiyun 	p->umem_fq_initialized = 1;
522*4882a593Smuzhiyun 
523*4882a593Smuzhiyun 	return p;
524*4882a593Smuzhiyun }
525*4882a593Smuzhiyun 
526*4882a593Smuzhiyun static inline u32
port_rx_burst(struct port * p,struct burst_rx * b)527*4882a593Smuzhiyun port_rx_burst(struct port *p, struct burst_rx *b)
528*4882a593Smuzhiyun {
529*4882a593Smuzhiyun 	u32 n_pkts, pos, i;
530*4882a593Smuzhiyun 
531*4882a593Smuzhiyun 	/* Free buffers for FQ replenish. */
532*4882a593Smuzhiyun 	n_pkts = ARRAY_SIZE(b->addr);
533*4882a593Smuzhiyun 
534*4882a593Smuzhiyun 	n_pkts = bcache_cons_check(p->bc, n_pkts);
535*4882a593Smuzhiyun 	if (!n_pkts)
536*4882a593Smuzhiyun 		return 0;
537*4882a593Smuzhiyun 
538*4882a593Smuzhiyun 	/* RXQ. */
539*4882a593Smuzhiyun 	n_pkts = xsk_ring_cons__peek(&p->rxq, n_pkts, &pos);
540*4882a593Smuzhiyun 	if (!n_pkts) {
541*4882a593Smuzhiyun 		if (xsk_ring_prod__needs_wakeup(&p->umem_fq)) {
542*4882a593Smuzhiyun 			struct pollfd pollfd = {
543*4882a593Smuzhiyun 				.fd = xsk_socket__fd(p->xsk),
544*4882a593Smuzhiyun 				.events = POLLIN,
545*4882a593Smuzhiyun 			};
546*4882a593Smuzhiyun 
547*4882a593Smuzhiyun 			poll(&pollfd, 1, 0);
548*4882a593Smuzhiyun 		}
549*4882a593Smuzhiyun 		return 0;
550*4882a593Smuzhiyun 	}
551*4882a593Smuzhiyun 
552*4882a593Smuzhiyun 	for (i = 0; i < n_pkts; i++) {
553*4882a593Smuzhiyun 		b->addr[i] = xsk_ring_cons__rx_desc(&p->rxq, pos + i)->addr;
554*4882a593Smuzhiyun 		b->len[i] = xsk_ring_cons__rx_desc(&p->rxq, pos + i)->len;
555*4882a593Smuzhiyun 	}
556*4882a593Smuzhiyun 
557*4882a593Smuzhiyun 	xsk_ring_cons__release(&p->rxq, n_pkts);
558*4882a593Smuzhiyun 	p->n_pkts_rx += n_pkts;
559*4882a593Smuzhiyun 
560*4882a593Smuzhiyun 	/* UMEM FQ. */
561*4882a593Smuzhiyun 	for ( ; ; ) {
562*4882a593Smuzhiyun 		int status;
563*4882a593Smuzhiyun 
564*4882a593Smuzhiyun 		status = xsk_ring_prod__reserve(&p->umem_fq, n_pkts, &pos);
565*4882a593Smuzhiyun 		if (status == n_pkts)
566*4882a593Smuzhiyun 			break;
567*4882a593Smuzhiyun 
568*4882a593Smuzhiyun 		if (xsk_ring_prod__needs_wakeup(&p->umem_fq)) {
569*4882a593Smuzhiyun 			struct pollfd pollfd = {
570*4882a593Smuzhiyun 				.fd = xsk_socket__fd(p->xsk),
571*4882a593Smuzhiyun 				.events = POLLIN,
572*4882a593Smuzhiyun 			};
573*4882a593Smuzhiyun 
574*4882a593Smuzhiyun 			poll(&pollfd, 1, 0);
575*4882a593Smuzhiyun 		}
576*4882a593Smuzhiyun 	}
577*4882a593Smuzhiyun 
578*4882a593Smuzhiyun 	for (i = 0; i < n_pkts; i++)
579*4882a593Smuzhiyun 		*xsk_ring_prod__fill_addr(&p->umem_fq, pos + i) =
580*4882a593Smuzhiyun 			bcache_cons(p->bc);
581*4882a593Smuzhiyun 
582*4882a593Smuzhiyun 	xsk_ring_prod__submit(&p->umem_fq, n_pkts);
583*4882a593Smuzhiyun 
584*4882a593Smuzhiyun 	return n_pkts;
585*4882a593Smuzhiyun }
586*4882a593Smuzhiyun 
587*4882a593Smuzhiyun static inline void
port_tx_burst(struct port * p,struct burst_tx * b)588*4882a593Smuzhiyun port_tx_burst(struct port *p, struct burst_tx *b)
589*4882a593Smuzhiyun {
590*4882a593Smuzhiyun 	u32 n_pkts, pos, i;
591*4882a593Smuzhiyun 	int status;
592*4882a593Smuzhiyun 
593*4882a593Smuzhiyun 	/* UMEM CQ. */
594*4882a593Smuzhiyun 	n_pkts = p->params.bp->umem_cfg.comp_size;
595*4882a593Smuzhiyun 
596*4882a593Smuzhiyun 	n_pkts = xsk_ring_cons__peek(&p->umem_cq, n_pkts, &pos);
597*4882a593Smuzhiyun 
598*4882a593Smuzhiyun 	for (i = 0; i < n_pkts; i++) {
599*4882a593Smuzhiyun 		u64 addr = *xsk_ring_cons__comp_addr(&p->umem_cq, pos + i);
600*4882a593Smuzhiyun 
601*4882a593Smuzhiyun 		bcache_prod(p->bc, addr);
602*4882a593Smuzhiyun 	}
603*4882a593Smuzhiyun 
604*4882a593Smuzhiyun 	xsk_ring_cons__release(&p->umem_cq, n_pkts);
605*4882a593Smuzhiyun 
606*4882a593Smuzhiyun 	/* TXQ. */
607*4882a593Smuzhiyun 	n_pkts = b->n_pkts;
608*4882a593Smuzhiyun 
609*4882a593Smuzhiyun 	for ( ; ; ) {
610*4882a593Smuzhiyun 		status = xsk_ring_prod__reserve(&p->txq, n_pkts, &pos);
611*4882a593Smuzhiyun 		if (status == n_pkts)
612*4882a593Smuzhiyun 			break;
613*4882a593Smuzhiyun 
614*4882a593Smuzhiyun 		if (xsk_ring_prod__needs_wakeup(&p->txq))
615*4882a593Smuzhiyun 			sendto(xsk_socket__fd(p->xsk), NULL, 0, MSG_DONTWAIT,
616*4882a593Smuzhiyun 			       NULL, 0);
617*4882a593Smuzhiyun 	}
618*4882a593Smuzhiyun 
619*4882a593Smuzhiyun 	for (i = 0; i < n_pkts; i++) {
620*4882a593Smuzhiyun 		xsk_ring_prod__tx_desc(&p->txq, pos + i)->addr = b->addr[i];
621*4882a593Smuzhiyun 		xsk_ring_prod__tx_desc(&p->txq, pos + i)->len = b->len[i];
622*4882a593Smuzhiyun 	}
623*4882a593Smuzhiyun 
624*4882a593Smuzhiyun 	xsk_ring_prod__submit(&p->txq, n_pkts);
625*4882a593Smuzhiyun 	if (xsk_ring_prod__needs_wakeup(&p->txq))
626*4882a593Smuzhiyun 		sendto(xsk_socket__fd(p->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0);
627*4882a593Smuzhiyun 	p->n_pkts_tx += n_pkts;
628*4882a593Smuzhiyun }
629*4882a593Smuzhiyun 
630*4882a593Smuzhiyun /*
631*4882a593Smuzhiyun  * Thread
632*4882a593Smuzhiyun  *
633*4882a593Smuzhiyun  * Packet forwarding threads.
634*4882a593Smuzhiyun  */
635*4882a593Smuzhiyun #ifndef MAX_PORTS_PER_THREAD
636*4882a593Smuzhiyun #define MAX_PORTS_PER_THREAD 16
637*4882a593Smuzhiyun #endif
638*4882a593Smuzhiyun 
639*4882a593Smuzhiyun struct thread_data {
640*4882a593Smuzhiyun 	struct port *ports_rx[MAX_PORTS_PER_THREAD];
641*4882a593Smuzhiyun 	struct port *ports_tx[MAX_PORTS_PER_THREAD];
642*4882a593Smuzhiyun 	u32 n_ports_rx;
643*4882a593Smuzhiyun 	struct burst_rx burst_rx;
644*4882a593Smuzhiyun 	struct burst_tx burst_tx[MAX_PORTS_PER_THREAD];
645*4882a593Smuzhiyun 	u32 cpu_core_id;
646*4882a593Smuzhiyun 	int quit;
647*4882a593Smuzhiyun };
648*4882a593Smuzhiyun 
swap_mac_addresses(void * data)649*4882a593Smuzhiyun static void swap_mac_addresses(void *data)
650*4882a593Smuzhiyun {
651*4882a593Smuzhiyun 	struct ether_header *eth = (struct ether_header *)data;
652*4882a593Smuzhiyun 	struct ether_addr *src_addr = (struct ether_addr *)&eth->ether_shost;
653*4882a593Smuzhiyun 	struct ether_addr *dst_addr = (struct ether_addr *)&eth->ether_dhost;
654*4882a593Smuzhiyun 	struct ether_addr tmp;
655*4882a593Smuzhiyun 
656*4882a593Smuzhiyun 	tmp = *src_addr;
657*4882a593Smuzhiyun 	*src_addr = *dst_addr;
658*4882a593Smuzhiyun 	*dst_addr = tmp;
659*4882a593Smuzhiyun }
660*4882a593Smuzhiyun 
661*4882a593Smuzhiyun static void *
thread_func(void * arg)662*4882a593Smuzhiyun thread_func(void *arg)
663*4882a593Smuzhiyun {
664*4882a593Smuzhiyun 	struct thread_data *t = arg;
665*4882a593Smuzhiyun 	cpu_set_t cpu_cores;
666*4882a593Smuzhiyun 	u32 i;
667*4882a593Smuzhiyun 
668*4882a593Smuzhiyun 	CPU_ZERO(&cpu_cores);
669*4882a593Smuzhiyun 	CPU_SET(t->cpu_core_id, &cpu_cores);
670*4882a593Smuzhiyun 	pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_cores);
671*4882a593Smuzhiyun 
672*4882a593Smuzhiyun 	for (i = 0; !t->quit; i = (i + 1) & (t->n_ports_rx - 1)) {
673*4882a593Smuzhiyun 		struct port *port_rx = t->ports_rx[i];
674*4882a593Smuzhiyun 		struct port *port_tx = t->ports_tx[i];
675*4882a593Smuzhiyun 		struct burst_rx *brx = &t->burst_rx;
676*4882a593Smuzhiyun 		struct burst_tx *btx = &t->burst_tx[i];
677*4882a593Smuzhiyun 		u32 n_pkts, j;
678*4882a593Smuzhiyun 
679*4882a593Smuzhiyun 		/* RX. */
680*4882a593Smuzhiyun 		n_pkts = port_rx_burst(port_rx, brx);
681*4882a593Smuzhiyun 		if (!n_pkts)
682*4882a593Smuzhiyun 			continue;
683*4882a593Smuzhiyun 
684*4882a593Smuzhiyun 		/* Process & TX. */
685*4882a593Smuzhiyun 		for (j = 0; j < n_pkts; j++) {
686*4882a593Smuzhiyun 			u64 addr = xsk_umem__add_offset_to_addr(brx->addr[j]);
687*4882a593Smuzhiyun 			u8 *pkt = xsk_umem__get_data(port_rx->params.bp->addr,
688*4882a593Smuzhiyun 						     addr);
689*4882a593Smuzhiyun 
690*4882a593Smuzhiyun 			swap_mac_addresses(pkt);
691*4882a593Smuzhiyun 
692*4882a593Smuzhiyun 			btx->addr[btx->n_pkts] = brx->addr[j];
693*4882a593Smuzhiyun 			btx->len[btx->n_pkts] = brx->len[j];
694*4882a593Smuzhiyun 			btx->n_pkts++;
695*4882a593Smuzhiyun 
696*4882a593Smuzhiyun 			if (btx->n_pkts == MAX_BURST_TX) {
697*4882a593Smuzhiyun 				port_tx_burst(port_tx, btx);
698*4882a593Smuzhiyun 				btx->n_pkts = 0;
699*4882a593Smuzhiyun 			}
700*4882a593Smuzhiyun 		}
701*4882a593Smuzhiyun 	}
702*4882a593Smuzhiyun 
703*4882a593Smuzhiyun 	return NULL;
704*4882a593Smuzhiyun }
705*4882a593Smuzhiyun 
706*4882a593Smuzhiyun /*
707*4882a593Smuzhiyun  * Process
708*4882a593Smuzhiyun  */
709*4882a593Smuzhiyun static const struct bpool_params bpool_params_default = {
710*4882a593Smuzhiyun 	.n_buffers = 64 * 1024,
711*4882a593Smuzhiyun 	.buffer_size = XSK_UMEM__DEFAULT_FRAME_SIZE,
712*4882a593Smuzhiyun 	.mmap_flags = 0,
713*4882a593Smuzhiyun 
714*4882a593Smuzhiyun 	.n_users_max = 16,
715*4882a593Smuzhiyun 	.n_buffers_per_slab = XSK_RING_PROD__DEFAULT_NUM_DESCS * 2,
716*4882a593Smuzhiyun };
717*4882a593Smuzhiyun 
718*4882a593Smuzhiyun static const struct xsk_umem_config umem_cfg_default = {
719*4882a593Smuzhiyun 	.fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS * 2,
720*4882a593Smuzhiyun 	.comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
721*4882a593Smuzhiyun 	.frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE,
722*4882a593Smuzhiyun 	.frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM,
723*4882a593Smuzhiyun 	.flags = 0,
724*4882a593Smuzhiyun };
725*4882a593Smuzhiyun 
726*4882a593Smuzhiyun static const struct port_params port_params_default = {
727*4882a593Smuzhiyun 	.xsk_cfg = {
728*4882a593Smuzhiyun 		.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
729*4882a593Smuzhiyun 		.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
730*4882a593Smuzhiyun 		.libbpf_flags = 0,
731*4882a593Smuzhiyun 		.xdp_flags = XDP_FLAGS_DRV_MODE,
732*4882a593Smuzhiyun 		.bind_flags = XDP_USE_NEED_WAKEUP | XDP_ZEROCOPY,
733*4882a593Smuzhiyun 	},
734*4882a593Smuzhiyun 
735*4882a593Smuzhiyun 	.bp = NULL,
736*4882a593Smuzhiyun 	.iface = NULL,
737*4882a593Smuzhiyun 	.iface_queue = 0,
738*4882a593Smuzhiyun };
739*4882a593Smuzhiyun 
740*4882a593Smuzhiyun #ifndef MAX_PORTS
741*4882a593Smuzhiyun #define MAX_PORTS 64
742*4882a593Smuzhiyun #endif
743*4882a593Smuzhiyun 
744*4882a593Smuzhiyun #ifndef MAX_THREADS
745*4882a593Smuzhiyun #define MAX_THREADS 64
746*4882a593Smuzhiyun #endif
747*4882a593Smuzhiyun 
748*4882a593Smuzhiyun static struct bpool_params bpool_params;
749*4882a593Smuzhiyun static struct xsk_umem_config umem_cfg;
750*4882a593Smuzhiyun static struct bpool *bp;
751*4882a593Smuzhiyun 
752*4882a593Smuzhiyun static struct port_params port_params[MAX_PORTS];
753*4882a593Smuzhiyun static struct port *ports[MAX_PORTS];
754*4882a593Smuzhiyun static u64 n_pkts_rx[MAX_PORTS];
755*4882a593Smuzhiyun static u64 n_pkts_tx[MAX_PORTS];
756*4882a593Smuzhiyun static int n_ports;
757*4882a593Smuzhiyun 
758*4882a593Smuzhiyun static pthread_t threads[MAX_THREADS];
759*4882a593Smuzhiyun static struct thread_data thread_data[MAX_THREADS];
760*4882a593Smuzhiyun static int n_threads;
761*4882a593Smuzhiyun 
762*4882a593Smuzhiyun static void
print_usage(char * prog_name)763*4882a593Smuzhiyun print_usage(char *prog_name)
764*4882a593Smuzhiyun {
765*4882a593Smuzhiyun 	const char *usage =
766*4882a593Smuzhiyun 		"Usage:\n"
767*4882a593Smuzhiyun 		"\t%s [ -b SIZE ] -c CORE -i INTERFACE [ -q QUEUE ]\n"
768*4882a593Smuzhiyun 		"\n"
769*4882a593Smuzhiyun 		"-c CORE        CPU core to run a packet forwarding thread\n"
770*4882a593Smuzhiyun 		"               on. May be invoked multiple times.\n"
771*4882a593Smuzhiyun 		"\n"
772*4882a593Smuzhiyun 		"-b SIZE        Number of buffers in the buffer pool shared\n"
773*4882a593Smuzhiyun 		"               by all the forwarding threads. Default: %u.\n"
774*4882a593Smuzhiyun 		"\n"
775*4882a593Smuzhiyun 		"-i INTERFACE   Network interface. Each (INTERFACE, QUEUE)\n"
776*4882a593Smuzhiyun 		"               pair specifies one forwarding port. May be\n"
777*4882a593Smuzhiyun 		"               invoked multiple times.\n"
778*4882a593Smuzhiyun 		"\n"
779*4882a593Smuzhiyun 		"-q QUEUE       Network interface queue for RX and TX. Each\n"
780*4882a593Smuzhiyun 		"               (INTERFACE, QUEUE) pair specified one\n"
781*4882a593Smuzhiyun 		"               forwarding port. Default: %u. May be invoked\n"
782*4882a593Smuzhiyun 		"               multiple times.\n"
783*4882a593Smuzhiyun 		"\n";
784*4882a593Smuzhiyun 	printf(usage,
785*4882a593Smuzhiyun 	       prog_name,
786*4882a593Smuzhiyun 	       bpool_params_default.n_buffers,
787*4882a593Smuzhiyun 	       port_params_default.iface_queue);
788*4882a593Smuzhiyun }
789*4882a593Smuzhiyun 
790*4882a593Smuzhiyun static int
parse_args(int argc,char ** argv)791*4882a593Smuzhiyun parse_args(int argc, char **argv)
792*4882a593Smuzhiyun {
793*4882a593Smuzhiyun 	struct option lgopts[] = {
794*4882a593Smuzhiyun 		{ NULL,  0, 0, 0 }
795*4882a593Smuzhiyun 	};
796*4882a593Smuzhiyun 	int opt, option_index;
797*4882a593Smuzhiyun 
798*4882a593Smuzhiyun 	/* Parse the input arguments. */
799*4882a593Smuzhiyun 	for ( ; ;) {
800*4882a593Smuzhiyun 		opt = getopt_long(argc, argv, "c:i:q:", lgopts, &option_index);
801*4882a593Smuzhiyun 		if (opt == EOF)
802*4882a593Smuzhiyun 			break;
803*4882a593Smuzhiyun 
804*4882a593Smuzhiyun 		switch (opt) {
805*4882a593Smuzhiyun 		case 'b':
806*4882a593Smuzhiyun 			bpool_params.n_buffers = atoi(optarg);
807*4882a593Smuzhiyun 			break;
808*4882a593Smuzhiyun 
809*4882a593Smuzhiyun 		case 'c':
810*4882a593Smuzhiyun 			if (n_threads == MAX_THREADS) {
811*4882a593Smuzhiyun 				printf("Max number of threads (%d) reached.\n",
812*4882a593Smuzhiyun 				       MAX_THREADS);
813*4882a593Smuzhiyun 				return -1;
814*4882a593Smuzhiyun 			}
815*4882a593Smuzhiyun 
816*4882a593Smuzhiyun 			thread_data[n_threads].cpu_core_id = atoi(optarg);
817*4882a593Smuzhiyun 			n_threads++;
818*4882a593Smuzhiyun 			break;
819*4882a593Smuzhiyun 
820*4882a593Smuzhiyun 		case 'i':
821*4882a593Smuzhiyun 			if (n_ports == MAX_PORTS) {
822*4882a593Smuzhiyun 				printf("Max number of ports (%d) reached.\n",
823*4882a593Smuzhiyun 				       MAX_PORTS);
824*4882a593Smuzhiyun 				return -1;
825*4882a593Smuzhiyun 			}
826*4882a593Smuzhiyun 
827*4882a593Smuzhiyun 			port_params[n_ports].iface = optarg;
828*4882a593Smuzhiyun 			port_params[n_ports].iface_queue = 0;
829*4882a593Smuzhiyun 			n_ports++;
830*4882a593Smuzhiyun 			break;
831*4882a593Smuzhiyun 
832*4882a593Smuzhiyun 		case 'q':
833*4882a593Smuzhiyun 			if (n_ports == 0) {
834*4882a593Smuzhiyun 				printf("No port specified for queue.\n");
835*4882a593Smuzhiyun 				return -1;
836*4882a593Smuzhiyun 			}
837*4882a593Smuzhiyun 			port_params[n_ports - 1].iface_queue = atoi(optarg);
838*4882a593Smuzhiyun 			break;
839*4882a593Smuzhiyun 
840*4882a593Smuzhiyun 		default:
841*4882a593Smuzhiyun 			printf("Illegal argument.\n");
842*4882a593Smuzhiyun 			return -1;
843*4882a593Smuzhiyun 		}
844*4882a593Smuzhiyun 	}
845*4882a593Smuzhiyun 
846*4882a593Smuzhiyun 	optind = 1; /* reset getopt lib */
847*4882a593Smuzhiyun 
848*4882a593Smuzhiyun 	/* Check the input arguments. */
849*4882a593Smuzhiyun 	if (!n_ports) {
850*4882a593Smuzhiyun 		printf("No ports specified.\n");
851*4882a593Smuzhiyun 		return -1;
852*4882a593Smuzhiyun 	}
853*4882a593Smuzhiyun 
854*4882a593Smuzhiyun 	if (!n_threads) {
855*4882a593Smuzhiyun 		printf("No threads specified.\n");
856*4882a593Smuzhiyun 		return -1;
857*4882a593Smuzhiyun 	}
858*4882a593Smuzhiyun 
859*4882a593Smuzhiyun 	if (n_ports % n_threads) {
860*4882a593Smuzhiyun 		printf("Ports cannot be evenly distributed to threads.\n");
861*4882a593Smuzhiyun 		return -1;
862*4882a593Smuzhiyun 	}
863*4882a593Smuzhiyun 
864*4882a593Smuzhiyun 	return 0;
865*4882a593Smuzhiyun }
866*4882a593Smuzhiyun 
867*4882a593Smuzhiyun static void
print_port(u32 port_id)868*4882a593Smuzhiyun print_port(u32 port_id)
869*4882a593Smuzhiyun {
870*4882a593Smuzhiyun 	struct port *port = ports[port_id];
871*4882a593Smuzhiyun 
872*4882a593Smuzhiyun 	printf("Port %u: interface = %s, queue = %u\n",
873*4882a593Smuzhiyun 	       port_id, port->params.iface, port->params.iface_queue);
874*4882a593Smuzhiyun }
875*4882a593Smuzhiyun 
876*4882a593Smuzhiyun static void
print_thread(u32 thread_id)877*4882a593Smuzhiyun print_thread(u32 thread_id)
878*4882a593Smuzhiyun {
879*4882a593Smuzhiyun 	struct thread_data *t = &thread_data[thread_id];
880*4882a593Smuzhiyun 	u32 i;
881*4882a593Smuzhiyun 
882*4882a593Smuzhiyun 	printf("Thread %u (CPU core %u): ",
883*4882a593Smuzhiyun 	       thread_id, t->cpu_core_id);
884*4882a593Smuzhiyun 
885*4882a593Smuzhiyun 	for (i = 0; i < t->n_ports_rx; i++) {
886*4882a593Smuzhiyun 		struct port *port_rx = t->ports_rx[i];
887*4882a593Smuzhiyun 		struct port *port_tx = t->ports_tx[i];
888*4882a593Smuzhiyun 
889*4882a593Smuzhiyun 		printf("(%s, %u) -> (%s, %u), ",
890*4882a593Smuzhiyun 		       port_rx->params.iface,
891*4882a593Smuzhiyun 		       port_rx->params.iface_queue,
892*4882a593Smuzhiyun 		       port_tx->params.iface,
893*4882a593Smuzhiyun 		       port_tx->params.iface_queue);
894*4882a593Smuzhiyun 	}
895*4882a593Smuzhiyun 
896*4882a593Smuzhiyun 	printf("\n");
897*4882a593Smuzhiyun }
898*4882a593Smuzhiyun 
899*4882a593Smuzhiyun static void
print_port_stats_separator(void)900*4882a593Smuzhiyun print_port_stats_separator(void)
901*4882a593Smuzhiyun {
902*4882a593Smuzhiyun 	printf("+-%4s-+-%12s-+-%13s-+-%12s-+-%13s-+\n",
903*4882a593Smuzhiyun 	       "----",
904*4882a593Smuzhiyun 	       "------------",
905*4882a593Smuzhiyun 	       "-------------",
906*4882a593Smuzhiyun 	       "------------",
907*4882a593Smuzhiyun 	       "-------------");
908*4882a593Smuzhiyun }
909*4882a593Smuzhiyun 
910*4882a593Smuzhiyun static void
print_port_stats_header(void)911*4882a593Smuzhiyun print_port_stats_header(void)
912*4882a593Smuzhiyun {
913*4882a593Smuzhiyun 	print_port_stats_separator();
914*4882a593Smuzhiyun 	printf("| %4s | %12s | %13s | %12s | %13s |\n",
915*4882a593Smuzhiyun 	       "Port",
916*4882a593Smuzhiyun 	       "RX packets",
917*4882a593Smuzhiyun 	       "RX rate (pps)",
918*4882a593Smuzhiyun 	       "TX packets",
919*4882a593Smuzhiyun 	       "TX_rate (pps)");
920*4882a593Smuzhiyun 	print_port_stats_separator();
921*4882a593Smuzhiyun }
922*4882a593Smuzhiyun 
923*4882a593Smuzhiyun static void
print_port_stats_trailer(void)924*4882a593Smuzhiyun print_port_stats_trailer(void)
925*4882a593Smuzhiyun {
926*4882a593Smuzhiyun 	print_port_stats_separator();
927*4882a593Smuzhiyun 	printf("\n");
928*4882a593Smuzhiyun }
929*4882a593Smuzhiyun 
930*4882a593Smuzhiyun static void
print_port_stats(int port_id,u64 ns_diff)931*4882a593Smuzhiyun print_port_stats(int port_id, u64 ns_diff)
932*4882a593Smuzhiyun {
933*4882a593Smuzhiyun 	struct port *p = ports[port_id];
934*4882a593Smuzhiyun 	double rx_pps, tx_pps;
935*4882a593Smuzhiyun 
936*4882a593Smuzhiyun 	rx_pps = (p->n_pkts_rx - n_pkts_rx[port_id]) * 1000000000. / ns_diff;
937*4882a593Smuzhiyun 	tx_pps = (p->n_pkts_tx - n_pkts_tx[port_id]) * 1000000000. / ns_diff;
938*4882a593Smuzhiyun 
939*4882a593Smuzhiyun 	printf("| %4d | %12llu | %13.0f | %12llu | %13.0f |\n",
940*4882a593Smuzhiyun 	       port_id,
941*4882a593Smuzhiyun 	       p->n_pkts_rx,
942*4882a593Smuzhiyun 	       rx_pps,
943*4882a593Smuzhiyun 	       p->n_pkts_tx,
944*4882a593Smuzhiyun 	       tx_pps);
945*4882a593Smuzhiyun 
946*4882a593Smuzhiyun 	n_pkts_rx[port_id] = p->n_pkts_rx;
947*4882a593Smuzhiyun 	n_pkts_tx[port_id] = p->n_pkts_tx;
948*4882a593Smuzhiyun }
949*4882a593Smuzhiyun 
950*4882a593Smuzhiyun static void
print_port_stats_all(u64 ns_diff)951*4882a593Smuzhiyun print_port_stats_all(u64 ns_diff)
952*4882a593Smuzhiyun {
953*4882a593Smuzhiyun 	int i;
954*4882a593Smuzhiyun 
955*4882a593Smuzhiyun 	print_port_stats_header();
956*4882a593Smuzhiyun 	for (i = 0; i < n_ports; i++)
957*4882a593Smuzhiyun 		print_port_stats(i, ns_diff);
958*4882a593Smuzhiyun 	print_port_stats_trailer();
959*4882a593Smuzhiyun }
960*4882a593Smuzhiyun 
961*4882a593Smuzhiyun static int quit;
962*4882a593Smuzhiyun 
963*4882a593Smuzhiyun static void
signal_handler(int sig)964*4882a593Smuzhiyun signal_handler(int sig)
965*4882a593Smuzhiyun {
966*4882a593Smuzhiyun 	quit = 1;
967*4882a593Smuzhiyun }
968*4882a593Smuzhiyun 
remove_xdp_program(void)969*4882a593Smuzhiyun static void remove_xdp_program(void)
970*4882a593Smuzhiyun {
971*4882a593Smuzhiyun 	int i;
972*4882a593Smuzhiyun 
973*4882a593Smuzhiyun 	for (i = 0 ; i < n_ports; i++)
974*4882a593Smuzhiyun 		bpf_set_link_xdp_fd(if_nametoindex(port_params[i].iface), -1,
975*4882a593Smuzhiyun 				    port_params[i].xsk_cfg.xdp_flags);
976*4882a593Smuzhiyun }
977*4882a593Smuzhiyun 
main(int argc,char ** argv)978*4882a593Smuzhiyun int main(int argc, char **argv)
979*4882a593Smuzhiyun {
980*4882a593Smuzhiyun 	struct timespec time;
981*4882a593Smuzhiyun 	u64 ns0;
982*4882a593Smuzhiyun 	int i;
983*4882a593Smuzhiyun 
984*4882a593Smuzhiyun 	/* Parse args. */
985*4882a593Smuzhiyun 	memcpy(&bpool_params, &bpool_params_default,
986*4882a593Smuzhiyun 	       sizeof(struct bpool_params));
987*4882a593Smuzhiyun 	memcpy(&umem_cfg, &umem_cfg_default,
988*4882a593Smuzhiyun 	       sizeof(struct xsk_umem_config));
989*4882a593Smuzhiyun 	for (i = 0; i < MAX_PORTS; i++)
990*4882a593Smuzhiyun 		memcpy(&port_params[i], &port_params_default,
991*4882a593Smuzhiyun 		       sizeof(struct port_params));
992*4882a593Smuzhiyun 
993*4882a593Smuzhiyun 	if (parse_args(argc, argv)) {
994*4882a593Smuzhiyun 		print_usage(argv[0]);
995*4882a593Smuzhiyun 		return -1;
996*4882a593Smuzhiyun 	}
997*4882a593Smuzhiyun 
998*4882a593Smuzhiyun 	/* Buffer pool initialization. */
999*4882a593Smuzhiyun 	bp = bpool_init(&bpool_params, &umem_cfg);
1000*4882a593Smuzhiyun 	if (!bp) {
1001*4882a593Smuzhiyun 		printf("Buffer pool initialization failed.\n");
1002*4882a593Smuzhiyun 		return -1;
1003*4882a593Smuzhiyun 	}
1004*4882a593Smuzhiyun 	printf("Buffer pool created successfully.\n");
1005*4882a593Smuzhiyun 
1006*4882a593Smuzhiyun 	/* Ports initialization. */
1007*4882a593Smuzhiyun 	for (i = 0; i < MAX_PORTS; i++)
1008*4882a593Smuzhiyun 		port_params[i].bp = bp;
1009*4882a593Smuzhiyun 
1010*4882a593Smuzhiyun 	for (i = 0; i < n_ports; i++) {
1011*4882a593Smuzhiyun 		ports[i] = port_init(&port_params[i]);
1012*4882a593Smuzhiyun 		if (!ports[i]) {
1013*4882a593Smuzhiyun 			printf("Port %d initialization failed.\n", i);
1014*4882a593Smuzhiyun 			return -1;
1015*4882a593Smuzhiyun 		}
1016*4882a593Smuzhiyun 		print_port(i);
1017*4882a593Smuzhiyun 	}
1018*4882a593Smuzhiyun 	printf("All ports created successfully.\n");
1019*4882a593Smuzhiyun 
1020*4882a593Smuzhiyun 	/* Threads. */
1021*4882a593Smuzhiyun 	for (i = 0; i < n_threads; i++) {
1022*4882a593Smuzhiyun 		struct thread_data *t = &thread_data[i];
1023*4882a593Smuzhiyun 		u32 n_ports_per_thread = n_ports / n_threads, j;
1024*4882a593Smuzhiyun 
1025*4882a593Smuzhiyun 		for (j = 0; j < n_ports_per_thread; j++) {
1026*4882a593Smuzhiyun 			t->ports_rx[j] = ports[i * n_ports_per_thread + j];
1027*4882a593Smuzhiyun 			t->ports_tx[j] = ports[i * n_ports_per_thread +
1028*4882a593Smuzhiyun 				(j + 1) % n_ports_per_thread];
1029*4882a593Smuzhiyun 		}
1030*4882a593Smuzhiyun 
1031*4882a593Smuzhiyun 		t->n_ports_rx = n_ports_per_thread;
1032*4882a593Smuzhiyun 
1033*4882a593Smuzhiyun 		print_thread(i);
1034*4882a593Smuzhiyun 	}
1035*4882a593Smuzhiyun 
1036*4882a593Smuzhiyun 	for (i = 0; i < n_threads; i++) {
1037*4882a593Smuzhiyun 		int status;
1038*4882a593Smuzhiyun 
1039*4882a593Smuzhiyun 		status = pthread_create(&threads[i],
1040*4882a593Smuzhiyun 					NULL,
1041*4882a593Smuzhiyun 					thread_func,
1042*4882a593Smuzhiyun 					&thread_data[i]);
1043*4882a593Smuzhiyun 		if (status) {
1044*4882a593Smuzhiyun 			printf("Thread %d creation failed.\n", i);
1045*4882a593Smuzhiyun 			return -1;
1046*4882a593Smuzhiyun 		}
1047*4882a593Smuzhiyun 	}
1048*4882a593Smuzhiyun 	printf("All threads created successfully.\n");
1049*4882a593Smuzhiyun 
1050*4882a593Smuzhiyun 	/* Print statistics. */
1051*4882a593Smuzhiyun 	signal(SIGINT, signal_handler);
1052*4882a593Smuzhiyun 	signal(SIGTERM, signal_handler);
1053*4882a593Smuzhiyun 	signal(SIGABRT, signal_handler);
1054*4882a593Smuzhiyun 
1055*4882a593Smuzhiyun 	clock_gettime(CLOCK_MONOTONIC, &time);
1056*4882a593Smuzhiyun 	ns0 = time.tv_sec * 1000000000UL + time.tv_nsec;
1057*4882a593Smuzhiyun 	for ( ; !quit; ) {
1058*4882a593Smuzhiyun 		u64 ns1, ns_diff;
1059*4882a593Smuzhiyun 
1060*4882a593Smuzhiyun 		sleep(1);
1061*4882a593Smuzhiyun 		clock_gettime(CLOCK_MONOTONIC, &time);
1062*4882a593Smuzhiyun 		ns1 = time.tv_sec * 1000000000UL + time.tv_nsec;
1063*4882a593Smuzhiyun 		ns_diff = ns1 - ns0;
1064*4882a593Smuzhiyun 		ns0 = ns1;
1065*4882a593Smuzhiyun 
1066*4882a593Smuzhiyun 		print_port_stats_all(ns_diff);
1067*4882a593Smuzhiyun 	}
1068*4882a593Smuzhiyun 
1069*4882a593Smuzhiyun 	/* Threads completion. */
1070*4882a593Smuzhiyun 	printf("Quit.\n");
1071*4882a593Smuzhiyun 	for (i = 0; i < n_threads; i++)
1072*4882a593Smuzhiyun 		thread_data[i].quit = 1;
1073*4882a593Smuzhiyun 
1074*4882a593Smuzhiyun 	for (i = 0; i < n_threads; i++)
1075*4882a593Smuzhiyun 		pthread_join(threads[i], NULL);
1076*4882a593Smuzhiyun 
1077*4882a593Smuzhiyun 	for (i = 0; i < n_ports; i++)
1078*4882a593Smuzhiyun 		port_free(ports[i]);
1079*4882a593Smuzhiyun 
1080*4882a593Smuzhiyun 	bpool_free(bp);
1081*4882a593Smuzhiyun 
1082*4882a593Smuzhiyun 	remove_xdp_program();
1083*4882a593Smuzhiyun 
1084*4882a593Smuzhiyun 	return 0;
1085*4882a593Smuzhiyun }
1086