]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
net: aquantia: add infrastructure for ntuple rules
authorDmitry Bogdanov <dmitry.bogdanov@aquantia.com>
Mon, 12 Nov 2018 15:46:00 +0000 (15:46 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 14 Nov 2018 16:48:37 +0000 (08:48 -0800)
Add infrastructure to support ntuple filter configuration.
Add rule, remove rule, reapply on interface up.

Signed-off-by: Dmitry Bogdanov <dmitry.bogdanov@aquantia.com>
Signed-off-by: Igor Russkikh <igor.russkikh@aquantia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/aquantia/atlantic/Makefile
drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
drivers/net/ethernet/aquantia/atlantic/aq_filters.c [new file with mode: 0644]
drivers/net/ethernet/aquantia/atlantic/aq_filters.h [new file with mode: 0644]
drivers/net/ethernet/aquantia/atlantic/aq_main.c
drivers/net/ethernet/aquantia/atlantic/aq_nic.h
drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_b0.c

index 686f6d8c9e7969397913128d4ceca86ec4ee80db..4556630ee286cf42820ba2900727151012b4150c 100644 (file)
@@ -36,6 +36,7 @@ atlantic-objs := aq_main.o \
        aq_ring.o \
        aq_hw_utils.o \
        aq_ethtool.o \
+       aq_filters.o \
        hw_atl/hw_atl_a0.o \
        hw_atl/hw_atl_b0.o \
        hw_atl/hw_atl_utils.o \
index 99ef1daaa4d8027636cc5b0f7f542b7961f6764e..a5fd71692c8ba7789eb6cd212a8c1917128e757e 100644 (file)
@@ -12,6 +12,7 @@
 #include "aq_ethtool.h"
 #include "aq_nic.h"
 #include "aq_vec.h"
+#include "aq_filters.h"
 
 static void aq_ethtool_get_regs(struct net_device *ndev,
                                struct ethtool_regs *regs, void *p)
@@ -213,7 +214,36 @@ static int aq_ethtool_get_rxnfc(struct net_device *ndev,
        case ETHTOOL_GRXRINGS:
                cmd->data = cfg->vecs;
                break;
+       case ETHTOOL_GRXCLSRLCNT:
+               cmd->rule_cnt = aq_get_rxnfc_count_all_rules(aq_nic);
+               break;
+       case ETHTOOL_GRXCLSRULE:
+               err = aq_get_rxnfc_rule(aq_nic, cmd);
+               break;
+       case ETHTOOL_GRXCLSRLALL:
+               err = aq_get_rxnfc_all_rules(aq_nic, cmd, rule_locs);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
 
+       return err;
+}
+
+static int aq_ethtool_set_rxnfc(struct net_device *ndev,
+                               struct ethtool_rxnfc *cmd)
+{
+       int err = 0;
+       struct aq_nic_s *aq_nic = netdev_priv(ndev);
+
+       switch (cmd->cmd) {
+       case ETHTOOL_SRXCLSRLINS:
+               err = aq_add_rxnfc_rule(aq_nic, cmd);
+               break;
+       case ETHTOOL_SRXCLSRLDEL:
+               err = aq_del_rxnfc_rule(aq_nic, cmd);
+               break;
        default:
                err = -EOPNOTSUPP;
                break;
@@ -520,6 +550,7 @@ const struct ethtool_ops aq_ethtool_ops = {
        .get_rxfh_key_size   = aq_ethtool_get_rss_key_size,
        .get_rxfh            = aq_ethtool_get_rss,
        .get_rxnfc           = aq_ethtool_get_rxnfc,
+       .set_rxnfc           = aq_ethtool_set_rxnfc,
        .get_sset_count      = aq_ethtool_get_sset_count,
        .get_ethtool_stats   = aq_ethtool_stats,
        .get_link_ksettings  = aq_ethtool_get_link_ksettings,
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_filters.c b/drivers/net/ethernet/aquantia/atlantic/aq_filters.c
new file mode 100644 (file)
index 0000000..8cb4eae
--- /dev/null
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (C) 2014-2017 aQuantia Corporation. */
+
+/* File aq_filters.c: RX filters related functions. */
+
+#include "aq_filters.h"
+
+static bool __must_check
+aq_rule_is_approve(struct ethtool_rx_flow_spec *fsp)
+{
+       if (fsp->flow_type & FLOW_MAC_EXT)
+               return false;
+
+       switch (fsp->flow_type & ~FLOW_EXT) {
+       case ETHER_FLOW:
+       case TCP_V4_FLOW:
+       case UDP_V4_FLOW:
+       case SCTP_V4_FLOW:
+       case TCP_V6_FLOW:
+       case UDP_V6_FLOW:
+       case SCTP_V6_FLOW:
+       case IPV4_FLOW:
+       case IPV6_FLOW:
+               return true;
+       case IP_USER_FLOW:
+               switch (fsp->h_u.usr_ip4_spec.proto) {
+               case IPPROTO_TCP:
+               case IPPROTO_UDP:
+               case IPPROTO_SCTP:
+               case IPPROTO_IP:
+                       return true;
+               default:
+                       return false;
+                       }
+       case IPV6_USER_FLOW:
+               switch (fsp->h_u.usr_ip6_spec.l4_proto) {
+               case IPPROTO_TCP:
+               case IPPROTO_UDP:
+               case IPPROTO_SCTP:
+               case IPPROTO_IP:
+                       return true;
+               default:
+                       return false;
+                       }
+       default:
+               return false;
+       }
+
+       return false;
+}
+
+static bool __must_check
+aq_match_filter(struct ethtool_rx_flow_spec *fsp1,
+               struct ethtool_rx_flow_spec *fsp2)
+{
+       if (fsp1->flow_type != fsp2->flow_type ||
+           memcmp(&fsp1->h_u, &fsp2->h_u, sizeof(fsp2->h_u)) ||
+           memcmp(&fsp1->h_ext, &fsp2->h_ext, sizeof(fsp2->h_ext)) ||
+           memcmp(&fsp1->m_u, &fsp2->m_u, sizeof(fsp2->m_u)) ||
+           memcmp(&fsp1->m_ext, &fsp2->m_ext, sizeof(fsp2->m_ext)))
+               return false;
+
+       return true;
+}
+
+static bool __must_check
+aq_rule_already_exists(struct aq_nic_s *aq_nic,
+                      struct ethtool_rx_flow_spec *fsp)
+{
+       struct aq_rx_filter *rule;
+       struct hlist_node *aq_node2;
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node) {
+               if (rule->aq_fsp.location == fsp->location)
+                       continue;
+               if (aq_match_filter(&rule->aq_fsp, fsp)) {
+                       netdev_err(aq_nic->ndev,
+                                  "ethtool: This filter is already set\n");
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+static int __must_check
+aq_check_filter(struct aq_nic_s *aq_nic,
+               struct ethtool_rx_flow_spec *fsp)
+{
+       int err = 0;
+
+       if (fsp->flow_type & FLOW_EXT) {
+               err = -EOPNOTSUPP;
+       } else {
+               switch (fsp->flow_type & ~FLOW_EXT) {
+               case ETHER_FLOW:
+                       err = -EOPNOTSUPP;
+                       break;
+               case TCP_V4_FLOW:
+               case UDP_V4_FLOW:
+               case SCTP_V4_FLOW:
+               case IPV4_FLOW:
+               case IP_USER_FLOW:
+                       err = -EOPNOTSUPP;
+                       break;
+               case TCP_V6_FLOW:
+               case UDP_V6_FLOW:
+               case SCTP_V6_FLOW:
+               case IPV6_FLOW:
+               case IPV6_USER_FLOW:
+                       err = -EOPNOTSUPP;
+                       break;
+               default:
+                       netdev_err(aq_nic->ndev,
+                                  "ethtool: unknown flow-type specified");
+                       err = -EINVAL;
+               }
+       }
+
+       return err;
+}
+
+static bool __must_check
+aq_rule_is_not_support(struct aq_nic_s *aq_nic,
+                      struct ethtool_rx_flow_spec *fsp)
+{
+       bool rule_is_not_support = false;
+
+       if (!(aq_nic->ndev->features & NETIF_F_NTUPLE)) {
+               netdev_err(aq_nic->ndev,
+                          "ethtool: Please, to enable the RX flow control:\n"
+                          "ethtool -K %s ntuple on\n", aq_nic->ndev->name);
+               rule_is_not_support = true;
+       } else if (!aq_rule_is_approve(fsp)) {
+               netdev_err(aq_nic->ndev,
+                          "ethtool: The specified flow type is not supported\n");
+               rule_is_not_support = true;
+       } else if ((fsp->flow_type & ~FLOW_EXT) != ETHER_FLOW &&
+                  (fsp->h_u.tcp_ip4_spec.tos ||
+                   fsp->h_u.tcp_ip6_spec.tclass)) {
+               netdev_err(aq_nic->ndev,
+                          "ethtool: The specified tos tclass are not supported\n");
+               rule_is_not_support = true;
+       }
+
+       return rule_is_not_support;
+}
+
+static bool __must_check
+aq_rule_is_not_correct(struct aq_nic_s *aq_nic,
+                      struct ethtool_rx_flow_spec *fsp)
+{
+       bool rule_is_not_correct = false;
+
+       if (!aq_nic) {
+               rule_is_not_correct = true;
+       } else if (aq_check_filter(aq_nic, fsp)) {
+               rule_is_not_correct = true;
+       } else if (fsp->ring_cookie != RX_CLS_FLOW_DISC) {
+               if (fsp->ring_cookie >= aq_nic->aq_nic_cfg.num_rss_queues) {
+                       netdev_err(aq_nic->ndev,
+                                  "ethtool: The specified action is invalid.\n"
+                                  "Maximum allowable value action is %u.\n",
+                                  aq_nic->aq_nic_cfg.num_rss_queues - 1);
+                       rule_is_not_correct = true;
+               }
+       }
+
+       return rule_is_not_correct;
+}
+
+static int __must_check
+aq_check_rule(struct aq_nic_s *aq_nic,
+             struct ethtool_rx_flow_spec *fsp)
+{
+       int err = 0;
+
+       if (aq_rule_is_not_correct(aq_nic, fsp))
+               err = -EINVAL;
+       else if (aq_rule_is_not_support(aq_nic, fsp))
+               err = -EOPNOTSUPP;
+       else if (aq_rule_already_exists(aq_nic, fsp))
+               err = -EEXIST;
+
+       return err;
+}
+
+static int aq_add_del_rule(struct aq_nic_s *aq_nic,
+                          struct aq_rx_filter *aq_rx_fltr, bool add)
+{
+       int err = -EINVAL;
+
+       if (aq_rx_fltr->aq_fsp.flow_type & FLOW_EXT) {
+               err = -EOPNOTSUPP;
+       } else {
+               switch (aq_rx_fltr->aq_fsp.flow_type & ~FLOW_EXT) {
+               case ETHER_FLOW:
+                       err = -EOPNOTSUPP;
+                       break;
+               case TCP_V4_FLOW:
+               case UDP_V4_FLOW:
+               case SCTP_V4_FLOW:
+               case IP_USER_FLOW:
+               case TCP_V6_FLOW:
+               case UDP_V6_FLOW:
+               case SCTP_V6_FLOW:
+               case IPV6_USER_FLOW:
+                       err = -EOPNOTSUPP;
+                       break;
+               default:
+                       err = -EINVAL;
+                       break;
+               }
+       }
+
+       return err;
+}
+
+static int aq_update_table_filters(struct aq_nic_s *aq_nic,
+                                  struct aq_rx_filter *aq_rx_fltr, u16 index,
+                                  struct ethtool_rxnfc *cmd)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct aq_rx_filter *rule = NULL, *parent = NULL;
+       struct hlist_node *aq_node2;
+       int err = -EINVAL;
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node) {
+               if (rule->aq_fsp.location >= index)
+                       break;
+               parent = rule;
+       }
+
+       if (rule && rule->aq_fsp.location == index) {
+               err = aq_add_del_rule(aq_nic, rule, false);
+               hlist_del(&rule->aq_node);
+               kfree(rule);
+               --rx_fltrs->active_filters;
+       }
+
+       if (unlikely(!aq_rx_fltr))
+               return err;
+
+       INIT_HLIST_NODE(&aq_rx_fltr->aq_node);
+
+       if (parent)
+               hlist_add_behind(&aq_rx_fltr->aq_node, &parent->aq_node);
+       else
+               hlist_add_head(&aq_rx_fltr->aq_node, &rx_fltrs->filter_list);
+
+       ++rx_fltrs->active_filters;
+
+       return 0;
+}
+
+u16 aq_get_rxnfc_count_all_rules(struct aq_nic_s *aq_nic)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+
+       return rx_fltrs->active_filters;
+}
+
+struct aq_hw_rx_fltrs_s *aq_get_hw_rx_fltrs(struct aq_nic_s *aq_nic)
+{
+       return &aq_nic->aq_hw_rx_fltrs;
+}
+
+int aq_add_rxnfc_rule(struct aq_nic_s *aq_nic, const struct ethtool_rxnfc *cmd)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct aq_rx_filter *aq_rx_fltr;
+       int err = 0;
+
+       err = aq_check_rule(aq_nic, fsp);
+       if (err)
+               goto err_exit;
+
+       aq_rx_fltr = kzalloc(sizeof(*aq_rx_fltr), GFP_KERNEL);
+       if (unlikely(!aq_rx_fltr)) {
+               err = -ENOMEM;
+               goto err_exit;
+       }
+
+       memcpy(&aq_rx_fltr->aq_fsp, fsp, sizeof(*fsp));
+
+       err = aq_update_table_filters(aq_nic, aq_rx_fltr, fsp->location, NULL);
+       if (unlikely(err))
+               goto err_free;
+
+       err = aq_add_del_rule(aq_nic, aq_rx_fltr, true);
+       if (unlikely(err)) {
+               hlist_del(&aq_rx_fltr->aq_node);
+               --rx_fltrs->active_filters;
+               goto err_free;
+       }
+
+       return 0;
+
+err_free:
+       kfree(aq_rx_fltr);
+err_exit:
+       return err;
+}
+
+int aq_del_rxnfc_rule(struct aq_nic_s *aq_nic, const struct ethtool_rxnfc *cmd)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct aq_rx_filter *rule = NULL;
+       struct hlist_node *aq_node2;
+       int err = -EINVAL;
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node) {
+               if (rule->aq_fsp.location == cmd->fs.location)
+                       break;
+       }
+
+       if (rule && rule->aq_fsp.location == cmd->fs.location) {
+               err = aq_add_del_rule(aq_nic, rule, false);
+               hlist_del(&rule->aq_node);
+               kfree(rule);
+               --rx_fltrs->active_filters;
+       }
+       return err;
+}
+
+int aq_get_rxnfc_rule(struct aq_nic_s *aq_nic, struct ethtool_rxnfc *cmd)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct ethtool_rx_flow_spec *fsp =
+                       (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct aq_rx_filter *rule = NULL;
+       struct hlist_node *aq_node2;
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node)
+               if (fsp->location <= rule->aq_fsp.location)
+                       break;
+
+       if (unlikely(!rule || fsp->location != rule->aq_fsp.location))
+               return -EINVAL;
+
+       memcpy(fsp, &rule->aq_fsp, sizeof(*fsp));
+
+       return 0;
+}
+
+int aq_get_rxnfc_all_rules(struct aq_nic_s *aq_nic, struct ethtool_rxnfc *cmd,
+                          u32 *rule_locs)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct hlist_node *aq_node2;
+       struct aq_rx_filter *rule;
+       int count = 0;
+
+       cmd->data = aq_get_rxnfc_count_all_rules(aq_nic);
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node) {
+               if (unlikely(count == cmd->rule_cnt))
+                       return -EMSGSIZE;
+
+               rule_locs[count++] = rule->aq_fsp.location;
+       }
+
+       cmd->rule_cnt = count;
+
+       return 0;
+}
+
+int aq_clear_rxnfc_all_rules(struct aq_nic_s *aq_nic)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct hlist_node *aq_node2;
+       struct aq_rx_filter *rule;
+       int err = 0;
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node) {
+               err = aq_add_del_rule(aq_nic, rule, false);
+               if (err)
+                       goto err_exit;
+               hlist_del(&rule->aq_node);
+               kfree(rule);
+               --rx_fltrs->active_filters;
+       }
+
+err_exit:
+       return err;
+}
+
+int aq_reapply_rxnfc_all_rules(struct aq_nic_s *aq_nic)
+{
+       struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
+       struct hlist_node *aq_node2;
+       struct aq_rx_filter *rule;
+       int err = 0;
+
+       hlist_for_each_entry_safe(rule, aq_node2,
+                                 &rx_fltrs->filter_list, aq_node) {
+               err = aq_add_del_rule(aq_nic, rule, true);
+               if (err)
+                       goto err_exit;
+       }
+
+err_exit:
+       return err;
+}
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_filters.h b/drivers/net/ethernet/aquantia/atlantic/aq_filters.h
new file mode 100644 (file)
index 0000000..1f1368b
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2014-2017 aQuantia Corporation. */
+
+/* File aq_filters.h: RX filters related functions. */
+
+#ifndef AQ_FILTERS_H
+#define AQ_FILTERS_H
+
+#include "aq_nic.h"
+
+enum aq_rx_filter_type {
+       aq_rx_filter_l3l4
+};
+
+struct aq_rx_filter {
+       struct hlist_node aq_node;
+       enum aq_rx_filter_type type;
+       struct ethtool_rx_flow_spec aq_fsp;
+};
+
+u16 aq_get_rxnfc_count_all_rules(struct aq_nic_s *aq_nic);
+struct aq_hw_rx_fltrs_s *aq_get_hw_rx_fltrs(struct aq_nic_s *aq_nic);
+int aq_add_rxnfc_rule(struct aq_nic_s *aq_nic, const struct ethtool_rxnfc *cmd);
+int aq_del_rxnfc_rule(struct aq_nic_s *aq_nic, const struct ethtool_rxnfc *cmd);
+int aq_get_rxnfc_rule(struct aq_nic_s *aq_nic, struct ethtool_rxnfc *cmd);
+int aq_get_rxnfc_all_rules(struct aq_nic_s *aq_nic, struct ethtool_rxnfc *cmd,
+                          u32 *rule_locs);
+int aq_clear_rxnfc_all_rules(struct aq_nic_s *aq_nic);
+int aq_reapply_rxnfc_all_rules(struct aq_nic_s *aq_nic);
+
+#endif /* AQ_FILTERS_H */
index 7c07eef275eb8498ade72b676dc2eeda8532185e..ff15d3388add26e1a31ba08d2a2388dc543ebb5f 100644 (file)
@@ -13,6 +13,7 @@
 #include "aq_nic.h"
 #include "aq_pci_func.h"
 #include "aq_ethtool.h"
