#include <linux/if_vlan.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
+#include <linux/hash.h>
+#include <net/ip.h>
 
 #include <linux/mlx4/driver.h>
 #include <linux/mlx4/device.h>
        return 0;
 }
 
+#ifdef CONFIG_RFS_ACCEL
+
+struct mlx4_en_filter {
+       struct list_head next;
+       struct work_struct work;
+
+       __be32 src_ip;
+       __be32 dst_ip;
+       __be16 src_port;
+       __be16 dst_port;
+
+       int rxq_index;
+       struct mlx4_en_priv *priv;
+       u32 flow_id;                    /* RFS infrastructure id */
+       int id;                         /* mlx4_en driver id */
+       u64 reg_id;                     /* Flow steering API id */
+       u8 activated;                   /* Used to prevent expiry before filter
+                                        * is attached
+                                        */
+       struct hlist_node filter_chain;
+};
+
+static void mlx4_en_filter_rfs_expire(struct mlx4_en_priv *priv);
+
+static void mlx4_en_filter_work(struct work_struct *work)
+{
+       struct mlx4_en_filter *filter = container_of(work,
+                                                    struct mlx4_en_filter,
+                                                    work);
+       struct mlx4_en_priv *priv = filter->priv;
+       struct mlx4_spec_list spec_tcp = {
+               .id = MLX4_NET_TRANS_RULE_ID_TCP,
+               {
+                       .tcp_udp = {
+                               .dst_port = filter->dst_port,
+                               .dst_port_msk = (__force __be16)-1,
+                               .src_port = filter->src_port,
+                               .src_port_msk = (__force __be16)-1,
+                       },
+               },
+       };
+       struct mlx4_spec_list spec_ip = {
+               .id = MLX4_NET_TRANS_RULE_ID_IPV4,
+               {
+                       .ipv4 = {
+                               .dst_ip = filter->dst_ip,
+                               .dst_ip_msk = (__force __be32)-1,
+                               .src_ip = filter->src_ip,
+                               .src_ip_msk = (__force __be32)-1,
+                       },
+               },
+       };
+       struct mlx4_spec_list spec_eth = {
+               .id = MLX4_NET_TRANS_RULE_ID_ETH,
+       };
+       struct mlx4_net_trans_rule rule = {
+               .list = LIST_HEAD_INIT(rule.list),
+               .queue_mode = MLX4_NET_TRANS_Q_LIFO,
+               .exclusive = 1,
+               .allow_loopback = 1,
+               .promisc_mode = MLX4_FS_PROMISC_NONE,
+               .port = priv->port,
+               .priority = MLX4_DOMAIN_RFS,
+       };
+       int rc;
+       __be64 mac;
+       __be64 mac_mask = cpu_to_be64(MLX4_MAC_MASK << 16);
+
+       list_add_tail(&spec_eth.list, &rule.list);
+       list_add_tail(&spec_ip.list, &rule.list);
+       list_add_tail(&spec_tcp.list, &rule.list);
+
+       mac = cpu_to_be64((priv->mac & MLX4_MAC_MASK) << 16);
+
+       rule.qpn = priv->rss_map.qps[filter->rxq_index].qpn;
+       memcpy(spec_eth.eth.dst_mac, &mac, ETH_ALEN);
+       memcpy(spec_eth.eth.dst_mac_msk, &mac_mask, ETH_ALEN);
+
+       filter->activated = 0;
+
+       if (filter->reg_id) {
+               rc = mlx4_flow_detach(priv->mdev->dev, filter->reg_id);
+               if (rc && rc != -ENOENT)
+                       en_err(priv, "Error detaching flow. rc = %d\n", rc);
+       }
+
+       rc = mlx4_flow_attach(priv->mdev->dev, &rule, &filter->reg_id);
+       if (rc)
+               en_err(priv, "Error attaching flow. err = %d\n", rc);
+
+       mlx4_en_filter_rfs_expire(priv);
+
+       filter->activated = 1;
+}
+
+static inline struct hlist_head *
+filter_hash_bucket(struct mlx4_en_priv *priv, __be32 src_ip, __be32 dst_ip,
+                  __be16 src_port, __be16 dst_port)
+{
+       unsigned long l;
+       int bucket_idx;
+
+       l = (__force unsigned long)src_port |
+           ((__force unsigned long)dst_port << 2);
+       l ^= (__force unsigned long)(src_ip ^ dst_ip);
+
+       bucket_idx = hash_long(l, MLX4_EN_FILTER_HASH_SHIFT);
+
+       return &priv->filter_hash[bucket_idx];
+}
+
+static struct mlx4_en_filter *
+mlx4_en_filter_alloc(struct mlx4_en_priv *priv, int rxq_index, __be32 src_ip,
+                    __be32 dst_ip, __be16 src_port, __be16 dst_port,
+                    u32 flow_id)
+{
+       struct mlx4_en_filter *filter = NULL;
+
+       filter = kzalloc(sizeof(struct mlx4_en_filter), GFP_ATOMIC);
+       if (!filter)
+               return NULL;
+
+       filter->priv = priv;
+       filter->rxq_index = rxq_index;
+       INIT_WORK(&filter->work, mlx4_en_filter_work);
+
+       filter->src_ip = src_ip;
+       filter->dst_ip = dst_ip;
+       filter->src_port = src_port;
+       filter->dst_port = dst_port;
+
+       filter->flow_id = flow_id;
+
+       filter->id = priv->last_filter_id++;
+
+       list_add_tail(&filter->next, &priv->filters);
+       hlist_add_head(&filter->filter_chain,
+                      filter_hash_bucket(priv, src_ip, dst_ip, src_port,
+                                         dst_port));
+
+       return filter;
+}
+
+static void mlx4_en_filter_free(struct mlx4_en_filter *filter)
+{
+       struct mlx4_en_priv *priv = filter->priv;
+       int rc;
+
+       list_del(&filter->next);
+
+       rc = mlx4_flow_detach(priv->mdev->dev, filter->reg_id);
+       if (rc && rc != -ENOENT)
+               en_err(priv, "Error detaching flow. rc = %d\n", rc);
+
+       kfree(filter);
+}
+
+static inline struct mlx4_en_filter *
+mlx4_en_filter_find(struct mlx4_en_priv *priv, __be32 src_ip, __be32 dst_ip,
+                   __be16 src_port, __be16 dst_port)
+{
+       struct hlist_node *elem;
+       struct mlx4_en_filter *filter;
+       struct mlx4_en_filter *ret = NULL;
+
+       hlist_for_each_entry(filter, elem,
+                            filter_hash_bucket(priv, src_ip, dst_ip,
+                                               src_port, dst_port),
+                            filter_chain) {
+               if (filter->src_ip == src_ip &&
+                   filter->dst_ip == dst_ip &&
+                   filter->src_port == src_port &&
+                   filter->dst_port == dst_port) {
+                       ret = filter;
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+static int
+mlx4_en_filter_rfs(struct net_device *net_dev, const struct sk_buff *skb,
+                  u16 rxq_index, u32 flow_id)
+{
+       struct mlx4_en_priv *priv = netdev_priv(net_dev);
+       struct mlx4_en_filter *filter;
+       const struct iphdr *ip;
+       const __be16 *ports;
+       __be32 src_ip;
+       __be32 dst_ip;
+       __be16 src_port;
+       __be16 dst_port;
+       int nhoff = skb_network_offset(skb);
+       int ret = 0;
+
+       if (skb->protocol != htons(ETH_P_IP))
+               return -EPROTONOSUPPORT;
+
+       ip = (const struct iphdr *)(skb->data + nhoff);
+       if (ip_is_fragment(ip))
+               return -EPROTONOSUPPORT;
+
+       ports = (const __be16 *)(skb->data + nhoff + 4 * ip->ihl);
+
+       src_ip = ip->saddr;
+       dst_ip = ip->daddr;
+       src_port = ports[0];
+       dst_port = ports[1];
+
+       if (ip->protocol != IPPROTO_TCP)
+               return -EPROTONOSUPPORT;
+
+       spin_lock_bh(&priv->filters_lock);
+       filter = mlx4_en_filter_find(priv, src_ip, dst_ip, src_port, dst_port);
+       if (filter) {
+               if (filter->rxq_index == rxq_index)
+                       goto out;
+
+               filter->rxq_index = rxq_index;
+       } else {
+               filter = mlx4_en_filter_alloc(priv, rxq_index,
+                                             src_ip, dst_ip,
+                                             src_port, dst_port, flow_id);
+               if (!filter) {
+                       ret = -ENOMEM;
+                       goto err;
+               }
+       }
+
+       queue_work(priv->mdev->workqueue, &filter->work);
+
+out:
+       ret = filter->id;
+err:
+       spin_unlock_bh(&priv->filters_lock);
+
+       return ret;
+}
+
+void mlx4_en_cleanup_filters(struct mlx4_en_priv *priv,
+                            struct mlx4_en_rx_ring *rx_ring)
+{
+       struct mlx4_en_filter *filter, *tmp;
+       LIST_HEAD(del_list);
+
+       spin_lock_bh(&priv->filters_lock);
+       list_for_each_entry_safe(filter, tmp, &priv->filters, next) {
+               list_move(&filter->next, &del_list);
+               hlist_del(&filter->filter_chain);
+       }
+       spin_unlock_bh(&priv->filters_lock);
+
+       list_for_each_entry_safe(filter, tmp, &del_list, next) {
+               cancel_work_sync(&filter->work);
+               mlx4_en_filter_free(filter);
+       }
+}
+
+static void mlx4_en_filter_rfs_expire(struct mlx4_en_priv *priv)
+{
+       struct mlx4_en_filter *filter = NULL, *tmp, *last_filter = NULL;
+       LIST_HEAD(del_list);
+       int i = 0;
+
+       spin_lock_bh(&priv->filters_lock);
+       list_for_each_entry_safe(filter, tmp, &priv->filters, next) {
+               if (i > MLX4_EN_FILTER_EXPIRY_QUOTA)
+                       break;
+
+               if (filter->activated &&
+                   !work_pending(&filter->work) &&
+                   rps_may_expire_flow(priv->dev,
+                                       filter->rxq_index, filter->flow_id,
+                                       filter->id)) {
+                       list_move(&filter->next, &del_list);
+                       hlist_del(&filter->filter_chain);
+               } else
+                       last_filter = filter;
+
+               i++;
+       }
+
+       if (last_filter && (&last_filter->next != priv->filters.next))
+               list_move(&priv->filters, &last_filter->next);
+
+       spin_unlock_bh(&priv->filters_lock);
+
+       list_for_each_entry_safe(filter, tmp, &del_list, next)
+               mlx4_en_filter_free(filter);
+}
+#endif
+
 static int mlx4_en_vlan_rx_add_vid(struct net_device *dev, unsigned short vid)
 {
        struct mlx4_en_priv *priv = netdev_priv(dev);
 {
        int i;
 
+#ifdef CONFIG_RFS_ACCEL
+       free_irq_cpu_rmap(priv->dev->rx_cpu_rmap);
+       priv->dev->rx_cpu_rmap = NULL;
+#endif
+
        for (i = 0; i < priv->tx_ring_num; i++) {
                if (priv->tx_ring[i].tx_info)
                        mlx4_en_destroy_tx_ring(priv, &priv->tx_ring[i]);
                        goto err;
        }
 
+#ifdef CONFIG_RFS_ACCEL
+       priv->dev->rx_cpu_rmap = alloc_irq_cpu_rmap(priv->rx_ring_num);
+       if (!priv->dev->rx_cpu_rmap)
+               goto err;
+
+       INIT_LIST_HEAD(&priv->filters);
+       spin_lock_init(&priv->filters_lock);
+#endif
+
        return 0;
 
 err:
 #endif
        .ndo_set_features       = mlx4_en_set_features,
        .ndo_setup_tc           = mlx4_en_setup_tc,
+#ifdef CONFIG_RFS_ACCEL
+       .ndo_rx_flow_steer      = mlx4_en_filter_rfs,
+#endif
 };
 
 int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
                        NETIF_F_HW_VLAN_FILTER;
        dev->hw_features |= NETIF_F_LOOPBACK;
 
+       if (mdev->dev->caps.steering_mode ==
+           MLX4_STEERING_MODE_DEVICE_MANAGED)
+               dev->hw_features |= NETIF_F_NTUPLE;
+
        mdev->pndev[port] = dev;
 
        netif_carrier_off(dev);