#include <linux/mlx5/fs.h>
 #include "eswitch.h"
+#include "fs_core.h"
 
 struct mlx5_termtbl_handle {
        struct hlist_node termtbl_hlist;
                     sizeof(dest->vport.num), hash);
        hash = jhash((const void *)&dest->vport.vhca_id,
                     sizeof(dest->vport.num), hash);
+       if (dest->vport.pkt_reformat)
+               hash = jhash(dest->vport.pkt_reformat,
+                            sizeof(*dest->vport.pkt_reformat),
+                            hash);
        return hash;
 }
 
                         struct mlx5_flow_act *flow_act2,
                         struct mlx5_flow_destination *dest2)
 {
-       return flow_act1->action != flow_act2->action ||
-              dest1->vport.num != dest2->vport.num ||
-              dest1->vport.vhca_id != dest2->vport.vhca_id ||
-              memcmp(&flow_act1->vlan, &flow_act2->vlan,
-                     sizeof(flow_act1->vlan));
+       int ret;
+
+       ret = flow_act1->action != flow_act2->action ||
+             dest1->vport.num != dest2->vport.num ||
+             dest1->vport.vhca_id != dest2->vport.vhca_id ||
+             memcmp(&flow_act1->vlan, &flow_act2->vlan,
+                    sizeof(flow_act1->vlan));
+       if (ret)
+               return ret;
+
+       return dest1->vport.pkt_reformat && dest2->vport.pkt_reformat ?
+              memcmp(dest1->vport.pkt_reformat, dest2->vport.pkt_reformat,
+                     sizeof(*dest1->vport.pkt_reformat)) : 0;
 }
 
 static int
        /* As this is the terminating action then the termination table is the
         * same prio as the slow path
         */
-       ft_attr.flags = MLX5_FLOW_TABLE_TERMINATION;
+       ft_attr.flags = MLX5_FLOW_TABLE_TERMINATION |
+                       MLX5_FLOW_TABLE_TUNNEL_EN_REFORMAT;
        ft_attr.prio = FDB_SLOW_PATH;
        ft_attr.max_fte = 1;
        ft_attr.autogroup.max_num_groups = 1;
 
        tt->rule = mlx5_add_flow_rules(tt->termtbl, NULL, flow_act,
                                       &tt->dest, 1);
-
        if (IS_ERR(tt->rule)) {
                esw_warn(dev, "Failed to create termination table rule\n");
                goto add_flow_err;
 static struct mlx5_termtbl_handle *
 mlx5_eswitch_termtbl_get_create(struct mlx5_eswitch *esw,
                                struct mlx5_flow_act *flow_act,
-                               struct mlx5_flow_destination *dest)
+                               struct mlx5_flow_destination *dest,
+                               struct mlx5_esw_flow_attr *attr)
 {
        struct mlx5_termtbl_handle *tt;
        bool found = false;
        int err;
 
        mutex_lock(&esw->offloads.termtbl_mutex);
-
        hash_key = mlx5_eswitch_termtbl_hash(flow_act, dest);
        hash_for_each_possible(esw->offloads.termtbl_tbl, tt,
                               termtbl_hlist, hash_key) {
        tt->dest.type = MLX5_FLOW_DESTINATION_TYPE_VPORT;
        tt->dest.vport.num = dest->vport.num;
        tt->dest.vport.vhca_id = dest->vport.vhca_id;
+       tt->dest.vport.flags = dest->vport.flags;
        memcpy(&tt->flow_act, flow_act, sizeof(*flow_act));
 
        err = mlx5_eswitch_termtbl_create(esw->dev, tt, flow_act);
        }
 }
 
+static bool mlx5_eswitch_termtbl_is_encap_reformat(struct mlx5_pkt_reformat *rt)
+{
+       switch (rt->reformat_type) {
+       case MLX5_REFORMAT_TYPE_L2_TO_VXLAN:
+       case MLX5_REFORMAT_TYPE_L2_TO_NVGRE:
+       case MLX5_REFORMAT_TYPE_L2_TO_L2_TUNNEL:
+       case MLX5_REFORMAT_TYPE_L2_TO_L3_TUNNEL:
+               return true;
+       default:
+               return false;
+       }
+}
+
 static void
 mlx5_eswitch_termtbl_actions_move(struct mlx5_flow_act *src,
                                  struct mlx5_flow_act *dst)
 {
-       if (!(src->action & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH))
-               return;
-
-       src->action &= ~MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH;
-       dst->action |= MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH;
-       memcpy(&dst->vlan[0], &src->vlan[0], sizeof(src->vlan[0]));
-       memset(&src->vlan[0], 0, sizeof(src->vlan[0]));
-
-       if (!(src->action & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2))
-               return;
+       if (src->action & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH) {
+               src->action &= ~MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH;
+               dst->action |= MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH;
+               memcpy(&dst->vlan[0], &src->vlan[0], sizeof(src->vlan[0]));
+               memset(&src->vlan[0], 0, sizeof(src->vlan[0]));
+
+               if (src->action & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2) {
+                       src->action &= ~MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2;
+                       dst->action |= MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2;
+                       memcpy(&dst->vlan[1], &src->vlan[1], sizeof(src->vlan[1]));
+                       memset(&src->vlan[1], 0, sizeof(src->vlan[1]));
+               }
+       }
 
-       src->action &= ~MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2;
-       dst->action |= MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH_2;
-       memcpy(&dst->vlan[1], &src->vlan[1], sizeof(src->vlan[1]));
-       memset(&src->vlan[1], 0, sizeof(src->vlan[1]));
+       if (src->action & MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT &&
+           mlx5_eswitch_termtbl_is_encap_reformat(src->pkt_reformat)) {
+               src->action &= ~MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT;
+               dst->action |= MLX5_FLOW_CONTEXT_ACTION_PACKET_REFORMAT;
+               dst->pkt_reformat = src->pkt_reformat;
+               src->pkt_reformat = NULL;
+       }
 }
 
 static bool mlx5_eswitch_offload_is_uplink_port(const struct mlx5_eswitch *esw,
                              struct mlx5_flow_act *flow_act,
                              struct mlx5_flow_spec *spec)
 {
+       int i;
+
        if (!MLX5_CAP_ESW_FLOWTABLE_FDB(esw->dev, termination_table) ||
-           attr->flags & MLX5_ESW_ATTR_FLAG_SLOW_PATH)
+           attr->flags & MLX5_ESW_ATTR_FLAG_SLOW_PATH ||
+           !mlx5_eswitch_offload_is_uplink_port(esw, spec))
                return false;
 
        /* push vlan on RX */
-       return (flow_act->action & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH) &&
-               mlx5_eswitch_offload_is_uplink_port(esw, spec);
+       if (flow_act->action & MLX5_FLOW_CONTEXT_ACTION_VLAN_PUSH)
+               return true;
+
+       /* hairpin */
+       for (i = attr->split_count; i < attr->out_count; i++)
+               if (attr->dests[i].rep->vport == MLX5_VPORT_UPLINK)
+                       return true;
+
+       return false;
 }
 
 struct mlx5_flow_handle *
 
                /* get the terminating table for the action list */
                tt = mlx5_eswitch_termtbl_get_create(esw, &term_tbl_act,
-                                                    &dest[i]);
+                                                    &dest[i], attr);
                if (IS_ERR(tt)) {
                        esw_warn(esw->dev, "Failed to create termination table\n");
                        goto revert_changes;