net/mlx5e: Add RSS support for hairpin
[sfrench/cifs-2.6.git] / drivers / net / ethernet / mellanox / mlx5 / core / en_tc.c
index 55979ec2e88a14f7123ad164769feefe9c730618..7a4f577d1c5b272f2313274903336b9104c1f1ee 100644 (file)
 #include "en_tc.h"
 #include "eswitch.h"
 #include "vxlan.h"
+#include "fs_core.h"
 
 struct mlx5_nic_flow_attr {
        u32 action;
        u32 flow_tag;
        u32 mod_hdr_id;
+       u32 hairpin_tirn;
+       struct mlx5_flow_table  *hairpin_ft;
 };
 
 enum {
        MLX5E_TC_FLOW_ESWITCH   = BIT(0),
        MLX5E_TC_FLOW_NIC       = BIT(1),
        MLX5E_TC_FLOW_OFFLOADED = BIT(2),
+       MLX5E_TC_FLOW_HAIRPIN   = BIT(3),
+       MLX5E_TC_FLOW_HAIRPIN_RSS = BIT(4),
 };
 
 struct mlx5e_tc_flow {
@@ -71,6 +76,7 @@ struct mlx5e_tc_flow {
        struct mlx5_flow_handle *rule;
        struct list_head        encap;   /* flows sharing the same encap ID */
        struct list_head        mod_hdr; /* flows sharing the same mod hdr ID */
+       struct list_head        hairpin; /* flows sharing the same hairpin */
        union {
                struct mlx5_esw_flow_attr esw_attr[0];
                struct mlx5_nic_flow_attr nic_attr[0];
@@ -93,6 +99,32 @@ enum {
 #define MLX5E_TC_TABLE_NUM_GROUPS 4
 #define MLX5E_TC_TABLE_MAX_GROUP_SIZE (1 << 16)
 
+struct mlx5e_hairpin {
+       struct mlx5_hairpin *pair;
+
+       struct mlx5_core_dev *func_mdev;
+       struct mlx5e_priv *func_priv;
+       u32 tdn;
+       u32 tirn;
+
+       int num_channels;
+       struct mlx5e_rqt indir_rqt;
+       u32 indir_tirn[MLX5E_NUM_INDIR_TIRS];
+       struct mlx5e_ttc_table ttc;
+};
+
+struct mlx5e_hairpin_entry {
+       /* a node of a hash table which keeps all the  hairpin entries */
+       struct hlist_node hairpin_hlist;
+
+       /* flows sharing the same hairpin */
+       struct list_head flows;
+
+       u16 peer_vhca_id;
+       u8 prio;
+       struct mlx5e_hairpin *hp;
+};
+
 struct mod_hdr_key {
        int num_actions;
        void *actions;
@@ -222,6 +254,412 @@ static void mlx5e_detach_mod_hdr(struct mlx5e_priv *priv,
        }
 }
 
+static
+struct mlx5_core_dev *mlx5e_hairpin_get_mdev(struct net *net, int ifindex)
+{
+       struct net_device *netdev;
+       struct mlx5e_priv *priv;
+
+       netdev = __dev_get_by_index(net, ifindex);
+       priv = netdev_priv(netdev);
+       return priv->mdev;
+}
+
+static int mlx5e_hairpin_create_transport(struct mlx5e_hairpin *hp)
+{
+       u32 in[MLX5_ST_SZ_DW(create_tir_in)] = {0};
+       void *tirc;
+       int err;
+
+       err = mlx5_core_alloc_transport_domain(hp->func_mdev, &hp->tdn);
+       if (err)
+               goto alloc_tdn_err;
+
+       tirc = MLX5_ADDR_OF(create_tir_in, in, ctx);
+
+       MLX5_SET(tirc, tirc, disp_type, MLX5_TIRC_DISP_TYPE_DIRECT);
+       MLX5_SET(tirc, tirc, inline_rqn, hp->pair->rqn[0]);
+       MLX5_SET(tirc, tirc, transport_domain, hp->tdn);
+
+       err = mlx5_core_create_tir(hp->func_mdev, in, MLX5_ST_SZ_BYTES(create_tir_in), &hp->tirn);
+       if (err)
+               goto create_tir_err;
+
+       return 0;
+
+create_tir_err:
+       mlx5_core_dealloc_transport_domain(hp->func_mdev, hp->tdn);
+alloc_tdn_err:
+       return err;
+}
+
+static void mlx5e_hairpin_destroy_transport(struct mlx5e_hairpin *hp)
+{
+       mlx5_core_destroy_tir(hp->func_mdev, hp->tirn);
+       mlx5_core_dealloc_transport_domain(hp->func_mdev, hp->tdn);
+}
+
+static void mlx5e_hairpin_fill_rqt_rqns(struct mlx5e_hairpin *hp, void *rqtc)
+{
+       u32 indirection_rqt[MLX5E_INDIR_RQT_SIZE], rqn;
+       struct mlx5e_priv *priv = hp->func_priv;
+       int i, ix, sz = MLX5E_INDIR_RQT_SIZE;
+
+       mlx5e_build_default_indir_rqt(indirection_rqt, sz,
+                                     hp->num_channels);
+
+       for (i = 0; i < sz; i++) {
+               ix = i;
+               if (priv->channels.params.rss_hfunc == ETH_RSS_HASH_XOR)
+                       ix = mlx5e_bits_invert(i, ilog2(sz));
+               ix = indirection_rqt[ix];
+               rqn = hp->pair->rqn[ix];
+               MLX5_SET(rqtc, rqtc, rq_num[i], rqn);
+       }
+}
+
+static int mlx5e_hairpin_create_indirect_rqt(struct mlx5e_hairpin *hp)
+{
+       int inlen, err, sz = MLX5E_INDIR_RQT_SIZE;
+       struct mlx5e_priv *priv = hp->func_priv;
+       struct mlx5_core_dev *mdev = priv->mdev;
+       void *rqtc;
+       u32 *in;
+
+       inlen = MLX5_ST_SZ_BYTES(create_rqt_in) + sizeof(u32) * sz;
+       in = kvzalloc(inlen, GFP_KERNEL);
+       if (!in)
+               return -ENOMEM;
+
+       rqtc = MLX5_ADDR_OF(create_rqt_in, in, rqt_context);
+
+       MLX5_SET(rqtc, rqtc, rqt_actual_size, sz);
+       MLX5_SET(rqtc, rqtc, rqt_max_size, sz);
+
+       mlx5e_hairpin_fill_rqt_rqns(hp, rqtc);
+
+       err = mlx5_core_create_rqt(mdev, in, inlen, &hp->indir_rqt.rqtn);
+       if (!err)
+               hp->indir_rqt.enabled = true;
+
+       kvfree(in);
+       return err;
+}
+
+static int mlx5e_hairpin_create_indirect_tirs(struct mlx5e_hairpin *hp)
+{
+       struct mlx5e_priv *priv = hp->func_priv;
+       u32 in[MLX5_ST_SZ_DW(create_tir_in)];
+       int tt, i, err;
+       void *tirc;
+
+       for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++) {
+               memset(in, 0, MLX5_ST_SZ_BYTES(create_tir_in));
+               tirc = MLX5_ADDR_OF(create_tir_in, in, ctx);
+
+               MLX5_SET(tirc, tirc, transport_domain, hp->tdn);
+               MLX5_SET(tirc, tirc, disp_type, MLX5_TIRC_DISP_TYPE_INDIRECT);
+               MLX5_SET(tirc, tirc, indirect_table, hp->indir_rqt.rqtn);
+               mlx5e_build_indir_tir_ctx_hash(&priv->channels.params, tt, tirc, false);
+
+               err = mlx5_core_create_tir(hp->func_mdev, in,
+                                          MLX5_ST_SZ_BYTES(create_tir_in), &hp->indir_tirn[tt]);
+               if (err) {
+                       mlx5_core_warn(hp->func_mdev, "create indirect tirs failed, %d\n", err);
+                       goto err_destroy_tirs;
+               }
+       }
+       return 0;
+
+err_destroy_tirs:
+       for (i = 0; i < tt; i++)
+               mlx5_core_destroy_tir(hp->func_mdev, hp->indir_tirn[i]);
+       return err;
+}
+
+static void mlx5e_hairpin_destroy_indirect_tirs(struct mlx5e_hairpin *hp)
+{
+       int tt;
+
+       for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++)
+               mlx5_core_destroy_tir(hp->func_mdev, hp->indir_tirn[tt]);
+}
+
+static void mlx5e_hairpin_set_ttc_params(struct mlx5e_hairpin *hp,
+                                        struct ttc_params *ttc_params)
+{
+       struct mlx5_flow_table_attr *ft_attr = &ttc_params->ft_attr;
+       int tt;
+
+       memset(ttc_params, 0, sizeof(*ttc_params));
+
+       ttc_params->any_tt_tirn = hp->tirn;
+
+       for (tt = 0; tt < MLX5E_NUM_INDIR_TIRS; tt++)
+               ttc_params->indir_tirn[tt] = hp->indir_tirn[tt];
+
+       ft_attr->max_fte = MLX5E_NUM_TT;
+       ft_attr->level = MLX5E_TC_TTC_FT_LEVEL;
+       ft_attr->prio = MLX5E_TC_PRIO;
+}
+
+static int mlx5e_hairpin_rss_init(struct mlx5e_hairpin *hp)
+{
+       struct mlx5e_priv *priv = hp->func_priv;
+       struct ttc_params ttc_params;
+       int err;
+
+       err = mlx5e_hairpin_create_indirect_rqt(hp);
+       if (err)
+               return err;
+
+       err = mlx5e_hairpin_create_indirect_tirs(hp);
+       if (err)
+               goto err_create_indirect_tirs;
+
+       mlx5e_hairpin_set_ttc_params(hp, &ttc_params);
+       err = mlx5e_create_ttc_table(priv, &ttc_params, &hp->ttc);
+       if (err)
+               goto err_create_ttc_table;
+
+       netdev_dbg(priv->netdev, "add hairpin: using %d channels rss ttc table id %x\n",
+                  hp->num_channels, hp->ttc.ft.t->id);
+
+       return 0;
+
+err_create_ttc_table:
+       mlx5e_hairpin_destroy_indirect_tirs(hp);
+err_create_indirect_tirs:
+       mlx5e_destroy_rqt(priv, &hp->indir_rqt);
+
+       return err;
+}
+
+static void mlx5e_hairpin_rss_cleanup(struct mlx5e_hairpin *hp)
+{
+       struct mlx5e_priv *priv = hp->func_priv;
+
+       mlx5e_destroy_ttc_table(priv, &hp->ttc);
+       mlx5e_hairpin_destroy_indirect_tirs(hp);
+       mlx5e_destroy_rqt(priv, &hp->indir_rqt);
+}
+
+static struct mlx5e_hairpin *
+mlx5e_hairpin_create(struct mlx5e_priv *priv, struct mlx5_hairpin_params *params,
+                    int peer_ifindex)
+{
+       struct mlx5_core_dev *func_mdev, *peer_mdev;
+       struct mlx5e_hairpin *hp;
+       struct mlx5_hairpin *pair;
+       int err;
+
+       hp = kzalloc(sizeof(*hp), GFP_KERNEL);
+       if (!hp)
+               return ERR_PTR(-ENOMEM);
+
+       func_mdev = priv->mdev;
+       peer_mdev = mlx5e_hairpin_get_mdev(dev_net(priv->netdev), peer_ifindex);
+
+       pair = mlx5_core_hairpin_create(func_mdev, peer_mdev, params);
+       if (IS_ERR(pair)) {
+               err = PTR_ERR(pair);
+               goto create_pair_err;
+       }
+       hp->pair = pair;
+       hp->func_mdev = func_mdev;
+       hp->func_priv = priv;
+       hp->num_channels = params->num_channels;
+
+       err = mlx5e_hairpin_create_transport(hp);
+       if (err)
+               goto create_transport_err;
+
+       if (hp->num_channels > 1) {
+               err = mlx5e_hairpin_rss_init(hp);
+               if (err)
+                       goto rss_init_err;
+       }
+
+       return hp;
+
+rss_init_err:
+       mlx5e_hairpin_destroy_transport(hp);
+create_transport_err:
+       mlx5_core_hairpin_destroy(hp->pair);
+create_pair_err:
+       kfree(hp);
+       return ERR_PTR(err);
+}
+
+static void mlx5e_hairpin_destroy(struct mlx5e_hairpin *hp)
+{
+       if (hp->num_channels > 1)
+               mlx5e_hairpin_rss_cleanup(hp);
+       mlx5e_hairpin_destroy_transport(hp);
+       mlx5_core_hairpin_destroy(hp->pair);
+       kvfree(hp);
+}
+
+static inline u32 hash_hairpin_info(u16 peer_vhca_id, u8 prio)
+{
+       return (peer_vhca_id << 16 | prio);
+}
+
+static struct mlx5e_hairpin_entry *mlx5e_hairpin_get(struct mlx5e_priv *priv,
+                                                    u16 peer_vhca_id, u8 prio)
+{
+       struct mlx5e_hairpin_entry *hpe;
+       u32 hash_key = hash_hairpin_info(peer_vhca_id, prio);
+
+       hash_for_each_possible(priv->fs.tc.hairpin_tbl, hpe,
+                              hairpin_hlist, hash_key) {
+               if (hpe->peer_vhca_id == peer_vhca_id && hpe->prio == prio)
+                       return hpe;
+       }
+
+       return NULL;
+}
+
+#define UNKNOWN_MATCH_PRIO 8
+
+static int mlx5e_hairpin_get_prio(struct mlx5e_priv *priv,
+                                 struct mlx5_flow_spec *spec, u8 *match_prio)
+{
+       void *headers_c, *headers_v;
+       u8 prio_val, prio_mask = 0;
+       bool vlan_present;
+
+#ifdef CONFIG_MLX5_CORE_EN_DCB
+       if (priv->dcbx_dp.trust_state != MLX5_QPTS_TRUST_PCP) {
+               netdev_warn(priv->netdev,
+                           "only PCP trust state supported for hairpin\n");
+               return -EOPNOTSUPP;
+       }
+#endif
+       headers_c = MLX5_ADDR_OF(fte_match_param, spec->match_criteria, outer_headers);
+       headers_v = MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers);
+
+       vlan_present = MLX5_GET(fte_match_set_lyr_2_4, headers_v, cvlan_tag);
+       if (vlan_present) {
+               prio_mask = MLX5_GET(fte_match_set_lyr_2_4, headers_c, first_prio);
+               prio_val = MLX5_GET(fte_match_set_lyr_2_4, headers_v, first_prio);
+       }
+
+       if (!vlan_present || !prio_mask) {
+               prio_val = UNKNOWN_MATCH_PRIO;
+       } else if (prio_mask != 0x7) {
+               netdev_warn(priv->netdev,
+                           "masked priority match not supported for hairpin\n");
+               return -EOPNOTSUPP;
+       }
+
+       *match_prio = prio_val;
+       return 0;
+}
+
+static int mlx5e_hairpin_flow_add(struct mlx5e_priv *priv,
+                                 struct mlx5e_tc_flow *flow,
+                                 struct mlx5e_tc_flow_parse_attr *parse_attr)
+{
+       int peer_ifindex = parse_attr->mirred_ifindex;
+       struct mlx5_hairpin_params params;
+       struct mlx5_core_dev *peer_mdev;
+       struct mlx5e_hairpin_entry *hpe;
+       struct mlx5e_hairpin *hp;
+       u64 link_speed64;
+       u32 link_speed;
+       u8 match_prio;
+       u16 peer_id;
+       int err;
+
+       peer_mdev = mlx5e_hairpin_get_mdev(dev_net(priv->netdev), peer_ifindex);
+       if (!MLX5_CAP_GEN(priv->mdev, hairpin) || !MLX5_CAP_GEN(peer_mdev, hairpin)) {
+               netdev_warn(priv->netdev, "hairpin is not supported\n");
+               return -EOPNOTSUPP;
+       }
+
+       peer_id = MLX5_CAP_GEN(peer_mdev, vhca_id);
+       err = mlx5e_hairpin_get_prio(priv, &parse_attr->spec, &match_prio);
+       if (err)
+               return err;
+       hpe = mlx5e_hairpin_get(priv, peer_id, match_prio);
+       if (hpe)
+               goto attach_flow;
+
+       hpe = kzalloc(sizeof(*hpe), GFP_KERNEL);
+       if (!hpe)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&hpe->flows);
+       hpe->peer_vhca_id = peer_id;
+       hpe->prio = match_prio;
+
+       params.log_data_size = 15;
+       params.log_data_size = min_t(u8, params.log_data_size,
+                                    MLX5_CAP_GEN(priv->mdev, log_max_hairpin_wq_data_sz));
+       params.log_data_size = max_t(u8, params.log_data_size,
+                                    MLX5_CAP_GEN(priv->mdev, log_min_hairpin_wq_data_sz));
+       params.q_counter = priv->q_counter;
+
+       /* set hairpin pair per each 50Gbs share of the link */
+       mlx5e_get_max_linkspeed(priv->mdev, &link_speed);
+       link_speed = max_t(u32, link_speed, 50000);
+       link_speed64 = link_speed;
+       do_div(link_speed64, 50000);
+       params.num_channels = link_speed64;
+
+       hp = mlx5e_hairpin_create(priv, &params, peer_ifindex);
+       if (IS_ERR(hp)) {
+               err = PTR_ERR(hp);
+               goto create_hairpin_err;
+       }
+
+       netdev_dbg(priv->netdev, "add hairpin: tirn %x rqn %x peer %s sqn %x prio %d log data size %d\n",
+                  hp->tirn, hp->pair->rqn[0], hp->pair->peer_mdev->priv.name,
+                  hp->pair->sqn[0], match_prio, params.log_data_size);
+
+       hpe->hp = hp;
+       hash_add(priv->fs.tc.hairpin_tbl, &hpe->hairpin_hlist,
+                hash_hairpin_info(peer_id, match_prio));
+
+attach_flow:
+       if (hpe->hp->num_channels > 1) {
+               flow->flags |= MLX5E_TC_FLOW_HAIRPIN_RSS;
+               flow->nic_attr->hairpin_ft = hpe->hp->ttc.ft.t;
+       } else {
+               flow->nic_attr->hairpin_tirn = hpe->hp->tirn;
+       }
+       list_add(&flow->hairpin, &hpe->flows);
+
+       return 0;
+
+create_hairpin_err:
+       kfree(hpe);
+       return err;
+}
+
+static void mlx5e_hairpin_flow_del(struct mlx5e_priv *priv,
+                                  struct mlx5e_tc_flow *flow)
+{
+       struct list_head *next = flow->hairpin.next;
+
+       list_del(&flow->hairpin);
+
+       /* no more hairpin flows for us, release the hairpin pair */
+       if (list_empty(next)) {
+               struct mlx5e_hairpin_entry *hpe;
+
+               hpe = list_entry(next, struct mlx5e_hairpin_entry, flows);
+
+               netdev_dbg(priv->netdev, "del hairpin: peer %s\n",
+                          hpe->hp->pair->peer_mdev->priv.name);
+
+               mlx5e_hairpin_destroy(hpe->hp);
+               hash_del(&hpe->hairpin_hlist);
+               kfree(hpe);
+       }
+}
+
 static struct mlx5_flow_handle *
 mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
                      struct mlx5e_tc_flow_parse_attr *parse_attr,
