Coverage Report

Created: 2026-06-22 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openvswitch/lib/dpif-offload-dummy.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2025 Red Hat, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at:
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <config.h>
18
#include <errno.h>
19
20
#include "dpif.h"
21
#include "dpif-offload.h"
22
#include "dpif-offload-provider.h"
23
#include "dummy.h"
24
#include "id-fpool.h"
25
#include "netdev-native-tnl.h"
26
#include "netdev-provider.h"
27
#include "odp-util.h"
28
#include "util.h"
29
#include "uuid.h"
30
31
#include "openvswitch/json.h"
32
#include "openvswitch/match.h"
33
#include "openvswitch/vlog.h"
34
35
VLOG_DEFINE_THIS_MODULE(dpif_offload_dummy);
36
37
struct pmd_id_data {
38
    struct hmap_node node;
39
    void *flow_reference;
40
    unsigned pmd_id;
41
};
42
43
struct dummy_offloaded_flow {
44
    struct hmap_node node;
45
    struct match match;
46
    const struct nlattr *actions;
47
    size_t actions_len;
48
    ovs_u128 ufid;
49
    uint32_t mark;
50
    struct dpif_flow_stats stats;
51
52
    /* The pmd_id_map below is also protected by the port_mutex. */
53
    struct hmap pmd_id_map;
54
 };
55
56
struct dummy_offload {
57
    struct dpif_offload offload;
58
    struct id_fpool *flow_mark_pool;
59
    dpif_offload_flow_unreference_cb *unreference_cb;
60
61
    /* Configuration specific variables. */
62
    struct ovsthread_once once_enable; /* Track first-time enablement. */
63
};
64
65
struct dummy_offload_port {
66
    struct dpif_offload_port pm_port;
67
68
    struct ovs_mutex port_mutex; /* Protect all below members. */
69
    struct hmap offloaded_flows OVS_GUARDED;
70
    struct ovs_list hw_recv_queue OVS_GUARDED;
71
72
    /* Some simulated offload statistics. */
73
    uint64_t rx_offload_partial OVS_GUARDED; /* Match found, CPU continues. */
74
    uint64_t rx_offload_full OVS_GUARDED; /* Fully offloaded, CPU bypassed. */
75
    uint64_t rx_offload_miss OVS_GUARDED; /* No HW offload rule matched. */
76
    uint64_t rx_offload_pipe_abort OVS_GUARDED; /* Pipeline abort. */
77
};
78
79
struct hw_pkt_node {
80
    struct dp_packet *pkt;
81
    int queue_id;
82
    struct ovs_list list_node;
83
};
84
85
static void dummy_flow_unreference(struct dummy_offload *, unsigned pmd_id,
86
                                   void *flow_reference);
87
88
static uint32_t
89
dummy_allocate_flow_mark(struct dummy_offload *offload)
90
0
{
91
0
    static struct ovsthread_once init_once = OVSTHREAD_ONCE_INITIALIZER;
92
0
    uint32_t flow_mark;
93
94
0
    if (ovsthread_once_start(&init_once)) {
95
        /* Haven't initiated yet, do it here. */
96
0
        offload->flow_mark_pool = id_fpool_create(1, 1, UINT32_MAX - 1);
97
0
        ovsthread_once_done(&init_once);
98
0
    }
99
100
0
    if (id_fpool_new_id(offload->flow_mark_pool, 0, &flow_mark)) {
101
0
        return flow_mark;
102
0
    }
103
104
0
    return INVALID_FLOW_MARK;
105
0
}
106
107
static void
108
dummy_free_flow_mark(struct dummy_offload *offload, uint32_t flow_mark)
109
0
{
110
0
    if (flow_mark != INVALID_FLOW_MARK) {
111
0
        id_fpool_free_id(offload->flow_mark_pool, 0, flow_mark);
112
0
    }
113
0
}
114
115
static struct dummy_offload_port *
116
dummy_offload_port_cast(struct dpif_offload_port *port)
117
0
{
118
0
    return CONTAINER_OF(port, struct dummy_offload_port, pm_port);
119
0
}
120
121
static struct dummy_offload *
122
dummy_offload_cast(const struct dpif_offload *offload)
123
0
{
124
0
    return CONTAINER_OF(offload, struct dummy_offload, offload);
125
0
}
126
127
static uint32_t
128
dummy_flow_hash(const ovs_u128 *ufid)
129
0
{
130
0
    return ufid->u32[0];
131
0
}
132
133
static struct pmd_id_data *
134
dummy_find_flow_pmd_data(struct dummy_offload_port *port OVS_UNUSED,
135
                         struct dummy_offloaded_flow *off_flow,
136
                         unsigned pmd_id)
137
    OVS_REQUIRES(port->port_mutex)
138
0
{
139
0
    size_t hash = hash_int(pmd_id, 0);
140
0
    struct pmd_id_data *data;
141
142
0
    HMAP_FOR_EACH_WITH_HASH (data, node, hash, &off_flow->pmd_id_map) {
143
0
        if (data->pmd_id == pmd_id) {
144
0
            return data;
145
0
        }
146
0
    }
147
0
    return NULL;
148
0
}
149
150
static void
151
dummy_add_flow_pmd_data(struct dummy_offload_port *port OVS_UNUSED,
152
                        struct dummy_offloaded_flow *off_flow, unsigned pmd_id,
153
                        void *flow_reference)
154
    OVS_REQUIRES(port->port_mutex)
155
0
{
156
0
    struct pmd_id_data *pmd_data = xmalloc(sizeof *pmd_data);
157
158
0
    pmd_data->pmd_id = pmd_id;
159
0
    pmd_data->flow_reference = flow_reference;
160
0
    hmap_insert(&off_flow->pmd_id_map, &pmd_data->node,
161
0
                hash_int(pmd_id, 0));
162
0
}
163
164
static void
165
dummy_update_flow_pmd_data(struct dummy_offload_port *port,
166
                           struct dummy_offloaded_flow *off_flow,
167
                           unsigned pmd_id, void *flow_reference,
168
                           void **previous_flow_reference)
169
    OVS_REQUIRES(port->port_mutex)
