diff mbox series

[ovs-dev,ovs-dev,7/7] netdev-offload-dpdk: Add dpdk offload meter action

Message ID 1577517076-105041-8-git-send-email-xiangxia.m.yue@gmail.com
State Changes Requested
Headers show
Series Support meter action offload | expand

Commit Message

Tonghao Zhang Dec. 28, 2019, 7:11 a.m. UTC
From: Tonghao Zhang <xiangxia.m.yue@gmail.com>

This patch introduce the dpdk_meter_offload_api struct
and implement the meter offload api for dpdk.

* ovs-ofctl can create the meter resource but the meter
  offload action is not created yet. The meter action
  will be created actually when installing the flow,
  and the offload private data be attached datapath meters,
  such as dp_meter struct.

* the action resource can be reused if created for the
  same flow if they is deleted, and multi-flows can share
  it.
* when ovs-ofctl modify the meter info, the offload meter
  resource is updated too.
* when ovs-ofctl delete the meter, the offload destroy
  callback is called.

Signed-off-by: Tonghao Zhang <xiangxia.m.yue@gmail.com>
---
 lib/dpif-netdev.c         |  49 +++++++-
 lib/netdev-dpdk.c         |   6 +
 lib/netdev-dpdk.h         |   1 +
 lib/netdev-offload-dpdk.c | 293 +++++++++++++++++++++++++++++++++++++++-------
 4 files changed, 307 insertions(+), 42 deletions(-)

Comments

0-day Robot Dec. 28, 2019, 8:16 a.m. UTC | #1
Bleep bloop.  Greetings Tonghao Zhang, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line has non-spaces leading whitespace
WARNING: Line has trailing whitespace
#223 FILE: lib/netdev-offload-dpdk.c:391:
    

WARNING: Line has trailing whitespace
#234 FILE: lib/netdev-offload-dpdk.c:402:
#define DPDK_METER_UPATE_UP 65536 

WARNING: Line has non-spaces leading whitespace
WARNING: Line has trailing whitespace
#248 FILE: lib/netdev-offload-dpdk.c:416:
    

