Blame SOURCES/0243-netdrv-net-mlx5e-CT-Introduce-connection-tracking.patch

d8f823
From c500913088310b727470cefa9b1a04c3bd62142d Mon Sep 17 00:00:00 2001
d8f823
From: Alaa Hleihel <ahleihel@redhat.com>
d8f823
Date: Tue, 19 May 2020 07:49:05 -0400
d8f823
Subject: [PATCH 243/312] [netdrv] net/mlx5e: CT: Introduce connection tracking
d8f823
d8f823
Message-id: <20200519074934.6303-35-ahleihel@redhat.com>
d8f823
Patchwork-id: 310535
d8f823
Patchwork-instance: patchwork
d8f823
O-Subject: [RHEL8.3 BZ 1663246 34/63] net/mlx5e: CT: Introduce connection tracking
d8f823
Bugzilla: 1790219 1663246
d8f823
RH-Acked-by: Marcelo Leitner <mleitner@redhat.com>
d8f823
RH-Acked-by: Jarod Wilson <jarod@redhat.com>
d8f823
RH-Acked-by: John Linville <linville@redhat.com>
d8f823
RH-Acked-by: Ivan Vecera <ivecera@redhat.com>
d8f823
RH-Acked-by: Tony Camuso <tcamuso@redhat.com>
d8f823
RH-Acked-by: Kamal Heib <kheib@redhat.com>
d8f823
d8f823
Bugzilla: http://bugzilla.redhat.com/1663246
d8f823
Bugzilla: http://bugzilla.redhat.com/1790219
d8f823
Upstream: v5.7-rc1
d8f823
d8f823
commit 4c3844d9e97e10f0cf024fe7f24dcefa133fe9e2
d8f823
Author: Paul Blakey <paulb@mellanox.com>
d8f823
Date:   Thu Mar 12 12:23:14 2020 +0200
d8f823
d8f823
    net/mlx5e: CT: Introduce connection tracking
d8f823
d8f823
    Add support for offloading tc ct action and ct matches.
d8f823
    We translate the tc filter with CT action the following HW model:
d8f823
d8f823
    +-------------------+      +--------------------+    +--------------+
d8f823
    + pre_ct (tc chain) +----->+ CT (nat or no nat) +--->+ post_ct      +----->
d8f823
    + original match    +  |   + tuple + zone match + |  + fte_id match +  |
d8f823
    +-------------------+  |   +--------------------+ |  +--------------+  |
d8f823
                           v                          v                    v
d8f823
                          set chain miss mapping  set mark             original
d8f823
                          set fte_id              set label            filter
d8f823
                          set zone                set established      actions
d8f823
                          set tunnel_id           do nat (if needed)
d8f823
                          do decap
d8f823
d8f823
    Signed-off-by: Paul Blakey <paulb@mellanox.com>
d8f823
    Reviewed-by: Oz Shlomo <ozsh@mellanox.com>
d8f823
    Reviewed-by: Roi Dayan <roid@mellanox.com>
d8f823
    Signed-off-by: David S. Miller <davem@davemloft.net>
d8f823
d8f823
Signed-off-by: Alaa Hleihel <ahleihel@redhat.com>
d8f823
Signed-off-by: Frantisek Hrbata <fhrbata@redhat.com>
d8f823
---
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/Kconfig    |  10 +
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/Makefile   |   1 +
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c | 541 +++++++++++++++++++++
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.h | 140 ++++++
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/en_rep.h   |   3 +
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/en_tc.c    | 104 +++-
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/en_tc.h    |   8 +
d8f823
 drivers/net/ethernet/mellanox/mlx5/core/eswitch.h  |   4 +
d8f823
 8 files changed, 795 insertions(+), 16 deletions(-)
d8f823
 create mode 100644 drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c
d8f823
 create mode 100644 drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.h
d8f823
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/Kconfig b/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
d8f823
index 6919161c8f9b..e1dff89804f6 100644
d8f823
--- a/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
d8f823
@@ -77,6 +77,16 @@ config MLX5_ESWITCH
d8f823
 	        Legacy SRIOV mode (L2 mac vlan steering based).
d8f823
 	        Switchdev mode (eswitch offloads).
d8f823
 
d8f823
+config MLX5_TC_CT
d8f823
+	bool "MLX5 TC connection tracking offload support"
d8f823
+	depends on MLX5_CORE_EN && NET_SWITCHDEV && NF_FLOW_TABLE && NET_ACT_CT && NET_TC_SKB_EXT
d8f823
+	default y
d8f823
+	help
d8f823
+	  Say Y here if you want to support offloading connection tracking rules
d8f823
+	  via tc ct action.
d8f823
+
d8f823
+	  If unsure, set to Y
d8f823
+
d8f823
 config MLX5_CORE_EN_DCB
d8f823
 	bool "Data Center Bridging (DCB) Support"
d8f823
 	default y
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/Makefile b/drivers/net/ethernet/mellanox/mlx5/core/Makefile
d8f823
index be4e7470830f..9e85def607b9 100644
d8f823
--- a/drivers/net/ethernet/mellanox/mlx5/core/Makefile
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/Makefile
d8f823
@@ -36,6 +36,7 @@ mlx5_core-$(CONFIG_MLX5_CORE_EN_DCB) += en_dcbnl.o en/port_buffer.o
d8f823
 mlx5_core-$(CONFIG_MLX5_ESWITCH)     += en_rep.o en_tc.o en/tc_tun.o lib/port_tun.o lag_mp.o \
d8f823
 					lib/geneve.o en/mapping.o en/tc_tun_vxlan.o en/tc_tun_gre.o \
d8f823
 					en/tc_tun_geneve.o diag/en_tc_tracepoint.o
d8f823
+mlx5_core-$(CONFIG_MLX5_TC_CT)	     += en/tc_ct.o
d8f823
 
d8f823
 #