170
0
{
171
0
    struct pmd_id_data *data = dummy_find_flow_pmd_data(port, off_flow,
172
0
                                                        pmd_id);
173
174
0
    if (data) {
175
0
        *previous_flow_reference = data->flow_reference;
176
0
        data->flow_reference = flow_reference;
177
0
    } else {
178
0
        dummy_add_flow_pmd_data(port, off_flow, pmd_id, flow_reference);
179
0
        *previous_flow_reference = NULL;
180
0
    }
181
0
}
182
183
static bool
184
dummy_del_flow_pmd_data(struct dummy_offload_port *port OVS_UNUSED,
185
                        struct dummy_offloaded_flow *off_flow, unsigned pmd_id,
186
                        void *flow_reference)
187
    OVS_REQUIRES(port->port_mutex)
188
0
{
189
0
    size_t hash = hash_int(pmd_id, 0);
190
0
    struct pmd_id_data *data;
191
192
0
    HMAP_FOR_EACH_WITH_HASH (data, node, hash, &off_flow->pmd_id_map) {
193
0
        if (data->pmd_id == pmd_id && data->flow_reference == flow_reference) {
194
0
            hmap_remove(&off_flow->pmd_id_map, &data->node);
195
0
            free(data);
196
0
            return true;
197
0
        }
198
0
    }
199
200
0
    return false;
201
0
}
202
203
static void
204
dummy_cleanup_flow_pmd_data(struct dummy_offload *offload,
205
                            struct dummy_offload_port *port OVS_UNUSED,
206
                            struct dummy_offloaded_flow *off_flow)
207
    OVS_REQUIRES(port->port_mutex)
208
0
{
209
0
    struct pmd_id_data *data;
210
211
0
    HMAP_FOR_EACH_SAFE (data, node, &off_flow->pmd_id_map) {
212
0
        hmap_remove(&off_flow->pmd_id_map, &data->node);
213
214
0
        dummy_flow_unreference(offload, data->pmd_id, data->flow_reference);
215
0
        free(data);
216
0
    }
217
0
}
218
219
static struct dummy_offloaded_flow *
220
dummy_add_flow(struct dummy_offload_port *port, const ovs_u128 *ufid,
221
               unsigned pmd_id, void *flow_reference, uint32_t mark)
222
    OVS_REQUIRES(port->port_mutex)
223
0
{
224
0
    struct dummy_offloaded_flow *off_flow = xzalloc(sizeof *off_flow);
225
226
0
    off_flow->mark = mark;
227
0
    memcpy(&off_flow->ufid, ufid, sizeof off_flow->ufid);
228
0
    hmap_init(&off_flow->pmd_id_map);
229
0
    dummy_add_flow_pmd_data(port, off_flow, pmd_id, flow_reference);
230
231
0
    hmap_insert(&port->offloaded_flows, &off_flow->node,
232
0
                dummy_flow_hash(ufid));
233
234
0
    return off_flow;
235
0
}
236
237
static void
238
dummy_free_flow(struct dummy_offload_port *port,
239
                struct dummy_offloaded_flow *off_flow, bool remove_from_port)
240
    OVS_REQUIRES(port->port_mutex)
241
0
{
242
0
    if (remove_from_port) {
243
0
        hmap_remove(&port->offloaded_flows, &off_flow->node);
244
0
    }
245
0
    ovs_assert(!hmap_count(&off_flow->pmd_id_map));
246
247
0
    hmap_destroy(&off_flow->pmd_id_map);
248
0
    free(CONST_CAST(struct nlattr *, off_flow->actions));
249
0
    free(off_flow);
250
0
}
251
252
static struct dummy_offloaded_flow *
253
dummy_find_offloaded_flow(struct dummy_offload_port *port,
254
                          const ovs_u128 *ufid)
255
    OVS_REQUIRES(port->port_mutex)
256
0
{
257
0
    uint32_t hash = dummy_flow_hash(ufid);
258
0
    struct dummy_offloaded_flow *data;
259
260
0
    HMAP_FOR_EACH_WITH_HASH (data, node, hash, &port->offloaded_flows) {
261
0
        if (ovs_u128_equals(*ufid, data->ufid)) {
262
0
            return data;
263
0
        }
264
0
    }
265
266
0
    return NULL;
267
0
}
268
269
static struct dummy_offloaded_flow *
270
dummy_find_offloaded_flow_and_update(struct dummy_offload_port *port,
271
                                     const ovs_u128 *ufid, unsigned pmd_id,
272
                                     void *new_flow_reference,
273
                                     void **previous_flow_reference)
274
    OVS_REQUIRES(port->port_mutex)
275
0
{
276
0
    struct dummy_offloaded_flow *off_flow;
277
278
0
    off_flow = dummy_find_offloaded_flow(port, ufid);
279
0
    if (!off_flow) {
280
0
        return NULL;
281
0
    }
282
283
0
    dummy_update_flow_pmd_data(port, off_flow, pmd_id, new_flow_reference,
284
0
                               previous_flow_reference);
285
286
0
    return off_flow;
287
0
}
288
289
static void
290
dummy_offload_enable(struct dpif_offload *dpif_offload,
291
                     struct dpif_offload_port *port)
292
0
{
293
0
    atomic_store_relaxed(&port->netdev->hw_info.post_process_api_supported,
294
0
                         true);
295
0
    dpif_offload_set_netdev_offload(port->netdev, dpif_offload);
296
0
}
297
298
static void
299
dummy_offload_cleanup(struct dpif_offload_port *port)
300
0
{
301
0
    dpif_offload_set_netdev_offload(port->netdev, NULL);
302
0
}
303
304
static void
305
dummy_free_port__(struct dummy_offload *offload,
306
                  struct dummy_offload_port *port, bool close_netdev)
