API Reference Manual 1.51.0
Loading...
Searching...
No Matches
odp_dmafwd.c
1/* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright (c) 2023-2025 Nokia
3 */
4
16#ifndef _GNU_SOURCE
17#define _GNU_SOURCE
18#endif
19
20#include <inttypes.h>
21#include <stdlib.h>
22#include <signal.h>
23#include <stdio.h>
24#include <unistd.h>
25#include <time.h>
26
27#include <odp_api.h>
28#include <odp/helper/odph_api.h>
29
30#define EXIT_NOT_SUP 2
31#define PROG_NAME "odp_dmafwd"
32#define DELIMITER ","
33
34enum {
35 SW_COPY = 0U,
36 DMA_COPY_EV,
37 DMA_COPY_POLL
38};
39
40#define DEF_CPY_TYPE SW_COPY
41#define DEF_CNT 32768U
42#define DEF_LEN 1024U
43#define DEF_WORKERS 1U
44#define DEF_TIME 0U
45
46#define MAX_IFS 2U
47#define MAX_OUT_QS 32U
48#define MAX_BURST 32U
49#define MAX_WORKERS (ODP_THREAD_COUNT_MAX - 1)
50
51#define DIV_IF(a, b) ((b) > 0U ? ((a) / (b)) : 0U)
52
53ODP_STATIC_ASSERT(MAX_IFS < UINT8_MAX, "Too large maximum interface count");
54ODP_STATIC_ASSERT(MAX_OUT_QS < UINT8_MAX, "Too large maximum output queue count");
55
56typedef struct {
57 uint32_t burst_size;
58 uint32_t num_pkts;
59 uint32_t pkt_len;
60 uint32_t cache_size;
61} dynamic_defs_t;
62
63typedef enum {
64 PRS_OK,
65 PRS_NOK,
66 PRS_TERM,
67 PRS_NOT_SUP
68} parse_result_t;
69
70typedef struct prog_config_s prog_config_t;
71
72typedef struct {
73 uint64_t copy_errs;
74 uint64_t trs;
75 uint64_t start_errs;
76 uint64_t trs_errs;
77 uint64_t buf_alloc_errs;
78 uint64_t compl_alloc_errs;
79 uint64_t pkt_alloc_errs;
80 uint64_t trs_poll_errs;
81 uint64_t trs_polled;
82 uint64_t fwd_pkts;
83 uint64_t discards;
84 uint64_t sched_cc;
85 uint64_t tot_cc;
86 uint64_t sched_rounds;
87 uint64_t pkt_cleanup;
88} stats_t;
89
90typedef struct ODP_ALIGNED_CACHE {
91 prog_config_t *prog_config;
92 odp_dma_t dma_handle;
93 odp_pool_t compl_pool;
94 odp_pool_t copy_pool;
95 odp_pool_t trs_pool;
96 odp_queue_t compl_q;
97 odp_stash_t inflight_stash;
98 stats_t stats;
99 int thr_idx;
100} thread_config_t;
101
102typedef struct pktio_s {
103 odp_pktout_queue_t out_qs[MAX_OUT_QS];
104 char *name;
105 odp_pktio_t handle;
106 uint8_t num_out_qs;
107} pktio_t;
108
109typedef struct {
110 odp_packet_t src_pkts[MAX_BURST];
111 odp_packet_t dst_pkts[MAX_BURST];
112 pktio_t *pktio;
113 int num;
114} transfer_t;
115
116/* Function for initializing transfer structures */
117typedef transfer_t *(*init_fn_t)(odp_dma_transfer_param_t *trs_param,
118 odp_dma_compl_param_t *compl_param, odp_dma_seg_t *src_segs,
119 odp_dma_seg_t *dst_segs, pktio_t *pktio, thread_config_t *config);
120/* Function for starting transfers */
121typedef odp_bool_t (*start_fn_t)(odp_dma_transfer_param_t *trs_param,
122 odp_dma_compl_param_t *compl_param, thread_config_t *config);
123/* Function for setting up packets for copy */
124typedef void (*pkt_fn_t)(odp_packet_t pkts[], int num, pktio_t *pktio, init_fn_t init_fn,
125 start_fn_t start_fn, thread_config_t *config);
126/* Function for draining and tearing down inflight operations */
127typedef void (*drain_fn_t)(thread_config_t *config);
128
129typedef struct prog_config_s {
130 uint8_t pktio_idx_map[ODP_PKTIO_MAX_INDEX + 1];
131 odph_thread_t thread_tbl[MAX_WORKERS];
132 thread_config_t thread_config[MAX_WORKERS];
133 pktio_t pktios[MAX_IFS];
134 dynamic_defs_t dyn_defs;
136 odp_barrier_t init_barrier;
137 odp_barrier_t term_barrier;
138 odp_atomic_u32_t is_running;
139 odp_pool_t pktio_pool;
140 odp_pool_t copy_pool;
141 odp_pool_t trs_pool;
142
143 struct {
144 init_fn_t init_fn;
145 start_fn_t start_fn;
146 pkt_fn_t pkt_fn;
147 drain_fn_t drain_fn;
148 };
149
150 uint64_t inflight_obj_size;
151 uint32_t burst_size;
152 uint32_t num_pkts;
153 uint32_t pkt_len;
154 uint32_t cache_size;
155 uint32_t num_inflight;
156 uint32_t trs_cache_size;
157 uint32_t compl_cache_size;
158 uint32_t stash_cache_size;
159 double time_sec;
160 odp_bool_t seg_free;
161 odp_stash_type_t stash_type;
162 int num_thrs;
163 uint8_t num_ifs;
164 uint8_t copy_type;
165} prog_config_t;
166
167typedef struct {
168 odp_packet_t pkts[MAX_BURST * 2U];
169 pktio_t *pktio;
170 int num;
171} pkt_vec_t;
172
173static prog_config_t *prog_conf;
174
175static void terminate(int signal ODP_UNUSED)
176{
177 odp_atomic_store_u32(&prog_conf->is_running, 0U);
178}
179
180static void init_config(prog_config_t *config)
181{
182 odp_dma_capability_t dma_capa;
183 uint32_t burst_size;
184 odp_pool_capability_t pool_capa;
185 odp_pool_param_t pool_param;
186 thread_config_t *thr;
187
188 memset(config, 0, sizeof(*config));
189
190 if (odp_dma_capability(&dma_capa) == 0) {
191 burst_size = ODPH_MIN(dma_capa.max_src_segs, dma_capa.max_dst_segs);
192 burst_size = ODPH_MIN(burst_size, MAX_BURST);
193 config->dyn_defs.burst_size = burst_size;
194 }
195
196 if (odp_pool_capability(&pool_capa) == 0) {
197 config->dyn_defs.num_pkts = pool_capa.pkt.max_num > 0U ?
198 ODPH_MIN(pool_capa.pkt.max_num, DEF_CNT) : DEF_CNT;
199 config->dyn_defs.pkt_len = pool_capa.pkt.max_len > 0U ?
200 ODPH_MIN(pool_capa.pkt.max_len, DEF_LEN) : DEF_LEN;
201 odp_pool_param_init(&pool_param);
202 config->dyn_defs.cache_size = pool_param.pkt.cache_size;
203 }
204
205 config->pktio_pool = ODP_POOL_INVALID;
206 config->copy_pool = ODP_POOL_INVALID;
207 config->trs_pool = ODP_POOL_INVALID;
208 config->burst_size = config->dyn_defs.burst_size;
209 config->num_pkts = config->dyn_defs.num_pkts;
210 config->pkt_len = config->dyn_defs.pkt_len;
211 config->cache_size = config->dyn_defs.cache_size;
212 config->time_sec = DEF_TIME;
213 config->num_thrs = DEF_WORKERS;
214 config->copy_type = DEF_CPY_TYPE;
215
216 for (int i = 0; i < MAX_WORKERS; ++i) {
217 thr = &config->thread_config[i];
218 thr->dma_handle = ODP_DMA_INVALID;
219 thr->compl_pool = ODP_POOL_INVALID;
220 thr->compl_q = ODP_QUEUE_INVALID;
221 thr->inflight_stash = ODP_STASH_INVALID;
222 }
223
224 for (uint32_t i = 0U; i < MAX_IFS; ++i)
225 config->pktios[i].handle = ODP_PKTIO_INVALID;
226}
227
228static void print_usage(dynamic_defs_t *dyn_defs)
229{
230 printf("\n"
231 "DMA performance tester with packet I/O. Receive and forward packets after\n"
232 "software copy or DMA offload copy.\n"
233 "\n"
234 "Usage: " PROG_NAME " OPTIONS\n"
235 "\n"
236 " E.g. " PROG_NAME " -i eth0\n"
237 " " PROG_NAME " -i eth0 -t 0\n"
238 " " PROG_NAME " -i eth0 -t 1 -b 15 -l 4096 -c 5\n"
239 "\n"
240 "Mandatory OPTIONS:\n"
241 "\n"
242 " -i, --interfaces Ethernet interfaces for packet I/O, comma-separated, no\n"
243 " spaces.\n"
244 "\n"
245 "Optional OPTIONS:\n"
246 "\n"
247 " -t, --copy_type Type of copy. %u by default.\n"
248 " 0: SW\n"
249 " 1: DMA with event completion\n"
250 " 2: DMA with poll completion\n"
251 " -b, --burst_size Copy burst size. This many packets are accumulated before\n"
252 " copy. %u by default.\n"
253 " -n, --num_pkts Number of packet buffers allocated for packet I/O pool.\n"
254 " %u by default.\n"
255 " -l, --pkt_len Maximum size of packet buffers in packet I/O pool. %u by\n"
256 " default.\n"
257 " -c, --worker_count Amount of workers. %u by default.\n"
258 " -C, --cache_size Maximum cache size for pools. %u by default.\n"
259 " -T, --time_sec Time in seconds to run. 0 means infinite. %u by default.\n"
260 " -s, --seg_free Use DMA source segment free offload. Disabled by default.\n"
261 " -h, --help This help.\n"
262 "\n", DEF_CPY_TYPE, dyn_defs->burst_size, dyn_defs->num_pkts, dyn_defs->pkt_len,
263 DEF_WORKERS, dyn_defs->cache_size, DEF_TIME);
264}
265
266static void parse_interfaces(prog_config_t *config, const char *optarg)
267{
268 char *tmp_str = strdup(optarg), *tmp;
269
270 if (tmp_str == NULL)
271 return;
272
273 tmp = strtok(tmp_str, DELIMITER);
274
275 while (tmp && config->num_ifs < MAX_IFS) {
276 config->pktios[config->num_ifs].name = strdup(tmp);
277
278 if (config->pktios[config->num_ifs].name != NULL)
279 ++config->num_ifs;
280
281 tmp = strtok(NULL, DELIMITER);
282 }
283
284 free(tmp_str);
285}
286
287static odp_bool_t get_stash_capa(odp_stash_capability_t *stash_capa, odp_stash_type_t *stash_type)
288{
289 if (odp_stash_capability(stash_capa, ODP_STASH_TYPE_FIFO) == 0) {
290 *stash_type = ODP_STASH_TYPE_FIFO;
291 return true;
292 }
293
294 if (odp_stash_capability(stash_capa, ODP_STASH_TYPE_DEFAULT) == 0) {
295 *stash_type = ODP_STASH_TYPE_DEFAULT;
296 return true;
297 }
298
299 return false;
300}
301
302static parse_result_t check_options(prog_config_t *config)
303{
304 odp_dma_capability_t dma_capa;
305 uint32_t burst_size;
306 odp_stash_capability_t stash_capa;
307 const uint64_t obj_size = sizeof(odp_dma_transfer_id_t);
308 uint64_t max_num;
309 odp_pool_capability_t pool_capa;
310
311 if (config->num_ifs == 0U) {
312 ODPH_ERR("Invalid number of interfaces: %u (min: 1, max: %u)\n", config->num_ifs,
313 MAX_IFS);
314 return PRS_NOK;
315 }
316
317 if (config->copy_type != SW_COPY && config->copy_type != DMA_COPY_EV &&
318 config->copy_type != DMA_COPY_POLL) {
319 ODPH_ERR("Invalid copy type: %u\n", config->copy_type);
320 return PRS_NOK;
321 }
322
323 if (config->num_thrs <= 0 || config->num_thrs > MAX_WORKERS) {
324 ODPH_ERR("Invalid worker count: %d (min: 1, max: %d)\n", config->num_thrs,
325 MAX_WORKERS);
326 return PRS_NOK;
327 }
328
329 if (odp_dma_capability(&dma_capa) < 0) {
330 ODPH_ERR("Error querying DMA capabilities\n");
331 return PRS_NOK;
332 }
333
334 if (config->seg_free && dma_capa.src_seg_free == 0) {
335 ODPH_ERR("Unsupported source segment free by DMA\n");
336 return PRS_NOT_SUP;
337 }
338
339 if ((uint32_t)config->num_thrs > dma_capa.max_sessions) {
340 ODPH_ERR("Unsupported DMA session count: %d (max: %u)\n", config->num_thrs,
341 dma_capa.max_sessions);
342 return PRS_NOT_SUP;
343 }
344
345 burst_size = ODPH_MIN(dma_capa.max_src_segs, dma_capa.max_dst_segs);
346 burst_size = ODPH_MIN(burst_size, MAX_BURST);
347
348 if (config->burst_size == 0U || config->burst_size > burst_size) {
349 ODPH_ERR("Invalid segment count for DMA: %u (min: 1, max: %u)\n",
350 config->burst_size, burst_size);
351 return PRS_NOK;
352 }
353
354 if (config->pkt_len > dma_capa.max_seg_len) {
355 ODPH_ERR("Invalid packet length for DMA: %u (max: %u)\n", config->pkt_len,
356 dma_capa.max_seg_len);
357 return PRS_NOK;
358 }
359
360 config->num_inflight = dma_capa.max_transfers;
361
362 if (odp_pool_capability(&pool_capa) < 0) {
363 ODPH_ERR("Error querying pool capabilities\n");
364 return PRS_NOK;
365 }
366
367 if (config->cache_size < pool_capa.pkt.min_cache_size ||
368 config->cache_size > pool_capa.pkt.max_cache_size) {
369 ODPH_ERR("Invalid pool cache size: %u (min: %u, max: %u)\n", config->cache_size,
370 pool_capa.pkt.min_cache_size, pool_capa.pkt.max_cache_size);
371 return PRS_NOK;
372 }
373
374 if (config->copy_type != SW_COPY)
375 config->trs_cache_size = ODPH_MIN(ODPH_MAX(config->cache_size,
376 pool_capa.buf.min_cache_size),
377 pool_capa.buf.max_cache_size);
378
379 if (config->copy_type == DMA_COPY_EV) {
380 if ((dma_capa.compl_mode_mask & ODP_DMA_COMPL_EVENT) == 0U ||
381 !dma_capa.queue_type_sched) {
382 ODPH_ERR("Unsupported DMA completion mode: event (mode support: %x, "
383 "scheduled queue support: %u)\n", dma_capa.compl_mode_mask,
384 dma_capa.queue_type_sched);
385 return PRS_NOT_SUP;
386 }
387
388 if ((uint32_t)config->num_thrs > dma_capa.pool.max_pools) {
389 ODPH_ERR("Invalid amount of DMA completion pools: %d (max: %u)\n",
390 config->num_thrs, dma_capa.pool.max_pools);
391 return PRS_NOK;
392 }
393
394 if (config->num_inflight > dma_capa.pool.max_num) {
395 ODPH_ERR("Invalid amount of DMA completion events: %u (max: %u)\n",
396 config->num_inflight, dma_capa.pool.max_num);
397 return PRS_NOK;
398 }
399
400 config->compl_cache_size = ODPH_MIN(ODPH_MAX(config->cache_size,
401 dma_capa.pool.min_cache_size),
402 dma_capa.pool.max_cache_size);
403 } else if (config->copy_type == DMA_COPY_POLL) {
404 if ((dma_capa.compl_mode_mask & ODP_DMA_COMPL_POLL) == 0U) {
405 ODPH_ERR("Unsupported DMA completion mode: poll (mode support: %x)\n",
406 dma_capa.compl_mode_mask);
407 return PRS_NOT_SUP;
408 }
409
410 if (!get_stash_capa(&stash_capa, &config->stash_type)) {
411 ODPH_ERR("Error querying stash capabilities\n");
412 return PRS_NOK;
413 }
414
415 if ((uint32_t)config->num_thrs > stash_capa.max_stashes) {
416 ODPH_ERR("Invalid amount of stashes: %d (max: %u)\n", config->num_thrs,
417 stash_capa.max_stashes);
418 return PRS_NOK;
419 }
420
421 if (obj_size == sizeof(uint8_t)) {
422 max_num = stash_capa.max_num.u8;
423 } else if (obj_size == sizeof(uint16_t)) {
424 max_num = stash_capa.max_num.u16;
425 } else if (obj_size <= sizeof(uint32_t)) {
426 max_num = stash_capa.max_num.u32;
427 } else if (obj_size <= sizeof(uint64_t)) {
428 max_num = stash_capa.max_num.u64;
429 } else if (obj_size <= sizeof(odp_u128_t)) {
430 max_num = stash_capa.max_num.u128;
431 } else {
432 ODPH_ERR("Invalid stash object size: %" PRIu64 "\n", obj_size);
433 return PRS_NOK;
434 }
435
436 if (config->num_inflight > max_num) {
437 ODPH_ERR("Invalid stash size: %u (max: %" PRIu64 ")\n",
438 config->num_inflight, max_num);
439 return PRS_NOK;
440 }
441
442 config->inflight_obj_size = obj_size;
443 config->stash_cache_size = ODPH_MIN(config->cache_size, stash_capa.max_cache_size);
444 }
445
446 if (config->num_pkts == 0U ||
447 (pool_capa.pkt.max_num > 0U && config->num_pkts > pool_capa.pkt.max_num)) {
448 ODPH_ERR("Invalid pool packet count: %u (min: 1, max: %u)\n", config->num_pkts,
449 pool_capa.pkt.max_num);
450 return PRS_NOK;
451 }
452
453 if (config->pkt_len == 0U ||
454 (pool_capa.pkt.max_len > 0U && config->pkt_len > pool_capa.pkt.max_len)) {
455 ODPH_ERR("Invalid pool packet length: %u (min: 1, max: %u)\n", config->pkt_len,
456 pool_capa.pkt.max_len);
457 return PRS_NOK;
458 }
459
460 if (config->num_inflight > pool_capa.buf.max_num) {
461 ODPH_ERR("Invalid pool buffer count: %u (max: %u)\n", config->num_inflight,
462 pool_capa.buf.max_num);
463 return PRS_NOK;
464 }
465
466 return PRS_OK;
467}
468
469static parse_result_t parse_options(int argc, char **argv, prog_config_t *config)
470{
471 int opt;
472
473 static const struct option longopts[] = {
474 { "interfaces", required_argument, NULL, 'i' },
475 { "copy_type", required_argument, NULL, 't' },
476 { "burst_size", required_argument, NULL, 'b' },
477 { "num_pkts", required_argument, NULL, 'n' },
478 { "pkt_len", required_argument, NULL, 'l' },
479 { "worker_count", required_argument, NULL, 'c' },
480 { "cache_size", required_argument, NULL, 'C' },
481 { "time_sec", required_argument, NULL, 'T' },
482 { "seg_free", no_argument, NULL, 's' },
483 { "help", no_argument, NULL, 'h' },
484 { NULL, 0, NULL, 0 }
485 };
486
487 static const char *shortopts = "i:t:b:n:l:c:C:T:sh";
488
489 init_config(config);
490
491 while (1) {
492 opt = getopt_long(argc, argv, shortopts, longopts, NULL);
493
494 if (opt == -1)
495 break;
496
497 switch (opt) {
498 case 'i':
499 parse_interfaces(config, optarg);
500 break;
501 case 't':
502 config->copy_type = atoi(optarg);
503 break;
504 case 'b':
505 config->burst_size = atoi(optarg);
506 break;
507 case 'n':
508 config->num_pkts = atoi(optarg);
509 break;
510 case 'l':
511 config->pkt_len = atoi(optarg);
512 break;
513 case 'c':
514 config->num_thrs = atoi(optarg);
515 break;
516 case 'C':
517 config->cache_size = atoi(optarg);
518 break;
519 case 'T':
520 config->time_sec = atof(optarg);
521 break;
522 case 's':
523 config->seg_free = 1;
524 break;
525 case 'h':
526 print_usage(&config->dyn_defs);
527 return PRS_TERM;
528 case '?':
529 default:
530 print_usage(&config->dyn_defs);
531 return PRS_NOK;
532 }
533 }
534
535 return check_options(config);
536}
537
538static parse_result_t setup_program(int argc, char **argv, prog_config_t *config)
539{
540 struct sigaction action = { .sa_handler = terminate };
541
542 if (sigemptyset(&action.sa_mask) == -1 || sigaddset(&action.sa_mask, SIGINT) == -1 ||
543 sigaddset(&action.sa_mask, SIGTERM) == -1 ||
544 sigaddset(&action.sa_mask, SIGHUP) == -1 || sigaction(SIGINT, &action, NULL) == -1 ||
545 sigaction(SIGTERM, &action, NULL) == -1 || sigaction(SIGHUP, &action, NULL) == -1) {
546 ODPH_ERR("Error installing signal handler\n");
547 return PRS_NOK;
548 }
549
550 return parse_options(argc, argv, config);
551}
552
553static inline int send_packets(odp_pktout_queue_t queue, odp_packet_t pkts[], int num)
554{
555 int ret = odp_pktout_send(queue, pkts, num);
556
557 if (odp_unlikely(ret < num)) {
558 ret = ret < 0 ? 0 : ret;
559 odp_packet_free_multi(&pkts[ret], num - ret);
560 }
561
562 return ret;
563}
564
565static void sw_copy_and_send_packets(odp_packet_t pkts[], int num, pktio_t *pktio,
566 init_fn_t init_fn ODP_UNUSED, start_fn_t start_fn ODP_UNUSED,
567 thread_config_t *config)
568{
569 odp_packet_t old_pkt, new_pkt;
570 odp_pool_t copy_pool = config->copy_pool;
571 odp_packet_t out_pkts[num];
572 int num_out_pkts = 0, num_sent;
573 stats_t *stats = &config->stats;
574
575 for (int i = 0; i < num; ++i) {
576 old_pkt = pkts[i];
577 new_pkt = odp_packet_copy(old_pkt, copy_pool);
578
579 if (new_pkt != ODP_PACKET_INVALID)
580 out_pkts[num_out_pkts++] = new_pkt;
581 else
582 ++stats->copy_errs;
583
584 odp_packet_free(old_pkt);
585 }
586
587 if (num_out_pkts > 0) {
588 num_sent = send_packets(pktio->out_qs[config->thr_idx % pktio->num_out_qs],
589 out_pkts, num_out_pkts);
590 stats->fwd_pkts += num_sent;
591 stats->discards += num_out_pkts - num_sent;
592 }
593}
594
595static transfer_t *init_dma_ev_trs(odp_dma_transfer_param_t *trs_param,
596 odp_dma_compl_param_t *compl_param, odp_dma_seg_t *src_segs,
597 odp_dma_seg_t *dst_segs, pktio_t *pktio,
598 thread_config_t *config)
599{
600 odp_buffer_t buf;
601 stats_t *stats = &config->stats;
602 transfer_t *trs;
603 odp_dma_compl_t c_ev;
604
605 buf = odp_buffer_alloc(config->trs_pool);
606
607 if (odp_unlikely(buf == ODP_BUFFER_INVALID)) {
608 ++stats->buf_alloc_errs;
609 return NULL;
610 }
611
612 trs = (transfer_t *)odp_buffer_addr(buf);
613 trs->num = 0;
614 trs->pktio = pktio;
617 trs_param->num_src = 0U;
618 trs_param->num_dst = 0U;
619 trs_param->src_seg = src_segs;
620 trs_param->dst_seg = dst_segs;
621
622 if (config->prog_config->seg_free) {
623 trs_param->opts.seg_free = 1;
624 /* Always unique packets in a transfer, optimize */
625 trs_param->opts.unique_src_segs = 1;
626 }
627
628 compl_param->compl_mode = ODP_DMA_COMPL_EVENT;
629 c_ev = odp_dma_compl_alloc(config->compl_pool);
630
631 if (odp_unlikely(c_ev == ODP_DMA_COMPL_INVALID)) {
632 odp_buffer_free(buf);
633 ++stats->compl_alloc_errs;
634 return NULL;
635 }
636
637 compl_param->event = odp_dma_compl_to_event(c_ev);
638 compl_param->queue = config->compl_q;
639 compl_param->user_ptr = buf;
640 memset(src_segs, 0, sizeof(*src_segs) * MAX_BURST);
641 memset(dst_segs, 0, sizeof(*dst_segs) * MAX_BURST);
642
643 return trs;
644}
645
646static transfer_t *init_dma_poll_trs(odp_dma_transfer_param_t *trs_param,
647 odp_dma_compl_param_t *compl_param, odp_dma_seg_t *src_segs,
648 odp_dma_seg_t *dst_segs, pktio_t *pktio,
649 thread_config_t *config)
650{
651 odp_buffer_t buf;
652 stats_t *stats = &config->stats;
653 transfer_t *trs;
654
655 buf = odp_buffer_alloc(config->trs_pool);
656
657 if (odp_unlikely(buf == ODP_BUFFER_INVALID)) {
658 ++stats->buf_alloc_errs;
659 return NULL;
660 }
661
662 trs = (transfer_t *)odp_buffer_addr(buf);
663 trs->num = 0;
664 trs->pktio = pktio;
667 trs_param->num_src = 0U;
668 trs_param->num_dst = 0U;
669 trs_param->src_seg = src_segs;
670 trs_param->dst_seg = dst_segs;
671
672 if (config->prog_config->seg_free) {
673 trs_param->opts.seg_free = 1;
674 /* Always unique packets in a transfer, optimize */
675 trs_param->opts.unique_src_segs = 1;
676 }
677
678 compl_param->compl_mode = ODP_DMA_COMPL_POLL;
679 compl_param->transfer_id = odp_dma_transfer_id_alloc(config->dma_handle);
680
682 odp_buffer_free(buf);
683 ++stats->compl_alloc_errs;
684 return NULL;
685 }
686
687 compl_param->user_ptr = buf;
688 memset(src_segs, 0, sizeof(*src_segs) * MAX_BURST);
689 memset(dst_segs, 0, sizeof(*dst_segs) * MAX_BURST);
690
691 return trs;
692}
693
694static odp_bool_t start_dma_ev_trs(odp_dma_transfer_param_t *trs_param,
695 odp_dma_compl_param_t *compl_param, thread_config_t *config)
696{
697 const int ret = odp_dma_transfer_start(config->dma_handle, trs_param, compl_param);
698
699 if (odp_unlikely(ret <= 0)) {
700 odp_buffer_free(compl_param->user_ptr);
701 odp_event_free(compl_param->event);
702 return false;
703 }
704
705 return true;
706}
707
708static odp_bool_t start_dma_poll_trs(odp_dma_transfer_param_t *trs_param,
709 odp_dma_compl_param_t *compl_param, thread_config_t *config)
710{
711 const int ret = odp_dma_transfer_start(config->dma_handle, trs_param, compl_param);
712
713 if (odp_unlikely(ret <= 0)) {
714 odp_buffer_free(compl_param->user_ptr);
715 odp_dma_transfer_id_free(config->dma_handle, compl_param->transfer_id);
716 return false;
717 }
718
719 if (odp_unlikely(odp_stash_put(config->inflight_stash, &compl_param->transfer_id, 1) != 1))
720 /* Should not happen, but make it visible if it somehow does */
721 ODPH_ABORT("DMA inflight transfer stash overflow, aborting");
722
723 return true;
724}
725
726static void dma_copy(odp_packet_t pkts[], int num, pktio_t *pktio, init_fn_t init_fn,
727 start_fn_t start_fn, thread_config_t *config)
728{
729 odp_dma_transfer_param_t trs_param;
730 odp_dma_compl_param_t compl_param;
731 odp_packet_t pkt;
732 transfer_t *trs = NULL;
733 odp_dma_seg_t src_segs[MAX_BURST], dst_segs[MAX_BURST];
734 uint32_t num_segs = 0U, pkt_len;
735 odp_pool_t copy_pool = config->copy_pool;
736 stats_t *stats = &config->stats;
737
738 odp_dma_transfer_param_init(&trs_param);
739 odp_dma_compl_param_init(&compl_param);
740
741 for (int i = 0; i < num; ++i) {
742 pkt = pkts[i];
743
744 if (odp_unlikely(trs == NULL)) {
745 trs = init_fn(&trs_param, &compl_param, src_segs, dst_segs, pktio, config);
746
747 if (trs == NULL) {
748 odp_packet_free(pkt);
749 continue;
750 }
751 }
752
753 pkt_len = odp_packet_len(pkt);
754 src_segs[num_segs].packet = pkt;
755 src_segs[num_segs].len = pkt_len;
756 dst_segs[num_segs].packet = odp_packet_alloc(copy_pool, pkt_len);
757
758 if (odp_unlikely(dst_segs[num_segs].packet == ODP_PACKET_INVALID)) {
759 odp_packet_free(pkt);
760 ++stats->pkt_alloc_errs;
761 continue;
762 }
763
764 dst_segs[num_segs].len = pkt_len;
765 trs->src_pkts[num_segs] = src_segs[num_segs].packet;
766 trs->dst_pkts[num_segs] = dst_segs[num_segs].packet;
767 ++trs->num;
768 ++trs_param.num_src;
769 ++trs_param.num_dst;
770 ++num_segs;
771 }
772
773 if (num_segs > 0U)
774 if (odp_unlikely(!start_fn(&trs_param, &compl_param, config))) {
775 odp_packet_free_multi(trs->src_pkts, trs->num);
776 odp_packet_free_multi(trs->dst_pkts, trs->num);
777 ++stats->start_errs;
778 }
779}
780
781static void drain_events(thread_config_t *config ODP_UNUSED)
782{
783 odp_event_t ev;
784 odp_event_type_t type;
786 odp_buffer_t buf;
787 transfer_t *trs;
788 const odp_bool_t seg_free = config->prog_config->seg_free;
789
790 while (true) {
792
793 if (ev == ODP_EVENT_INVALID)
794 break;
795
796 type = odp_event_type(ev);
797
798 if (type == ODP_EVENT_DMA_COMPL) {
799 memset(&res, 0, sizeof(res));
801 buf = (odp_buffer_t)res.user_ptr;
802 trs = (transfer_t *)odp_buffer_addr(buf);
803
804 if (!seg_free)
805 odp_packet_free_multi(trs->src_pkts, trs->num);
806
807 odp_packet_free_multi(trs->dst_pkts, trs->num);
808 odp_buffer_free(buf);
809 }
810
811 odp_event_free(ev);
812 }
813}
814
815static void drain_polled(thread_config_t *config)
816{
819 int ret;
820 odp_buffer_t buf;
821 transfer_t *trs;
822 const odp_bool_t seg_free = config->prog_config->seg_free;
823
824 while (true) {
825 if (odp_stash_get(config->inflight_stash, &id, 1) != 1)
826 break;
827
828 memset(&res, 0, sizeof(res));
829
830 do {
831 ret = odp_dma_transfer_done(config->dma_handle, id, &res);
832 } while (ret == 0);
833
834 odp_dma_transfer_id_free(config->dma_handle, id);
835
836 if (ret < 0)
837 continue;
838
839 buf = (odp_buffer_t)res.user_ptr;
840 trs = (transfer_t *)odp_buffer_addr(buf);
841
842 if (!seg_free)
843 odp_packet_free_multi(trs->src_pkts, trs->num);
844
845 odp_packet_free_multi(trs->dst_pkts, trs->num);
846 odp_buffer_free(buf);
847 }
848}
849
850static odp_bool_t setup_copy(prog_config_t *config)
851{
852 odp_pool_param_t pool_param;
853 thread_config_t *thr;
854 const odp_dma_param_t dma_param = {
856 .type = ODP_DMA_TYPE_COPY,
857 .compl_mode_mask = ODP_DMA_COMPL_EVENT | ODP_DMA_COMPL_POLL,
858 .mt_mode = ODP_DMA_MT_SERIAL,
859 .order = ODP_DMA_ORDER_NONE };
860 odp_dma_pool_param_t compl_pool_param;
861 odp_queue_param_t queue_param;
862 odp_stash_param_t stash_param;
863
864 odp_pool_param_init(&pool_param);
865 pool_param.pkt.seg_len = config->pkt_len;
866 pool_param.pkt.len = config->pkt_len;
867 pool_param.pkt.num = config->num_pkts;
868 pool_param.pkt.cache_size = config->cache_size;
869 pool_param.type = ODP_POOL_PACKET;
870 config->copy_pool = odp_pool_create(PROG_NAME "_copy", &pool_param);
871
872 if (config->copy_pool == ODP_POOL_INVALID) {
873 ODPH_ERR("Error creating packet copy pool\n");
874 return false;
875 }
876
877 if (config->copy_type == SW_COPY) {
878 config->pkt_fn = sw_copy_and_send_packets;
879
880 for (int i = 0; i < config->num_thrs; ++i)
881 config->thread_config[i].copy_pool = config->copy_pool;
882
883 return true;
884 }
885
886 pool_param.buf.num = config->num_inflight;
887 pool_param.buf.size = sizeof(transfer_t);
888 pool_param.buf.cache_size = config->trs_cache_size;
889 pool_param.type = ODP_POOL_BUFFER;
890 config->trs_pool = odp_pool_create(PROG_NAME "_dma_trs", &pool_param);
891
892 if (config->trs_pool == ODP_POOL_INVALID) {
893 ODPH_ERR("Error creating DMA transfer tracking pool\n");
894 return false;
895 }
896
897 for (int i = 0; i < config->num_thrs; ++i) {
898 thr = &config->thread_config[i];
899 thr->copy_pool = config->copy_pool;
900 thr->trs_pool = config->trs_pool;
901 thr->dma_handle = odp_dma_create(PROG_NAME "_dma", &dma_param);
902
903 if (thr->dma_handle == ODP_DMA_INVALID) {
904 ODPH_ERR("Error creating DMA session\n");
905 return false;
906 }
907
908 if (config->copy_type == DMA_COPY_EV) {
909 odp_dma_pool_param_init(&compl_pool_param);
910 compl_pool_param.num = config->num_inflight;
911 compl_pool_param.cache_size = config->compl_cache_size;
912 thr->compl_pool = odp_dma_pool_create(PROG_NAME "_dma_compl",
913 &compl_pool_param);
914
915 if (thr->compl_pool == ODP_POOL_INVALID) {
916 ODPH_ERR("Error creating DMA event completion pool\n");
917 return false;
918 }
919
920 odp_queue_param_init(&queue_param);
921 queue_param.type = ODP_QUEUE_TYPE_SCHED;
922 queue_param.sched.sync = ODP_SCHED_SYNC_PARALLEL;
923 queue_param.sched.prio = odp_schedule_max_prio();
924 thr->compl_q = odp_queue_create(PROG_NAME "_dma_compl", &queue_param);
925
926 if (thr->compl_q == ODP_QUEUE_INVALID) {
927 ODPH_ERR("Error creating DMA completion queue\n");
928 return false;
929 }
930
931 config->init_fn = init_dma_ev_trs;
932 config->start_fn = start_dma_ev_trs;
933 config->drain_fn = drain_events;
934 } else {
935 odp_stash_param_init(&stash_param);
936 stash_param.type = config->stash_type;
937 stash_param.put_mode = ODP_STASH_OP_LOCAL;
938 stash_param.get_mode = ODP_STASH_OP_LOCAL;
939 stash_param.num_obj = config->num_inflight;
940 stash_param.obj_size = config->inflight_obj_size;
941 stash_param.cache_size = config->stash_cache_size;
942 thr->inflight_stash = odp_stash_create("_dma_inflight", &stash_param);
943
944 if (thr->inflight_stash == ODP_STASH_INVALID) {
945 ODPH_ERR("Error creating DMA inflight transfer stash\n");
946 return false;
947 }
948
949 config->init_fn = init_dma_poll_trs;
950 config->start_fn = start_dma_poll_trs;
951 config->drain_fn = drain_polled;
952 }
953 }
954
955 config->pkt_fn = dma_copy;
956
957 return true;
958}
959
960static odp_bool_t setup_pktios(prog_config_t *config)
961{
962 odp_pool_param_t pool_param;
963 pktio_t *pktio;
964 odp_pktio_param_t pktio_param;
966 uint32_t num_input_qs, num_output_qs;
967 odp_pktin_queue_param_t pktin_param;
968 odp_pktout_queue_param_t pktout_param;
969
970 odp_pool_param_init(&pool_param);
971 pool_param.pkt.seg_len = config->pkt_len;
972 pool_param.pkt.len = config->pkt_len;
973 pool_param.pkt.num = config->num_pkts;
974 pool_param.pkt.cache_size = config->cache_size;
975 pool_param.type = ODP_POOL_PACKET;
976 config->pktio_pool = odp_pool_create(PROG_NAME, &pool_param);
977
978 if (config->pktio_pool == ODP_POOL_INVALID) {
979 ODPH_ERR("Error creating packet I/O pool\n");
980 return false;
981 }
982
983 for (uint32_t i = 0U; i < config->num_ifs; ++i) {
984 pktio = &config->pktios[i];
985 odp_pktio_param_init(&pktio_param);
986 pktio_param.in_mode = ODP_PKTIN_MODE_SCHED;
987 pktio_param.out_mode = ODP_PKTOUT_MODE_DIRECT;
988 pktio->handle = odp_pktio_open(pktio->name, config->pktio_pool, &pktio_param);
989
990 if (pktio->handle == ODP_PKTIO_INVALID) {
991 ODPH_ERR("Error opening packet I/O (%s)\n", pktio->name);
992 return false;
993 }
994
995 config->pktio_idx_map[odp_pktio_index(pktio->handle)] = i;
996
997 if (odp_pktio_capability(pktio->handle, &capa) < 0) {
998 ODPH_ERR("Error querying packet I/O capabilities (%s)\n", pktio->name);
999 return false;
1000 }
1001
1002 num_input_qs = ODPH_MIN((uint32_t)config->num_thrs, capa.max_input_queues);
1003 num_output_qs = ODPH_MIN((uint32_t)config->num_thrs, capa.max_output_queues);
1004 num_output_qs = ODPH_MIN(num_output_qs, MAX_OUT_QS);
1005 odp_pktin_queue_param_init(&pktin_param);
1006
1007 if (num_input_qs > 1) {
1008 pktin_param.hash_enable = true;
1009 pktin_param.hash_proto.proto.ipv4 = 1U;
1010 }
1011
1012 pktin_param.num_queues = num_input_qs;
1014
1015 if (odp_pktin_queue_config(pktio->handle, &pktin_param) < 0) {
1016 ODPH_ERR("Error configuring packet I/O input queues (%s)\n", pktio->name);
1017 return false;
1018 }
1019
1020 odp_pktout_queue_param_init(&pktout_param);
1021
1022 if (num_output_qs == (uint32_t)config->num_thrs)
1023 pktout_param.op_mode = ODP_PKTIO_OP_MT_UNSAFE;
1024
1025 pktout_param.num_queues = num_output_qs;
1026 pktio->num_out_qs = num_output_qs;
1027
1028 if (odp_pktout_queue_config(pktio->handle, &pktout_param) < 0) {
1029 ODPH_ERR("Error configuring packet I/O output queues (%s)\n", pktio->name);
1030 return false;
1031 }
1032
1033 if (odp_pktout_queue(pktio->handle, pktio->out_qs, num_output_qs) !=
1034 (int)num_output_qs) {
1035 ODPH_ERR("Error querying packet I/O output queues (%s)\n", pktio->name);
1036 return false;
1037 }
1038
1039 if (odp_pktio_start(pktio->handle) < 0) {
1040 ODPH_ERR("Error starting packet I/O (%s)\n", pktio->name);
1041 return false;
1042 }
1043 }
1044
1045 return true;
1046}
1047
1048static inline void send_dma_poll_trs_pkts(int burst_size, thread_config_t *config)
1049{
1050 odp_stash_t stash_handle = config->inflight_stash;
1051 odp_dma_transfer_id_t ids[burst_size], id;
1052 int32_t num;
1053 odp_dma_t dma_handle = config->dma_handle;
1054 odp_dma_result_t res;
1055 int ret;
1056 odp_buffer_t buf;
1057 transfer_t *trs;
1058 pktio_t *pktio;
1059 int num_sent;
1060 stats_t *stats = &config->stats;
1061 const odp_bool_t seg_free = config->prog_config->seg_free;
1062
1063 while (true) {
1064 num = odp_stash_get(stash_handle, &ids, burst_size);
1065
1066 if (num <= 0)
1067 break;
1068
1069 for (int32_t i = 0; i < num; ++i) {
1070 id = ids[i];
1071 ret = odp_dma_transfer_done(dma_handle, id, &res);
1072
1073 if (ret == 0) {
1074 if (odp_unlikely(odp_stash_put(stash_handle, &id, 1) != 1))
1075 /* Should not happen, but make it visible if it somehow
1076 * does */
1077 ODPH_ABORT("DMA inflight transfer stash overflow,"
1078 " aborting");
1079
1080 ++stats->trs_polled;
1081 continue;
1082 }
1083
1084 odp_dma_transfer_id_free(dma_handle, id);
1085
1086 if (ret < 0) {
1087 ++stats->trs_poll_errs;
1088 continue;
1089 }
1090
1091 buf = (odp_buffer_t)res.user_ptr;
1092 trs = (transfer_t *)odp_buffer_addr(buf);
1093
1094 if (res.success) {
1095 pktio = trs->pktio;
1096 num_sent = send_packets(pktio->out_qs[config->thr_idx %
1097 pktio->num_out_qs],
1098 trs->dst_pkts, trs->num);
1099 ++stats->trs;
1100 stats->fwd_pkts += num_sent;
1101 stats->discards += trs->num - num_sent;
1102
1103 if (!seg_free)
1104 odp_packet_free_multi(trs->src_pkts, trs->num);
1105 } else {
1106 odp_packet_free_multi(trs->src_pkts, trs->num);
1107 odp_packet_free_multi(trs->dst_pkts, trs->num);
1108 ++stats->trs_errs;
1109 }
1110
1111 odp_buffer_free(buf);
1112 }
1113 }
1114}
1115
1116static inline void send_dma_ev_trs_pkts(odp_dma_compl_t compl_ev, thread_config_t *config)
1117{
1118 odp_dma_result_t res;
1119 odp_buffer_t buf;
1120 transfer_t *trs;
1121 pktio_t *pktio;
1122 int num_sent;
1123 stats_t *stats = &config->stats;
1124 const odp_bool_t seg_free = config->prog_config->seg_free;
1125
1126 memset(&res, 0, sizeof(res));
1127 odp_dma_compl_result(compl_ev, &res);
1128 buf = (odp_buffer_t)res.user_ptr;
1129 trs = (transfer_t *)odp_buffer_addr(buf);
1130
1131 if (res.success) {
1132 pktio = trs->pktio;
1133 num_sent = send_packets(pktio->out_qs[config->thr_idx % pktio->num_out_qs],
1134 trs->dst_pkts, trs->num);
1135 ++stats->trs;
1136 stats->fwd_pkts += num_sent;
1137 stats->discards += trs->num - num_sent;
1138
1139 if (!seg_free)
1140 odp_packet_free_multi(trs->src_pkts, trs->num);
1141 } else {
1142 odp_packet_free_multi(trs->src_pkts, trs->num);
1143 odp_packet_free_multi(trs->dst_pkts, trs->num);
1144 ++stats->trs_errs;
1145 }
1146
1147 odp_buffer_free(buf);
1148 odp_dma_compl_free(compl_ev);
1149}
1150
1151static inline void push_packet(odp_packet_t pkt, pkt_vec_t pkt_vecs[], uint8_t *pktio_idx_map)
1152{
1153 uint8_t idx = pktio_idx_map[odp_packet_input_index(pkt)];
1154 pkt_vec_t *pkt_vec = &pkt_vecs[idx];
1155
1156 pkt_vec->pkts[pkt_vec->num++] = pkt;
1157}
1158
1159static inline void pop_packets(pkt_vec_t *pkt_vec, int num_procd)
1160{
1161 pkt_vec->num -= num_procd;
1162
1163 for (int i = 0, j = num_procd; i < pkt_vec->num; ++i, ++j)
1164 pkt_vec->pkts[i] = pkt_vec->pkts[j];
1165}
1166
1167static void free_pending_packets(pkt_vec_t pkt_vecs[], uint32_t num_ifs, stats_t *stats)
1168{
1169 for (uint32_t i = 0U; i < num_ifs; ++i) {
1170 stats->pkt_cleanup += pkt_vecs[i].num;
1171 odp_packet_free_multi(pkt_vecs[i].pkts, pkt_vecs[i].num);
1172 }
1173}
1174
1175static int process_packets(void *args)
1176{
1177 thread_config_t *config = args;
1178 const uint8_t num_ifs = config->prog_config->num_ifs;
1179 pkt_vec_t pkt_vecs[num_ifs], *pkt_vec;
1180 odp_atomic_u32_t *is_running = &config->prog_config->is_running;
1181 uint64_t c1, c2, c3, c4, cdiff = 0U, rounds = 0U;
1182 const uint8_t copy_type = config->prog_config->copy_type;
1183 const int burst_size = config->prog_config->burst_size;
1184 odp_event_t evs[burst_size];
1185 int num_evs;
1186 odp_event_t ev;
1187 odp_event_type_t type;
1188 uint8_t *pktio_map = config->prog_config->pktio_idx_map;
1189 stats_t *stats = &config->stats;
1190 init_fn_t init_fn = config->prog_config->init_fn;
1191 start_fn_t start_fn = config->prog_config->start_fn;
1192 pkt_fn_t pkt_fn = config->prog_config->pkt_fn;
1193
1194 for (uint32_t i = 0U; i < num_ifs; ++i) {
1195 pkt_vecs[i].pktio = &config->prog_config->pktios[i];
1196 pkt_vecs[i].num = 0;
1197 }
1198
1199 config->thr_idx = odp_thread_id();
1200 odp_barrier_wait(&config->prog_config->init_barrier);
1201 c1 = odp_cpu_cycles();
1202
1203 while (odp_atomic_load_u32(is_running)) {
1204 c3 = odp_cpu_cycles();
1205 num_evs = odp_schedule_multi_no_wait(NULL, evs, burst_size);
1206 c4 = odp_cpu_cycles();
1207 cdiff += odp_cpu_cycles_diff(c4, c3);
1208 ++rounds;
1209
1210 if (copy_type == DMA_COPY_POLL)
1211 send_dma_poll_trs_pkts(burst_size, config);
1212
1213 if (num_evs == 0)
1214 continue;
1215
1216 for (int i = 0; i < num_evs; ++i) {
1217 ev = evs[i];
1218 type = odp_event_type(ev);
1219
1220 if (type == ODP_EVENT_DMA_COMPL) {
1221 send_dma_ev_trs_pkts(odp_dma_compl_from_event(ev), config);
1222 } else if (type == ODP_EVENT_PACKET) {
1223 push_packet(odp_packet_from_event(ev), pkt_vecs, pktio_map);
1224 } else {
1225 odp_event_free(ev);
1226 ++stats->discards;
1227 }
1228 }
1229
1230 for (uint32_t i = 0U; i < num_ifs; ++i) {
1231 pkt_vec = &pkt_vecs[i];
1232
1233 if (pkt_vec->num >= burst_size) {
1234 pkt_fn(pkt_vec->pkts, burst_size, pkt_vec->pktio, init_fn,
1235 start_fn, config);
1236 pop_packets(pkt_vec, burst_size);
1237 }
1238 }
1239 }
1240
1241 c2 = odp_cpu_cycles();
1242 stats->sched_cc = cdiff;
1243 stats->tot_cc = odp_cpu_cycles_diff(c2, c1);
1244 stats->sched_rounds = rounds;
1245 free_pending_packets(pkt_vecs, num_ifs, stats);
1246 odp_barrier_wait(&config->prog_config->term_barrier);
1247
1248 if (config->prog_config->drain_fn)
1249 config->prog_config->drain_fn(config);
1250
1251 return 0;
1252}
1253
1254static odp_bool_t setup_workers(prog_config_t *config)
1255{
1256 odp_cpumask_t cpumask;
1257 int num_workers;
1258 odph_thread_common_param_t thr_common;
1259 odph_thread_param_t thr_param[config->num_thrs];
1260
1261 num_workers = odp_cpumask_default_worker(&cpumask, config->num_thrs);
1262 odph_thread_common_param_init(&thr_common);
1263 thr_common.instance = config->odp_instance;
1264 thr_common.cpumask = &cpumask;
1265
1266 for (int i = 0; i < config->num_thrs; ++i) {
1267 odph_thread_param_init(&thr_param[i]);
1268 thr_param[i].start = process_packets;
1269 thr_param[i].thr_type = ODP_THREAD_WORKER;
1270 config->thread_config[i].prog_config = config;
1271 thr_param[i].arg = &config->thread_config[i];
1272 }
1273
1274 num_workers = odph_thread_create(config->thread_tbl, &thr_common, thr_param, num_workers);
1275
1276 if (num_workers != config->num_thrs) {
1277 ODPH_ERR("Error configuring worker threads\n");
1278 return false;
1279 }
1280
1281 return true;
1282}
1283
1284static odp_bool_t setup_test(prog_config_t *config)
1285{
1286 odp_barrier_init(&config->init_barrier, config->num_thrs + 1);
1287 odp_barrier_init(&config->term_barrier, config->num_thrs + 1);
1288
1289 if (!setup_copy(config))
1290 return false;
1291
1292 if (!setup_pktios(config))
1293 return false;
1294
1295 if (!setup_workers(config))
1296 return false;
1297
1298 odp_barrier_wait(&config->init_barrier);
1299
1300 return true;
1301}
1302
1303static void stop_test(prog_config_t *config)
1304{
1305 for (uint32_t i = 0U; i < config->num_ifs; ++i)
1306 if (config->pktios[i].handle != ODP_PKTIO_INVALID)
1307 (void)odp_pktio_stop(config->pktios[i].handle);
1308
1309 odp_barrier_wait(&config->term_barrier);
1310 (void)odph_thread_join(config->thread_tbl, config->num_thrs);
1311}
1312
1313static void teardown(prog_config_t *config)
1314{
1315 thread_config_t *thr;
1316
1317 for (uint32_t i = 0U; i < config->num_ifs; ++i) {
1318 free(config->pktios[i].name);
1319
1320 if (config->pktios[i].handle != ODP_PKTIO_INVALID)
1321 (void)odp_pktio_close(config->pktios[i].handle);
1322 }
1323
1324 if (config->pktio_pool != ODP_POOL_INVALID)
1325 (void)odp_pool_destroy(config->pktio_pool);
1326
1327 for (int i = 0; i < config->num_thrs; ++i) {
1328 thr = &config->thread_config[i];
1329
1330 if (thr->inflight_stash != ODP_STASH_INVALID)
1331 (void)odp_stash_destroy(thr->inflight_stash);
1332
1333 if (thr->compl_q != ODP_QUEUE_INVALID)
1334 (void)odp_queue_destroy(thr->compl_q);
1335
1336 if (thr->compl_pool != ODP_POOL_INVALID)
1337 (void)odp_pool_destroy(thr->compl_pool);
1338
1339 if (thr->dma_handle != ODP_DMA_INVALID)
1340 (void)odp_dma_destroy(thr->dma_handle);
1341 }
1342
1343 if (config->copy_pool != ODP_POOL_INVALID)
1344 (void)odp_pool_destroy(config->copy_pool);
1345
1346 if (config->trs_pool != ODP_POOL_INVALID)
1347 (void)odp_pool_destroy(config->trs_pool);
1348}
1349
1350static void print_stats(const prog_config_t *config)
1351{
1352 const stats_t *stats;
1353 const char *align1 = config->copy_type == DMA_COPY_EV ? " " : "";
1354 const char *align2 = config->copy_type == SW_COPY ? " " :
1355 config->copy_type == DMA_COPY_EV ? " " :
1356 " ";
1357
1358 printf("\n==================\n\n"
1359 "DMA forwarder done\n\n"
1360 " copy mode: %s\n"
1361 " burst size: %u\n"
1362 " packet length: %u\n"
1363 " max cache size: %u\n"
1364 " segment free: %s\n", config->copy_type == SW_COPY ? "SW" :
1365 config->copy_type == DMA_COPY_EV ? "DMA-event" : "DMA-poll",
1366 config->burst_size, config->pkt_len, config->cache_size,
1367 config->seg_free ? "yes" : "no");
1368
1369 for (int i = 0; i < config->num_thrs; ++i) {
1370 stats = &config->thread_config[i].stats;
1371
1372 printf("\n worker %d:\n", i);
1373
1374 if (config->copy_type == SW_COPY) {
1375 printf(" packet copy errors: %" PRIu64 "\n",
1376 stats->copy_errs);
1377 } else {
1378 printf(" successful DMA transfers: %s%" PRIu64 "\n"
1379 " DMA transfer start errors: %s%" PRIu64 "\n"
1380 " DMA transfer errors: %s%" PRIu64 "\n"
1381 " transfer buffer allocation errors: %s%" PRIu64 "\n"
1382 " copy packet allocation errors: %s%" PRIu64 "\n",
1383 align1, stats->trs, align1, stats->start_errs, align1,
1384 stats->trs_errs, align1, stats->buf_alloc_errs, align1,
1385 stats->pkt_alloc_errs);
1386
1387 if (config->copy_type == DMA_COPY_EV)
1388 printf(" completion event allocation errors: %" PRIu64 "\n",
1389 stats->compl_alloc_errs);
1390 else
1391 printf(" transfer ID allocation errors: %" PRIu64 "\n"
1392 " transfer poll errors: %" PRIu64 "\n"
1393 " transfers polled: %" PRIu64 "\n",
1394 stats->compl_alloc_errs, stats->trs_poll_errs,
1395 stats->trs_polled);
1396 }
1397
1398 printf(" packets forwarded:%s%" PRIu64 "\n"
1399 " packets dropped: %s%" PRIu64 "\n"
1400 " packets cleanup: %s%" PRIu64 "\n"
1401 " call cycles per schedule round:\n"
1402 " total: %" PRIu64 "\n"
1403 " schedule: %" PRIu64 "\n"
1404 " rounds: %" PRIu64 "\n", align2, stats->fwd_pkts, align2,
1405 stats->discards, align2, stats->pkt_cleanup, DIV_IF(stats->tot_cc,
1406 stats->sched_rounds), DIV_IF(stats->sched_cc, stats->sched_rounds),
1407 stats->sched_rounds);
1408 }
1409
1410 printf("\n==================\n");
1411}
1412
1413int main(int argc, char **argv)
1414{
1415 odph_helper_options_t odph_opts;
1416 odp_init_t init_param;
1418 odp_shm_t shm_cfg = ODP_SHM_INVALID;
1419 int ret = EXIT_SUCCESS;
1420 parse_result_t parse_res;
1421
1422 argc = odph_parse_options(argc, argv);
1423
1424 if (odph_options(&odph_opts) == -1) {
1425 ODPH_ERR("Error while reading ODP helper options, exiting\n");
1426 exit(EXIT_FAILURE);
1427 }
1428
1429 odp_init_param_init(&init_param);
1430 init_param.mem_model = odph_opts.mem_model;
1431
1432 if (odp_init_global(&odp_instance, &init_param, NULL)) {
1433 ODPH_ERR("ODP global init failed, exiting\n");
1434 exit(EXIT_FAILURE);
1435 }
1436
1438 ODPH_ERR("ODP local init failed, exiting\n");
1439 exit(EXIT_FAILURE);
1440 }
1441
1442 shm_cfg = odp_shm_reserve(PROG_NAME "_cfg", sizeof(prog_config_t), ODP_CACHE_LINE_SIZE,
1443 0U);
1444
1445 if (shm_cfg == ODP_SHM_INVALID) {
1446 ODPH_ERR("Error reserving shared memory\n");
1447 ret = EXIT_FAILURE;
1448 goto out;
1449 }
1450
1451 prog_conf = odp_shm_addr(shm_cfg);
1452
1453 if (prog_conf == NULL) {
1454 ODPH_ERR("Error resolving shared memory address\n");
1455 ret = EXIT_FAILURE;
1456 goto out;
1457 }
1458
1459 parse_res = setup_program(argc, argv, prog_conf);
1460
1461 if (parse_res == PRS_NOK) {
1462 ret = EXIT_FAILURE;
1463 goto out_test;
1464 }
1465
1466 if (parse_res == PRS_TERM) {
1467 ret = EXIT_SUCCESS;
1468 goto out_test;
1469 }
1470
1471 if (parse_res == PRS_NOT_SUP) {
1472 ret = EXIT_NOT_SUP;
1473 goto out_test;
1474 }
1475
1476 if (odp_schedule_config(NULL) < 0) {
1477 ODPH_ERR("Error configuring scheduler\n");
1478 ret = EXIT_FAILURE;
1479 goto out_test;
1480 }
1481
1482 prog_conf->odp_instance = odp_instance;
1483 odp_atomic_init_u32(&prog_conf->is_running, 1U);
1484
1485 if (!setup_test(prog_conf)) {
1486 ret = EXIT_FAILURE;
1487 goto out_test;
1488 }
1489
1490 if (prog_conf->time_sec > 0.001) {
1491 struct timespec ts;
1492
1493 ts.tv_sec = prog_conf->time_sec;
1494 ts.tv_nsec = (prog_conf->time_sec - ts.tv_sec) * ODP_TIME_SEC_IN_NS;
1495 nanosleep(&ts, NULL);
1496 odp_atomic_store_u32(&prog_conf->is_running, 0U);
1497 } else {
1498 while (odp_atomic_load_u32(&prog_conf->is_running))
1499 sleep(1U);
1500 }
1501
1502 stop_test(prog_conf);
1503 print_stats(prog_conf);
1504
1505out_test:
1506 teardown(prog_conf);
1507
1508out:
1509 if (shm_cfg != ODP_SHM_INVALID)
1510 (void)odp_shm_free(shm_cfg);
1511
1512 if (odp_term_local()) {
1513 ODPH_ERR("ODP local terminate failed, exiting\n");
1514 exit(EXIT_FAILURE);
1515 }
1516
1518 ODPH_ERR("ODP global terminate failed, exiting\n");
1519 exit(EXIT_FAILURE);
1520 }
1521
1522 return ret;
1523}
void odp_atomic_init_u32(odp_atomic_u32_t *atom, uint32_t val)
Initialize atomic uint32 variable.
uint32_t odp_atomic_load_u32(odp_atomic_u32_t *atom)
Load value of atomic uint32 variable.
void odp_atomic_store_u32(odp_atomic_u32_t *atom, uint32_t val)
Store value to atomic uint32 variable.
void odp_barrier_init(odp_barrier_t *barr, int count)
Initialize barrier with thread count.
void odp_barrier_wait(odp_barrier_t *barr)
Synchronize thread execution on barrier.
odp_buffer_t odp_buffer_alloc(odp_pool_t pool)
Buffer alloc.
void odp_buffer_free(odp_buffer_t buf)
Buffer free.
void * odp_buffer_addr(odp_buffer_t buf)
Buffer start address.
_odp_abi_buffer_t * odp_buffer_t
ODP buffer.
#define ODP_BUFFER_INVALID
Invalid buffer.
#define ODP_ALIGNED_CACHE
Defines type/struct/variable to be cache line size aligned.
#define odp_unlikely(x)
Branch unlikely taken.
Definition spec/hints.h:64
#define ODP_UNUSED
Intentionally unused variables of functions.
Definition spec/hints.h:54
uint64_t odp_cpu_cycles_diff(uint64_t c2, uint64_t c1)
CPU cycle count difference.
uint64_t odp_cpu_cycles(void)
Current CPU cycle count.
int odp_cpumask_default_worker(odp_cpumask_t *mask, int num)
Default CPU mask for worker threads.
odp_dma_t odp_dma_create(const char *name, const odp_dma_param_t *param)
Create DMA session.
odp_pool_t odp_dma_pool_create(const char *name, const odp_dma_pool_param_t *pool_param)
Create DMA completion event pool.
int odp_dma_transfer_done(odp_dma_t dma, odp_dma_transfer_id_t transfer_id, odp_dma_result_t *result)
Check if DMA transfer has completed.
#define ODP_DMA_TYPE_COPY
Copy data.
void odp_dma_transfer_id_free(odp_dma_t dma, odp_dma_transfer_id_t transfer_id)
Free DMA transfer identifier.
#define ODP_DMA_COMPL_EVENT
Asynchronous transfer with completion event.
void odp_dma_transfer_param_init(odp_dma_transfer_param_t *trs_param)
Initialize DMA transfer parameters.
int odp_dma_destroy(odp_dma_t dma)
Destroy DMA session.
int odp_dma_compl_result(odp_dma_compl_t dma_compl, odp_dma_result_t *result)
Check DMA completion event.
void odp_dma_compl_param_init(odp_dma_compl_param_t *compl_param)
Initialize DMA transfer completion parameters.
#define ODP_DMA_TRANSFER_ID_INVALID
Invalid DMA transfer identifier.
#define ODP_DMA_COMPL_INVALID
Invalid DMA completion event.
odp_event_t odp_dma_compl_to_event(odp_dma_compl_t dma_compl)
Convert DMA completion event to event.
void odp_dma_pool_param_init(odp_dma_pool_param_t *pool_param)
Initialize DMA completion event pool parameters.
int odp_dma_transfer_start(odp_dma_t dma, const odp_dma_transfer_param_t *trs_param, const odp_dma_compl_param_t *compl_param)
Start DMA transfer.
uint64_t odp_dma_transfer_id_t
DMA transfer identifier.
odp_dma_compl_t odp_dma_compl_from_event(odp_event_t ev)
Convert event to DMA completion event.
#define ODP_DMA_MAIN_TO_MAIN
DMA transfer within the main memory.
int odp_dma_capability(odp_dma_capability_t *capa)
Query DMA capabilities.
odp_dma_transfer_id_t odp_dma_transfer_id_alloc(odp_dma_t dma)
Allocate DMA transfer identifier.
void odp_dma_compl_free(odp_dma_compl_t dma_compl)
Free DMA completion event.
#define ODP_DMA_INVALID
Invalid DMA session.
#define ODP_DMA_COMPL_POLL
Asynchronous transfer with completion polling.
odp_dma_compl_t odp_dma_compl_alloc(odp_pool_t pool)
Allocate DMA completion event.
@ ODP_DMA_MT_SERIAL
Application serializes operations.
@ ODP_DMA_FORMAT_PACKET
Data format is odp_packet_t.
@ ODP_DMA_ORDER_NONE
No specific ordering between transfers.
void odp_event_free(odp_event_t event)
Free event.
odp_event_type_t odp_event_type(odp_event_t event)
Event type of an event.
odp_event_type_t
Event type.
#define ODP_EVENT_INVALID
Invalid event.
int odp_instance(odp_instance_t *instance)
Get instance handle.
void odp_init_param_init(odp_init_t *param)
Initialize the odp_init_t to default values for all fields.
#define ODP_STATIC_ASSERT(cond, msg)
Compile time assertion macro.
int odp_init_local(odp_instance_t instance, odp_thread_type_t thr_type)
Thread local ODP initialization.
int odp_init_global(odp_instance_t *instance, const odp_init_t *params, const odp_platform_init_t *platform_params)
Global ODP initialization.
int odp_term_local(void)
Thread local ODP termination.
int odp_term_global(odp_instance_t instance)
Global ODP termination.
uint64_t odp_instance_t
ODP instance ID.
void odp_pktin_queue_param_init(odp_pktin_queue_param_t *param)
Initialize packet input queue parameters.
void odp_pktio_param_init(odp_pktio_param_t *param)
Initialize pktio params.
int odp_pktio_close(odp_pktio_t pktio)
Close a packet IO interface.
int odp_pktout_queue(odp_pktio_t pktio, odp_pktout_queue_t queues[], int num)
Direct packet output queues.
odp_pktio_t odp_pktio_open(const char *name, odp_pool_t pool, const odp_pktio_param_t *param)
Open a packet IO interface.
int odp_pktio_start(odp_pktio_t pktio)
Start packet receive and transmit.
#define ODP_PKTIO_INVALID
Invalid packet IO handle.
void odp_pktout_queue_param_init(odp_pktout_queue_param_t *param)
Initialize packet output queue parameters.
int odp_pktio_stop(odp_pktio_t pktio)
Stop packet receive and transmit.
int odp_pktio_index(odp_pktio_t pktio)
Get pktio interface index.
int odp_pktio_capability(odp_pktio_t pktio, odp_pktio_capability_t *capa)
Query packet IO interface capabilities.
#define ODP_PKTIO_MAX_INDEX
Maximum packet IO interface index.
int odp_pktout_send(odp_pktout_queue_t queue, const odp_packet_t packets[], int num)
Send packets directly to an interface output queue.
int odp_pktin_queue_config(odp_pktio_t pktio, const odp_pktin_queue_param_t *param)
Configure packet input queues.
int odp_pktout_queue_config(odp_pktio_t pktio, const odp_pktout_queue_param_t *param)
Configure packet output queues.
@ ODP_PKTOUT_MODE_DIRECT
Direct packet output on the interface.
@ ODP_PKTIO_OP_MT_UNSAFE
Not multithread safe operation.
@ ODP_PKTIN_MODE_SCHED
Packet input through scheduler and scheduled event queues.
int odp_packet_input_index(odp_packet_t pkt)
Packet input interface index.
odp_packet_t odp_packet_copy(odp_packet_t pkt, odp_pool_t pool)
Full copy of a packet.
uint32_t odp_packet_len(odp_packet_t pkt)
Packet data length.
odp_packet_t odp_packet_alloc(odp_pool_t pool, uint32_t len)
Allocate a packet from a packet pool.
odp_packet_t odp_packet_from_event(odp_event_t ev)
Get packet handle from event.
void odp_packet_free(odp_packet_t pkt)
Free packet.
#define ODP_PACKET_INVALID
Invalid packet.
void odp_packet_free_multi(const odp_packet_t pkt[], int num)
Free multiple packets.
odp_pool_t odp_pool_create(const char *name, const odp_pool_param_t *param)
Create a pool.
int odp_pool_capability(odp_pool_capability_t *capa)
Query pool capabilities.
void odp_pool_param_init(odp_pool_param_t *param)
Initialize pool params.
int odp_pool_destroy(odp_pool_t pool)
Destroy a pool previously created by odp_pool_create()
#define ODP_POOL_INVALID
Invalid pool.
@ ODP_POOL_BUFFER
Buffer pool.
@ ODP_POOL_PACKET
Packet pool.
void odp_queue_param_init(odp_queue_param_t *param)
Initialize queue params.
#define ODP_QUEUE_INVALID
Invalid queue.
odp_queue_t odp_queue_create(const char *name, const odp_queue_param_t *param)
Queue create.
int odp_queue_destroy(odp_queue_t queue)
Destroy ODP queue.
@ ODP_QUEUE_TYPE_SCHED
Scheduled queue.
int odp_schedule_multi_no_wait(odp_queue_t *from, odp_event_t events[], int num)
Schedule, do not wait for events.
#define ODP_SCHED_SYNC_PARALLEL
Parallel scheduled queues.
int odp_schedule_default_prio(void)
Default scheduling priority level.
int odp_schedule_max_prio(void)
Maximum scheduling priority level.
int odp_schedule_config(const odp_schedule_config_t *config)
Global schedule configuration.
uint64_t odp_schedule_wait_time(uint64_t ns)
Schedule wait time.
odp_event_t odp_schedule(odp_queue_t *from, uint64_t wait)
Schedule an event.
int odp_shm_free(odp_shm_t shm)
Free a contiguous block of shared memory.
void * odp_shm_addr(odp_shm_t shm)
Shared memory block address.
#define ODP_SHM_INVALID
Invalid shared memory block.
odp_shm_t odp_shm_reserve(const char *name, uint64_t size, uint64_t align, uint32_t flags)
Reserve a contiguous block of shared memory.
odp_stash_type_t
Stash types.
#define ODP_STASH_INVALID
Invalid stash handle.
int32_t odp_stash_put(odp_stash_t stash, const void *obj, int32_t num)
Put object handles into a stash.
void odp_stash_param_init(odp_stash_param_t *param)
Initialize stash params.
int32_t odp_stash_get(odp_stash_t stash, void *obj, int32_t num)
Get object handles from a stash.
int odp_stash_destroy(odp_stash_t stash)
Destroy a stash.
int odp_stash_capability(odp_stash_capability_t *capa, odp_stash_type_t type)
Query stash capabilities.
odp_stash_t odp_stash_create(const char *name, const odp_stash_param_t *param)
Create a stash.
@ ODP_STASH_TYPE_DEFAULT
The default stash type.
@ ODP_STASH_TYPE_FIFO
Stash type FIFO.
@ ODP_STASH_OP_LOCAL
Thread local operation.
bool odp_bool_t
Boolean type.
int odp_thread_id(void)
Get thread identifier.
@ ODP_THREAD_WORKER
Worker thread.
@ ODP_THREAD_CONTROL
Control thread.
#define ODP_TIME_SEC_IN_NS
A second in nanoseconds.
#define ODP_TIME_MSEC_IN_NS
A millisecond in nanoseconds.
The OpenDataPlane API.
uint32_t max_sessions
Maximum number of DMA sessions.
uint32_t max_transfers
Maximum number of transfers per DMA session.
odp_dma_pool_capability_t pool
DMA completion event pool capabilities.
uint32_t max_dst_segs
Maximum number of destination segments in a single transfer.
uint32_t max_src_segs
Maximum number of source segments in a single transfer.
odp_bool_t queue_type_sched
Scheduled queue support.
odp_dma_compl_mode_t compl_mode_mask
Supported completion modes.
uint32_t max_seg_len
Maximum segment length in bytes.
odp_bool_t src_seg_free
Source segment free support for data format ODP_DMA_FORMAT_PACKET.
DMA transfer completion parameters.
odp_dma_transfer_id_t transfer_id
Transfer identifier.
void * user_ptr
User context pointer.
odp_event_t event
Completion event.
odp_dma_compl_mode_t compl_mode
Completion mode.
odp_queue_t queue
Completion queue.
DMA session parameters.
odp_dma_direction_t direction
Transfer direction.
uint32_t min_cache_size
Minimum size of thread local cache.
uint32_t max_cache_size
Maximum size of thread local cache.
uint32_t max_pools
Maximum number of DMA completion event pools.
uint32_t max_num
Maximum number of DMA completion events in a pool.
DMA completion event pool parameters.
uint32_t cache_size
Maximum number of events cached locally per thread.
uint32_t num
Number of DMA completion events in the pool.
DMA transfer results.
void * user_ptr
User context pointer.
odp_bool_t success
DMA transfer success.
odp_packet_t packet
Packet handle.
uint32_t len
Segment length in bytes.
DMA transfer parameters.
struct odp_dma_transfer_param_t::@38::@40 opts
Option bit fields.
odp_dma_seg_t * dst_seg
Table of destination segments.
odp_dma_data_format_t dst_format
Destination data format.
uint16_t seg_free
Allow freeing all the source segments.
uint32_t num_dst
Number of destination segments.
uint32_t num_src
Number of source segments.
odp_dma_seg_t * src_seg
Table of source segments.
odp_dma_data_format_t src_format
Source data format.
uint16_t unique_src_segs
Option bit to denote a source segment of data format ODP_DMA_FORMAT_PACKET have unique packets or not...
Global initialization parameters.
odp_mem_model_t mem_model
Application memory model.
Packet input queue parameters.
uint32_t num_queues
Number of input queues to be created.
odp_queue_param_t queue_param
Queue parameters.
odp_pktin_hash_proto_t hash_proto
Protocol field selection for hashing.
odp_bool_t hash_enable
Enable flow hashing.
uint32_t max_input_queues
Maximum number of input queues.
uint32_t max_output_queues
Maximum number of output queues.
Packet IO parameters.
odp_pktin_mode_t in_mode
Packet input mode.
odp_pktout_mode_t out_mode
Packet output mode.
Packet output queue parameters.
odp_pktio_op_mode_t op_mode
Operation mode.
uint32_t num_queues
Number of output queues to be created.
uint32_t max_num
Maximum number of buffers of any size.
uint32_t min_cache_size
Minimum size of thread local cache.
struct odp_pool_capability_t::@133 buf
Buffer pool capabilities
struct odp_pool_capability_t::@134 pkt
Packet pool capabilities
uint32_t max_cache_size
Maximum size of thread local cache.
uint32_t max_len
Maximum packet data length in bytes.
uint32_t num
Number of buffers in the pool.
struct odp_pool_param_t::@139 pkt
Parameters for packet pools.
uint32_t cache_size
Maximum number of buffers cached locally per thread.
uint32_t size
Minimum buffer size in bytes.
odp_pool_type_t type
Pool type.
uint32_t len
Minimum length of 'num' packets.
struct odp_pool_param_t::@138 buf
Parameters for buffer pools.
uint32_t seg_len
Minimum number of packet data bytes that can be stored in the first segment of a newly allocated pack...
ODP Queue parameters.
odp_schedule_param_t sched
Scheduler parameters.
odp_queue_type_t type
Queue type.
odp_schedule_prio_t prio
Priority level.
odp_schedule_sync_t sync
Synchronization method.
Stash capabilities (per stash type)
uint64_t u16
Maximum number of 2 byte object handles.
uint64_t u128
Maximum number of 16 byte object handles.
uint32_t max_stashes
Maximum number of stashes of this type.
uint64_t u64
Maximum number of 8 byte object handles.
uint32_t max_cache_size
Maximum size of thread local cache.
uint64_t u32
Maximum number of 4 byte object handles.
uint64_t u8
Maximum number of 1 byte object handles.
struct odp_stash_capability_t::@171 max_num
Maximum number of object handles per stash for each object size.
uint32_t cache_size
Maximum number of object handles cached locally per thread.
odp_stash_op_mode_t get_mode
Get operation mode.
uint32_t obj_size
Object handle size in bytes.
odp_stash_type_t type
Stash type.
odp_stash_op_mode_t put_mode
Put operation mode.
uint64_t num_obj
Number of object handles.
128-bit unsigned integer structure
struct odp_pktin_hash_proto_t::@107 proto
Protocol header fields for hashing.
uint32_t ipv4
IPv4 addresses.