d8f823
 # Core extra
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c
d8f823
new file mode 100644
d8f823
index 000000000000..c1130460bb60
d8f823
--- /dev/null
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c
d8f823
@@ -0,0 +1,541 @@
d8f823
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
d8f823
+/* Copyright (c) 2019 Mellanox Technologies. */
d8f823
+
d8f823
+#include <net/netfilter/nf_conntrack.h>
d8f823
+#include <net/netfilter/nf_conntrack_core.h>
d8f823
+#include <net/netfilter/nf_conntrack_zones.h>
d8f823
+#include <net/netfilter/nf_conntrack_labels.h>
d8f823
+#include <net/netfilter/nf_conntrack_helper.h>
d8f823
+#include <net/netfilter/nf_conntrack_acct.h>
d8f823
+#include <uapi/linux/tc_act/tc_pedit.h>
d8f823
+#include <net/tc_act/tc_ct.h>
d8f823
+#include <net/flow_offload.h>
d8f823
+#include <linux/workqueue.h>
d8f823
+
d8f823
+#include "en/tc_ct.h"
d8f823
+#include "en.h"
d8f823
+#include "en_tc.h"
d8f823
+#include "en_rep.h"
d8f823
+#include "eswitch_offloads_chains.h"
d8f823
+
d8f823
+#define MLX5_CT_ZONE_BITS (mlx5e_tc_attr_to_reg_mappings[ZONE_TO_REG].mlen * 8)
d8f823
+#define MLX5_CT_ZONE_MASK GENMASK(MLX5_CT_ZONE_BITS - 1, 0)
d8f823
+#define MLX5_CT_STATE_ESTABLISHED_BIT BIT(1)
d8f823
+#define MLX5_CT_STATE_TRK_BIT BIT(2)
d8f823
+
d8f823
+#define MLX5_FTE_ID_BITS (mlx5e_tc_attr_to_reg_mappings[FTEID_TO_REG].mlen * 8)
d8f823
+#define MLX5_FTE_ID_MAX GENMASK(MLX5_FTE_ID_BITS - 1, 0)
d8f823
+#define MLX5_FTE_ID_MASK MLX5_FTE_ID_MAX
d8f823
+
d8f823
+#define ct_dbg(fmt, args...)\
d8f823
+	netdev_dbg(ct_priv->netdev, "ct_debug: " fmt "\n", ##args)
d8f823
+
d8f823
+struct mlx5_tc_ct_priv {
d8f823
+	struct mlx5_eswitch *esw;
d8f823
+	const struct net_device *netdev;
d8f823
+	struct idr fte_ids;
d8f823
+	struct mlx5_flow_table *ct;
d8f823
+	struct mlx5_flow_table *ct_nat;
d8f823
+	struct mlx5_flow_table *post_ct;
d8f823
+	struct mutex control_lock; /* guards parallel adds/dels */
d8f823
+};
d8f823
+
d8f823
+struct mlx5_ct_flow {
d8f823
+	struct mlx5_esw_flow_attr pre_ct_attr;
d8f823
+	struct mlx5_esw_flow_attr post_ct_attr;
d8f823
+	struct mlx5_flow_handle *pre_ct_rule;
d8f823
+	struct mlx5_flow_handle *post_ct_rule;
d8f823
+	u32 fte_id;
d8f823
+	u32 chain_mapping;
d8f823
+};
d8f823
+
d8f823
+static struct mlx5_tc_ct_priv *
d8f823
+mlx5_tc_ct_get_ct_priv(struct mlx5e_priv *priv)
d8f823
+{
d8f823
+	struct mlx5_eswitch *esw = priv->mdev->priv.eswitch;
d8f823
+	struct mlx5_rep_uplink_priv *uplink_priv;
d8f823
+	struct mlx5e_rep_priv *uplink_rpriv;
d8f823
+
d8f823
+	uplink_rpriv = mlx5_eswitch_get_uplink_priv(esw, REP_ETH);
d8f823
+	uplink_priv = &uplink_rpriv->uplink_priv;
d8f823
+	return uplink_priv->ct_priv;
d8f823
+}
d8f823
+
d8f823
+int
d8f823
+mlx5_tc_ct_parse_match(struct mlx5e_priv *priv,
d8f823
+		       struct mlx5_flow_spec *spec,
d8f823
+		       struct flow_cls_offload *f,
d8f823
+		       struct netlink_ext_ack *extack)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv = mlx5_tc_ct_get_ct_priv(priv);
d8f823
+	struct flow_dissector_key_ct *mask, *key;
d8f823
+	bool trk, est, untrk, unest, new, unnew;
d8f823
+	u32 ctstate = 0, ctstate_mask = 0;
d8f823
+	u16 ct_state_on, ct_state_off;
d8f823
+	u16 ct_state, ct_state_mask;
d8f823
+	struct flow_match_ct match;
d8f823
+
d8f823
+	if (!flow_rule_match_key(f->rule, FLOW_DISSECTOR_KEY_CT))
d8f823
+		return 0;
d8f823
+
d8f823
+	if (!ct_priv) {
d8f823
+		NL_SET_ERR_MSG_MOD(extack,
d8f823
+				   "offload of ct matching isn't available");
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	flow_rule_match_ct(f->rule, &match);
d8f823
+
d8f823
+	key = match.key;
d8f823
+	mask = match.mask;
d8f823
+
d8f823
+	ct_state = key->ct_state;
d8f823
+	ct_state_mask = mask->ct_state;
d8f823
+
d8f823
+	if (ct_state_mask & ~(TCA_FLOWER_KEY_CT_FLAGS_TRACKED |
d8f823
+			      TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED |
d8f823
+			      TCA_FLOWER_KEY_CT_FLAGS_NEW)) {
d8f823
+		NL_SET_ERR_MSG_MOD(extack,
d8f823
+				   "only ct_state trk, est and new are supported for offload");
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	if (mask->ct_labels[1] || mask->ct_labels[2] || mask->ct_labels[3]) {
d8f823
+		NL_SET_ERR_MSG_MOD(extack,
d8f823
+				   "only lower 32bits of ct_labels are supported for offload");
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	ct_state_on = ct_state & ct_state_mask;
d8f823
+	ct_state_off = (ct_state & ct_state_mask) ^ ct_state_mask;
d8f823
+	trk = ct_state_on & TCA_FLOWER_KEY_CT_FLAGS_TRACKED;
d8f823
+	new = ct_state_on & TCA_FLOWER_KEY_CT_FLAGS_NEW;
d8f823
+	est = ct_state_on & TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED;
d8f823
+	untrk = ct_state_off & TCA_FLOWER_KEY_CT_FLAGS_TRACKED;
d8f823
+	unnew = ct_state_off & TCA_FLOWER_KEY_CT_FLAGS_NEW;
d8f823
+	unest = ct_state_off & TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED;
d8f823
+
d8f823
+	ctstate |= trk ? MLX5_CT_STATE_TRK_BIT : 0;
d8f823
+	ctstate |= est ? MLX5_CT_STATE_ESTABLISHED_BIT : 0;
d8f823
+	ctstate_mask |= (untrk || trk) ? MLX5_CT_STATE_TRK_BIT : 0;
d8f823
+	ctstate_mask |= (unest || est) ? MLX5_CT_STATE_ESTABLISHED_BIT : 0;
d8f823
+
d8f823
+	if (new) {
d8f823
+		NL_SET_ERR_MSG_MOD(extack,
d8f823
+				   "matching on ct_state +new isn't supported");
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	if (mask->ct_zone)
d8f823
+		mlx5e_tc_match_to_reg_match(spec, ZONE_TO_REG,
d8f823
+					    key->ct_zone, MLX5_CT_ZONE_MASK);
d8f823
+	if (ctstate_mask)
d8f823
+		mlx5e_tc_match_to_reg_match(spec, CTSTATE_TO_REG,
d8f823
+					    ctstate, ctstate_mask);
d8f823
+	if (mask->ct_mark)
d8f823
+		mlx5e_tc_match_to_reg_match(spec, MARK_TO_REG,
d8f823
+					    key->ct_mark, mask->ct_mark);
d8f823
+	if (mask->ct_labels[0])
d8f823
+		mlx5e_tc_match_to_reg_match(spec, LABELS_TO_REG,
d8f823
+					    key->ct_labels[0],
d8f823
+					    mask->ct_labels[0]);
d8f823
+
d8f823
+	return 0;
d8f823
+}
d8f823
+
d8f823
+int
d8f823
+mlx5_tc_ct_parse_action(struct mlx5e_priv *priv,
d8f823
+			struct mlx5_esw_flow_attr *attr,
d8f823
+			const struct flow_action_entry *act,
d8f823
+			struct netlink_ext_ack *extack)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv = mlx5_tc_ct_get_ct_priv(priv);
d8f823
+
d8f823
+	if (!ct_priv) {
d8f823
+		NL_SET_ERR_MSG_MOD(extack,
d8f823
+				   "offload of ct action isn't available");
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	attr->ct_attr.zone = act->ct.zone;
d8f823
+	attr->ct_attr.ct_action = act->ct.action;
d8f823
+
d8f823
+	return 0;
d8f823
+}
d8f823
+
d8f823
+/* We translate the tc filter with CT action to the following HW model:
d8f823
+ *
d8f823
+ * +-------------------+      +--------------------+    +--------------+
d8f823
+ * + pre_ct (tc chain) +----->+ CT (nat or no nat) +--->+ post_ct      +----->
d8f823
+ * + original match    +  |   + tuple + zone match + |  + fte_id match +  |
d8f823
+ * +-------------------+  |   +--------------------+ |  +--------------+  |
d8f823
+ *                        v                          v                    v
d8f823
+ *                       set chain miss mapping  set mark             original
d8f823
+ *                       set fte_id              set label            filter
d8f823
+ *                       set zone                set established      actions
d8f823
+ *                       set tunnel_id           do nat (if needed)
d8f823
+ *                       do decap
d8f823
+ */
d8f823
+static int
d8f823
+__mlx5_tc_ct_flow_offload(struct mlx5e_priv *priv,
d8f823
+			  struct mlx5e_tc_flow *flow,
d8f823
+			  struct mlx5_flow_spec *orig_spec,
d8f823
+			  struct mlx5_esw_flow_attr *attr,
d8f823
+			  struct mlx5_flow_handle **flow_rule)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv = mlx5_tc_ct_get_ct_priv(priv);
d8f823
+	bool nat = attr->ct_attr.ct_action & TCA_CT_ACT_NAT;
d8f823
+	struct mlx5e_tc_mod_hdr_acts pre_mod_acts = {};
d8f823
+	struct mlx5_eswitch *esw = ct_priv->esw;
d8f823
+	struct mlx5_flow_spec post_ct_spec = {};
d8f823
+	struct mlx5_esw_flow_attr *pre_ct_attr;
d8f823
+	struct  mlx5_modify_hdr *mod_hdr;
d8f823
+	struct mlx5_flow_handle *rule;
d8f823
+	struct mlx5_ct_flow *ct_flow;
d8f823
+	int chain_mapping = 0, err;
d8f823
+	u32 fte_id = 1;
d8f823
+
d8f823
+	ct_flow = kzalloc(sizeof(*ct_flow), GFP_KERNEL);
d8f823
+	if (!ct_flow)
d8f823
+		return -ENOMEM;
d8f823
+
d8f823
+	err = idr_alloc_u32(&ct_priv->fte_ids, ct_flow, &fte_id,
d8f823
+			    MLX5_FTE_ID_MAX, GFP_KERNEL);
d8f823
+	if (err) {
d8f823
+		netdev_warn(priv->netdev,
d8f823
+			    "Failed to allocate fte id, err: %d\n", err);
d8f823
+		goto err_idr;
d8f823
+	}
d8f823
+	ct_flow->fte_id = fte_id;
d8f823
+
d8f823
+	/* Base esw attributes of both rules on original rule attribute */
d8f823
+	pre_ct_attr = &ct_flow->pre_ct_attr;
d8f823
+	memcpy(pre_ct_attr, attr, sizeof(*attr));
d8f823
+	memcpy(&ct_flow->post_ct_attr, attr, sizeof(*attr));
d8f823
+
d8f823
+	/* Modify the original rule's action to fwd and modify, leave decap */
d8f823
+	pre_ct_attr->action = attr->action & MLX5_FLOW_CONTEXT_ACTION_DECAP;
d8f823
+	pre_ct_attr->action |= MLX5_FLOW_CONTEXT_ACTION_FWD_DEST |
d8f823
+			       MLX5_FLOW_CONTEXT_ACTION_MOD_HDR;
d8f823
+
d8f823
+	/* Write chain miss tag for miss in ct table as we
d8f823
+	 * don't go though all prios of this chain as normal tc rules
d8f823
+	 * miss.
d8f823
+	 */
d8f823
+	err = mlx5_esw_chains_get_chain_mapping(esw, attr->chain,
d8f823
+						&chain_mapping);
d8f823
+	if (err) {
d8f823
+		ct_dbg("Failed to get chain register mapping for chain");
d8f823
+		goto err_get_chain;
d8f823
+	}
d8f823
+	ct_flow->chain_mapping = chain_mapping;
d8f823
+
d8f823
+	err = mlx5e_tc_match_to_reg_set(esw->dev, &pre_mod_acts,
d8f823
+					CHAIN_TO_REG, chain_mapping);
d8f823
+	if (err) {
d8f823
+		ct_dbg("Failed to set chain register mapping");
d8f823
+		goto err_mapping;
d8f823
+	}
d8f823
+
d8f823
+	err = mlx5e_tc_match_to_reg_set(esw->dev, &pre_mod_acts, ZONE_TO_REG,
d8f823
+					attr->ct_attr.zone &
d8f823
+					MLX5_CT_ZONE_MASK);
d8f823
+	if (err) {
d8f823
+		ct_dbg("Failed to set zone register mapping");
d8f823
+		goto err_mapping;
d8f823
+	}
d8f823
+
d8f823
+	err = mlx5e_tc_match_to_reg_set(esw->dev, &pre_mod_acts,
d8f823
+					FTEID_TO_REG, fte_id);
d8f823
+	if (err) {
d8f823
+		ct_dbg("Failed to set fte_id register mapping");
d8f823
+		goto err_mapping;
d8f823
+	}
d8f823
+
d8f823
+	/* If original flow is decap, we do it before going into ct table
d8f823
+	 * so add a rewrite for the tunnel match_id.
d8f823
+	 */
d8f823
+	if ((pre_ct_attr->action & MLX5_FLOW_CONTEXT_ACTION_DECAP) &&
d8f823
+	    attr->chain == 0) {
d8f823
+		u32 tun_id = mlx5e_tc_get_flow_tun_id(flow);
d8f823
+
d8f823
+		err = mlx5e_tc_match_to_reg_set(esw->dev, &pre_mod_acts,
d8f823
+						TUNNEL_TO_REG,
d8f823
+						tun_id);
d8f823
+		if (err) {
d8f823
+			ct_dbg("Failed to set tunnel register mapping");
d8f823
+			goto err_mapping;
d8f823
+		}
d8f823
+	}
d8f823
+
d8f823
+	mod_hdr = mlx5_modify_header_alloc(esw->dev,
d8f823
+					   MLX5_FLOW_NAMESPACE_FDB,
d8f823
+					   pre_mod_acts.num_actions,
d8f823
+					   pre_mod_acts.actions);
d8f823
+	if (IS_ERR(mod_hdr)) {
d8f823
+		err = PTR_ERR(mod_hdr);
d8f823
+		ct_dbg("Failed to create pre ct mod hdr");
d8f823
+		goto err_mapping;
d8f823
+	}
d8f823
+	pre_ct_attr->modify_hdr = mod_hdr;
d8f823
+
d8f823
+	/* Post ct rule matches on fte_id and executes original rule's
d8f823
+	 * tc rule action
d8f823
+	 */
d8f823
+	mlx5e_tc_match_to_reg_match(&post_ct_spec, FTEID_TO_REG,
d8f823
+				    fte_id, MLX5_FTE_ID_MASK);
d8f823
+
d8f823
+	/* Put post_ct rule on post_ct fdb */
d8f823
+	ct_flow->post_ct_attr.chain = 0;
d8f823
+	ct_flow->post_ct_attr.prio = 0;
d8f823
+	ct_flow->post_ct_attr.fdb = ct_priv->post_ct;
d8f823
+
d8f823
+	ct_flow->post_ct_attr.inner_match_level = MLX5_MATCH_NONE;
d8f823
+	ct_flow->post_ct_attr.outer_match_level = MLX5_MATCH_NONE;
d8f823
+	ct_flow->post_ct_attr.action &= ~(MLX5_FLOW_CONTEXT_ACTION_DECAP);
d8f823
+	rule = mlx5_eswitch_add_offloaded_rule(esw, &post_ct_spec,
d8f823
+					       &ct_flow->post_ct_attr);
d8f823
+	ct_flow->post_ct_rule = rule;
d8f823
+	if (IS_ERR(ct_flow->post_ct_rule)) {
d8f823
+		err = PTR_ERR(ct_flow->post_ct_rule);
d8f823
+		ct_dbg("Failed to add post ct rule");
d8f823
+		goto err_insert_post_ct;
d8f823
+	}
d8f823
+
d8f823
+	/* Change original rule point to ct table */
d8f823
+	pre_ct_attr->dest_chain = 0;
d8f823
+	pre_ct_attr->dest_ft = nat ? ct_priv->ct_nat : ct_priv->ct;
d8f823
+	ct_flow->pre_ct_rule = mlx5_eswitch_add_offloaded_rule(esw,
d8f823
+							       orig_spec,
d8f823
+							       pre_ct_attr);
d8f823
+	if (IS_ERR(ct_flow->pre_ct_rule)) {
d8f823
+		err = PTR_ERR(ct_flow->pre_ct_rule);
d8f823
+		ct_dbg("Failed to add pre ct rule");
d8f823
+		goto err_insert_orig;
d8f823
+	}
d8f823
+
d8f823
+	attr->ct_attr.ct_flow = ct_flow;
d8f823
+	*flow_rule = ct_flow->post_ct_rule;
d8f823
+	dealloc_mod_hdr_actions(&pre_mod_acts);
d8f823
+
d8f823
+	return 0;
d8f823
+
d8f823
+err_insert_orig:
d8f823
+	mlx5_eswitch_del_offloaded_rule(ct_priv->esw, ct_flow->post_ct_rule,
d8f823
+					&ct_flow->post_ct_attr);
d8f823
+err_insert_post_ct:
d8f823
+	mlx5_modify_header_dealloc(priv->mdev, pre_ct_attr->modify_hdr);
d8f823
+err_mapping:
d8f823
+	dealloc_mod_hdr_actions(&pre_mod_acts);
d8f823
+	mlx5_esw_chains_put_chain_mapping(esw, ct_flow->chain_mapping);
d8f823
+err_get_chain:
d8f823
+	idr_remove(&ct_priv->fte_ids, fte_id);
d8f823
+err_idr:
d8f823
+	kfree(ct_flow);
d8f823
+	netdev_warn(priv->netdev, "Failed to offload ct flow, err %d\n", err);
d8f823
+	return err;
d8f823
+}
d8f823
+
d8f823
+struct mlx5_flow_handle *
d8f823
+mlx5_tc_ct_flow_offload(struct mlx5e_priv *priv,
d8f823
+			struct mlx5e_tc_flow *flow,
d8f823
+			struct mlx5_flow_spec *spec,
d8f823
+			struct mlx5_esw_flow_attr *attr)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv = mlx5_tc_ct_get_ct_priv(priv);
d8f823
+	struct mlx5_flow_handle *rule;
d8f823
+	int err;
d8f823
+
d8f823
+	if (!ct_priv)
d8f823
+		return ERR_PTR(-EOPNOTSUPP);
d8f823
+
d8f823
+	mutex_lock(&ct_priv->control_lock);
d8f823
+	err = __mlx5_tc_ct_flow_offload(priv, flow, spec, attr, &rule;;
d8f823
+	mutex_unlock(&ct_priv->control_lock);
d8f823
+	if (err)
d8f823
+		return ERR_PTR(err);
d8f823
+
d8f823
+	return rule;
d8f823
+}
d8f823
+
d8f823
+static void
d8f823
+__mlx5_tc_ct_delete_flow(struct mlx5_tc_ct_priv *ct_priv,
d8f823
+			 struct mlx5_ct_flow *ct_flow)
d8f823
+{
d8f823
+	struct mlx5_esw_flow_attr *pre_ct_attr = &ct_flow->pre_ct_attr;
d8f823
+	struct mlx5_eswitch *esw = ct_priv->esw;
d8f823
+
d8f823
+	mlx5_eswitch_del_offloaded_rule(esw, ct_flow->pre_ct_rule,
d8f823
+					pre_ct_attr);
d8f823
+	mlx5_modify_header_dealloc(esw->dev, pre_ct_attr->modify_hdr);
d8f823
+	mlx5_eswitch_del_offloaded_rule(esw, ct_flow->post_ct_rule,
d8f823
+					&ct_flow->post_ct_attr);
d8f823
+	mlx5_esw_chains_put_chain_mapping(esw, ct_flow->chain_mapping);
d8f823
+	idr_remove(&ct_priv->fte_ids, ct_flow->fte_id);
d8f823
+	kfree(ct_flow);
d8f823
+}
d8f823
+
d8f823
+void
d8f823
+mlx5_tc_ct_delete_flow(struct mlx5e_priv *priv, struct mlx5e_tc_flow *flow,
d8f823
+		       struct mlx5_esw_flow_attr *attr)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv = mlx5_tc_ct_get_ct_priv(priv);
d8f823
+	struct mlx5_ct_flow *ct_flow = attr->ct_attr.ct_flow;
d8f823
+
d8f823
+	/* We are called on error to clean up stuff from parsing
d8f823
+	 * but we don't have anything for now
d8f823
+	 */
d8f823
+	if (!ct_flow)
d8f823
+		return;
d8f823
+
d8f823
+	mutex_lock(&ct_priv->control_lock);
d8f823
+	__mlx5_tc_ct_delete_flow(ct_priv, ct_flow);
d8f823
+	mutex_unlock(&ct_priv->control_lock);
d8f823
+}
d8f823
+
d8f823
+static int
d8f823
+mlx5_tc_ct_init_check_support(struct mlx5_eswitch *esw,
d8f823
+			      const char **err_msg)
d8f823
+{
d8f823
+#if !IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
d8f823
+	/* cannot restore chain ID on HW miss */
d8f823
+
d8f823
+	*err_msg = "tc skb extension missing";
d8f823
+	return -EOPNOTSUPP;
d8f823
+#endif
d8f823
+
d8f823
+	if (!MLX5_CAP_ESW_FLOWTABLE_FDB(esw->dev, ignore_flow_level)) {
d8f823
+		*err_msg = "firmware level support is missing";
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	if (!mlx5_eswitch_vlan_actions_supported(esw->dev, 1)) {
d8f823
+		/* vlan workaround should be avoided for multi chain rules.
d8f823
+		 * This is just a sanity check as pop vlan action should
d8f823
+		 * be supported by any FW that supports ignore_flow_level
d8f823
+		 */
d8f823
+
d8f823
+		*err_msg = "firmware vlan actions support is missing";
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	if (!MLX5_CAP_ESW_FLOWTABLE(esw->dev,
d8f823
+				    fdb_modify_header_fwd_to_table)) {
d8f823
+		/* CT always writes to registers which are mod header actions.
d8f823
+		 * Therefore, mod header and goto is required
d8f823
+		 */
d8f823
+
d8f823
+		*err_msg = "firmware fwd and modify support is missing";
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	if (!mlx5_eswitch_reg_c1_loopback_enabled(esw)) {
d8f823
+		*err_msg = "register loopback isn't supported";
d8f823
+		return -EOPNOTSUPP;
d8f823
+	}
d8f823
+
d8f823
+	return 0;
d8f823
+}
d8f823
+
d8f823
+static void
d8f823
+mlx5_tc_ct_init_err(struct mlx5e_rep_priv *rpriv, const char *msg, int err)
d8f823
+{
d8f823
+	if (msg)
d8f823
+		netdev_warn(rpriv->netdev,
d8f823
+			    "tc ct offload not supported, %s, err: %d\n",
d8f823
+			    msg, err);
d8f823
+	else
d8f823
+		netdev_warn(rpriv->netdev,
d8f823
+			    "tc ct offload not supported, err: %d\n",
d8f823
+			    err);
d8f823
+}
d8f823
+
d8f823
+int
d8f823
+mlx5_tc_ct_init(struct mlx5_rep_uplink_priv *uplink_priv)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv;
d8f823
+	struct mlx5e_rep_priv *rpriv;
d8f823
+	struct mlx5_eswitch *esw;
d8f823
+	struct mlx5e_priv *priv;
d8f823
+	const char *msg;
d8f823
+	int err;
d8f823
+
d8f823
+	rpriv = container_of(uplink_priv, struct mlx5e_rep_priv, uplink_priv);
d8f823
+	priv = netdev_priv(rpriv->netdev);
d8f823
+	esw = priv->mdev->priv.eswitch;
d8f823
+
d8f823
+	err = mlx5_tc_ct_init_check_support(esw, &msg;;
d8f823
+	if (err) {
d8f823
+		mlx5_tc_ct_init_err(rpriv, msg, err);
d8f823
+		goto err_support;
d8f823
+	}
d8f823
+
d8f823
+	ct_priv = kzalloc(sizeof(*ct_priv), GFP_KERNEL);
d8f823
+	if (!ct_priv) {
d8f823
+		mlx5_tc_ct_init_err(rpriv, NULL, -ENOMEM);
d8f823
+		goto err_alloc;
d8f823
+	}
d8f823
+
d8f823
+	ct_priv->esw = esw;
d8f823
+	ct_priv->netdev = rpriv->netdev;
d8f823
+	ct_priv->ct = mlx5_esw_chains_create_global_table(esw);
d8f823
+	if (IS_ERR(ct_priv->ct)) {
d8f823
+		err = PTR_ERR(ct_priv->ct);
d8f823
+		mlx5_tc_ct_init_err(rpriv, "failed to create ct table", err);
d8f823
+		goto err_ct_tbl;
d8f823
+	}
d8f823
+
d8f823
+	ct_priv->ct_nat = mlx5_esw_chains_create_global_table(esw);
d8f823
+	if (IS_ERR(ct_priv->ct_nat)) {
d8f823
+		err = PTR_ERR(ct_priv->ct_nat);
d8f823
+		mlx5_tc_ct_init_err(rpriv, "failed to create ct nat table",
d8f823
+				    err);
d8f823
+		goto err_ct_nat_tbl;
d8f823
+	}
d8f823
+
d8f823
+	ct_priv->post_ct = mlx5_esw_chains_create_global_table(esw);
d8f823
+	if (IS_ERR(ct_priv->post_ct)) {
d8f823
+		err = PTR_ERR(ct_priv->post_ct);
d8f823
+		mlx5_tc_ct_init_err(rpriv, "failed to create post ct table",
d8f823
+				    err);
d8f823
+		goto err_post_ct_tbl;
d8f823
+	}
d8f823
+
d8f823
+	idr_init(&ct_priv->fte_ids);
d8f823
+	mutex_init(&ct_priv->control_lock);
d8f823
+
d8f823
+	/* Done, set ct_priv to know it initializted */
d8f823
+	uplink_priv->ct_priv = ct_priv;
d8f823
+
d8f823
+	return 0;
d8f823
+
d8f823
+err_post_ct_tbl:
d8f823
+	mlx5_esw_chains_destroy_global_table(esw, ct_priv->ct_nat);
d8f823
+err_ct_nat_tbl:
d8f823
+	mlx5_esw_chains_destroy_global_table(esw, ct_priv->ct);
d8f823
+err_ct_tbl:
d8f823
+	kfree(ct_priv);
d8f823
+err_alloc:
d8f823
+err_support:
d8f823
+
d8f823
+	return 0;
d8f823
+}
d8f823
+
d8f823
+void
d8f823
+mlx5_tc_ct_clean(struct mlx5_rep_uplink_priv *uplink_priv)
d8f823
+{
d8f823
+	struct mlx5_tc_ct_priv *ct_priv = uplink_priv->ct_priv;
d8f823
+
d8f823
+	if (!ct_priv)
d8f823
+		return;
d8f823
+
d8f823
+	mlx5_esw_chains_destroy_global_table(ct_priv->esw, ct_priv->post_ct);
d8f823
+	mlx5_esw_chains_destroy_global_table(ct_priv->esw, ct_priv->ct_nat);
d8f823
+	mlx5_esw_chains_destroy_global_table(ct_priv->esw, ct_priv->ct);
d8f823
+
d8f823
+	mutex_destroy(&ct_priv->control_lock);
d8f823
+	idr_destroy(&ct_priv->fte_ids);
d8f823
+	kfree(ct_priv);
d8f823
+
d8f823
+	uplink_priv->ct_priv = NULL;
d8f823
+}
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.h b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.h
d8f823
new file mode 100644
d8f823
index 000000000000..3a8421671c23
d8f823
--- /dev/null
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.h
d8f823
@@ -0,0 +1,140 @@
d8f823
+/* SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB */
d8f823
+/* Copyright (c) 2018 Mellanox Technologies. */
d8f823
+
d8f823
+#ifndef __MLX5_EN_TC_CT_H__
d8f823
+#define __MLX5_EN_TC_CT_H__
d8f823
+
d8f823
+#include <net/pkt_cls.h>
d8f823
+#include <linux/mlx5/fs.h>
d8f823
+#include <net/tc_act/tc_ct.h>
d8f823
+
d8f823
+struct mlx5_esw_flow_attr;
d8f823
+struct mlx5_rep_uplink_priv;
d8f823
+struct mlx5e_tc_flow;
d8f823
+struct mlx5e_priv;
d8f823
+
d8f823
+struct mlx5_ct_flow;
d8f823
+
d8f823
+struct mlx5_ct_attr {
d8f823
+	u16 zone;
d8f823
+	u16 ct_action;
d8f823
+	struct mlx5_ct_flow *ct_flow;
d8f823
+};
d8f823
+
d8f823
+#define zone_to_reg_ct {\
d8f823
+	.mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_2,\
d8f823
+	.moffset = 0,\
d8f823
+	.mlen = 2,\
d8f823
+	.soffset = MLX5_BYTE_OFF(fte_match_param,\
d8f823
+				 misc_parameters_2.metadata_reg_c_2) + 2,\
d8f823
+}
d8f823
+
d8f823
+#define ctstate_to_reg_ct {\
d8f823
+	.mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_2,\
d8f823
+	.moffset = 2,\
d8f823
+	.mlen = 2,\
d8f823
+	.soffset = MLX5_BYTE_OFF(fte_match_param,\
d8f823
+				 misc_parameters_2.metadata_reg_c_2),\
d8f823
+}
d8f823
+
d8f823
+#define mark_to_reg_ct {\
d8f823
+	.mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_3,\
d8f823
+	.moffset = 0,\
d8f823
+	.mlen = 4,\
d8f823
+	.soffset = MLX5_BYTE_OFF(fte_match_param,\
d8f823
+				 misc_parameters_2.metadata_reg_c_3),\
d8f823
+}
d8f823
+
d8f823
+#define labels_to_reg_ct {\
d8f823
+	.mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_4,\
d8f823
+	.moffset = 0,\
d8f823
+	.mlen = 4,\
d8f823
+	.soffset = MLX5_BYTE_OFF(fte_match_param,\
d8f823
+				 misc_parameters_2.metadata_reg_c_4),\
d8f823
+}
d8f823
+
d8f823
+#define fteid_to_reg_ct {\
d8f823
+	.mfield = MLX5_ACTION_IN_FIELD_METADATA_REG_C_5,\
d8f823
+	.moffset = 0,\
d8f823
+	.mlen = 4,\
d8f823
+	.soffset = MLX5_BYTE_OFF(fte_match_param,\
d8f823
+				 misc_parameters_2.metadata_reg_c_5),\
d8f823
+}
d8f823
+
d8f823
+#if IS_ENABLED(CONFIG_MLX5_TC_CT)
d8f823
+
d8f823
+int
d8f823
+mlx5_tc_ct_init(struct mlx5_rep_uplink_priv *uplink_priv);
d8f823
+void
d8f823
+mlx5_tc_ct_clean(struct mlx5_rep_uplink_priv *uplink_priv);
d8f823
+
d8f823
+int
d8f823
+mlx5_tc_ct_parse_match(struct mlx5e_priv *priv,
d8f823
+		       struct mlx5_flow_spec *spec,
d8f823
+		       struct flow_cls_offload *f,
d8f823
+		       struct netlink_ext_ack *extack);
d8f823
+int
d8f823
+mlx5_tc_ct_parse_action(struct mlx5e_priv *priv,
d8f823
+			struct mlx5_esw_flow_attr *attr,
d8f823
+			const struct flow_action_entry *act,
d8f823
+			struct netlink_ext_ack *extack);
d8f823
+
d8f823
+struct mlx5_flow_handle *
d8f823
+mlx5_tc_ct_flow_offload(struct mlx5e_priv *priv,
d8f823
+			struct mlx5e_tc_flow *flow,
d8f823
+			struct mlx5_flow_spec *spec,
d8f823
+			struct mlx5_esw_flow_attr *attr);
d8f823
+void
d8f823
+mlx5_tc_ct_delete_flow(struct mlx5e_priv *priv,
d8f823
+		       struct mlx5e_tc_flow *flow,
d8f823
+		       struct mlx5_esw_flow_attr *attr);
d8f823
+
d8f823
+#else /* CONFIG_MLX5_TC_CT */
d8f823
+
d8f823
+static inline int
d8f823
+mlx5_tc_ct_init(struct mlx5_rep_uplink_priv *uplink_priv)
d8f823
+{
d8f823
+	return 0;
d8f823
+}
d8f823
+
d8f823
+static inline void
d8f823
+mlx5_tc_ct_clean(struct mlx5_rep_uplink_priv *uplink_priv)
d8f823
+{
d8f823
+}
d8f823
+
d8f823
+static inline int
d8f823
+mlx5_tc_ct_parse_match(struct mlx5e_priv *priv,
d8f823
+		       struct mlx5_flow_spec *spec,
d8f823
+		       struct flow_cls_offload *f,
d8f823
+		       struct netlink_ext_ack *extack)
d8f823
+{
d8f823
+	return -EOPNOTSUPP;
d8f823
+}
d8f823
+
d8f823
+static inline int
d8f823
+mlx5_tc_ct_parse_action(struct mlx5e_priv *priv,
d8f823
+			struct mlx5_esw_flow_attr *attr,
d8f823
+			const struct flow_action_entry *act,
d8f823
+			struct netlink_ext_ack *extack)
d8f823
+{
d8f823
+	return -EOPNOTSUPP;
d8f823
+}
d8f823
+
d8f823
+static inline struct mlx5_flow_handle *
d8f823
+mlx5_tc_ct_flow_offload(struct mlx5e_priv *priv,
d8f823
+			struct mlx5e_tc_flow *flow,
d8f823
+			struct mlx5_flow_spec *spec,
d8f823
+			struct mlx5_esw_flow_attr *attr)
d8f823
+{
d8f823
+	return ERR_PTR(-EOPNOTSUPP);
d8f823
+}
d8f823
+
d8f823
+static inline void
d8f823
+mlx5_tc_ct_delete_flow(struct mlx5e_priv *priv,
d8f823
+		       struct mlx5e_tc_flow *flow,
d8f823
+		       struct mlx5_esw_flow_attr *attr)
d8f823
+{
d8f823
+}
d8f823
+
d8f823
+#endif /* !IS_ENABLED(CONFIG_MLX5_TC_CT) */
d8f823
+#endif /* __MLX5_EN_TC_CT_H__ */
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h
d8f823
index 100b9a2d3ea6..eccf61e1ac96 100644
d8f823
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.h
d8f823
@@ -55,6 +55,7 @@ struct mlx5e_neigh_update_table {
d8f823
 	unsigned long           min_interval; /* jiffies */
d8f823
 };
