datapath: collect mega flow mask stats
[sliver-openvswitch.git] / datapath / datapath.c
index 6b7cbc1..ebd2492 100644 (file)
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
 
-#include "checksum.h"
 #include "datapath.h"
 #include "flow.h"
+#include "flow_netlink.h"
 #include "vlan.h"
-#include "tunnel.h"
 #include "vport-internal_dev.h"
 #include "vport-netdev.h"
 
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) || \
-    LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
-#error Kernels before 2.6.18 or after 3.9 are not supported by this version of Open vSwitch.
-#endif
-
-#define REHASH_FLOW_INTERVAL (10 * 60 * HZ)
-static void rehash_flow_table(struct work_struct *work);
-static DECLARE_DELAYED_WORK(rehash_flow_wq, rehash_flow_table);
-
 int ovs_net_id __read_mostly;
 
 static void ovs_notify(struct sk_buff *skb, struct genl_info *info,
@@ -173,7 +163,7 @@ static void destroy_dp_rcu(struct rcu_head *rcu)
 {
        struct datapath *dp = container_of(rcu, struct datapath, rcu);
 
-       ovs_flow_tbl_destroy((__force struct flow_table *)dp->table, false);
+       ovs_flow_tbl_destroy(&dp->table);
        free_percpu(dp->stats_percpu);
        release_net(ovs_dp_get_net(dp));
        kfree(dp->ports);
@@ -233,6 +223,7 @@ void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
        struct dp_stats_percpu *stats;
        struct sw_flow_key key;
        u64 *stats_counter;
+       u32 n_mask_hit;
        int error;
 
        stats = this_cpu_ptr(dp->stats_percpu);
@@ -245,7 +236,7 @@ void ovs_dp_process_received_packet(struct vport *p, struct sk_buff *skb)
        }
 
        /* Look up flow. */
-       flow = ovs_flow_lookup(rcu_dereference(dp->table), &key);
+       flow = ovs_flow_tbl_lookup(&dp->table, &key, &n_mask_hit);
        if (unlikely(!flow)) {
                struct dp_upcall_info upcall;
 
@@ -270,6 +261,7 @@ out:
        /* Update datapath statistics. */
        u64_stats_update_begin(&stats->sync);
        (*stats_counter)++;
+       stats->n_mask_hit += n_mask_hit;
        u64_stats_update_end(&stats->sync);
 }
 
@@ -279,7 +271,8 @@ static struct genl_family dp_packet_genl_family = {
        .name = OVS_PACKET_FAMILY,
        .version = OVS_PACKET_VERSION,
        .maxattr = OVS_PACKET_ATTR_MAX,
-        SET_NETNSOK
+       .netnsok = true,
+        SET_PARALLEL_OPS
 };
 
 int ovs_dp_upcall(struct datapath *dp, struct sk_buff *skb,
@@ -300,8 +293,6 @@ int ovs_dp_upcall(struct datapath *dp, struct sk_buff *skb,
                goto err;
        }
 
-       forward_ip_summed(skb, true);
-
        if (!skb_is_gso(skb))
                err = queue_userspace_packet(ovs_dp_get_net(dp), dp_ifindex, skb, upcall_info);
        else
@@ -419,10 +410,12 @@ static int queue_userspace_packet(struct net *net, int dp_ifindex,
                nskb = skb_clone(skb, GFP_ATOMIC);
                if (!nskb)
                        return -ENOMEM;
-               
-               err = vlan_deaccel_tag(nskb);
-               if (err)
-                       return err;
+
+               nskb = __vlan_put_tag(nskb, nskb->vlan_proto, vlan_tx_tag_get(nskb));
+               if (!nskb)
+                       return -ENOMEM;
+
+               vlan_set_tci(nskb, 0);
 
                skb = nskb;
        }
@@ -443,7 +436,7 @@ static int queue_userspace_packet(struct net *net, int dp_ifindex,
        upcall->dp_ifindex = dp_ifindex;
 
        nla = nla_nest_start(user_skb, OVS_PACKET_ATTR_KEY);
-       ovs_flow_to_nlattrs(upcall_info->key, upcall_info->key, user_skb);
+       ovs_nla_put_flow(upcall_info->key, upcall_info->key, user_skb);
        nla_nest_end(user_skb, nla);
 
        if (upcall_info->userdata)
@@ -463,398 +456,6 @@ out:
        return err;
 }
 
-/* Called with ovs_mutex. */
-static int flush_flows(struct datapath *dp)
-{
-       struct flow_table *old_table;
-       struct flow_table *new_table;
-
-       old_table = ovsl_dereference(dp->table);
-       new_table = ovs_flow_tbl_alloc(TBL_MIN_BUCKETS);
-       if (!new_table)
-               return -ENOMEM;
-
-       rcu_assign_pointer(dp->table, new_table);
-
-       ovs_flow_tbl_destroy(old_table, true);
-       return 0;
-}
-
-static struct nlattr *reserve_sfa_size(struct sw_flow_actions **sfa, int attr_len)
-{
-
-       struct sw_flow_actions *acts;
-       int new_acts_size;
-       int req_size = NLA_ALIGN(attr_len);
-       int next_offset = offsetof(struct sw_flow_actions, actions) +
-                                       (*sfa)->actions_len;
-
-       if (req_size <= (ksize(*sfa) - next_offset))
-               goto out;
-
-       new_acts_size = ksize(*sfa) * 2;
-
-       if (new_acts_size > MAX_ACTIONS_BUFSIZE) {
-               if ((MAX_ACTIONS_BUFSIZE - next_offset) < req_size)
-                       return ERR_PTR(-EMSGSIZE);
-               new_acts_size = MAX_ACTIONS_BUFSIZE;
-       }
-
-       acts = ovs_flow_actions_alloc(new_acts_size);
-       if (IS_ERR(acts))
-               return (void *)acts;
-
-       memcpy(acts->actions, (*sfa)->actions, (*sfa)->actions_len);
-       acts->actions_len = (*sfa)->actions_len;
-       kfree(*sfa);
-       *sfa = acts;
-
-out:
-       (*sfa)->actions_len += req_size;
-       return  (struct nlattr *) ((unsigned char *)(*sfa) + next_offset);
-}
-
-static int add_action(struct sw_flow_actions **sfa, int attrtype, void *data, int len)
-{
-       struct nlattr *a;
-
-       a = reserve_sfa_size(sfa, nla_attr_size(len));
-       if (IS_ERR(a))
-               return PTR_ERR(a);
-
-       a->nla_type = attrtype;
-       a->nla_len = nla_attr_size(len);
-
-       if (data)
-               memcpy(nla_data(a), data, len);
-       memset((unsigned char *) a + a->nla_len, 0, nla_padlen(len));
-
-       return 0;
-}
-
-static inline int add_nested_action_start(struct sw_flow_actions **sfa, int attrtype)
-{
-       int used = (*sfa)->actions_len;
-       int err;
-
-       err = add_action(sfa, attrtype, NULL, 0);
-       if (err)
-               return err;
-
-       return used;
-}
-
-static inline void add_nested_action_end(struct sw_flow_actions *sfa, int st_offset)
-{
-       struct nlattr *a = (struct nlattr *) ((unsigned char *)sfa->actions + st_offset);
-
-       a->nla_len = sfa->actions_len - st_offset;
-}
-
-static int validate_and_copy_actions(const struct nlattr *attr,
-                               const struct sw_flow_key *key, int depth,
-                               struct sw_flow_actions **sfa);
-
-static int validate_and_copy_sample(const struct nlattr *attr,
-                          const struct sw_flow_key *key, int depth,
-                          struct sw_flow_actions **sfa)
-{
-       const struct nlattr *attrs[OVS_SAMPLE_ATTR_MAX + 1];
-       const struct nlattr *probability, *actions;
-       const struct nlattr *a;
-       int rem, start, err, st_acts;
-
-       memset(attrs, 0, sizeof(attrs));
-       nla_for_each_nested(a, attr, rem) {
-               int type = nla_type(a);
-               if (!type || type > OVS_SAMPLE_ATTR_MAX || attrs[type])
-                       return -EINVAL;
-               attrs[type] = a;
-       }
-       if (rem)
-               return -EINVAL;
-
-       probability = attrs[OVS_SAMPLE_ATTR_PROBABILITY];
-       if (!probability || nla_len(probability) != sizeof(u32))
-               return -EINVAL;
-
-       actions = attrs[OVS_SAMPLE_ATTR_ACTIONS];
-       if (!actions || (nla_len(actions) && nla_len(actions) < NLA_HDRLEN))
-               return -EINVAL;
-
-       /* validation done, copy sample action. */
-       start = add_nested_action_start(sfa, OVS_ACTION_ATTR_SAMPLE);
-       if (start < 0)
-               return start;
-       err = add_action(sfa, OVS_SAMPLE_ATTR_PROBABILITY, nla_data(probability), sizeof(u32));
-       if (err)
-               return err;
-       st_acts = add_nested_action_start(sfa, OVS_SAMPLE_ATTR_ACTIONS);
-       if (st_acts < 0)
-               return st_acts;
-
-       err = validate_and_copy_actions(actions, key, depth + 1, sfa);
-       if (err)
-               return err;
-
-       add_nested_action_end(*sfa, st_acts);
-       add_nested_action_end(*sfa, start);
-
-       return 0;
-}
-
-static int validate_tp_port(const struct sw_flow_key *flow_key)
-{
-       if (flow_key->eth.type == htons(ETH_P_IP)) {
-               if (flow_key->ipv4.tp.src || flow_key->ipv4.tp.dst)
-                       return 0;
-       } else if (flow_key->eth.type == htons(ETH_P_IPV6)) {
-               if (flow_key->ipv6.tp.src || flow_key->ipv6.tp.dst)
-                       return 0;
-       }
-
-       return -EINVAL;
-}
-
-static int validate_and_copy_set_tun(const struct nlattr *attr,
-                                    struct sw_flow_actions **sfa)
-{
-       struct sw_flow_match match;
-       struct sw_flow_key key;
-       int err, start;
-
-       ovs_match_init(&match, &key, NULL);
-       err = ovs_ipv4_tun_from_nlattr(nla_data(attr), &match, false);
-       if (err)
-               return err;
-
-       start = add_nested_action_start(sfa, OVS_ACTION_ATTR_SET);
-       if (start < 0)
-               return start;
-
-       err = add_action(sfa, OVS_KEY_ATTR_IPV4_TUNNEL, &match.key->tun_key,
-                       sizeof(match.key->tun_key));
-       add_nested_action_end(*sfa, start);
-
-       return err;
-}
-
-static int validate_set(const struct nlattr *a,
-                       const struct sw_flow_key *flow_key,
-                       struct sw_flow_actions **sfa,
-                       bool *set_tun)
-{
-       const struct nlattr *ovs_key = nla_data(a);
-       int key_type = nla_type(ovs_key);
-
-       /* There can be only one key in a action */
-       if (nla_total_size(nla_len(ovs_key)) != nla_len(a))
-               return -EINVAL;
-
-       if (key_type > OVS_KEY_ATTR_MAX ||
-           (ovs_key_lens[key_type] != nla_len(ovs_key) &&
-            ovs_key_lens[key_type] != -1))
-               return -EINVAL;
-
-       switch (key_type) {
-       const struct ovs_key_ipv4 *ipv4_key;
-       const struct ovs_key_ipv6 *ipv6_key;
-       int err;
-
-       case OVS_KEY_ATTR_PRIORITY:
-       case OVS_KEY_ATTR_ETHERNET:
-               break;
-
-       case OVS_KEY_ATTR_SKB_MARK:
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) && !defined(CONFIG_NETFILTER)
-               if (nla_get_u32(ovs_key) != 0)
-                       return -EINVAL;
-#endif
-               break;
-
-       case OVS_KEY_ATTR_TUNNEL:
-               *set_tun = true;
-               err = validate_and_copy_set_tun(a, sfa);
-               if (err)
-                       return err;
-               break;
-
-       case OVS_KEY_ATTR_IPV4:
-               if (flow_key->eth.type != htons(ETH_P_IP))
-                       return -EINVAL;
-
-               if (!flow_key->ip.proto)
-                       return -EINVAL;
-
-               ipv4_key = nla_data(ovs_key);
-               if (ipv4_key->ipv4_proto != flow_key->ip.proto)
-                       return -EINVAL;
-
-               if (ipv4_key->ipv4_frag != flow_key->ip.frag)
-                       return -EINVAL;
-
-               break;
-
-       case OVS_KEY_ATTR_IPV6:
-               if (flow_key->eth.type != htons(ETH_P_IPV6))
-                       return -EINVAL;
-
-               if (!flow_key->ip.proto)
-                       return -EINVAL;
-
-               ipv6_key = nla_data(ovs_key);
-               if (ipv6_key->ipv6_proto != flow_key->ip.proto)
-                       return -EINVAL;
-
-               if (ipv6_key->ipv6_frag != flow_key->ip.frag)
-                       return -EINVAL;
-
-               if (ntohl(ipv6_key->ipv6_label) & 0xFFF00000)
-                       return -EINVAL;
-
-               break;
-
-       case OVS_KEY_ATTR_TCP:
-               if (flow_key->ip.proto != IPPROTO_TCP)
-                       return -EINVAL;
-
-               return validate_tp_port(flow_key);
-
-       case OVS_KEY_ATTR_UDP:
-               if (flow_key->ip.proto != IPPROTO_UDP)
-                       return -EINVAL;
-
-               return validate_tp_port(flow_key);
-
-       default:
-               return -EINVAL;
-       }
-
-       return 0;
-}
-
-static int validate_userspace(const struct nlattr *attr)
-{
-       static const struct nla_policy userspace_policy[OVS_USERSPACE_ATTR_MAX + 1] =   {
-               [OVS_USERSPACE_ATTR_PID] = {.type = NLA_U32 },
-               [OVS_USERSPACE_ATTR_USERDATA] = {.type = NLA_UNSPEC },
-       };
-       struct nlattr *a[OVS_USERSPACE_ATTR_MAX + 1];
-       int error;
-
-       error = nla_parse_nested(a, OVS_USERSPACE_ATTR_MAX,
-                                attr, userspace_policy);
-       if (error)
-               return error;
-
-       if (!a[OVS_USERSPACE_ATTR_PID] ||
-           !nla_get_u32(a[OVS_USERSPACE_ATTR_PID]))
-               return -EINVAL;
-
-       return 0;
-}
-
-static int copy_action(const struct nlattr *from,
-                     struct sw_flow_actions **sfa)
-{
-       int totlen = NLA_ALIGN(from->nla_len);
-       struct nlattr *to;
-
-       to = reserve_sfa_size(sfa, from->nla_len);
-       if (IS_ERR(to))
-               return PTR_ERR(to);
-
-       memcpy(to, from, totlen);
-       return 0;
-}
-
-static int validate_and_copy_actions(const struct nlattr *attr,
-                               const struct sw_flow_key *key,
-                               int depth,
-                               struct sw_flow_actions **sfa)
-{
-       const struct nlattr *a;
-       int rem, err;
-
-       if (depth >= SAMPLE_ACTION_DEPTH)
-               return -EOVERFLOW;
-
-       nla_for_each_nested(a, attr, rem) {
-               /* Expected argument lengths, (u32)-1 for variable length. */
-               static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
-                       [OVS_ACTION_ATTR_OUTPUT] = sizeof(u32),
-                       [OVS_ACTION_ATTR_USERSPACE] = (u32)-1,
-                       [OVS_ACTION_ATTR_PUSH_VLAN] = sizeof(struct ovs_action_push_vlan),
-                       [OVS_ACTION_ATTR_POP_VLAN] = 0,
-                       [OVS_ACTION_ATTR_SET] = (u32)-1,
-                       [OVS_ACTION_ATTR_SAMPLE] = (u32)-1
-               };
-               const struct ovs_action_push_vlan *vlan;
-               int type = nla_type(a);
-               bool skip_copy;
-
-               if (type > OVS_ACTION_ATTR_MAX ||
-                   (action_lens[type] != nla_len(a) &&
-                    action_lens[type] != (u32)-1))
-                       return -EINVAL;
-
-               skip_copy = false;
-               switch (type) {
-               case OVS_ACTION_ATTR_UNSPEC:
-                       return -EINVAL;
-
-               case OVS_ACTION_ATTR_USERSPACE:
-                       err = validate_userspace(a);
-                       if (err)
-                               return err;
-                       break;
-
-               case OVS_ACTION_ATTR_OUTPUT:
-                       if (nla_get_u32(a) >= DP_MAX_PORTS)
-                               return -EINVAL;
-                       break;
-
-
-               case OVS_ACTION_ATTR_POP_VLAN:
-                       break;
-
-               case OVS_ACTION_ATTR_PUSH_VLAN:
-                       vlan = nla_data(a);
-                       if (vlan->vlan_tpid != htons(ETH_P_8021Q))
-                               return -EINVAL;
-                       if (!(vlan->vlan_tci & htons(VLAN_TAG_PRESENT)))
-                               return -EINVAL;
-                       break;
-
-               case OVS_ACTION_ATTR_SET:
-                       err = validate_set(a, key, sfa, &skip_copy);
-                       if (err)
-                               return err;
-                       break;
-
-               case OVS_ACTION_ATTR_SAMPLE:
-                       err = validate_and_copy_sample(a, key, depth, sfa);
-                       if (err)
-                               return err;
-                       skip_copy = true;
-                       break;
-
-               default:
-                       return -EINVAL;
-               }
-               if (!skip_copy) {
-                       err = copy_action(a, sfa);
-                       if (err)
-                               return err;
-               }
-       }
-
-       if (rem > 0)
-               return -EINVAL;
-
-       return 0;
-}
-
 static void clear_stats(struct sw_flow *flow)
 {
        flow->used = 0;
@@ -910,15 +511,16 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
        if (err)
                goto err_flow_free;
 
-       err = ovs_flow_metadata_from_nlattrs(flow, a[OVS_PACKET_ATTR_KEY]);
+       err = ovs_nla_get_flow_metadata(flow, a[OVS_PACKET_ATTR_KEY]);
        if (err)
                goto err_flow_free;
-       acts = ovs_flow_actions_alloc(nla_len(a[OVS_PACKET_ATTR_ACTIONS]));
+       acts = ovs_nla_alloc_flow_actions(nla_len(a[OVS_PACKET_ATTR_ACTIONS]));
        err = PTR_ERR(acts);
        if (IS_ERR(acts))
                goto err_flow_free;
 
-       err = validate_and_copy_actions(a[OVS_PACKET_ATTR_ACTIONS], &flow->key, 0, &acts);
+       err = ovs_nla_copy_actions(a[OVS_PACKET_ATTR_ACTIONS],
+                                  &flow->key, 0, &acts);
        rcu_assign_pointer(flow->sf_acts, acts);
        if (err)
                goto err_flow_free;
@@ -926,7 +528,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)
        OVS_CB(packet)->flow = flow;
        OVS_CB(packet)->pkt_key = &flow->key;
        packet->priority = flow->key.phy.priority;