307
0
{
308
0
    struct dummy_offloaded_flow *off_flow;
309
0
    struct hw_pkt_node *pkt;
310
311
0
    ovs_mutex_lock(&port->port_mutex);
312
0
    HMAP_FOR_EACH_POP (off_flow, node, &port->offloaded_flows) {
313
0
        dummy_cleanup_flow_pmd_data(offload, port, off_flow);
314
0
        dummy_free_flow(port, off_flow, false);
315
0
    }
316
0
    hmap_destroy(&port->offloaded_flows);
317
318
0
    LIST_FOR_EACH_POP (pkt, list_node, &port->hw_recv_queue) {
319
0
        dp_packet_delete(pkt->pkt);
320
0
        free(pkt);
321
0
    }
322
323
0
    ovs_mutex_unlock(&port->port_mutex);
324
0
    ovs_mutex_destroy(&port->port_mutex);
325
0
    if (close_netdev) {
326
0
        netdev_close(port->pm_port.netdev);
327
0
    }
328
0
    free(port);
329
0
}
330
331
struct free_port_rcu {
332
    struct dummy_offload *offload;
333
    struct dummy_offload_port *port;
334
};
335
336
static void
337
dummy_free_port_rcu(struct free_port_rcu *fpc)
338
0
{
339
0
    dummy_free_port__(fpc->offload, fpc->port, true);
340
0
    free(fpc);
341
0
}
342
343
static void
344
dummy_free_port(struct dummy_offload *offload, struct dummy_offload_port *port)
345
0
{
346
0
    struct free_port_rcu *fpc = xmalloc(sizeof *fpc);
347
348
0
    fpc->offload = offload;
349
0
    fpc->port = port;
350
0
    ovsrcu_postpone(dummy_free_port_rcu, fpc);
351
0
}
352
353
static int
354
dummy_offload_port_add(struct dpif_offload *dpif_offload,
355
                       struct netdev *netdev, odp_port_t port_no)
356
0
{
357
0
    struct dummy_offload *offload = dummy_offload_cast(dpif_offload);
358
0
    struct dummy_offload_port *port = xzalloc(sizeof *port);
359
360
0
    ovs_mutex_init(&port->port_mutex);
361
0
    ovs_mutex_lock(&port->port_mutex);
362
0
    hmap_init(&port->offloaded_flows);
363
0
    ovs_list_init(&port->hw_recv_queue);
364
0
    ovs_mutex_unlock(&port->port_mutex);
365
366
0
    if (dpif_offload_port_mgr_add(dpif_offload, &port->pm_port, netdev,
367
0
                                  port_no, false)) {
368
369
0
        if (dpif_offload_enabled()) {
370
0
            dummy_offload_enable(dpif_offload, &port->pm_port);
371
0
        }
372
0
        return 0;
373
0
    }
374
375
0
    dummy_free_port__(offload, port, false);
376
0
    return EEXIST;
377
0
}
378
379
static int
380
dummy_offload_port_del(struct dpif_offload *dpif_offload, odp_port_t port_no)
381
0
{
382
0
    struct dummy_offload *offload = dummy_offload_cast(dpif_offload);
383
0
    struct dpif_offload_port *port;
384
385
0
    port = dpif_offload_port_mgr_remove(dpif_offload, port_no);
386
0
    if (port) {
387
0
        struct dummy_offload_port *dummy_port;
388
389
0
        dummy_port = dummy_offload_port_cast(port);
390
0
        if (dpif_offload_enabled()) {
391
0
            dummy_offload_cleanup(port);
392
0
        }
393
0
        dummy_free_port(offload, dummy_port);
394
0
    }
395
0
    return 0;
396
0
}
397
398
static struct netdev *
399
dummy_offload_get_netdev(const struct dpif_offload *dpif_offload,
400
                         odp_port_t port_no)
401
0
{
402
0
    struct dpif_offload_port *port;
403
404
0
    port = dpif_offload_port_mgr_find_by_odp_port(dpif_offload, port_no);
405
0
    if (!port) {
406
0
        return NULL;
407
0
    }
408
409
0
    return port->netdev;
410
0
}
411
412
static int
413
dummy_offload_open(const struct dpif_offload_class *offload_class,
414
                   struct dpif *dpif, struct dpif_offload **dpif_offload)
415
0
{
416
0
    struct dummy_offload *offload;
417
418
0
    offload = xmalloc(sizeof *offload);
419
420
0
    dpif_offload_init(&offload->offload, offload_class, dpif);
421
0
    offload->once_enable = (struct ovsthread_once) OVSTHREAD_ONCE_INITIALIZER;
422
0
    offload->flow_mark_pool = NULL;
423
0
    offload->unreference_cb = NULL;
424
425
0
    *dpif_offload = &offload->offload;
426
0
    return 0;
427
0
}
428
429
static void
430
dummy_offload_close(struct dpif_offload *dpif_offload)
431
0
{
432
0
    struct dummy_offload *offload = dummy_offload_cast(dpif_offload);
433
0
    struct dpif_offload_port *port;
434
435
    /* The ofproto layer may not call dpif_port_del() for all ports,
436
     * especially internal ones, so we need to clean up any remaining ports. */
437
0
    DPIF_OFFLOAD_PORT_FOR_EACH (port, dpif_offload) {
438
0
        dummy_offload_port_del(dpif_offload, port->port_no);
439
0
    }
440
441
0
    if (offload->flow_mark_pool) {
442
0
        id_fpool_destroy(offload->flow_mark_pool);
443
0
    }
444
0
    ovsthread_once_destroy(&offload->once_enable);
445
0
    dpif_offload_destroy(dpif_offload);
446
0
    free(offload);
447
0
}
448
449
static void
450
dummy_offload_set_config(struct dpif_offload *dpif_offload,
451
                         const struct smap *other_cfg)
452
0
{
453
0
    struct dummy_offload *offload = dummy_offload_cast(dpif_offload);
454
455
0
    if (smap_get_bool(other_cfg, "hw-offload", false)) {
456
0
        if (ovsthread_once_start(&offload->once_enable)) {
457
0
            struct dpif_offload_port *port;
458
459
0
            DPIF_OFFLOAD_PORT_FOR_EACH (port, dpif_offload) {
460
0
                dummy_offload_enable(dpif_offload, port);
461
0
            }
462
463
0
            ovsthread_once_done(&offload->once_enable);
464
0
        }
465
0
    }
466
0
}
467
468
static void
469
dummy_offload_get_debug(const struct dpif_offload *offload, struct ds *ds,
470
                        struct json *json)