d8f823
 
d8f823
+struct mlx5_tc_ct_priv;
d8f823
 struct mlx5_rep_uplink_priv {
d8f823
 	/* Filters DB - instantiated by the uplink representor and shared by
d8f823
 	 * the uplink's VFs
d8f823
@@ -85,6 +86,8 @@ struct mlx5_rep_uplink_priv {
d8f823
 	struct mapping_ctx *tunnel_mapping;
d8f823
 	/* maps tun_enc_opts to a unique id*/
d8f823
 	struct mapping_ctx *tunnel_enc_opts_mapping;
d8f823
+
d8f823
+	struct mlx5_tc_ct_priv *ct_priv;
d8f823
 };
d8f823
 
d8f823
 struct mlx5e_rep_priv {
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c
d8f823
index 269258c5f961..3e15cb4992a6 100644
d8f823
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.c
d8f823
@@ -56,6 +56,7 @@
d8f823
 #include "en/port.h"
d8f823
 #include "en/tc_tun.h"
d8f823
 #include "en/mapping.h"
d8f823
+#include "en/tc_ct.h"
d8f823
 #include "lib/devcom.h"
d8f823
 #include "lib/geneve.h"
d8f823
 #include "diag/en_tc_tracepoint.h"
d8f823
@@ -87,6 +88,7 @@ enum {
d8f823
 	MLX5E_TC_FLOW_FLAG_DUP		= MLX5E_TC_FLOW_BASE + 4,
d8f823
 	MLX5E_TC_FLOW_FLAG_NOT_READY	= MLX5E_TC_FLOW_BASE + 5,
d8f823
 	MLX5E_TC_FLOW_FLAG_DELETED	= MLX5E_TC_FLOW_BASE + 6,
d8f823
+	MLX5E_TC_FLOW_FLAG_CT		= MLX5E_TC_FLOW_BASE + 7,
d8f823
 };
d8f823
 
d8f823
 #define MLX5E_TC_MAX_SPLITS 1
d8f823
@@ -193,6 +195,11 @@ struct mlx5e_tc_attr_to_reg_mapping mlx5e_tc_attr_to_reg_mappings[] = {
d8f823
 		.soffset = MLX5_BYTE_OFF(fte_match_param,
d8f823
 					 misc_parameters_2.metadata_reg_c_1),
d8f823
 	},
d8f823
+	[ZONE_TO_REG] = zone_to_reg_ct,
d8f823
+	[CTSTATE_TO_REG] = ctstate_to_reg_ct,
d8f823
+	[MARK_TO_REG] = mark_to_reg_ct,
d8f823
+	[LABELS_TO_REG] = labels_to_reg_ct,
d8f823
+	[FTEID_TO_REG] = fteid_to_reg_ct,
d8f823
 };