-       skb_set_mark(packet, flow->key.phy.skb_mark);
+       packet->mark = flow->key.phy.skb_mark;
 
        rcu_read_lock();
        dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
@@ -953,11 +555,7 @@ err:
 }
 
 static const struct nla_policy packet_policy[OVS_PACKET_ATTR_MAX + 1] = {
-#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)
        [OVS_PACKET_ATTR_PACKET] = { .len = ETH_HLEN },
-#else
-       [OVS_PACKET_ATTR_PACKET] = { .minlen = ETH_HLEN },
-#endif
        [OVS_PACKET_ATTR_KEY] = { .type = NLA_NESTED },
        [OVS_PACKET_ATTR_ACTIONS] = { .type = NLA_NESTED },
 };
@@ -970,15 +568,18 @@ static struct genl_ops dp_packet_genl_ops[] = {
        }
 };
 
-static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats)
+static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats,
+                        struct ovs_dp_megaflow_stats *mega_stats)
 {
-       struct flow_table *table;
        int i;
 
-       table = rcu_dereference_check(dp->table, lockdep_ovsl_is_held());
-       stats->n_flows = ovs_flow_tbl_count(table);
+       memset(mega_stats, 0, sizeof(*mega_stats));
+
+       stats->n_flows = ovs_flow_tbl_count(&dp->table);
+       mega_stats->n_masks = ovs_flow_tbl_num_masks(&dp->table);
 
        stats->n_hit = stats->n_missed = stats->n_lost = 0;
+
        for_each_possible_cpu(i) {
                const struct dp_stats_percpu *percpu_stats;
                struct dp_stats_percpu local_stats;
@@ -994,6 +595,7 @@ static void get_dp_stats(struct datapath *dp, struct ovs_dp_stats *stats)
                stats->n_hit += local_stats.n_hit;
                stats->n_missed += local_stats.n_missed;
                stats->n_lost += local_stats.n_lost;
+               mega_stats->n_mask_hit += local_stats.n_mask_hit;
        }
 }
 