@@ -229,7 +667,7 @@ mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
 {
        struct mlx5_nic_flow_attr *attr = flow->nic_attr;
        struct mlx5_core_dev *dev = priv->mdev;
-       struct mlx5_flow_destination dest = {};
+       struct mlx5_flow_destination dest[2] = {};
        struct mlx5_flow_act flow_act = {
                .action = attr->action,
                .flow_tag = attr->flow_tag,
@@ -238,18 +676,37 @@ mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
        struct mlx5_fc *counter = NULL;
        struct mlx5_flow_handle *rule;
        bool table_created = false;
-       int err;
+       int err, dest_ix = 0;
 
-       if (attr->action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST) {
-               dest.type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
-               dest.ft = priv->fs.vlan.ft.t;
-       } else if (attr->action & MLX5_FLOW_CONTEXT_ACTION_COUNT) {
-               counter = mlx5_fc_create(dev, true);
-               if (IS_ERR(counter))
-                       return ERR_CAST(counter);
+       if (flow->flags & MLX5E_TC_FLOW_HAIRPIN) {
+               err = mlx5e_hairpin_flow_add(priv, flow, parse_attr);
+               if (err) {
+                       rule = ERR_PTR(err);
+                       goto err_add_hairpin_flow;
+               }
+               if (flow->flags & MLX5E_TC_FLOW_HAIRPIN_RSS) {
+                       dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
+                       dest[dest_ix].ft = attr->hairpin_ft;
+               } else {
+                       dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_TIR;
+                       dest[dest_ix].tir_num = attr->hairpin_tirn;
+               }
+               dest_ix++;
+       } else if (attr->action & MLX5_FLOW_CONTEXT_ACTION_FWD_DEST) {
+               dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE;
+               dest[dest_ix].ft = priv->fs.vlan.ft.t;
+               dest_ix++;
+       }
 
-               dest.type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
-               dest.counter = counter;
+       if (attr->action & MLX5_FLOW_CONTEXT_ACTION_COUNT) {
+               counter = mlx5_fc_create(dev, true);
+               if (IS_ERR(counter)) {
+                       rule = ERR_CAST(counter);
+                       goto err_fc_create;
+               }
+               dest[dest_ix].type = MLX5_FLOW_DESTINATION_TYPE_COUNTER;
+               dest[dest_ix].counter = counter;
+               dest_ix++;
        }
 
        if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR) {
@@ -279,7 +736,7 @@ mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
                                                            MLX5E_TC_PRIO,
                                                            tc_tbl_size,
                                                            MLX5E_TC_TABLE_NUM_GROUPS,
-                                                           0, 0);
+                                                           MLX5E_TC_FT_LEVEL, 0);
                if (IS_ERR(priv->fs.tc.t)) {
                        netdev_err(priv->netdev,
                                   "Failed to create tc offload table\n");
@@ -292,7 +749,7 @@ mlx5e_tc_add_nic_flow(struct mlx5e_priv *priv,
 
        parse_attr->spec.match_criteria_enable = MLX5_MATCH_OUTER_HEADERS;
        rule = mlx5_add_flow_rules(priv->fs.tc.t, &parse_attr->spec,
-                                  &flow_act, &dest, 1);
+                                  &flow_act, dest, dest_ix);
 
        if (IS_ERR(rule))
                goto err_add_rule;
@@ -309,7 +766,10 @@ err_create_ft:
                mlx5e_detach_mod_hdr(priv, flow);
 err_create_mod_hdr_id:
        mlx5_fc_destroy(dev, counter);
-
+err_fc_create:
+       if (flow->flags & MLX5E_TC_FLOW_HAIRPIN)
+               mlx5e_hairpin_flow_del(priv, flow);
+err_add_hairpin_flow:
        return rule;
 }
 
@@ -330,6 +790,9 @@ static void mlx5e_tc_del_nic_flow(struct mlx5e_priv *priv,
 
        if (attr->action & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR)
                mlx5e_detach_mod_hdr(priv, flow);
+
+       if (flow->flags & MLX5E_TC_FLOW_HAIRPIN)
+               mlx5e_hairpin_flow_del(priv, flow);
 }
 
 static void mlx5e_detach_encap(struct mlx5e_priv *priv,
@@ -617,7 +1080,8 @@ static int parse_tunnel_attr(struct mlx5e_priv *priv,
                                                  FLOW_DISSECTOR_KEY_ENC_PORTS,
                                                  f->mask);
                struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
-               struct net_device *up_dev = mlx5_eswitch_get_uplink_netdev(esw);
+               struct mlx5e_rep_priv *uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
+               struct net_device *up_dev = uplink_rpriv->netdev;
                struct mlx5e_priv *up_priv = netdev_priv(up_dev);
 
                /* Full udp dst port must be given */
@@ -1421,6 +1885,20 @@ static bool actions_match_supported(struct mlx5e_priv *priv,
        return true;
 }
 
+static bool same_hw_devs(struct mlx5e_priv *priv, struct mlx5e_priv *peer_priv)
+{
+       struct mlx5_core_dev *fmdev, *pmdev;
+       u16 func_id, peer_id;
+
+       fmdev = priv->mdev;
+       pmdev = peer_priv->mdev;
+
+       func_id = (u16)((fmdev->pdev->bus->number << 8) | PCI_SLOT(fmdev->pdev->devfn));
+       peer_id = (u16)((pmdev->pdev->bus->number << 8) | PCI_SLOT(pmdev->pdev->devfn));
+
+       return (func_id == peer_id);
+}
+
 static int parse_tc_nic_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                                struct mlx5e_tc_flow_parse_attr *parse_attr,
                                struct mlx5e_tc_flow *flow)
@@ -1465,6 +1943,23 @@ static int parse_tc_nic_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                        return -EOPNOTSUPP;
                }
 