d8f823
 
d8f823
 static void mlx5e_put_flow_tunnel_id(struct mlx5e_tc_flow *flow);
d8f823
@@ -1144,6 +1151,10 @@ mlx5e_tc_offload_fdb_rules(struct mlx5_eswitch *esw,
d8f823
 			   struct mlx5_esw_flow_attr *attr)
d8f823
 {
d8f823
 	struct mlx5_flow_handle *rule;
d8f823
+	struct mlx5e_tc_mod_hdr_acts;
d8f823
+
d8f823
+	if (flow_flag_test(flow, CT))
d8f823
+		return mlx5_tc_ct_flow_offload(flow->priv, flow, spec, attr);
d8f823
 
d8f823
 	rule = mlx5_eswitch_add_offloaded_rule(esw, spec, attr);
d8f823
 	if (IS_ERR(rule))
d8f823
@@ -1163,10 +1174,15 @@ mlx5e_tc_offload_fdb_rules(struct mlx5_eswitch *esw,
d8f823
 static void
d8f823
 mlx5e_tc_unoffload_fdb_rules(struct mlx5_eswitch *esw,
d8f823
 			     struct mlx5e_tc_flow *flow,
d8f823
-			   struct mlx5_esw_flow_attr *attr)
d8f823
+			     struct mlx5_esw_flow_attr *attr)
d8f823
 {
d8f823
 	flow_flag_clear(flow, OFFLOADED);
d8f823
 
d8f823
+	if (flow_flag_test(flow, CT)) {
d8f823
+		mlx5_tc_ct_delete_flow(flow->priv, flow, attr);
d8f823
+		return;
d8f823
+	}
d8f823
+
d8f823
 	if (attr->split_count)
d8f823
 		mlx5_eswitch_del_fwd_rule(esw, flow->rule[1], attr);
d8f823
 
d8f823
@@ -1938,6 +1954,11 @@ static void mlx5e_put_flow_tunnel_id(struct mlx5e_tc_flow *flow)
d8f823
 			       enc_opts_id);
d8f823
 }