471
0
{
472
0
    if (json) {
473
0
        struct json *json_ports = json_object_create();
474
0
        struct dpif_offload_port *port_;
475
476
0
        DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
477
0
            struct dummy_offload_port *port = dummy_offload_port_cast(port_);
478
0
            struct json *json_port = json_object_create();
479
480
0
            json_object_put(json_port, "port_no",
481
0
                            json_integer_create(odp_to_u32(port_->port_no)));
482
483
0
            ovs_mutex_lock(&port->port_mutex);
484
0
            json_object_put(json_port, "rx_offload_partial",
485
0
                            json_integer_create(port->rx_offload_partial));
486
0
            json_object_put(json_port, "rx_offload_full",
487
0
                            json_integer_create(port->rx_offload_full));
488
0
            json_object_put(json_port, "rx_offload_miss",
489
0
                            json_integer_create(port->rx_offload_miss));
490
0
            json_object_put(json_port, "rx_offload_pipe_abort",
491
0
                            json_integer_create(port->rx_offload_pipe_abort));
492
0
            ovs_mutex_unlock(&port->port_mutex);
493
494
0
            json_object_put(json_ports, netdev_get_name(port_->netdev),
495
0
                            json_port);
496
0
        }
497
498
0
        if (!json_object_is_empty(json_ports)) {
499
0
            json_object_put(json, "ports", json_ports);
500
0
        } else {
501
0
            json_destroy(json_ports);
502
0
        }
503
0
    } else if (ds) {
504
0
        struct dpif_offload_port *port_;
505
506
0
        DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
507
0
            struct dummy_offload_port *port = dummy_offload_port_cast(port_);
508
509
0
            ovs_mutex_lock(&port->port_mutex);
510
0
            ds_put_format(ds,
511
0
                          "  - %s: port_no: %u\n"
512
0
                          "    rx_offload_partial   : %" PRIu64 "\n"
513
0
                          "    rx_offload_full      : %" PRIu64 "\n"
514
0
                          "    rx_offload_miss      : %" PRIu64 "\n"
515
0
                          "    rx_offload_pipe_abort: %" PRIu64 "\n",
516
0
                          netdev_get_name(port_->netdev), port_->port_no,
517
0
                          port->rx_offload_partial, port->rx_offload_full,
518
0
                          port->rx_offload_miss, port->rx_offload_pipe_abort);
519
0
            ovs_mutex_unlock(&port->port_mutex);
520
0
        }
521
0
    }
522
0
}
523
524
static int
525
dummy_offload_get_global_stats(const struct dpif_offload *offload,
526
                               struct netdev_custom_stats *stats)
527
0
{
528
    /* Add a single counter telling how many ports we are servicing. */
529
0
    stats->label = xstrdup(dpif_offload_name(offload));
530
0
    stats->size = 1;
531
0
    stats->counters = xmalloc(sizeof(struct netdev_custom_counter) * 1);
532
0
    stats->counters[0].value = dpif_offload_port_mgr_port_count(offload);
533
0
    ovs_strzcpy(stats->counters[0].name, "Offloaded port count",
534
0
                sizeof stats->counters[0].name);
535
536
0
    return 0;
537
0
}
538
539
static bool
540
dummy_can_offload(struct dpif_offload *dpif_offload OVS_UNUSED,
541
                  struct netdev *netdev)
542
0
{
543
0
    return is_dummy_netdev_class(netdev->netdev_class);
544
0
}
545
546
static void
547
dummy_offload_log_operation(const char *op, int error, const ovs_u128 *ufid)
548
0
{
549
0
    VLOG_DBG("%s to %s netdev flow "UUID_FMT,
550
0
             error == 0 ? "succeed" : "failed", op,
551
0
             UUID_ARGS((struct uuid *) ufid));
552
0
}
553
554
static struct dummy_offload_port *
555
dummy_offload_get_port_by_netdev(const struct dpif_offload *offload,
556
                                 struct netdev *netdev)
557
0
{
558
0
    struct dpif_offload_port *port;
559
560
0
    port = dpif_offload_port_mgr_find_by_netdev(offload, netdev);
561
0
    if (!port) {
562
0
        return NULL;
563
0
    }
564
0
    return dummy_offload_port_cast(port);
565
0
}
566
567
static struct dummy_offload_port *
568
dummy_offload_get_port_by_odp_port(const struct dpif_offload *offload_,
569
                                   odp_port_t port_no)
570
0
{
571
0
    struct dpif_offload_port *port;
572
573
0
    port = dpif_offload_port_mgr_find_by_odp_port(offload_, port_no);
574
0
    if (!port) {
575
0
        return NULL;
576
0
    }
577
0
    return dummy_offload_port_cast(port);
578
0
}
579
580
static int
581
dummy_offload_hw_post_process(const struct dpif_offload *offload_,
582
                              struct netdev *netdev, unsigned pmd_id,
583
                              struct dp_packet *packet, void **flow_reference_)
584
0
{
585
0
    struct dummy_offloaded_flow *off_flow;
586
0
    struct dummy_offload_port *port;
587
0
    void *flow_reference = NULL;
588
0
    uint32_t flow_mark;
589
590
0
    port = dummy_offload_get_port_by_netdev(offload_, netdev);
591
0
    if (!port || !dp_packet_has_flow_mark(packet, &flow_mark)) {
592
0
        *flow_reference_ = NULL;
593
0
        return 0;
594
0
    }
595
596
0
    ovs_mutex_lock(&port->port_mutex);
597
0
    HMAP_FOR_EACH (off_flow, node, &port->offloaded_flows) {
598
0
        struct pmd_id_data *pmd_data;
599
600
0
        if (flow_mark == off_flow->mark) {
601
0
            pmd_data = dummy_find_flow_pmd_data(port, off_flow, pmd_id);
602
0
            if (pmd_data) {
603
0
                flow_reference = pmd_data->flow_reference;
604
0
            }
605
0
            break;
606
0
        }
607
0
    }
608
0
    ovs_mutex_unlock(&port->port_mutex);
609
610
0
     *flow_reference_ = flow_reference;
611
0
    return 0;
612
0
}
613
614
static ovs_be16
615
dummy_offload_udp_tnl_get_src_port__(struct dp_packet *packet)
616
0
{
617
    /* Use FNV-1a hash to ensure consistent results across all platforms.  The
618
     * standard OVS hash functions have architecture-specific implementations
619
     * (SSE4.2, ARM64 optimizations, etc.) that produce different outputs for
620
     * identical inputs, making tests non-deterministic. */
621
0
    const uint8_t *data = dp_packet_data(packet);
622
0
    size_t len = dp_packet_size(packet);
623
0
    uint32_t hash = 2166136261U;
624
0
    uint32_t prime = 16777619U;
625
626
0
    for (size_t i = 0; i < len; i++) {
627
0
        hash ^= data[i];
628
0
        hash *= prime;
629
0
    }
630
0
    return htons((uint16_t) hash);
631
0
}
632
633
static bool
634
dummy_offload_udp_tnl_get_src_port(
635
    const struct dpif_offload *offload OVS_UNUSED,
636
    const struct netdev *ingress_netdev OVS_UNUSED,
637
    struct dp_packet *packet, ovs_be16 *src_port)