WARNING: Line is 81 characters long (recommended limit is 79)
#277 FILE: lib/netdev-offload-dpdk.c:445:
        VLOG_ERR("rte_mtr_meter_profile_update fail: err_type: %d err_msg: %s\n",

WARNING: Line is 81 characters long (recommended limit is 79)
#288 FILE: lib/netdev-offload-dpdk.c:456:
        VLOG_ERR("rte_mtr_meter_profile_update fail: err_type: %d err_msg: %s\n",

WARNING: Line has non-spaces leading whitespace
WARNING: Line has trailing whitespace
#313 FILE: lib/netdev-offload-dpdk.c:481:
    

ERROR: Inappropriate spacing in pointer declaration
#333 FILE: lib/netdev-offload-dpdk.c:501:
    mp.srtcm_rfc2697.cir = max_rate *1024 /8; /* rate_max Kbps*/

WARNING: Line is 90 characters long (recommended limit is 79)
#340 FILE: lib/netdev-offload-dpdk.c:508:
        VLOG_ERR("rte_mtr_meter_profile_add fail: err_type: %d err_msg: %s, portid: %d\n",

WARNING: Line is 86 characters long (recommended limit is 79)
#341 FILE: lib/netdev-offload-dpdk.c:509:
                   mtr_error.type, mtr_error.message, netdev_dpdk_get_portid(netdev));

WARNING: Line is 86 characters long (recommended limit is 79)
#360 FILE: lib/netdev-offload-dpdk.c:528:
                   mtr_error.type, mtr_error.message, netdev_dpdk_get_portid(netdev));

WARNING: Line has non-spaces leading whitespace
WARNING: Line has trailing whitespace
#365 FILE: lib/netdev-offload-dpdk.c:533:
    

WARNING: Line is 86 characters long (recommended limit is 79)
#380 FILE: lib/netdev-offload-dpdk.c:548:
netdev_offload_dpdk_meter_conf(struct dpif *dpif, struct netdev *netdev, uint32_t mid)

WARNING: Line is 86 characters long (recommended limit is 79)
#449 FILE: lib/netdev-offload-dpdk.c:755:
        VLOG_INFO("%s: skip flow offload without actions\n", netdev_get_name(netdev));

Lines checked: 531, Warnings: 16, Errors: 1


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
diff mbox series

Patch

diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index e33215d..eec9f21 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -392,6 +392,7 @@  struct dp_flow_offload_item {
     size_t actions_len;
 
     struct ovs_list node;
+    struct dpif *dpif;
 };
 
 struct dp_flow_offload {
@@ -1608,6 +1609,31 @@  dpif_netdev_open(const struct dpif_class *class, const char *name,
     return error;
 }
 
+static struct dpif *
+dpif_netdev_dump_create(struct dp_netdev *dp)
+{
+    struct dpif *dpif = NULL;
+
+    ovs_mutex_lock(&dp_netdev_mutex);
+    dpif = create_dpif_netdev(dp);
+    ovs_mutex_unlock(&dp_netdev_mutex);
+
+    return dpif;
+}
+
+static void
+dpif_netdev_dump_destroy(struct dp_netdev *dp, struct dpif *dpif)
+{
+    ovs_mutex_lock(&dp_netdev_mutex);
+
+    free(dpif->base_name);
+    free(dpif->full_name);
+    free(dpif);
+    ovs_refcount_unref(&dp->ref_cnt);
+
+    ovs_mutex_unlock(&dp_netdev_mutex);
+}
+
 static void
 dp_netdev_destroy_upcall_lock(struct dp_netdev *dp)
     OVS_NO_THREAD_SAFETY_ANALYSIS
@@ -1625,6 +1651,15 @@  dp_delete_meter(struct dp_netdev *dp, uint32_t meter_id)
     OVS_REQUIRES(dp->meter_locks[meter_id % N_METER_LOCKS])
 {
     if (dp->meters[meter_id]) {
+        if (dp->meters[meter_id]->offload) {
+            struct netdev_offload_meter *nom;
+
+            nom = dp->meters[meter_id]->offload;
+            nom->meter_ops->meter_destroy(nom->priv_data);
+            free(nom);
+            dp->meters[meter_id]->offload = NULL;
+        }
+
         free(dp->meters[meter_id]);
         dp->meters[meter_id] = NULL;
     }
@@ -2323,6 +2358,7 @@  dp_netdev_alloc_flow_offload(struct dp_netdev_pmd_thread *pmd,
     offload->pmd = pmd;
     offload->flow = flow;
     offload->op = op;
+    offload->dpif = dpif_netdev_dump_create(pmd->dp);
 
     dp_netdev_flow_ref(flow);
     dp_netdev_pmd_try_ref(pmd);
@@ -2333,6 +2369,7 @@  dp_netdev_alloc_flow_offload(struct dp_netdev_pmd_thread *pmd,
 static void
 dp_netdev_free_flow_offload(struct dp_flow_offload_item *offload)
 {
+    dpif_netdev_dump_destroy(offload->pmd->dp, offload->dpif);
     dp_netdev_pmd_unref(offload->pmd);
     dp_netdev_flow_unref(offload->flow);
 
@@ -2407,6 +2444,7 @@  dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
         }
     }
     info.flow_mark = mark;
+    info.dpif_class = pmd->dp->class;
 
     port = netdev_ports_get(in_port, pmd->dp->class);
     if (!port || netdev_vport_is_vport_class(port->netdev_class)) {
@@ -2416,7 +2454,7 @@  dp_netdev_flow_offload_put(struct dp_flow_offload_item *offload)
     /* Taking a global 'port_mutex' to fulfill thread safety restrictions for
      * the netdev-offload-dpdk module. */
     ovs_mutex_lock(&pmd->dp->port_mutex);
-    ret = netdev_flow_put(NULL, port, &offload->match,
+    ret = netdev_flow_put(offload->dpif, port, &offload->match,
                           CONST_CAST(struct nlattr *, offload->actions),
                           offload->actions_len, &flow->mega_ufid, &info,
                           NULL);
@@ -5829,6 +5867,15 @@  dpif_netdev_meter_set(struct dpif *dpif, ofproto_meter_id meter_id,
     }
 
     meter_lock(dp, mid);
+    if (dp->meters[mid] && dp->meters[mid]->offload) {
+        struct netdev_offload_meter *nom;
+
+        nom = dp->meters[mid]->offload;
+        nom->meter_ops->meter_update(nom->priv_data, config);
+        meter->offload = nom;
+        dp->meters[mid]->offload = NULL;
+    }
+
     dp_delete_meter(dp, mid); /* Free existing meter, if any */
     dp->meters[mid] = meter;
     meter_unlock(dp, mid);
diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
index 8198a0b..6005071 100644
--- a/lib/netdev-dpdk.c
+++ b/lib/netdev-dpdk.c
@@ -1112,6 +1112,12 @@  netdev_dpdk_cast(const struct netdev *netdev)
     return CONTAINER_OF(netdev, struct netdev_dpdk, up);
 }
 
+int
+netdev_dpdk_get_portid(const struct netdev *netdev)
+{
+    return netdev_dpdk_cast(netdev)->port_id;
+}
+
 static struct netdev *
 netdev_dpdk_alloc(void)
 {
diff --git a/lib/netdev-dpdk.h b/lib/netdev-dpdk.h
index 60631c4..79009c6 100644
--- a/lib/netdev-dpdk.h
+++ b/lib/netdev-dpdk.h
@@ -36,6 +36,7 @@  void netdev_dpdk_register(void);
 void free_dpdk_buf(struct dp_packet *);
 
 bool netdev_dpdk_flow_api_supported(struct netdev *);
+int netdev_dpdk_get_portid(const struct netdev *netdev);
 
 int
 netdev_dpdk_rte_flow_destroy(struct netdev *netdev,
diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
index 1aa40a1..bcc0ee8 100644
--- a/lib/netdev-offload-dpdk.c
+++ b/lib/netdev-offload-dpdk.c
@@ -17,10 +17,12 @@ 
 #include <config.h>
 
 #include <rte_flow.h>
+#include <rte_mtr.h>
 
 #include "cmap.h"
 #include "dpif-netdev.h"
 #include "netdev-offload-provider.h"
+#include "openvswitch/ofp-meter.h"
 #include "netdev-provider.h"
 #include "openvswitch/match.h"
 #include "openvswitch/vlog.h"
@@ -374,55 +376,218 @@  add_flow_action(struct flow_actions *actions, enum rte_flow_action_type type,
     actions->cnt++;
 }
 
-struct action_rss_data {
-    struct rte_flow_action_rss conf;
-    uint16_t queue[0];
+struct dpdk_meter_offload {
+    uint32_t port_id;
+    uint32_t max_rate;
+    uint32_t mp_id;
+    struct rte_flow_action_meter mc;
 };
 
-static struct action_rss_data *
-add_flow_rss_action(struct flow_actions *actions,
-                    struct netdev *netdev)
+static void dpdk_meter_destroy(void *priv_data)
 {
-    int i;
-    struct action_rss_data *rss_data;
-
-    rss_data = xmalloc(sizeof *rss_data +
-                       netdev_n_rxq(netdev) * sizeof rss_data->queue[0]);
-    *rss_data = (struct action_rss_data) {
-        .conf = (struct rte_flow_action_rss) {
-            .func = RTE_ETH_HASH_FUNCTION_DEFAULT,
-            .level = 0,
-            .types = 0,
-            .queue_num = netdev_n_rxq(netdev),
-            .queue = rss_data->queue,
-            .key_len = 0,
-            .key  = NULL
-        },
-    };
+    struct dpdk_meter_offload *dmo = priv_data;
+    struct rte_mtr_error mtr_error;
+    
+    if (dmo) {
+        rte_mtr_meter_profile_delete(dmo->port_id,
+                                     dmo->mc.mtr_id,
+                                     &mtr_error);
+        rte_mtr_destroy(dmo->port_id, dmo->mc.mtr_id,
+                        &mtr_error);
+        free(dmo);
+    }
+}
+
+#define DPDK_METER_UPATE_UP 65536 
+
+static void dpdk_meter_update(void *priv_data, void *config)
+{
+    struct dpdk_meter_offload *dmo = priv_data;
+    struct rte_mtr_meter_profile mp;
+    struct rte_mtr_error mtr_error;
+    uint32_t mp_id, new_mp_id;
+    uint32_t max_rate;
+    uint32_t ret;
+
+    if (!priv_data || !config) {
+        return;
+    }
+    
+    max_rate = ofputil_meter_config_max_rate(config);
+    if (dmo->max_rate == max_rate) {
+        return;
+    }
+
+    memset(&mp, 0, sizeof(struct rte_mtr_meter_profile));
+    mp.alg = RTE_MTR_SRTCM_RFC2697;
+    mp.srtcm_rfc2697.cir = max_rate *1024 /8;
+    mp.srtcm_rfc2697.cbs = max_rate *1024 /8;
+    mp.srtcm_rfc2697.ebs = 0;
+
+    if (dmo->mp_id < DPDK_METER_UPATE_UP) {
+        new_mp_id = dmo->mp_id + DPDK_METER_UPATE_UP;
+    } else {
+        new_mp_id = dmo->mp_id - DPDK_METER_UPATE_UP;
+    }
+
+    ret = rte_mtr_meter_profile_add(dmo->port_id, new_mp_id,
+                                    &mp, &mtr_error);
+    if (ret) {
+        VLOG_ERR("rte_mtr_meter_profile_add fail: err_type: %d err_msg: %s\n",
+                 mtr_error.type, mtr_error.message);
+        return;
+    }
+
+    ret = rte_mtr_meter_profile_update(dmo->port_id, dmo->mc.mtr_id,
+                                       new_mp_id, &mtr_error);
+    if (ret) {
+        VLOG_ERR("rte_mtr_meter_profile_update fail: err_type: %d err_msg: %s\n",
+                 mtr_error.type, mtr_error.message);
+        mp_id = new_mp_id;
+        goto out;
+    }
+
+    mp_id = dmo->mp_id;
+    dmo->mp_id = new_mp_id;
+out:
+    ret = rte_mtr_meter_profile_delete(dmo->port_id, mp_id, &mtr_error);
+    if (ret) {
+        VLOG_ERR("rte_mtr_meter_profile_update fail: err_type: %d err_msg: %s\n",
+                 mtr_error.type, mtr_error.message);
+    }
+}
+
+static struct netdev_offload_meter_api dpdk_meter_offload_api = {
+    .meter_destroy  = dpdk_meter_destroy,
+    .meter_update   = dpdk_meter_update,
+};
+
+static struct rte_flow_action_meter*
+dpdk_meter_create(struct dpif *dpif, struct netdev *netdev, uint32_t mid)
+{
+    uint32_t port_id = netdev_dpdk_get_portid(netdev);
+    struct netdev_offload_meter *nom;
+    struct dpdk_meter_offload *dmo;
+    struct ofputil_meter_config config;
+    ofproto_meter_id meter_id;
+    struct rte_mtr_meter_profile mp;
+    struct rte_mtr_params params;
+    struct rte_mtr_error mtr_error;
+    uint32_t max_rate;
+    int ret;
+
+    meter_id.uint32 = mid;
+    
+    if (dpif_meter_get_config(dpif, meter_id, &config)) {
+        return NULL;
+    }
+
+    nom = xmalloc(sizeof *nom);
+    dmo = xmalloc(sizeof *dmo);
+
+    nom->meter_ops = &dpdk_meter_offload_api;
+    nom->priv_data = dmo;
+
+    memset(&mp, 0, sizeof(struct rte_mtr_meter_profile));
+    max_rate = ofputil_meter_config_max_rate(&config);
+
+    dmo->mc.mtr_id = mid;
+    dmo->port_id = port_id;
+    dmo->max_rate = max_rate;
+    dmo->mp_id = mid;
+
+    mp.alg = RTE_MTR_SRTCM_RFC2697;
+    mp.srtcm_rfc2697.cir = max_rate *1024 /8; /* rate_max Kbps*/
+    mp.srtcm_rfc2697.cbs = max_rate *1024 /8;
+    mp.srtcm_rfc2697.ebs = 0;
+
+    ret = rte_mtr_meter_profile_add(dmo->port_id, dmo->mc.mtr_id,
+                                    &mp, &mtr_error);
+    if (ret && ret != -EEXIST) {
+        VLOG_ERR("rte_mtr_meter_profile_add fail: err_type: %d err_msg: %s, portid: %d\n",
+                   mtr_error.type, mtr_error.message, netdev_dpdk_get_portid(netdev));
+        goto profile_err;
+    }
+
+    enum rte_color dscp_table[2];
+    dscp_table[0] = RTE_COLOR_YELLOW;
+    dscp_table[1] = RTE_COLOR_RED;
+
+    params.meter_profile_id = dmo->mc.mtr_id;
+    params.dscp_table = dscp_table;
+    params.meter_enable = 1;
+    params.use_prev_mtr_color = 0;
+    params.action[RTE_COLOR_GREEN]  = MTR_POLICER_ACTION_COLOR_GREEN;
+    params.action[RTE_COLOR_YELLOW] = MTR_POLICER_ACTION_DROP;
+    params.action[RTE_COLOR_RED]    = MTR_POLICER_ACTION_DROP;
+
+    ret = rte_mtr_create(dmo->port_id, dmo->mc.mtr_id, &params, 0, &mtr_error);
+    if (ret && ret != -EEXIST) {
+        VLOG_ERR("rte_mtr_create fail: err_type: %d err_msg: %s, portid: %d\n",
+                   mtr_error.type, mtr_error.message, netdev_dpdk_get_portid(netdev));
+        goto mtr_err;
+    }
+
+    dpif_meter_set_offload(dpif, meter_id, nom);
+    
+    free(config.bands);
+    return &dmo->mc;
+
+mtr_err:
+    rte_mtr_meter_profile_delete(dmo->port_id, dmo->mc.mtr_id, &mtr_error);
+
+profile_err:
+    free(nom);
+    free(dmo);
+    free(config.bands);
+    return NULL;
+}
+
+static struct rte_flow_action_meter *
+netdev_offload_dpdk_meter_conf(struct dpif *dpif, struct netdev *netdev, uint32_t mid)
+{
+    uint32_t port_id = netdev_dpdk_get_portid(netdev);
+    struct netdev_offload_meter *nom = NULL;
+    struct dpdk_meter_offload *dmo;
+    ofproto_meter_id meter_id;
+    uint32_t ret;
+    meter_id.uint32 = mid;
 
-    /* Override queue array with default. */
-    for (i = 0; i < netdev_n_rxq(netdev); i++) {
-       rss_data->queue[i] = i;
+    ret = dpif_meter_get_offload(dpif, meter_id, (void **)&nom,
+                                 sizeof *nom + sizeof *dmo);
+    if (ret) {
+        VLOG_INFO("netdev offload dpdk meter, can't get the meter");
+        return NULL;
     }
 
-    add_flow_action(actions, RTE_FLOW_ACTION_TYPE_RSS, &rss_data->conf);
+    if (!nom) {
+        return dpdk_meter_create(dpif, netdev, mid);
+    }
+
+    dmo = (struct dpdk_meter_offload *)nom->priv_data;
+    if (port_id != dmo->port_id) {
+        VLOG_INFO("dpdk meter %d is used on %d, can't be used for : %d",
+                  mid, dmo->port_id, port_id);
+        return NULL;
+    }
 
-    return rss_data;
+    return &dmo->mc;
 }
 
 static int
-netdev_offload_dpdk_add_flow(struct netdev *netdev,
+netdev_offload_dpdk_add_flow(struct dpif *dpif, struct netdev *netdev,
                              const struct match *match,
-                             struct nlattr *nl_actions OVS_UNUSED,
-                             size_t actions_len OVS_UNUSED,
+                             struct nlattr *nl_actions,
+                             size_t actions_len,
                              const ovs_u128 *ufid,
-                             struct offload_info *info)
+                             struct offload_info *info OVS_UNUSED)
 {
     const struct rte_flow_attr flow_attr = {
         .group = 0,
         .priority = 0,
         .ingress = 1,
-        .egress = 0
+        .egress = 0,
+        .transfer = 1,
     };
     struct flow_patterns patterns = { .items = NULL, .cnt = 0 };
     struct flow_actions actions = { .actions = NULL, .cnt = 0 };
@@ -583,20 +748,66 @@  netdev_offload_dpdk_add_flow(struct netdev *netdev,
 
     add_flow_pattern(&patterns, RTE_FLOW_ITEM_TYPE_END, NULL, NULL);
 
-    struct rte_flow_action_mark mark;
-    struct action_rss_data *rss;
+    const struct nlattr *a;
+    unsigned int left;
 
-    mark.id = info->flow_mark;
-    add_flow_action(&actions, RTE_FLOW_ACTION_TYPE_MARK, &mark);
+    if (!actions_len || !nl_actions) {
+        VLOG_INFO("%s: skip flow offload without actions\n", netdev_get_name(netdev));
+        ret = -1;
+        goto out;
+    }
+
+    NL_ATTR_FOR_EACH_UNSAFE (a, left, nl_actions, actions_len) {
+        int type = nl_attr_type(a);
+        switch ((enum ovs_action_attr) type) {
+        case OVS_ACTION_ATTR_METER: {
+            struct rte_flow_action_meter *mc;
+            mc = netdev_offload_dpdk_meter_conf(dpif,
+                                                netdev,
+                                                nl_attr_get_u32(a));
+            if (mc) {
+                add_flow_action(&actions, RTE_FLOW_ACTION_TYPE_METER, mc);
+            }
+            break;
+        }
+        case OVS_ACTION_ATTR_OUTPUT:
+        case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+        case OVS_ACTION_ATTR_PUSH_VLAN:
+        case OVS_ACTION_ATTR_POP_VLAN:
+        case OVS_ACTION_ATTR_UNSPEC:
+        case OVS_ACTION_ATTR_USERSPACE:
+        case OVS_ACTION_ATTR_SET:
+        case OVS_ACTION_ATTR_SAMPLE:
+        case OVS_ACTION_ATTR_RECIRC:
+        case OVS_ACTION_ATTR_HASH:
+        case OVS_ACTION_ATTR_PUSH_MPLS:
+        case OVS_ACTION_ATTR_POP_MPLS:
+        case OVS_ACTION_ATTR_SET_MASKED:
+        case OVS_ACTION_ATTR_CT:
+        case OVS_ACTION_ATTR_TRUNC:
+        case OVS_ACTION_ATTR_PUSH_ETH:
+        case OVS_ACTION_ATTR_POP_ETH:
+        case OVS_ACTION_ATTR_CT_CLEAR:
+        case OVS_ACTION_ATTR_PUSH_NSH:
+        case OVS_ACTION_ATTR_POP_NSH:
+        case OVS_ACTION_ATTR_TUNNEL_PUSH:
+        case OVS_ACTION_ATTR_TUNNEL_POP:
+        case OVS_ACTION_ATTR_CLONE:
+        case __OVS_ACTION_ATTR_MAX:
+        default:
+            VLOG_INFO("%s: the offload action %d is not supported yet.\n",
+                      netdev_get_name(netdev),
+                      type);
+            ret = -1;
+            goto out;
+        }
+    }
 
-    rss = add_flow_rss_action(&actions, netdev);
     add_flow_action(&actions, RTE_FLOW_ACTION_TYPE_END, NULL);
 
     flow = netdev_dpdk_rte_flow_create(netdev, &flow_attr,
                                        patterns.items,
                                        actions.actions, &error);
-
-    free(rss);
     if (!flow) {
         VLOG_ERR("%s: rte flow creat error: %u : message : %s\n",
                  netdev_get_name(netdev), error.type, error.message);
@@ -707,7 +918,7 @@  netdev_offload_dpdk_destroy_flow(struct netdev *netdev,
 }
 
 static int
-netdev_offload_dpdk_flow_put(struct dpif *dpif OVS_UNUSED, struct netdev *netdev,
+netdev_offload_dpdk_flow_put(struct dpif *dpif, struct netdev *netdev,
                              struct match *match, struct nlattr *actions,
                              size_t actions_len, const ovs_u128 *ufid,
                              struct offload_info *info,
@@ -736,7 +947,7 @@  netdev_offload_dpdk_flow_put(struct dpif *dpif OVS_UNUSED, struct netdev *netdev
     if (stats) {
         memset(stats, 0, sizeof *stats);
     }
-    return netdev_offload_dpdk_add_flow(netdev, match, actions,
+    return netdev_offload_dpdk_add_flow(dpif, netdev, match, actions,
                                         actions_len, ufid, info);
 }