d8f823
 
d8f823
+u32 mlx5e_tc_get_flow_tun_id(struct mlx5e_tc_flow *flow)
d8f823
+{
d8f823
+	return flow->tunnel_id;
d8f823
+}
d8f823
+
d8f823
 static int parse_tunnel_attr(struct mlx5e_priv *priv,
d8f823
 			     struct mlx5e_tc_flow *flow,
d8f823
 			     struct mlx5_flow_spec *spec,
d8f823
@@ -2103,6 +2124,7 @@ static int __parse_cls_flower(struct mlx5e_priv *priv,
d8f823
 	      BIT(FLOW_DISSECTOR_KEY_ENC_CONTROL) |
d8f823
 	      BIT(FLOW_DISSECTOR_KEY_TCP) |
d8f823
 	      BIT(FLOW_DISSECTOR_KEY_IP)  |
d8f823
+	      BIT(FLOW_DISSECTOR_KEY_CT) |
d8f823
 	      BIT(FLOW_DISSECTOR_KEY_ENC_IP) |
d8f823
 	      BIT(FLOW_DISSECTOR_KEY_ENC_OPTS))) {
d8f823
 		NL_SET_ERR_MSG_MOD(extack, "Unsupported key");
d8f823
@@ -2914,7 +2936,9 @@ struct ipv6_hoplimit_word {
d8f823
 	__u8	hop_limit;
d8f823
 };
d8f823
 
d8f823
-static bool is_action_keys_supported(const struct flow_action_entry *act)
d8f823
+static int is_action_keys_supported(const struct flow_action_entry *act,
d8f823
+				    bool ct_flow, bool *modify_ip_header,
d8f823
+				    struct netlink_ext_ack *extack)
d8f823
 {
d8f823
 	u32 mask, offset;
d8f823
 	u8 htype;
d8f823
@@ -2933,7 +2957,13 @@ static bool is_action_keys_supported(const struct flow_action_entry *act)
d8f823
 		if (offset != offsetof(struct iphdr, ttl) ||
d8f823
 		    ttl_word->protocol ||
d8f823
 		    ttl_word->check) {
d8f823
-			return true;
d8f823
+			*modify_ip_header = true;
d8f823
+		}
d8f823
+
d8f823
+		if (ct_flow && offset >= offsetof(struct iphdr, saddr)) {
d8f823
+			NL_SET_ERR_MSG_MOD(extack,
d8f823
+					   "can't offload re-write of ipv4 address with action ct");
d8f823
+			return -EOPNOTSUPP;
d8f823
 		}
d8f823
 	} else if (htype == FLOW_ACT_MANGLE_HDR_TYPE_IP6) {
d8f823
 		struct ipv6_hoplimit_word *hoplimit_word =
d8f823
@@ -2942,15 +2972,27 @@ static bool is_action_keys_supported(const struct flow_action_entry *act)
d8f823
 		if (offset != offsetof(struct ipv6hdr, payload_len) ||
d8f823
 		    hoplimit_word->payload_len ||
d8f823
 		    hoplimit_word->nexthdr) {
d8f823
-			return true;
d8f823
+			*modify_ip_header = true;
d8f823
+		}
d8f823
+
d8f823
+		if (ct_flow && offset >= offsetof(struct ipv6hdr, saddr)) {
d8f823
+			NL_SET_ERR_MSG_MOD(extack,
d8f823
+					   "can't offload re-write of ipv6 address with action ct");
d8f823
+			return -EOPNOTSUPP;
d8f823
 		}
d8f823
+	} else if (ct_flow && (htype == FLOW_ACT_MANGLE_HDR_TYPE_TCP ||
d8f823
+			       htype == FLOW_ACT_MANGLE_HDR_TYPE_UDP)) {
d8f823
+		NL_SET_ERR_MSG_MOD(extack,
d8f823
+				   "can't offload re-write of transport header ports with action ct");
d8f823
+		return -EOPNOTSUPP;
d8f823
 	}
d8f823
-	return false;
d8f823
+
d8f823
+	return 0;
d8f823
 }