638
0
{
639
0
    *src_port = dummy_offload_udp_tnl_get_src_port__(packet);
640
0
    return true;
641
0
}
642
643
static bool
644
dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_,
645
                                        odp_port_t in_odp,
646
                                        const struct nlattr *actions,
647
                                        size_t actions_len)
648
0
{
649
0
    const struct nlattr *nla;
650
0
    size_t left;
651
652
    /* Can we fully offload this flow? For now, only output actions are
653
     * supported, and only to dummy-pmd netdevs where the egress port differs
654
     * from the ingress port.  The latter restriction ensures that the partial
655
     * offload test cases pass.
656
     *
657
     * The reason for supporting only dummy-pmd netdevs as output targets is
658
     * that they provide full protection when calling netdev_send() from any
659
     * thread, via a netdev-level mutex. */
660
0
    NL_ATTR_FOR_EACH (nla, left, actions, actions_len) {
661
0
        enum ovs_action_attr action = nl_attr_type(nla);
662
663
0
        switch (action) {
664
0
        case OVS_ACTION_ATTR_OUTPUT: {
665
0
            odp_port_t out_odp = nl_attr_get_odp_port(nla);
666
0
            struct dummy_offload_port *out_port;
667
668
0
            out_port = dummy_offload_get_port_by_odp_port(offload_, out_odp);
669
0
            if (out_odp == in_odp || !out_port
670
0
                || strcmp("dummy-pmd",
671
0
                          netdev_get_type(out_port->pm_port.netdev))) {
672
0
                return false;
673
0
            }
674
0
            break;
675
0
        }
676
677
0
        case OVS_ACTION_ATTR_TUNNEL_PUSH: {
678
            /* We only support UDP tunnels, i.e. VXLAN and Geneve. */
679
0
            const struct ovs_action_push_tnl *data = nl_attr_get(nla);
680
681
0
            if (data->tnl_type != OVS_VPORT_TYPE_VXLAN
682
0
                && data->tnl_type != OVS_VPORT_TYPE_GENEVE) {
683
0
                return false;
684
0
            }
685
0
            break;
686
0
        }
687
688
0
        case OVS_ACTION_ATTR_UNSPEC:
689
0
        case OVS_ACTION_ATTR_USERSPACE:
690
0
        case OVS_ACTION_ATTR_SET:
691
0
        case OVS_ACTION_ATTR_PUSH_VLAN:
692
0
        case OVS_ACTION_ATTR_POP_VLAN:
693
0
        case OVS_ACTION_ATTR_SAMPLE:
694
0
        case OVS_ACTION_ATTR_RECIRC:
695
0
        case OVS_ACTION_ATTR_HASH:
696
0
        case OVS_ACTION_ATTR_PUSH_MPLS:
697
0
        case OVS_ACTION_ATTR_POP_MPLS:
698
0
        case OVS_ACTION_ATTR_SET_MASKED:
699
0
        case OVS_ACTION_ATTR_CT:
700
0
        case OVS_ACTION_ATTR_TRUNC:
701
0
        case OVS_ACTION_ATTR_PUSH_ETH:
702
0
        case OVS_ACTION_ATTR_POP_ETH:
703
0
        case OVS_ACTION_ATTR_CT_CLEAR:
704
0
        case OVS_ACTION_ATTR_PUSH_NSH:
705
0
        case OVS_ACTION_ATTR_POP_NSH:
706
0
        case OVS_ACTION_ATTR_METER:
707
0
        case OVS_ACTION_ATTR_CLONE:
708
0
        case OVS_ACTION_ATTR_CHECK_PKT_LEN:
709
0
        case OVS_ACTION_ATTR_ADD_MPLS:
710
0
        case OVS_ACTION_ATTR_DEC_TTL:
711
0
        case OVS_ACTION_ATTR_DROP:
712
0
        case OVS_ACTION_ATTR_PSAMPLE:
713
0
        case OVS_ACTION_ATTR_TUNNEL_POP:
714
0
        case OVS_ACTION_ATTR_LB_OUTPUT:
715
0
        case __OVS_ACTION_ATTR_MAX:
716
0
        default:
717
0
            return false;
718
0
        }
719
0
    }
720
0
    return true;
721
0
}
722
723
static bool
724
dummy_offload_hw_process_pkt(const struct dpif_offload *offload_,
725
                             struct dummy_offloaded_flow *flow,
726
                             struct dp_packet *pkt)