+#include "aq_filters.h"
 
 #include <linux/netdevice.h>
 #include <linux/module.h>
@@ -49,6 +50,11 @@ static int aq_ndev_open(struct net_device *ndev)
        err = aq_nic_init(aq_nic);
        if (err < 0)
                goto err_exit;
+
+       err = aq_reapply_rxnfc_all_rules(aq_nic);
+       if (err < 0)
+               goto err_exit;
+
        err = aq_nic_start(aq_nic);
        if (err < 0)
                goto err_exit;
@@ -101,6 +107,14 @@ static int aq_ndev_set_features(struct net_device *ndev,
        bool is_lro = false;
        int err = 0;
 
+       if (!(features & NETIF_F_NTUPLE)) {
+               if (aq_nic->ndev->features & NETIF_F_NTUPLE) {
+                       err = aq_clear_rxnfc_all_rules(aq_nic);
+                       if (unlikely(err))
+                               goto err_exit;
+               }
+       }
+
        aq_cfg->features = features;
 
        if (aq_cfg->aq_hw_caps->hw_features & NETIF_F_LRO) {
@@ -119,6 +133,7 @@ static int aq_ndev_set_features(struct net_device *ndev,
                err = aq_nic->aq_hw_ops->hw_set_offload(aq_nic->aq_hw,
                                                        aq_cfg);
 
+err_exit:
        return err;
 }
 
index 44ec47a3d60a57bee0c9b0a62907eb1988ee5a5b..c4a20dca181ae892d83ddc811658bd3b1072f3c4 100644 (file)
@@ -61,6 +61,11 @@ struct aq_nic_cfg_s {
 #define AQ_NIC_TCVEC2RING(_NIC_, _TC_, _VEC_) \
        ((_TC_) * AQ_CFG_TCS_MAX + (_VEC_))
 
+struct aq_hw_rx_fltrs_s {
+       struct hlist_head     filter_list;
+       u16                   active_filters;
+};
+
 struct aq_nic_s {
        atomic_t flags;
        struct aq_vec_s *aq_vec[AQ_CFG_VECS_MAX];
@@ -85,6 +90,7 @@ struct aq_nic_s {
        struct pci_dev *pdev;
        unsigned int msix_entry_mask;
        u32 irqvecs;
+       struct aq_hw_rx_fltrs_s aq_hw_rx_fltrs;
 };
 
 static inline struct device *aq_nic_get_dev(struct aq_nic_s *self)
index 1d5d6b8df855160c11f8f1124ee4491bd7860e90..c8b44cdb91c183e8ebb124c7a7e978dba9e0922c 100644 (file)
@@ -19,6 +19,7 @@
 #include "aq_pci_func.h"
 #include "hw_atl/hw_atl_a0.h"
 #include "hw_atl/hw_atl_b0.h"
+#include "aq_filters.h"
 
 static const struct pci_device_id aq_pci_tbl[] = {
        { PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_0001), },
@@ -309,6 +310,7 @@ static void aq_pci_remove(struct pci_dev *pdev)
        struct aq_nic_s *self = pci_get_drvdata(pdev);
 
        if (self->ndev) {
+               aq_clear_rxnfc_all_rules(self);
                if (self->ndev->reg_state == NETREG_REGISTERED)
                        unregister_netdev(self->ndev);
                aq_nic_free_vectors(self);
index f02592f43fe36f3af460672746ade204d1f574fb..eba1eb78dd5908caeb8040c5ab9aaaee50f7a3ca 100644 (file)
@@ -41,7 +41,8 @@
                        NETIF_F_RXHASH |  \
                        NETIF_F_SG |      \
                        NETIF_F_TSO |     \
-                       NETIF_F_LRO,      \
+                       NETIF_F_LRO |     \
+                       NETIF_F_NTUPLE,   \
        .hw_priv_flags = IFF_UNICAST_FLT, \
        .flow_control = true,             \
        .mtu = HW_ATL_B0_MTU_JUMBO,       \