d8f823
 
d8f823
 static bool modify_header_match_supported(struct mlx5_flow_spec *spec,
d8f823
 					  struct flow_action *flow_action,
d8f823
-					  u32 actions,
d8f823
+					  u32 actions, bool ct_flow,
d8f823
 					  struct netlink_ext_ack *extack)
d8f823
 {
d8f823
 	const struct flow_action_entry *act;
d8f823
@@ -2958,7 +3000,7 @@ static bool modify_header_match_supported(struct mlx5_flow_spec *spec,
d8f823
 	void *headers_v;
d8f823
 	u16 ethertype;
d8f823
 	u8 ip_proto;
d8f823
-	int i;
d8f823
+	int i, err;
d8f823
 
d8f823
 	headers_v = get_match_headers_value(actions, spec);
d8f823
 	ethertype = MLX5_GET(fte_match_set_lyr_2_4, headers_v, ethertype);
d8f823
@@ -2973,10 +3015,10 @@ static bool modify_header_match_supported(struct mlx5_flow_spec *spec,
d8f823
 		    act->id != FLOW_ACTION_ADD)
d8f823
 			continue;
d8f823
 
d8f823
-		if (is_action_keys_supported(act)) {
d8f823
-			modify_ip_header = true;
d8f823
-			break;
d8f823
-		}
d8f823
+		err = is_action_keys_supported(act, ct_flow,
d8f823
+					       &modify_ip_header, extack);
d8f823
+		if (err)
d8f823
+			return err;
d8f823
 	}
d8f823
 
d8f823
 	ip_proto = MLX5_GET(fte_match_set_lyr_2_4, headers_v, ip_protocol);
d8f823
@@ -2999,13 +3041,24 @@ static bool actions_match_supported(struct mlx5e_priv *priv,
d8f823
 				    struct netlink_ext_ack *extack)
d8f823
 {
d8f823
 	struct net_device *filter_dev = parse_attr->filter_dev;
d8f823
-	bool drop_action, pop_action;
d8f823
+	bool drop_action, pop_action, ct_flow;
d8f823
 	u32 actions;
d8f823
 
d8f823
-	if (mlx5e_is_eswitch_flow(flow))
d8f823
+	ct_flow = flow_flag_test(flow, CT);
d8f823
+	if (mlx5e_is_eswitch_flow(flow)) {
d8f823
 		actions = flow->esw_attr->action;
d8f823
-	else
d8f823
+
d8f823
+		if (flow->esw_attr->split_count && ct_flow) {
d8f823
+			/* All registers used by ct are cleared when using
d8f823
+			 * split rules.
d8f823
+			 */
d8f823
+			NL_SET_ERR_MSG_MOD(extack,
d8f823
+					   "Can't offload mirroring with action ct");
d8f823
+			return -EOPNOTSUPP;
d8f823
+		}
d8f823
+	} else {
d8f823
 		actions = flow->nic_attr->action;
d8f823
+	}
d8f823
 
d8f823
 	drop_action = actions & MLX5_FLOW_CONTEXT_ACTION_DROP;
d8f823
 	pop_action = actions & MLX5_FLOW_CONTEXT_ACTION_VLAN_POP;
d8f823
@@ -3022,7 +3075,7 @@ static bool actions_match_supported(struct mlx5e_priv *priv,
d8f823
 	if (actions & MLX5_FLOW_CONTEXT_ACTION_MOD_HDR)
d8f823
 		return modify_header_match_supported(&parse_attr->spec,
d8f823
 						     flow_action, actions,
d8f823
-						     extack);
d8f823
+						     ct_flow, extack);
d8f823
 
d8f823
 	return true;
d8f823
 }
d8f823
@@ -3826,6 +3879,13 @@ static int parse_tc_fdb_actions(struct mlx5e_priv *priv,
d8f823
 			action |= MLX5_FLOW_CONTEXT_ACTION_COUNT;
d8f823
 			attr->dest_chain = act->chain_index;
d8f823
 			break;
d8f823
+		case FLOW_ACTION_CT:
d8f823
+			err = mlx5_tc_ct_parse_action(priv, attr, act, extack);
d8f823
+			if (err)
d8f823
+				return err;
d8f823
+
d8f823
+			flow_flag_set(flow, CT);
d8f823
+			break;
d8f823
 		default:
d8f823
 			NL_SET_ERR_MSG_MOD(extack, "The offload action is not supported");
d8f823
 			return -EOPNOTSUPP;
d8f823
@@ -4066,6 +4126,10 @@ __mlx5e_add_fdb_flow(struct mlx5e_priv *priv,
d8f823
 	if (err)
d8f823
 		goto err_free;
d8f823
 
d8f823
+	err = mlx5_tc_ct_parse_match(priv, &parse_attr->spec, f, extack);
d8f823
+	if (err)
d8f823
+		goto err_free;
d8f823
+
d8f823
 	err = mlx5e_tc_add_fdb_flow(priv, flow, extack);
d8f823
 	complete_all(&flow->init_done);
d8f823
 	if (err) {
d8f823
@@ -4350,7 +4414,7 @@ int mlx5e_stats_flower(struct net_device *dev, struct mlx5e_priv *priv,
d8f823
 		goto errout;
d8f823
 	}
d8f823
 
d8f823
-	if (mlx5e_is_offloaded_flow(flow)) {
d8f823
+	if (mlx5e_is_offloaded_flow(flow) || flow_flag_test(flow, CT)) {
d8f823
 		counter = mlx5e_tc_get_counter(flow);
d8f823
 		if (!counter)
d8f823
 			goto errout;
d8f823
@@ -4617,6 +4681,10 @@ int mlx5e_tc_esw_init(struct rhashtable *tc_ht)
d8f823
 	uplink_priv = container_of(tc_ht, struct mlx5_rep_uplink_priv, tc_ht);
d8f823
 	priv = container_of(uplink_priv, struct mlx5e_rep_priv, uplink_priv);
d8f823
 
d8f823
+	err = mlx5_tc_ct_init(uplink_priv);
d8f823
+	if (err)
d8f823
+		goto err_ct;
d8f823
+
d8f823
 	mapping = mapping_create(sizeof(struct tunnel_match_key),
d8f823
 				 TUNNEL_INFO_BITS_MASK, true);
d8f823
 	if (IS_ERR(mapping)) {
d8f823
@@ -4643,6 +4711,8 @@ int mlx5e_tc_esw_init(struct rhashtable *tc_ht)
d8f823
 err_enc_opts_mapping:
d8f823
 	mapping_destroy(uplink_priv->tunnel_mapping);
d8f823
 err_tun_mapping:
d8f823
+	mlx5_tc_ct_clean(uplink_priv);
d8f823
+err_ct:
d8f823
 	netdev_warn(priv->netdev,
d8f823
 		    "Failed to initialize tc (eswitch), err: %d", err);
d8f823
 	return err;
d8f823
@@ -4657,6 +4727,8 @@ void mlx5e_tc_esw_cleanup(struct rhashtable *tc_ht)
d8f823
 	uplink_priv = container_of(tc_ht, struct mlx5_rep_uplink_priv, tc_ht);
d8f823
 	mapping_destroy(uplink_priv->tunnel_enc_opts_mapping);
d8f823
 	mapping_destroy(uplink_priv->tunnel_mapping);
d8f823
+
d8f823
+	mlx5_tc_ct_clean(uplink_priv);
d8f823
 }
d8f823
 
d8f823
 int mlx5e_tc_num_filters(struct mlx5e_priv *priv, unsigned long flags)
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.h b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.h
d8f823
index 21cbde472b64..31c9e81b9287 100644
d8f823
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_tc.h
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tc.h
d8f823
@@ -94,6 +94,11 @@ void mlx5e_tc_reoffload_flows_work(struct work_struct *work);
d8f823
 enum mlx5e_tc_attr_to_reg {
d8f823
 	CHAIN_TO_REG,
d8f823
 	TUNNEL_TO_REG,
d8f823
+	CTSTATE_TO_REG,
d8f823
+	ZONE_TO_REG,
d8f823
+	MARK_TO_REG,
d8f823
+	LABELS_TO_REG,
d8f823
+	FTEID_TO_REG,
d8f823
 };
d8f823
 
d8f823
 struct mlx5e_tc_attr_to_reg_mapping {
d8f823
@@ -139,6 +144,9 @@ int alloc_mod_hdr_actions(struct mlx5_core_dev *mdev,
d8f823
 			  struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts);
d8f823
 void dealloc_mod_hdr_actions(struct mlx5e_tc_mod_hdr_acts *mod_hdr_acts);
d8f823
 
d8f823
+struct mlx5e_tc_flow;
d8f823
+u32 mlx5e_tc_get_flow_tun_id(struct mlx5e_tc_flow *flow);
d8f823
+
d8f823
 #else /* CONFIG_MLX5_ESWITCH */
d8f823
 static inline int  mlx5e_tc_nic_init(struct mlx5e_priv *priv) { return 0; }
d8f823
 static inline void mlx5e_tc_nic_cleanup(struct mlx5e_priv *priv) {}
d8f823
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
d8f823
index ecf3d7157d4d..79e4dfa5368d 100644
d8f823
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
d8f823
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h
d8f823
@@ -42,6 +42,9 @@
d8f823
 #include <linux/mlx5/vport.h>
d8f823
 #include <linux/mlx5/fs.h>
d8f823
 #include "lib/mpfs.h"
d8f823
+#ifndef __GENKSYMS__
d8f823
+# include "en/tc_ct.h"
d8f823
+#endif
d8f823
 
d8f823
 #define FDB_TC_MAX_CHAIN 3
d8f823
 #define FDB_FT_CHAIN (FDB_TC_MAX_CHAIN + 1)
d8f823
@@ -465,6 +468,7 @@ struct mlx5_esw_flow_attr {
d8f823
 	u32	flags;
d8f823
 	struct mlx5_flow_table *fdb;
d8f823
 	struct mlx5_flow_table *dest_ft;
d8f823
+	struct mlx5_ct_attr ct_attr;
d8f823
 #endif
d8f823
 	struct mlx5e_tc_flow_parse_attr *parse_attr;
d8f823
 };
d8f823
-- 
d8f823
2.13.6
d8f823