727
0
{
728
0
    uint32_t hash = dp_packet_get_rss_hash(pkt);
729
0
    uint32_t pkt_size = dp_packet_size(pkt);
730
0
    const struct nlattr *nla;
731
0
    size_t left;
732
733
0
    if (!flow->actions) {
734
0
        return false;
735
0
    }
736
737
0
    NL_ATTR_FOR_EACH (nla, left, flow->actions, flow->actions_len) {
738
0
        bool last_action = (left <= NLA_ALIGN(nla->nla_len));
739
0
        enum ovs_action_attr action = nl_attr_type(nla);
740
741
0
        switch (action) {
742
0
        case OVS_ACTION_ATTR_OUTPUT: {
743
0
            odp_port_t odp_port = nl_attr_get_odp_port(nla);
744
0
            struct dummy_offload_port *port;
745
0
            struct dp_packet_batch batch;
746
0
            int n_txq;
747
748
0
            port = dummy_offload_get_port_by_odp_port(offload_, odp_port);
749
0
            if (!port) {
750
0
                return false;
751
0
            }
752
753
0
            n_txq = netdev_n_txq(port->pm_port.netdev);
754
0
            dp_packet_batch_init_packet(&batch, last_action
755
0
                                                ? pkt
756
0
                                                : dp_packet_clone(pkt));
757
            /* As the tx-steering option is not exposed to hardware offload,
758
             * for now we assume hash steering based on the number of queues
759
             * configured for the dummy-netdev. */
760
0
            netdev_send(port->pm_port.netdev, hash % n_txq, &batch, false);
761
0
            break;
762
0
        }
763
0
        case OVS_ACTION_ATTR_TUNNEL_PUSH: {
764
0
            const struct ovs_action_push_tnl *data = nl_attr_get(nla);
765
0
            struct udp_header *udp;
766
0
            struct flow ovs_flow;
767
0
            ovs_be16 src_port;
768
769
0
            src_port = dummy_offload_udp_tnl_get_src_port__(pkt);
770
0
            netdev_tnl_push_udp_header(NULL, NULL, pkt, data);
771
772
0
            flow_extract(pkt, &ovs_flow);
773
0
            udp = dp_packet_l4(pkt);
774
0
            ovs_assert(ovs_flow.nw_proto == IPPROTO_UDP && udp);
775
776
0
            udp->udp_src = src_port;
777
0
            break;
778
0
        }
779
780
0
        case OVS_ACTION_ATTR_UNSPEC:
781
0
        case OVS_ACTION_ATTR_USERSPACE:
782
0
        case OVS_ACTION_ATTR_SET:
783
0
        case OVS_ACTION_ATTR_PUSH_VLAN:
784
0
        case OVS_ACTION_ATTR_POP_VLAN:
785
0
        case OVS_ACTION_ATTR_SAMPLE:
786
0
        case OVS_ACTION_ATTR_RECIRC:
787
0
        case OVS_ACTION_ATTR_HASH:
788
0
        case OVS_ACTION_ATTR_PUSH_MPLS:
789
0
        case OVS_ACTION_ATTR_POP_MPLS:
790
0
        case OVS_ACTION_ATTR_SET_MASKED:
791
0
        case OVS_ACTION_ATTR_CT:
792
0
        case OVS_ACTION_ATTR_TRUNC:
793
0
        case OVS_ACTION_ATTR_PUSH_ETH:
794
0
        case OVS_ACTION_ATTR_POP_ETH:
795
0
        case OVS_ACTION_ATTR_CT_CLEAR:
796
0
        case OVS_ACTION_ATTR_PUSH_NSH:
797
0
        case OVS_ACTION_ATTR_POP_NSH:
798
0
        case OVS_ACTION_ATTR_METER:
799
0
        case OVS_ACTION_ATTR_CLONE:
800
0
        case OVS_ACTION_ATTR_CHECK_PKT_LEN:
801
0
        case OVS_ACTION_ATTR_ADD_MPLS:
802
0
        case OVS_ACTION_ATTR_DEC_TTL:
803
0
        case OVS_ACTION_ATTR_DROP:
804
0
        case OVS_ACTION_ATTR_PSAMPLE:
805
0
        case OVS_ACTION_ATTR_TUNNEL_POP:
806
0
        case OVS_ACTION_ATTR_LB_OUTPUT:
807
0
        case __OVS_ACTION_ATTR_MAX:
808
0
        default:
809
0
            OVS_NOT_REACHED();
810
0
        }
811
0
    }
812
813
0
    flow->stats.n_bytes += pkt_size;
814
0
    flow->stats.n_packets++;
815
0
    flow->stats.used = time_msec();
816
0
    return true;
817
0
}
818
819
static int
820
dummy_flow_put(const struct dpif_offload *offload_, struct netdev *netdev,
821
               struct dpif_offload_flow_put *put,
822
               void **previous_flow_reference)
823
0
{
824
0
    struct dummy_offload *offload = dummy_offload_cast(offload_);
825
0
    struct dummy_offloaded_flow *off_flow;
826
0
    struct dummy_offload_port *port;
827
0
    bool modify = true;
828
0
    bool full_offload;
829
0
    int error = 0;
830
831
0
    port = dummy_offload_get_port_by_netdev(offload_, netdev);
832
0
    if (!port) {
833
0
        error = ENODEV;
834
0
        goto exit;
835
0
    }
836
837
0
    full_offload = dummy_offload_are_all_actions_supported(
838
0
                        offload_, put->match->flow.in_port.odp_port,
839
0
                        put->actions, put->actions_len);
840
841
0
    ovs_mutex_lock(&port->port_mutex);
842
843
0
    off_flow = dummy_find_offloaded_flow_and_update(
844
0
        port, put->ufid, put->pmd_id, put->flow_reference,
845
0
        previous_flow_reference);
846
847
0
    if (!off_flow) {
848
        /* Create new offloaded flow. */
849
0
        uint32_t mark = dummy_allocate_flow_mark(offload);
850
851
0
        if (mark == INVALID_FLOW_MARK) {
852
0
            error = ENOSPC;
853
0
            goto exit_unlock;
854
0
        }
855
856
0
        off_flow = dummy_add_flow(port, put->ufid, put->pmd_id,
857
0
                                  put->flow_reference, mark);
858
0
        modify = false;
859
0
        *previous_flow_reference = NULL;
860
0
    }
861
0
    memcpy(&off_flow->match, put->match, sizeof *put->match);
862
0
    free(CONST_CAST(struct nlattr *, off_flow->actions));
863
0
    if (full_offload) {
864
0
        off_flow->actions = xmemdup(put->actions, put->actions_len);
865
0
        off_flow->actions_len = put->actions_len;
866
0
    } else {
867
0
        off_flow->actions = NULL;
868
0
        off_flow->actions_len = 0;
869
0
    }
870
871
    /* As we have per-netdev 'offloaded_flows', we don't need to match
872
     * the 'in_port' for received packets.  This will also allow offloading
873
     * for packets passed to 'receive' command without specifying the
874
     * 'in_port'. */
875
0
    off_flow->match.wc.masks.in_port.odp_port = 0;
876
877
0
    if (VLOG_IS_DBG_ENABLED()) {
878
0
        struct ds ds = DS_EMPTY_INITIALIZER;
879
880
0
        ds_put_format(&ds, "%s: flow put[%s]: ", netdev_get_name(netdev),
881
0
                      modify ? "modify" : "create");
882
0
        odp_format_ufid(put->ufid, &ds);
883
0
        ds_put_cstr(&ds, " flow match: ");
884
0
        match_format(put->match, NULL, &ds, OFP_DEFAULT_PRIORITY);
885
0
        ds_put_format(&ds, ", mark: %"PRIu32, off_flow->mark);
886
887
0
        VLOG_DBG("%s", ds_cstr(&ds));
888
0
        ds_destroy(&ds);
889
0
    }
890
891
0
exit_unlock:
892
0
    if (put->stats) {
893
0
        *put->stats = off_flow->stats;
894
0
    }
895
896
0
    ovs_mutex_unlock(&port->port_mutex);
897
898
0
exit:
899
0
    dummy_offload_log_operation(modify ? "modify" : "add", error, put->ufid);
900
0
    return error;
901
0
}
902
903
static int
904
dummy_flow_del(const struct dpif_offload *offload_, struct netdev *netdev,
905
               struct dpif_offload_flow_del *del)