+               if (is_tcf_mirred_egress_redirect(a)) {
+                       struct net_device *peer_dev = tcf_mirred_dev(a);
+
+                       if (priv->netdev->netdev_ops == peer_dev->netdev_ops &&
+                           same_hw_devs(priv, netdev_priv(peer_dev))) {
+                               parse_attr->mirred_ifindex = peer_dev->ifindex;
+                               flow->flags |= MLX5E_TC_FLOW_HAIRPIN;
+                               attr->action |= MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
+                                               MLX5_FLOW_CONTEXT_ACTION_COUNT;
+                       } else {
+                               netdev_warn(priv->netdev, "device %s not on same HW, can't offload\n",
+                                           peer_dev->name);
+                               return -EINVAL;
+                       }
+                       continue;
+               }
+
                if (is_tcf_skbedit_mark(a)) {
                        u32 mark = tcf_skbedit_mark(a);
 
@@ -1507,6 +2002,7 @@ static int mlx5e_route_lookup_ipv4(struct mlx5e_priv *priv,
                                   int *out_ttl)
 {
        struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
+       struct mlx5e_rep_priv *uplink_rpriv;
        struct rtable *rt;
        struct neighbour *n = NULL;
 
@@ -1520,9 +2016,10 @@ static int mlx5e_route_lookup_ipv4(struct mlx5e_priv *priv,
 #else
        return -EOPNOTSUPP;
 #endif
+       uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
        /* if the egress device isn't on the same HW e-switch, we use the uplink */
        if (!switchdev_port_same_parent_id(priv->netdev, rt->dst.dev))
-               *out_dev = mlx5_eswitch_get_uplink_netdev(esw);
+               *out_dev = uplink_rpriv->netdev;
        else
                *out_dev = rt->dst.dev;
 
@@ -1547,6 +2044,7 @@ static int mlx5e_route_lookup_ipv6(struct mlx5e_priv *priv,
        struct dst_entry *dst;
 
 #if IS_ENABLED(CONFIG_INET) && IS_ENABLED(CONFIG_IPV6)
+       struct mlx5e_rep_priv *uplink_rpriv;
        struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
        int ret;
 
@@ -1557,9 +2055,10 @@ static int mlx5e_route_lookup_ipv6(struct mlx5e_priv *priv,
 
        *out_ttl = ip6_dst_hoplimit(dst);
 
+       uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
        /* if the egress device isn't on the same HW e-switch, we use the uplink */
        if (!switchdev_port_same_parent_id(priv->netdev, dst->dev))
-               *out_dev = mlx5_eswitch_get_uplink_netdev(esw);
+               *out_dev = uplink_rpriv->netdev;
        else
                *out_dev = dst->dev;
 #else
@@ -1859,7 +2358,9 @@ static int mlx5e_attach_encap(struct mlx5e_priv *priv,
                              struct mlx5e_tc_flow *flow)
 {
        struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
-       struct net_device *up_dev = mlx5_eswitch_get_uplink_netdev(esw);
+       struct mlx5e_rep_priv *uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw,
+                                                                          REP_ETH);
+       struct net_device *up_dev = uplink_rpriv->netdev;
        unsigned short family = ip_tunnel_info_af(tun_info);
        struct mlx5e_priv *up_priv = netdev_priv(up_dev);
        struct mlx5_esw_flow_attr *attr = flow->esw_attr;
@@ -1982,11 +2483,10 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                }
 
                if (is_tcf_mirred_egress_redirect(a)) {
-                       int ifindex = tcf_mirred_ifindex(a);
                        struct net_device *out_dev;
                        struct mlx5e_priv *out_priv;
 
-                       out_dev = __dev_get_by_index(dev_net(priv->netdev), ifindex);
+                       out_dev = tcf_mirred_dev(a);
 
                        if (switchdev_port_same_parent_id(priv->netdev,
                                                          out_dev)) {
@@ -1996,7 +2496,7 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv, struct tcf_exts *exts,
                                rpriv = out_priv->ppriv;
                                attr->out_rep = rpriv->rep;
                        } else if (encap) {
-                               parse_attr->mirred_ifindex = ifindex;
+                               parse_attr->mirred_ifindex = out_dev->ifindex;
                                parse_attr->tun_info = *info;
                                attr->parse_attr = parse_attr;
                                attr->action |= MLX5_FLOW_CONTEXT_ACTION_ENCAP |
@@ -2182,6 +2682,7 @@ int mlx5e_tc_init(struct mlx5e_priv *priv)
        struct mlx5e_tc_table *tc = &priv->fs.tc;
 
        hash_init(tc->mod_hdr_tbl);
+       hash_init(tc->hairpin_tbl);
 
        tc->ht_params = mlx5e_tc_flow_ht_params;
        return rhashtable_init(&tc->ht, &tc->ht_params);