@@ -1009,107 +611,14 @@ static struct genl_family dp_flow_genl_family = {
        .name = OVS_FLOW_FAMILY,
        .version = OVS_FLOW_VERSION,
        .maxattr = OVS_FLOW_ATTR_MAX,
-        SET_NETNSOK
+       .netnsok = true,
+        SET_PARALLEL_OPS
 };
 
 static struct genl_multicast_group ovs_dp_flow_multicast_group = {
        .name = OVS_FLOW_MCGROUP
 };
 
-static int actions_to_attr(const struct nlattr *attr, int len, struct sk_buff *skb);
-static int sample_action_to_attr(const struct nlattr *attr, struct sk_buff *skb)
-{
-       const struct nlattr *a;
-       struct nlattr *start;
-       int err = 0, rem;
-
-       start = nla_nest_start(skb, OVS_ACTION_ATTR_SAMPLE);
-       if (!start)
-               return -EMSGSIZE;
-
-       nla_for_each_nested(a, attr, rem) {
-               int type = nla_type(a);
-               struct nlattr *st_sample;
-
-               switch (type) {
-               case OVS_SAMPLE_ATTR_PROBABILITY:
-                       if (nla_put(skb, OVS_SAMPLE_ATTR_PROBABILITY, sizeof(u32), nla_data(a)))
-                               return -EMSGSIZE;
-                       break;
-               case OVS_SAMPLE_ATTR_ACTIONS:
-                       st_sample = nla_nest_start(skb, OVS_SAMPLE_ATTR_ACTIONS);
-                       if (!st_sample)
-                               return -EMSGSIZE;
-                       err = actions_to_attr(nla_data(a), nla_len(a), skb);
-                       if (err)
-                               return err;
-                       nla_nest_end(skb, st_sample);
-                       break;
-               }
-       }
-
-       nla_nest_end(skb, start);
-       return err;
-}
-
-static int set_action_to_attr(const struct nlattr *a, struct sk_buff *skb)
-{
-       const struct nlattr *ovs_key = nla_data(a);
-       int key_type = nla_type(ovs_key);
-       struct nlattr *start;
-       int err;
-
-       switch (key_type) {
-       case OVS_KEY_ATTR_IPV4_TUNNEL:
-               start = nla_nest_start(skb, OVS_ACTION_ATTR_SET);
-               if (!start)
-                       return -EMSGSIZE;
-
-               err = ovs_ipv4_tun_to_nlattr(skb, nla_data(ovs_key),
-                                            nla_data(ovs_key));
-               if (err)
-                       return err;
-               nla_nest_end(skb, start);
-               break;
-       default:
-               if (nla_put(skb, OVS_ACTION_ATTR_SET, nla_len(a), ovs_key))
-                       return -EMSGSIZE;
-               break;
-       }
-
-       return 0;
-}
-
-static int actions_to_attr(const struct nlattr *attr, int len, struct sk_buff *skb)
-{
-       const struct nlattr *a;
-       int rem, err;
-
-       nla_for_each_attr(a, attr, len, rem) {
-               int type = nla_type(a);
-
-               switch (type) {
-               case OVS_ACTION_ATTR_SET:
-                       err = set_action_to_attr(a, skb);
-                       if (err)
-                               return err;
-                       break;
-
-               case OVS_ACTION_ATTR_SAMPLE:
-                       err = sample_action_to_attr(a, skb);
-                       if (err)
-                               return err;
-                       break;
-               default:
-                       if (nla_put(skb, type, nla_len(a), nla_data(a)))
-                               return -EMSGSIZE;
-                       break;
-               }
-       }
-
-       return 0;
-}
-
 static size_t ovs_flow_cmd_msg_size(const struct sw_flow_actions *acts)
 {
        return NLMSG_ALIGN(sizeof(struct ovs_header))
@@ -1146,8 +655,7 @@ static int ovs_flow_cmd_fill_info(struct sw_flow *flow, struct datapath *dp,
        if (!nla)
                goto nla_put_failure;
 
-       err = ovs_flow_to_nlattrs(&flow->unmasked_key,
-                       &flow->unmasked_key, skb);
+       err = ovs_nla_put_flow(&flow->unmasked_key, &flow->unmasked_key, skb);
        if (err)
                goto error;
        nla_nest_end(skb, nla);
@@ -1156,7 +664,7 @@ static int ovs_flow_cmd_fill_info(struct sw_flow *flow, struct datapath *dp,
        if (!nla)
                goto nla_put_failure;
 
-       err = ovs_flow_to_nlattrs(&flow->key, &flow->mask->key, skb);
+       err = ovs_nla_put_flow(&flow->key, &flow->mask->key, skb);
        if (err)
                goto error;
 
@@ -1199,7 +707,8 @@ static int ovs_flow_cmd_fill_info(struct sw_flow *flow, struct datapath *dp,
                sf_acts = rcu_dereference_check(flow->sf_acts,
                                                lockdep_ovsl_is_held());
 
-               err = actions_to_attr(sf_acts->actions, sf_acts->actions_len, skb);
+               err = ovs_nla_put_actions(sf_acts->actions,
+                                         sf_acts->actions_len, skb);
                if (!err)
                        nla_nest_end(skb, start);
                else {
@@ -1245,6 +754,14 @@ static struct sk_buff *ovs_flow_cmd_build_info(struct sw_flow *flow,
        return skb;
 }
 
+static struct sw_flow *__ovs_flow_tbl_lookup(struct flow_table *tbl,
+                                             const struct sw_flow_key *key)
+{
+       u32 __always_unused n_mask_hit;
+
+       return ovs_flow_tbl_lookup(tbl, key, &n_mask_hit);
+}
+
 static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
 {
        struct nlattr **a = info->attrs;
@@ -1254,7 +771,6 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
        struct sw_flow_mask mask;
        struct sk_buff *reply;
        struct datapath *dp;
-       struct flow_table *table;
        struct sw_flow_actions *acts = NULL;
        struct sw_flow_match match;
        int error;
@@ -1265,21 +781,21 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
                goto error;
 
        ovs_match_init(&match, &key, &mask);
-       error = ovs_match_from_nlattrs(&match,
-                       a[OVS_FLOW_ATTR_KEY], a[OVS_FLOW_ATTR_MASK]);
+       error = ovs_nla_get_match(&match,
+                                 a[OVS_FLOW_ATTR_KEY], a[OVS_FLOW_ATTR_MASK]);
        if (error)
                goto error;
 
        /* Validate actions. */
        if (a[OVS_FLOW_ATTR_ACTIONS]) {
-               acts = ovs_flow_actions_alloc(nla_len(a[OVS_FLOW_ATTR_ACTIONS]));
+               acts = ovs_nla_alloc_flow_actions(nla_len(a[OVS_FLOW_ATTR_ACTIONS]));
                error = PTR_ERR(acts);
                if (IS_ERR(acts))
                        goto error;
 
-               ovs_flow_key_mask(&masked_key, &key, &mask);
-               error = validate_and_copy_actions(a[OVS_FLOW_ATTR_ACTIONS],
-                                                 &masked_key, 0, &acts);
+               ovs_flow_mask_key(&masked_key, &key, &mask);
+               error = ovs_nla_copy_actions(a[OVS_FLOW_ATTR_ACTIONS],
+                                            &masked_key, 0, &acts);
                if (error) {
                        OVS_NLERR("Flow actions may not be safe on all matching packets.\n");
                        goto err_kfree;
@@ -1295,29 +811,14 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
        if (!dp)
                goto err_unlock_ovs;
 
-       table = ovsl_dereference(dp->table);
-
        /* Check if this is a duplicate flow */
-       flow = ovs_flow_lookup(table, &key);
+       flow = __ovs_flow_tbl_lookup(&dp->table, &key);
        if (!flow) {
-               struct sw_flow_mask *mask_p;
                /* Bail out if we're not allowed to create a new flow. */
                error = -ENOENT;
                if (info->genlhdr->cmd == OVS_FLOW_CMD_SET)
                        goto err_unlock_ovs;
 
-               /* Expand table, if necessary, to make room. */
-               if (ovs_flow_tbl_need_to_expand(table)) {
-                       struct flow_table *new_table;
-
-                       new_table = ovs_flow_tbl_expand(table);
-                       if (!IS_ERR(new_table)) {
-                               rcu_assign_pointer(dp->table, new_table);
-                               ovs_flow_tbl_destroy(table, true);
-                               table = ovsl_dereference(dp->table);
-                       }
-               }
-
                /* Allocate flow. */
                flow = ovs_flow_alloc();
                if (IS_ERR(flow)) {
@@ -1328,25 +829,14 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
 
                flow->key = masked_key;
                flow->unmasked_key = key;
-
-               /* Make sure mask is unique in the system */
-               mask_p = ovs_sw_flow_mask_find(table, &mask);
-               if (!mask_p) {
-                       /* Allocate a new mask if none exsits. */
-                       mask_p = ovs_sw_flow_mask_alloc();
-                       if (!mask_p)
-                               goto err_flow_free;
-                       mask_p->key = mask.key;
-                       mask_p->range = mask.range;
-                       ovs_sw_flow_mask_insert(table, mask_p);
-               }
-
-               ovs_sw_flow_mask_add_ref(mask_p);
-               flow->mask = mask_p;
                rcu_assign_pointer(flow->sf_acts, acts);
 
                /* Put flow in bucket. */
-               ovs_flow_insert(table, flow);
+               error = ovs_flow_tbl_insert(&dp->table, flow, &mask);
+               if (error) {
+                       acts = NULL;
+                       goto err_flow_free;
+               }
 
                reply = ovs_flow_cmd_build_info(flow, dp, info->snd_portid,
                                                info->snd_seq, OVS_FLOW_CMD_NEW);
@@ -1367,7 +857,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
 
                /* The unmasked key has to be the same for flow updates. */
                error = -EINVAL;
-               if (!ovs_flow_cmp_unmasked_key(flow, &key, match.range.end)) {
+               if (!ovs_flow_cmp_unmasked_key(flow, &match)) {
                        OVS_NLERR("Flow modification message rejected, unmasked key does not match.\n");
                        goto err_unlock_ovs;
                }
@@ -1375,7 +865,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
                /* Update actions. */
                old_acts = ovsl_dereference(flow->sf_acts);
                rcu_assign_pointer(flow->sf_acts, acts);
-               ovs_flow_deferred_free_acts(old_acts);
+               ovs_nla_free_flow_actions(old_acts);
 
                reply = ovs_flow_cmd_build_info(flow, dp, info->snd_portid,
                                               info->snd_seq, OVS_FLOW_CMD_NEW);
@@ -1392,7 +882,7 @@ static int ovs_flow_cmd_new_or_set(struct sk_buff *skb, struct genl_info *info)
        if (!IS_ERR(reply))
                ovs_notify(reply, info, &ovs_dp_flow_multicast_group);
        else
-               netlink_set_err(GENL_SOCK(sock_net(skb->sk)), 0,
+               netlink_set_err(sock_net(skb->sk)->genl_sock, 0,
                                ovs_dp_flow_multicast_group.id, PTR_ERR(reply));
        return 0;
 
@@ -1414,7 +904,6 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
        struct sk_buff *reply;
        struct sw_flow *flow;
        struct datapath *dp;
-       struct flow_table *table;
        struct sw_flow_match match;
        int err;
 
@@ -1424,7 +913,7 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
        }
 
        ovs_match_init(&match, &key, NULL);
-       err = ovs_match_from_nlattrs(&match, a[OVS_FLOW_ATTR_KEY], NULL);
+       err = ovs_nla_get_match(&match, a[OVS_FLOW_ATTR_KEY], NULL);
        if (err)
                return err;
 
@@ -1435,9 +924,8 @@ static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info *info)
                goto unlock;
        }
 
-       table = ovsl_dereference(dp->table);
-       flow = ovs_flow_lookup_unmasked_key(table, &match);
-       if (!flow) {
+       flow = __ovs_flow_tbl_lookup(&dp->table, &key);
+       if (!flow || !ovs_flow_cmp_unmasked_key(flow, &match)) {
                err = -ENOENT;
                goto unlock;
        }
@@ -1464,7 +952,6 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
        struct sk_buff *reply;
        struct sw_flow *flow;
        struct datapath *dp;
-       struct flow_table *table;
        struct sw_flow_match match;
        int err;
 
@@ -1476,18 +963,17 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
        }
 
        if (!a[OVS_FLOW_ATTR_KEY]) {
-               err = flush_flows(dp);
+               err = ovs_flow_tbl_flush(&dp->table);
                goto unlock;
        }
 
        ovs_match_init(&match, &key, NULL);
-       err = ovs_match_from_nlattrs(&match, a[OVS_FLOW_ATTR_KEY], NULL);
+       err = ovs_nla_get_match(&match, a[OVS_FLOW_ATTR_KEY], NULL);
        if (err)
                goto unlock;
 
-       table = ovsl_dereference(dp->table);
-       flow = ovs_flow_lookup_unmasked_key(table, &match);
-       if (!flow) {
+       flow = __ovs_flow_tbl_lookup(&dp->table, &key);
+       if (!flow || !ovs_flow_cmp_unmasked_key(flow, &match)) {
                err = -ENOENT;
                goto unlock;
        }
@@ -1498,7 +984,7 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info)
                goto unlock;
        }
 
-       ovs_flow_remove(table, flow);
+       ovs_flow_tbl_remove(&dp->table, flow);
 
        err = ovs_flow_cmd_fill_info(flow, dp, reply, info->snd_portid,
                                     info->snd_seq, 0, OVS_FLOW_CMD_DEL);
@@ -1517,8 +1003,8 @@ unlock:
 static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct ovs_header *ovs_header = genlmsg_data(nlmsg_data(cb->nlh));
+       struct table_instance *ti;
        struct datapath *dp;
-       struct flow_table *table;
 
        rcu_read_lock();
        dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
@@ -1527,14 +1013,14 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb)
                return -ENODEV;
        }
 
-       table = rcu_dereference(dp->table);
+       ti = rcu_dereference(dp->table.ti);
        for (;;) {
                struct sw_flow *flow;
                u32 bucket, obj;
 
                bucket = cb->args[0];
                obj = cb->args[1];
-               flow = ovs_flow_dump_next(table, &bucket, &obj);
+               flow = ovs_flow_tbl_dump_next(ti, &bucket, &obj);
                if (!flow)
                        break;
 
@@ -1576,9 +1062,7 @@ static struct genl_ops dp_flow_genl_ops[] = {
 };
 
 static const struct nla_policy datapath_policy[OVS_DP_ATTR_MAX + 1] = {
-#ifdef HAVE_NLA_NUL_STRING
        [OVS_DP_ATTR_NAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ - 1 },
-#endif
        [OVS_DP_ATTR_UPCALL_PID] = { .type = NLA_U32 },
 };
 
@@ -1588,7 +1072,8 @@ static struct genl_family dp_datapath_genl_family = {
        .name = OVS_DATAPATH_FAMILY,
        .version = OVS_DATAPATH_VERSION,
        .maxattr = OVS_DP_ATTR_MAX,
-        SET_NETNSOK
+       .netnsok = true,
+        SET_PARALLEL_OPS
 };
 
 static struct genl_multicast_group ovs_dp_datapath_multicast_group = {
@@ -1601,6 +1086,7 @@ static size_t ovs_dp_cmd_msg_size(void)
 
        msgsize += nla_total_size(IFNAMSIZ);
        msgsize += nla_total_size(sizeof(struct ovs_dp_stats));
+       msgsize += nla_total_size(sizeof(struct ovs_dp_megaflow_stats));
 
        return msgsize;
 }
@@ -1610,6 +1096,7 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
 {
        struct ovs_header *ovs_header;
        struct ovs_dp_stats dp_stats;
+       struct ovs_dp_megaflow_stats dp_megaflow_stats;
        int err;
 
        ovs_header = genlmsg_put(skb, portid, seq, &dp_datapath_genl_family,
@@ -1625,8 +1112,14 @@ static int ovs_dp_cmd_fill_info(struct datapath *dp, struct sk_buff *skb,
        if (err)
                goto nla_put_failure;
 
-       get_dp_stats(dp, &dp_stats);
-       if (nla_put(skb, OVS_DP_ATTR_STATS, sizeof(struct ovs_dp_stats), &dp_stats))
+       get_dp_stats(dp, &dp_stats, &dp_megaflow_stats);
+       if (nla_put(skb, OVS_DP_ATTR_STATS, sizeof(struct ovs_dp_stats),
+                       &dp_stats))
+               goto nla_put_failure;
+
+       if (nla_put(skb, OVS_DP_ATTR_MEGAFLOW_STATS,
+                       sizeof(struct ovs_dp_megaflow_stats),
+                       &dp_megaflow_stats))
                goto nla_put_failure;
 
        return genlmsg_end(skb, ovs_header);
@@ -1655,11 +1148,6 @@ static struct sk_buff *ovs_dp_cmd_build_info(struct datapath *dp, u32 portid,
        return skb;
 }
 
-static int ovs_dp_cmd_validate(struct nlattr *a[OVS_DP_ATTR_MAX + 1])
-{
-       return CHECK_NUL_STRING(a[OVS_DP_ATTR_NAME], IFNAMSIZ - 1);
-}
-
 /* Called with ovs_mutex. */
 static struct datapath *lookup_datapath(struct net *net,
                                        struct ovs_header *ovs_header,
@@ -1694,10 +1182,6 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
        if (!a[OVS_DP_ATTR_NAME] || !a[OVS_DP_ATTR_UPCALL_PID])
                goto err;
 
-       err = ovs_dp_cmd_validate(a);
-       if (err)
-               goto err;
-
        ovs_lock();
 
        err = -ENOMEM;
@@ -1708,9 +1192,8 @@ static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
        ovs_dp_set_net(dp, hold_net(sock_net(skb->sk)));
 
        /* Allocate table. */
-       err = -ENOMEM;
-       rcu_assign_pointer(dp->table, ovs_flow_tbl_alloc(TBL_MIN_BUCKETS));
-       if (!dp->table)
+       err = ovs_flow_tbl_init(&dp->table);
+       if (err)
                goto err_free_dp;
 
        dp->stats_percpu = alloc_percpu(struct dp_stats_percpu);
@@ -1767,7 +1250,7 @@ err_destroy_ports_array:
 err_destroy_percpu:
        free_percpu(dp->stats_percpu);
 err_destroy_table:
-       ovs_flow_tbl_destroy(ovsl_dereference(dp->table), false);
+       ovs_flow_tbl_destroy(&dp->table);
 err_free_dp:
        release_net(ovs_dp_get_net(dp));
        kfree(dp);
@@ -1807,10 +1290,6 @@ static int ovs_dp_cmd_del(struct sk_buff *skb, struct genl_info *info)
        struct datapath *dp;
        int err;
 
-       err = ovs_dp_cmd_validate(info->attrs);
-       if (err)
-               return err;
-
        ovs_lock();
        dp = lookup_datapath(sock_net(skb->sk), info->userhdr, info->attrs);
        err = PTR_ERR(dp);
@@ -1840,10 +1319,6 @@ static int ovs_dp_cmd_set(struct sk_buff *skb, struct genl_info *info)
        struct datapath *dp;
        int err;
 
-       err = ovs_dp_cmd_validate(info->attrs);
-       if (err)
-               return err;
-
        ovs_lock();
        dp = lookup_datapath(sock_net(skb->sk), info->userhdr, info->attrs);
        err = PTR_ERR(dp);
@@ -1854,7 +1329,7 @@ static int ovs_dp_cmd_set(struct sk_buff *skb, struct genl_info *info)
                                      info->snd_seq, OVS_DP_CMD_NEW);
        if (IS_ERR(reply)) {
                err = PTR_ERR(reply);
-               netlink_set_err(GENL_SOCK(sock_net(skb->sk)), 0,
+               netlink_set_err(sock_net(skb->sk)->genl_sock, 0,
                                ovs_dp_datapath_multicast_group.id, err);
                err = 0;
                goto unlock;
@@ -1875,10 +1350,6 @@ static int ovs_dp_cmd_get(struct sk_buff *skb, struct genl_info *info)
        struct datapath *dp;
        int err;
 
-       err = ovs_dp_cmd_validate(info->attrs);
-       if (err)
-               return err;
-
        ovs_lock();
        dp = lookup_datapath(sock_net(skb->sk), info->userhdr, info->attrs);
        if (IS_ERR(dp)) {
@@ -1949,12 +1420,8 @@ static struct genl_ops dp_datapath_genl_ops[] = {
 };
 
 static const struct nla_policy vport_policy[OVS_VPORT_ATTR_MAX + 1] = {
-#ifdef HAVE_NLA_NUL_STRING
        [OVS_VPORT_ATTR_NAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ - 1 },
        [OVS_VPORT_ATTR_STATS] = { .len = sizeof(struct ovs_vport_stats) },
-#else
-       [OVS_VPORT_ATTR_STATS] = { .minlen = sizeof(struct ovs_vport_stats) },
-#endif
        [OVS_VPORT_ATTR_PORT_NO] = { .type = NLA_U32 },
        [OVS_VPORT_ATTR_TYPE] = { .type = NLA_U32 },
        [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NLA_U32 },
@@ -1967,7 +1434,8 @@ static struct genl_family dp_vport_genl_family = {
        .name = OVS_VPORT_FAMILY,
        .version = OVS_VPORT_VERSION,
        .maxattr = OVS_VPORT_ATTR_MAX,
-        SET_NETNSOK
+       .netnsok = true,
+        SET_PARALLEL_OPS
 };
 
 struct genl_multicast_group ovs_dp_vport_multicast_group = {
@@ -2030,11 +1498,6 @@ struct sk_buff *ovs_vport_cmd_build_info(struct vport *vport, u32 portid,
        return skb;
 }
 
-static int ovs_vport_cmd_validate(struct nlattr *a[OVS_VPORT_ATTR_MAX + 1])
-{
-       return CHECK_NUL_STRING(a[OVS_VPORT_ATTR_NAME], IFNAMSIZ - 1);
-}
-
 /* Called with ovs_mutex or RCU read lock. */
 static struct vport *lookup_vport(struct net *net,
                                  struct ovs_header *ovs_header,
@@ -2085,10 +1548,6 @@ static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info *info)
            !a[OVS_VPORT_ATTR_UPCALL_PID])
                goto exit;
 
-       err = ovs_vport_cmd_validate(a);
-       if (err)
-               goto exit;
-
        ovs_lock();
        dp = get_dp(sock_net(skb->sk), ovs_header->dp_ifindex);
        err = -ENODEV;
@@ -2157,10 +1616,6 @@ static int ovs_vport_cmd_set(struct sk_buff *skb, struct genl_info *info)
        struct vport *vport;
        int err;
 
-       err = ovs_vport_cmd_validate(a);
-       if (err)
-               goto exit;
-
        ovs_lock();
        vport = lookup_vport(sock_net(skb->sk), info->userhdr, a);
        err = PTR_ERR(vport);
@@ -2203,7 +1658,6 @@ exit_free:
        kfree_skb(reply);
 exit_unlock:
        ovs_unlock();
-exit:
        return err;
 }
 
@@ -2214,10 +1668,6 @@ static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info *info)
        struct vport *vport;
        int err;
 
-       err = ovs_vport_cmd_validate(a);
-       if (err)
-               goto exit;
-
        ovs_lock();
        vport = lookup_vport(sock_net(skb->sk), info->userhdr, a);
        err = PTR_ERR(vport);
@@ -2242,7 +1692,6 @@ static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info *info)
 
 exit_unlock:
        ovs_unlock();
-exit:
        return err;
 }
 
@@ -2254,10 +1703,6 @@ static int ovs_vport_cmd_get(struct sk_buff *skb, struct genl_info *info)
        struct vport *vport;
        int err;
 
-       err = ovs_vport_cmd_validate(a);
-       if (err)
-               goto exit;
-
        rcu_read_lock();
        vport = lookup_vport(sock_net(skb->sk), ovs_header, a);
        err = PTR_ERR(vport);
@@ -2276,7 +1721,6 @@ static int ovs_vport_cmd_get(struct sk_buff *skb, struct genl_info *info)
 
 exit_unlock:
        rcu_read_unlock();
-exit:
        return err;
 }
 
@@ -2402,32 +1846,6 @@ error:
        return err;
 }
 
-static void rehash_flow_table(struct work_struct *work)
-{
-       struct datapath *dp;
-       struct net *net;
-
-       ovs_lock();
-       rtnl_lock();
-       for_each_net(net) {
-               struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
-
-               list_for_each_entry(dp, &ovs_net->dps, list_node) {
-                       struct flow_table *old_table = ovsl_dereference(dp->table);
-                       struct flow_table *new_table;
-
-                       new_table = ovs_flow_tbl_rehash(old_table);
-                       if (!IS_ERR(new_table)) {
-                               rcu_assign_pointer(dp->table, new_table);
-                               ovs_flow_tbl_destroy(old_table, true);
-                       }
-               }
-       }
-       rtnl_unlock();
-       ovs_unlock();
-       schedule_delayed_work(&rehash_flow_wq, REHASH_FLOW_INTERVAL);
-}
-
 static int __net_init ovs_init_net(struct net *net)
 {
        struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
@@ -2468,13 +1886,9 @@ static int __init dp_init(void)
        pr_info("Open vSwitch switching datapath %s, built "__DATE__" "__TIME__"\n",
                VERSION);
 
-       err = ovs_workqueues_init();
-       if (err)
-               goto error;
-
        err = ovs_flow_init();
        if (err)
-               goto error_wq;
+               goto error;
 
        err = ovs_vport_init();
        if (err)
@@ -2492,8 +1906,6 @@ static int __init dp_init(void)
        if (err < 0)
                goto error_unreg_notifier;
 
-       schedule_delayed_work(&rehash_flow_wq, REHASH_FLOW_INTERVAL);
-
        return 0;
 
 error_unreg_notifier:
@@ -2504,22 +1916,18 @@ error_vport_exit:
        ovs_vport_exit();
 error_flow_exit:
        ovs_flow_exit();
-error_wq:
-       ovs_workqueues_exit();
 error:
        return err;
 }
 
 static void dp_cleanup(void)
 {
-       cancel_delayed_work_sync(&rehash_flow_wq);
        dp_unregister_genl(ARRAY_SIZE(dp_genl_families));
        unregister_netdevice_notifier(&ovs_dp_device_notifier);
        unregister_pernet_device(&ovs_net_ops);
        rcu_barrier();
        ovs_vport_exit();
        ovs_flow_exit();
-       ovs_workqueues_exit();
 }
 
 module_init(dp_init);