906
0
{
907
0
    struct dummy_offload *offload = dummy_offload_cast(offload_);
908
0
    struct dummy_offloaded_flow *off_flow;
909
0
    uint32_t mark = INVALID_FLOW_MARK;
910
0
    struct dummy_offload_port *port;
911
0
    const char *error = NULL;
912
913
0
    port = dummy_offload_get_port_by_netdev(offload_, netdev);
914
0
    if (!port) {
915
0
        error = "No such (net)device.";
916
0
        goto exit;
917
0
    }
918
919
0
    ovs_mutex_lock(&port->port_mutex);
920
921
0
    off_flow = dummy_find_offloaded_flow(port, del->ufid);
922
0
    if (!off_flow) {
923
0
        error = "No such flow.";
924
0
        goto exit_unlock;
925
0
    }
926
927
0
    if (!dummy_del_flow_pmd_data(port, off_flow, del->pmd_id,
928
0
                                 del->flow_reference)) {
929
0
        error = "No such flow with pmd_id and reference.";
930
0
        goto exit_unlock;
931
0
    }
932
933
0
    if (del->stats) {
934
0
        memcpy(del->stats, &off_flow->stats, sizeof *del->stats);
935
0
    }
936
937
0
    mark = off_flow->mark;
938
0
    if (!hmap_count(&off_flow->pmd_id_map)) {
939
0
        dummy_free_flow_mark(offload, mark);
940
0
        dummy_free_flow(port, off_flow, true);
941
0
    }
942
943
0
exit_unlock:
944
0
    ovs_mutex_unlock(&port->port_mutex);
945
946
0
exit:
947
0
    if (error || VLOG_IS_DBG_ENABLED()) {
948
0
        struct ds ds = DS_EMPTY_INITIALIZER;
949
950
0
        ds_put_format(&ds, "%s: ", netdev_get_name(netdev));
951
0
        if (error) {
952
0
            ds_put_cstr(&ds, "failed to ");
953
0
        }
954
0
        ds_put_cstr(&ds, "flow del: ");
955
0
        odp_format_ufid(del->ufid, &ds);
956
0
        if (error) {
957
0
            ds_put_format(&ds, " error: %s", error);
958
0
        } else {
959
0
            ds_put_format(&ds, " mark: %"PRIu32, mark);
960
0
        }
961
0
        VLOG(error ? VLL_WARN : VLL_DBG, "%s", ds_cstr(&ds));
962
0
        ds_destroy(&ds);
963
0
    }
964
965
0
    dummy_offload_log_operation("delete", error ? -1 : 0, del->ufid);
966
0
    return error ? ENOENT : 0;
967
0
}
968
969
static bool
970
dummy_flow_stats(const struct dpif_offload *offload_, struct netdev *netdev,
971
                 const ovs_u128 *ufid, struct dpif_flow_stats *stats,
972
                 struct dpif_flow_attrs *attrs)
973
0
{
974
0
    struct dummy_offloaded_flow *off_flow = NULL;
975
0
    struct dummy_offload_port *port;
976
977
0
    port = dummy_offload_get_port_by_netdev(offload_, netdev);
978
0
    if (!port) {
979
0
        return false;
980
0
    }
981
982
0
    ovs_mutex_lock(&port->port_mutex);
983
0
    off_flow = dummy_find_offloaded_flow(port, ufid);
984
0
    if (off_flow) {
985
0
        memcpy(stats, &off_flow->stats, sizeof *stats);
986
0
        attrs->dp_layer = off_flow->actions ? "dummy" : "ovs";
987
0
        attrs->dp_extra_info = NULL;
988
0
        attrs->offloaded = true;
989
0
    }
990
0
    ovs_mutex_unlock(&port->port_mutex);
991
992
0
    if (!off_flow) {
993
0
        return false;
994
0
    }
995
996
0
    return true;
997
0
}
998
999
static void
1000
dummy_register_flow_unreference_cb(const struct dpif_offload *offload_,
1001
                                   dpif_offload_flow_unreference_cb *cb)
1002
0
{
1003
0
    struct dummy_offload *offload = dummy_offload_cast(offload_);
1004
1005
0
    offload->unreference_cb = cb;
1006
0
}
1007
1008
static void
1009
dummy_flow_unreference(struct dummy_offload *offload, unsigned pmd_id,
1010
                       void *flow_reference)
1011
0
{
1012
0
    if (offload->unreference_cb) {
1013
0
        offload->unreference_cb(pmd_id, flow_reference);
1014
0
    }
1015
0
}
1016
1017
bool
1018
dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet *packet,
1019
                              int queue_id, struct flow *flow)
1020
0
{
1021
0
    const struct dpif_offload *offload = ovsrcu_get(
1022
0
        const struct dpif_offload *, &netdev->dpif_offload);
1023
0
    struct dummy_offloaded_flow *data;
1024
0
    struct dummy_offload_port *port;
1025
0
    bool packet_stolen = false;
1026
0
    struct flow packet_flow;
1027
0
    bool offloaded = false;
1028
1029
0
    if (!dpif_offload_enabled() || !offload
1030
0
        || strcmp(dpif_offload_type(offload), "dummy")) {
1031
0
        return false;
1032
0
    }
1033
1034
0
    port = dummy_offload_get_port_by_netdev(offload, netdev);
1035
0
    if (!port) {
1036
0
        return false;
1037
0
    }
1038
1039
0
    if (!flow) {
1040
0
        flow = &packet_flow;
1041
0
        flow_extract(packet, flow);
1042
0
    }
1043
1044
0
    ovs_mutex_lock(&port->port_mutex);
1045
0
    HMAP_FOR_EACH (data, node, &port->offloaded_flows) {
1046
0
        if (flow_equal_except(flow, &data->match.flow, &data->match.wc)) {
1047
1048
0
            dp_packet_set_flow_mark(packet, data->mark);
1049
1050
0
            if (VLOG_IS_DBG_ENABLED()) {
1051
0
                struct ds ds = DS_EMPTY_INITIALIZER;
1052
1053
0
                ds_put_format(&ds, "%s: packet: ",
1054
0
                              netdev_get_name(netdev));
1055
                /* 'flow' does not contain proper port number here.
1056
                 * Let's just clear it as it's wildcarded anyway. */
1057
0
                flow->in_port.ofp_port = 0;
1058
0
                flow_format(&ds, flow, NULL);
1059
1060
0
                ds_put_cstr(&ds, " matches with flow: ");
1061
0
                odp_format_ufid(&data->ufid, &ds);
1062
0
                ds_put_cstr(&ds, " ");
1063
0
                match_format(&data->match, NULL, &ds, OFP_DEFAULT_PRIORITY);
1064
0
                ds_put_format(&ds, " with mark: %"PRIu32, data->mark);
1065
1066
0
                VLOG_DBG("%s", ds_cstr(&ds));
1067
0
                ds_destroy(&ds);
1068
0
            }
1069
1070
0
            if (data->actions) {
1071
                /* Perform hardware offload simulation.  The packet is stolen
1072
                 * here and handed off to the PMD thread callback for
1073
                 * processing. */
1074
0
                struct hw_pkt_node *pkt_node = xmalloc(sizeof *pkt_node);
1075
1076
0
                pkt_node->pkt = packet;
1077
0
                pkt_node->queue_id = queue_id;
1078
0
                ovs_list_push_back(&port->hw_recv_queue, &pkt_node->list_node);
1079
0
                packet_stolen = true;
1080
0
                port->rx_offload_full++;
1081
0
            } else {
1082
0
                port->rx_offload_partial++;
1083
0
            }
1084
1085
0
            offloaded = true;
1086
0
            break;
1087
0
        }
1088
0
    }
1089
1090
0
    if (!offloaded) {
1091
0
        port->rx_offload_miss++;
1092
0
    }
1093
1094
0
    ovs_mutex_unlock(&port->port_mutex);
1095
0
    return packet_stolen;
1096
0
}
1097
1098
void
1099
dummy_netdev_hw_offload_run(struct netdev *netdev)
1100
0
{
1101
0
    const struct dpif_offload *offload = ovsrcu_get(
1102
0
        const struct dpif_offload *, &netdev->dpif_offload);
1103
0
    struct dpif_offload_port *port_;
1104
1105
0
    if (!dpif_offload_enabled() || !offload
1106
0
        || strcmp(dpif_offload_type(offload), "dummy")) {
1107
0
        return;
1108
0
    }
1109
1110
0
    DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
1111
0
        struct dummy_offload_port *port;
1112
0
        struct hw_pkt_node *pkt_node;
1113
1114
0
        port = dummy_offload_port_cast(port_);
1115
1116
0
        if (ovs_mutex_trylock(&port->port_mutex)) {
1117
0
            continue;
1118
0
        }
1119
1120
0
        LIST_FOR_EACH_POP (pkt_node, list_node, &port->hw_recv_queue) {
1121
0
            struct dummy_offloaded_flow *offloaded_flow;
1122
0
            struct dp_packet *pkt = pkt_node->pkt;
1123
0
            bool processed = false;
1124
0
            struct flow flow;
1125
1126
0
            flow_extract(pkt, &flow);
1127
0
            HMAP_FOR_EACH (offloaded_flow, node, &port->offloaded_flows) {
1128
0
                if (flow_equal_except(&flow, &offloaded_flow->match.flow,
1129
0
                                      &offloaded_flow->match.wc)) {
1130
1131
0
                    processed = dummy_offload_hw_process_pkt(
1132
0
                                    offload, offloaded_flow, pkt);
1133
0
                    break;
1134
0
                }
1135
0
            }
1136
1137
0
            if (!processed) {
1138
0
                VLOG_DBG("Failed HW pipeline, sent to sw!");
1139
0
                port->rx_offload_pipe_abort++;
1140
0
                netdev_dummy_queue_simulate_offload_packet(
1141
0
                    port->pm_port.netdev, pkt, pkt_node->queue_id);
1142
0
            }
1143
0
            free(pkt_node);
1144
0
        }
1145
0
        ovs_mutex_unlock(&port->port_mutex);
1146
0
    }
1147
0
}
1148
1149
#define DEFINE_DPIF_DUMMY_CLASS(NAME, TYPE_STR)                             \
1150
    struct dpif_offload_class NAME = {                                      \
1151
        .type = TYPE_STR,                                                   \
1152
        .impl_type = DPIF_OFFLOAD_IMPL_FLOWS_DPIF_SYNCED,                   \
1153
        .supported_dpif_types = (const char *const[]) {"dummy", NULL},      \
1154
        .open = dummy_offload_open,                                         \
1155
        .close = dummy_offload_close,                                       \
1156
        .set_config = dummy_offload_set_config,                             \
1157
        .get_debug = dummy_offload_get_debug,                               \
1158
        .get_global_stats = dummy_offload_get_global_stats,                 \
1159
        .can_offload = dummy_can_offload,                                   \
1160
        .port_add = dummy_offload_port_add,                                 \
1161
        .port_del = dummy_offload_port_del,                                 \
1162
        .get_netdev = dummy_offload_get_netdev,                             \
1163
        .netdev_hw_post_process = dummy_offload_hw_post_process,            \
1164
        .netdev_udp_tnl_get_src_port = dummy_offload_udp_tnl_get_src_port,  \
1165
        .netdev_flow_put = dummy_flow_put,                                  \
1166
        .netdev_flow_del = dummy_flow_del,                                  \
1167
        .netdev_flow_stats = dummy_flow_stats,                              \
1168
        .register_flow_unreference_cb = dummy_register_flow_unreference_cb, \
1169
}
1170
1171
DEFINE_DPIF_DUMMY_CLASS(dpif_offload_dummy_class, "dummy");
1172
DEFINE_DPIF_DUMMY_CLASS(dpif_offload_dummy_x_class, "dummy_x");