[][openwrt][mt7988][tops][separate CLS_ENTRY and TOPS_ENTRY dependency]

[Description]
Refactor TOPS CLS_ENTRY and TOPS_ENTRY.

Separate CLS_ENTRY and TOPS_ENTRY implicit mapping relationship.

In previous implementation, 1 protocol can only occupy a CLS_ENTRY and a
CLS_ENTRY is corresponding to a TOPS_ENTRY. 1 TOPS_ENTRY will have a
tunnel offload function inside TOPS firmware.

However, some of the protocol may occupy several CLS_ENTRY such as ESP.
This kind of protocol may use different CLS_ENTRY to distinguish each
session.
This leads to a problem that TOPS firmware can not handle same protocol
with different TOPS_ENTRY.

To solve this problem, the idea of CLS_ENTRY and TOPS_ENTRY is no longer 1
to 1 mapping. CLS_ENTRY is now allocated dynamically and its index is
carried inside tops_tnl_params so that TOPS firmware can map that
CLS_ENTRY to a TOPS_ENTRY which will be corresponding to a tunnel offload
function.

[Release-log]
N/A

Change-Id: I162a899353b9935602d971947d8514ba8d4e7d3e
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7923904
diff --git a/package-21.02/kernel/tops/src/ctrl.c b/package-21.02/kernel/tops/src/ctrl.c
index e425e68..ed1c95b 100644
--- a/package-21.02/kernel/tops/src/ctrl.c
+++ b/package-21.02/kernel/tops/src/ctrl.c
@@ -163,7 +163,7 @@
 	tnl_params.flag |= TNL_ENCAP_ENABLE;
 	tnl_params.tops_entry_proto = tnl_type->tops_entry;
 
-	tnl_info = mtk_tops_tnl_info_alloc();
+	tnl_info = mtk_tops_tnl_info_alloc(tnl_type);
 	if (IS_ERR(tnl_info))
 		return -ENOMEM;
 
diff --git a/package-21.02/kernel/tops/src/inc/tunnel.h b/package-21.02/kernel/tops/src/inc/tunnel.h
index 961aa03..387d923 100644
--- a/package-21.02/kernel/tops/src/inc/tunnel.h
+++ b/package-21.02/kernel/tops/src/inc/tunnel.h
@@ -18,6 +18,8 @@
 #include <linux/spinlock.h>
 #include <linux/types.h>
 
+#include <pce/cls.h>
+
 #include "protocol/l2tp/l2tp.h"
 
 /* tunnel info status */
@@ -107,6 +109,13 @@
 	TNL_INFO_DEBUG_BIT,
 };
 
+struct tops_cls_entry {
+	struct cls_entry *cls;
+	struct list_head node;
+	refcount_t refcnt;
+	bool updated;
+};
+
 /* record outer tunnel header data for HW offloading */
 struct tops_tnl_params {
 	u8 daddr[ETH_ALEN];
@@ -117,6 +126,7 @@
 	__be16 sport;
 	u16 protocol;
 	u8 tops_entry_proto;
+	u8 cls_entry;
 	u8 flag; /* bit: enum tops_tnl_params_flag */
 	union {
 		struct l2tp_param l2tp; /* 4B */
@@ -126,19 +136,26 @@
 struct tops_tnl_info {
 	struct tops_tnl_params tnl_params;
 	struct tops_tnl_params cache;
+	struct tops_tnl_type *tnl_type;
+	struct tops_cls_entry *tcls;
 	struct list_head sync_node;
 	struct hlist_node hlist;
 	struct net_device *dev;
-	struct timer_list taging;
 	spinlock_t lock;
 	u32 tnl_idx;
 	u32 status;
 	u32 flag; /* bit: enum tops_tnl_info_flag */
-	refcount_t refcnt;
 } __aligned(16);
 
 struct tops_tnl_type {
 	const char *type_name;
+	enum tops_entry_type tops_entry;
+
+	int (*cls_entry_setup)(struct tops_tnl_info *tnl_info,
+			       struct cls_desc *cdesc);
+	struct list_head tcls_head;
+	bool use_multi_cls;
+
 	int (*tnl_decap_param_setup)(struct sk_buff *skb,
 				     struct tops_tnl_params *tnl_params);
 	int (*tnl_encap_param_setup)(struct sk_buff *skb,
@@ -149,14 +166,13 @@
 	bool (*tnl_info_match)(struct tops_tnl_params *params1,
 			       struct tops_tnl_params *params2);
 	bool (*tnl_decap_offloadable)(struct sk_buff *skb);
-	enum tops_entry_type tops_entry;
 	bool has_inner_eth;
 };
 
 void mtk_tops_tnl_info_submit_no_tnl_lock(struct tops_tnl_info *tnl_info);
 void mtk_tops_tnl_info_submit(struct tops_tnl_info *tnl_info);
 struct tops_tnl_info *mtk_tops_tnl_info_find(struct tops_tnl_params *tnl_params);
-struct tops_tnl_info *mtk_tops_tnl_info_alloc(void);
+struct tops_tnl_info *mtk_tops_tnl_info_alloc(struct tops_tnl_type *tnl_type);
 void mtk_tops_tnl_info_hash(struct tops_tnl_info *tnl_info);
 
 int mtk_tops_tnl_offload_init(struct platform_device *pdev);
diff --git a/package-21.02/kernel/tops/src/protocol/gre/gretap.c b/package-21.02/kernel/tops/src/protocol/gre/gretap.c
index 91a239f..9e937af 100644
--- a/package-21.02/kernel/tops/src/protocol/gre/gretap.c
+++ b/package-21.02/kernel/tops/src/protocol/gre/gretap.c
@@ -8,29 +8,30 @@
 #include <net/gre.h>
 
 #include <pce/cls.h>
+#include <pce/netsys.h>
 #include <pce/pce.h>
 
 #include "tunnel.h"
 
-static struct cls_entry gretap_cls_entry = {
-	.entry = CLS_ENTRY_GRETAP,
-	.cdesc = {
-		.fport = 0x3,
-		.tport_idx = 0x4,
-		.tag_m = 0x3,
-		.tag = 0x1,
-		.dip_match_m = 0x1,
-		.dip_match = 0x1,
-		.l4_type_m = 0xFF,
-		.l4_type = 0x2F,
-		.l4_udp_hdr_nez_m = 0x1,
-		.l4_udp_hdr_nez = 0x1,
-		.l4_valid_m = 0x7,
-		.l4_valid = 0x3,
-		.l4_hdr_usr_data_m = 0xFFFF,
-		.l4_hdr_usr_data = 0x6558,
-	},
-};
+static int gretap_cls_entry_setup(struct tops_tnl_info *tnl_info,
+				  struct cls_desc *cdesc)
+{
+	CLS_DESC_DATA(cdesc, fport, PSE_PORT_PPE0);
+	CLS_DESC_DATA(cdesc, tport_idx, 0x4);
+	CLS_DESC_MASK_DATA(cdesc, tag, CLS_DESC_TAG_MASK, CLS_DESC_TAG_MATCH_L4_HDR);
+	CLS_DESC_MASK_DATA(cdesc, dip_match, CLS_DESC_DIP_MATCH, CLS_DESC_DIP_MATCH);
+	CLS_DESC_MASK_DATA(cdesc, l4_type, CLS_DESC_L4_TYPE_MASK, IPPROTO_GRE);
+	CLS_DESC_MASK_DATA(cdesc, l4_udp_hdr_nez,
+			   CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK,
+			   CLS_DESC_UDPLITE_L4_HDR_NEZ_MASK);
+	CLS_DESC_MASK_DATA(cdesc, l4_valid,
+			   CLS_DESC_L4_VALID_MASK,
+			   CLS_DESC_VALID_UPPER_HALF_WORD_BIT |
+			   CLS_DESC_VALID_LOWER_HALF_WORD_BIT);
+	CLS_DESC_MASK_DATA(cdesc, l4_hdr_usr_data, 0x0000FFFF, 0x00006558);
+
+	return 0;
+}
 
 static int gretap_tnl_decap_param_setup(struct sk_buff *skb,
 					struct tops_tnl_params *tnl_params)
@@ -171,6 +172,7 @@
 
 static struct tops_tnl_type gretap_type = {
 	.type_name = "gretap",
+	.cls_entry_setup = gretap_cls_entry_setup,
 	.tnl_decap_param_setup = gretap_tnl_decap_param_setup,
 	.tnl_encap_param_setup = gretap_tnl_encap_param_setup,
 	.tnl_debug_param_setup = gretap_tnl_debug_param_setup,
@@ -182,24 +184,10 @@
 
 int mtk_tops_gretap_init(void)
 {
-	int ret;
-
-	ret = mtk_tops_tnl_type_register(&gretap_type);
-	if (ret)
-		return ret;
-
-	ret = mtk_pce_cls_entry_register(&gretap_cls_entry);
-	if (ret) {
-		mtk_tops_tnl_type_unregister(&gretap_type);
-		return ret;
-	}
-
-	return ret;
+	return mtk_tops_tnl_type_register(&gretap_type);
 }
 
 void mtk_tops_gretap_deinit(void)
 {
-	mtk_pce_cls_entry_unregister(&gretap_cls_entry);
-
 	mtk_tops_tnl_type_unregister(&gretap_type);
 }
diff --git a/package-21.02/kernel/tops/src/protocol/l2tp/udp_l2tp_data.c b/package-21.02/kernel/tops/src/protocol/l2tp/udp_l2tp_data.c
index bde94e5..e26dc62 100644
--- a/package-21.02/kernel/tops/src/protocol/l2tp/udp_l2tp_data.c
+++ b/package-21.02/kernel/tops/src/protocol/l2tp/udp_l2tp_data.c
@@ -12,31 +12,31 @@
 #include <linux/udp.h>
 
 #include <pce/cls.h>
+#include <pce/netsys.h>
 #include <pce/pce.h>
 
 #include "protocol/l2tp/l2tp.h"
 #include "protocol/ppp/ppp.h"
 #include "tunnel.h"
 
-static struct cls_entry udp_l2tp_data_cls_entry = {
-	.entry = CLS_ENTRY_UDP_L2TP_DATA,
-	.cdesc = {
-		.fport = 0x3,
-		.tport_idx = 0x4,
-		.tag_m = 0x3,
-		.tag = 0x2,
-		.dip_match_m = 0x1,
-		.dip_match = 0x1,
-		.l4_type_m = 0xFF,
-		.l4_type = 0x11,
-		.l4_valid_m = 0x7,
-		.l4_valid = 0x7,
-		.l4_dport_m = 0xFFFF,
-		.l4_dport = 1701,
-		.l4_hdr_usr_data_m = 0x80030000,
-		.l4_hdr_usr_data = 0x00020000,
-	},
-};
+static int udp_l2tp_data_cls_entry_setup(struct tops_tnl_info *tnl_info,
+					 struct cls_desc *cdesc)
+{
+	CLS_DESC_DATA(cdesc, fport, PSE_PORT_PPE0);
+	CLS_DESC_DATA(cdesc, tport_idx, 0x4);
+	CLS_DESC_MASK_DATA(cdesc, tag, CLS_DESC_TAG_MASK, CLS_DESC_TAG_MATCH_L4_USR);
+	CLS_DESC_MASK_DATA(cdesc, dip_match, CLS_DESC_DIP_MATCH, CLS_DESC_DIP_MATCH);
+	CLS_DESC_MASK_DATA(cdesc, l4_type, CLS_DESC_L4_TYPE_MASK, IPPROTO_UDP);
+	CLS_DESC_MASK_DATA(cdesc, l4_valid,
+			   CLS_DESC_L4_VALID_MASK,
+			   CLS_DESC_VALID_UPPER_HALF_WORD_BIT |
+			   CLS_DESC_VALID_LOWER_HALF_WORD_BIT |
+			   CLS_DESC_VALID_DPORT_BIT);
+	CLS_DESC_MASK_DATA(cdesc, l4_dport, CLS_DESC_L4_DPORT_MASK, 1701);
+	CLS_DESC_MASK_DATA(cdesc, l4_hdr_usr_data, 0x80030000, 0x00020000);
+
+	return 0;
+}
 
 static inline bool l2tpv2_offload_match(struct udp_l2tp_data_hdr *l2tp)
 {
@@ -293,6 +293,7 @@
 
 static struct tops_tnl_type udp_l2tp_data_type = {
 	.type_name = "udp-l2tp-data",
+	.cls_entry_setup = udp_l2tp_data_cls_entry_setup,
 	.tnl_decap_param_setup = udp_l2tp_data_tnl_decap_param_setup,
 	.tnl_encap_param_setup = udp_l2tp_data_tnl_encap_param_setup,
 	.tnl_debug_param_setup = udp_l2tp_data_tnl_debug_param_setup,
@@ -304,24 +305,10 @@
 
 int mtk_tops_udp_l2tp_data_init(void)
 {
-	int ret = 0;
-
-	ret = mtk_tops_tnl_type_register(&udp_l2tp_data_type);
-	if (ret)
-		return ret;
-
-	ret = mtk_pce_cls_entry_register(&udp_l2tp_data_cls_entry);
-	if (ret) {
-		mtk_tops_tnl_type_unregister(&udp_l2tp_data_type);
-		return ret;
-	}
-
-	return ret;
+	return mtk_tops_tnl_type_register(&udp_l2tp_data_type);
 }
 
 void mtk_tops_udp_l2tp_data_deinit(void)
 {
-	mtk_pce_cls_entry_unregister(&udp_l2tp_data_cls_entry);
-
 	mtk_tops_tnl_type_unregister(&udp_l2tp_data_type);
 }
diff --git a/package-21.02/kernel/tops/src/tnl_offload.c b/package-21.02/kernel/tops/src/tnl_offload.c
index aa19f75..c3b2cdf 100644
--- a/package-21.02/kernel/tops/src/tnl_offload.c
+++ b/package-21.02/kernel/tops/src/tnl_offload.c
@@ -326,6 +326,258 @@
 		wake_up_interruptible(&tops_tnl.tnl_sync_wait);
 }
 
+static void mtk_tops_tnl_info_cls_update_idx(struct tops_tnl_info *tnl_info)
+{
+	unsigned long flag;
+
+	tnl_info->tnl_params.cls_entry = tnl_info->tcls->cls->idx;
+	TOPS_NOTICE("cls entry: %u\n", tnl_info->tcls->cls->idx);
+
+	spin_lock_irqsave(&tnl_info->lock, flag);
+	tnl_info->cache.cls_entry = tnl_info->tcls->cls->idx;
+	spin_unlock_irqrestore(&tnl_info->lock, flag);
+}
+
+static void mtk_tops_tnl_info_cls_entry_unprepare(struct tops_tnl_info *tnl_info)
+{
+	struct tops_cls_entry *tcls = tnl_info->tcls;
+
+	pr_notice("cls entry unprepare\n");
+	tnl_info->tcls = NULL;
+
+	if (refcount_dec_and_test(&tcls->refcnt)) {
+		pr_notice("cls entry delete\n");
+		list_del(&tcls->node);
+
+		memset(&tcls->cls->cdesc, 0, sizeof(tcls->cls->cdesc));
+
+		mtk_pce_cls_entry_write(tcls->cls);
+
+		mtk_pce_cls_entry_free(tcls->cls);
+
+		devm_kfree(tops_dev, tcls);
+	}
+}
+
+static struct tops_cls_entry *
+mtk_tops_tnl_info_cls_entry_prepare(struct tops_tnl_info *tnl_info)
+{
+	struct tops_cls_entry *tcls;
+	int ret;
+
+	tcls = devm_kzalloc(tops_dev, sizeof(struct tops_cls_entry), GFP_KERNEL);
+	if (!tcls)
+		return ERR_PTR(-ENOMEM);
+
+	tcls->cls = mtk_pce_cls_entry_alloc();
+	if (IS_ERR(tcls->cls)) {
+		ret = PTR_ERR(tcls->cls);
+		goto free_tcls;
+	}
+
+	INIT_LIST_HEAD(&tcls->node);
+	list_add_tail(&tnl_info->tnl_type->tcls_head, &tcls->node);
+
+	tnl_info->tcls = tcls;
+	refcount_set(&tcls->refcnt, 1);
+
+	return tcls;
+
+free_tcls:
+	devm_kfree(tops_dev, tcls);
+
+	return ERR_PTR(ret);
+}
+
+static int mtk_tops_tnl_info_cls_entry_write(struct tops_tnl_info *tnl_info)
+{
+	int ret;
+
+	if (!tnl_info->tcls)
+		return -EINVAL;
+
+	ret = mtk_pce_cls_entry_write(tnl_info->tcls->cls);
+	if (ret) {
+		mtk_tops_tnl_info_cls_entry_unprepare(tnl_info);
+		return ret;
+	}
+
+	tnl_info->tcls->updated = true;
+
+	mtk_tops_tnl_info_cls_update_idx(tnl_info);
+
+	return 0;
+}
+
+static int mtk_tops_tnl_info_cls_tear_down(struct tops_tnl_info *tnl_info)
+{
+	mtk_tops_tnl_info_cls_entry_unprepare(tnl_info);
+
+	return 0;
+}
+
+/*
+ * check cls entry is updated for tunnel protocols that only use 1 CLS HW entry
+ *
+ * since only tunnel sync task will operate on tcls linked list,
+ * it is safe to access without lock
+ *
+ * return true on updated
+ * return false on need update
+ */
+static bool mtk_tops_tnl_info_cls_single_is_updated(struct tops_tnl_info *tnl_info,
+						    struct tops_tnl_type *tnl_type)
+{
+	/*
+	 * check tnl_type has already allocate a tops_cls_entry
+	 * if not, return false to prepare to allocate a new one
+	 */
+	if (list_empty(&tnl_type->tcls_head))
+		return false;
+
+	/*
+	 * if tnl_info is not associate to tnl_type's cls entry,
+	 * make a reference to tops_cls_entry
+	 */
+	if (!tnl_info->tcls) {
+		tnl_info->tcls = list_first_entry(&tnl_type->tcls_head,
+						  struct tops_cls_entry,
+						  node);
+
+		refcount_inc(&tnl_info->tcls->refcnt);
+		mtk_tops_tnl_info_cls_update_idx(tnl_info);
+	}
+
+	return tnl_info->tcls->updated;
+}
+
+static int mtk_tops_tnl_info_cls_single_setup(struct tops_tnl_info *tnl_info,
+					      struct tops_tnl_type *tnl_type)
+{
+	struct tops_cls_entry *tcls;
+	int ret;
+
+	if (mtk_tops_tnl_info_cls_single_is_updated(tnl_info, tnl_type))
+		return 0;
+
+	if (tnl_info->tcls)
+		return mtk_tops_tnl_info_cls_entry_write(tnl_info);
+
+	tcls = mtk_tops_tnl_info_cls_entry_prepare(tnl_info);
+	if (IS_ERR(tcls))
+		return PTR_ERR(tcls);
+
+	ret = tnl_type->cls_entry_setup(tnl_info, &tcls->cls->cdesc);
+	if (ret) {
+		TOPS_ERR("tops cls entry setup failed: %d\n", ret);
+		mtk_tops_tnl_info_cls_entry_unprepare(tnl_info);
+		return ret;
+	}
+
+	return mtk_tops_tnl_info_cls_entry_write(tnl_info);
+}
+
+static struct tops_cls_entry *
+mtk_tops_tnl_info_cls_entry_find(struct tops_tnl_type *tnl_type,
+				 struct cls_desc *cdesc)
+{
+	struct tops_cls_entry *tcls;
+
+	list_for_each_entry(tcls, &tnl_type->tcls_head, node)
+		if (!memcmp(&tcls->cls->cdesc, cdesc, sizeof(struct cls_desc)))
+			return tcls;
+
+	return NULL;
+}
+
+static bool mtk_tops_tnl_infO_cls_multi_is_updated(struct tops_tnl_info *tnl_info,
+						   struct tops_tnl_type *tnl_type,
+						   struct cls_desc *cdesc)
+{
+	struct tops_cls_entry *tcls;
+
+	if (list_empty(&tnl_type->tcls_head))
+		return false;
+
+	if (tnl_info->tcls) {
+		if (!memcmp(cdesc, &tnl_info->tcls->cls->cdesc, sizeof(*cdesc)))
+			return tnl_info->tcls->updated;
+
+		memcpy(&tnl_info->tcls->cls->cdesc, cdesc, sizeof(*cdesc));
+		tnl_info->tcls->updated = false;
+		return false;
+	}
+
+	tcls = mtk_tops_tnl_info_cls_entry_find(tnl_type, cdesc);
+	if (!tcls)
+		return false;
+
+	tnl_info->tcls = tcls;
+	refcount_inc(&tnl_info->tcls->refcnt);
+	mtk_tops_tnl_info_cls_update_idx(tnl_info);
+
+	return tcls->updated;
+}
+
+static int mtk_tops_tnl_info_cls_multi_setup(struct tops_tnl_info *tnl_info,
+					     struct tops_tnl_type *tnl_type)
+{
+	struct tops_cls_entry *tcls;
+	struct cls_desc cdesc;
+	int ret;
+
+	memset(&cdesc, 0, sizeof(struct cls_desc));
+
+	/* prepare cls_desc from tnl_type */
+	ret = tnl_type->cls_entry_setup(tnl_info, &cdesc);
+	if (ret) {
+		TOPS_ERR("tops cls entry setup failed: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * check cdesc is already updated, if tnl_info is not associate with a
+	 * tcls but we found a tcls has the same cls desc content as cdesc
+	 * tnl_info will setup an association with that tcls
+	 *
+	 * we only go further to this if condition when
+	 * a tcls is not yet updated or
+	 * tnl_info is not yet associated to a tcls
+	 */
+	if (mtk_tops_tnl_infO_cls_multi_is_updated(tnl_info, tnl_type, &cdesc))
+		return 0;
+
+	/* tcls is not yet updated, update this tcls */
+	if (tnl_info->tcls)
+		return mtk_tops_tnl_info_cls_entry_write(tnl_info);
+
+	/* create a new tcls entry and associate with tnl_info */
+	tcls = mtk_tops_tnl_info_cls_entry_prepare(tnl_info);
+	if (IS_ERR(tcls))
+		return PTR_ERR(tcls);
+
+	memcpy(&tcls->cls->cdesc, &cdesc, sizeof(struct cls_desc));
+
+	return mtk_tops_tnl_info_cls_entry_write(tnl_info);
+}
+
+static int mtk_tops_tnl_info_cls_setup(struct tops_tnl_info *tnl_info)
+{
+	struct tops_tnl_type *tnl_type;
+
+	if (tnl_info->tcls && tnl_info->tcls->updated)
+		return 0;
+
+	tnl_type = tnl_info->tnl_type;
+	if (!tnl_type)
+		return -EINVAL;
+
+	if (!tnl_type->use_multi_cls)
+		return mtk_tops_tnl_info_cls_single_setup(tnl_info, tnl_type);
+
+	return mtk_tops_tnl_info_cls_multi_setup(tnl_info, tnl_type);
+}
+
 static int mtk_tops_tnl_info_dipfilter_tear_down(struct tops_tnl_info *tnl_info)
 {
 	struct dip_desc dipd;
@@ -461,6 +713,7 @@
 	lockdep_assert_held(&tnl_info->lock);
 
 	tnl_params->flag |= tnl_info->cache.flag;
+	tnl_params->cls_entry = tnl_info->cache.cls_entry;
 
 	if (memcmp(&tnl_info->cache, tnl_params, sizeof(struct tops_tnl_params))) {
 		memcpy(&tnl_info->cache, tnl_params, sizeof(struct tops_tnl_params));
@@ -490,7 +743,8 @@
 }
 
 /* tops_tnl.tbl_lock should be acquired before calling this functions */
-static struct tops_tnl_info *mtk_tops_tnl_info_alloc_no_lock(void)
+static struct tops_tnl_info *
+mtk_tops_tnl_info_alloc_no_lock(struct tops_tnl_type *tnl_type)
 {
 	struct tops_tnl_info *tnl_info;
 	unsigned long flag = 0;
@@ -521,6 +775,8 @@
 	}
 	tnl_info_sta_init_no_tnl_lock(tnl_info);
 
+	tnl_info->tnl_type = tnl_type;
+
 	INIT_HLIST_NODE(&tnl_info->hlist);
 
 	spin_unlock_irqrestore(&tnl_info->lock, flag);
@@ -530,14 +786,14 @@
 	return tnl_info;
 }
 
-struct tops_tnl_info *mtk_tops_tnl_info_alloc(void)
+struct tops_tnl_info *mtk_tops_tnl_info_alloc(struct tops_tnl_type *tnl_type)
 {
 	struct tops_tnl_info *tnl_info;
 	unsigned long flag = 0;
 
 	spin_lock_irqsave(&tops_tnl.tbl_lock, flag);
 
-	tnl_info = mtk_tops_tnl_info_alloc_no_lock();
+	tnl_info = mtk_tops_tnl_info_alloc_no_lock(tnl_type);
 
 	spin_unlock_irqrestore(&tops_tnl.tbl_lock, flag);
 
@@ -581,6 +837,7 @@
 }
 
 static int mtk_tops_tnl_offload(struct sk_buff *skb,
+				struct tops_tnl_type *tnl_type,
 				struct tops_tnl_params *tnl_params)
 {
 	struct tops_tnl_info *tnl_info;
@@ -600,7 +857,7 @@
 		goto err_out;
 	} else if (IS_ERR(tnl_info) && PTR_ERR(tnl_info) == -ENODEV) {
 		/* not allocate yet */
-		tnl_info = mtk_tops_tnl_info_alloc_no_lock();
+		tnl_info = mtk_tops_tnl_info_alloc_no_lock(tnl_type);
 	}
 
 	if (IS_ERR(tnl_info)) {
@@ -611,7 +868,6 @@
 
 	spin_lock(&tnl_info->lock);
 	ret = mtk_tops_tnl_info_setup(skb, tnl_info, tnl_params);
-
 	spin_unlock(&tnl_info->lock);
 
 err_out:
@@ -707,7 +963,7 @@
 
 	tnl_params.tops_entry_proto = tnl_type->tops_entry;
 
-	ret = mtk_tops_tnl_offload(skb, &tnl_params);
+	ret = mtk_tops_tnl_offload(skb, tnl_type, &tnl_params);
 
 	/*
 	 * whether success or fail to offload a decapsulation tunnel
@@ -748,7 +1004,7 @@
 		return ret;
 	tnl_params.tops_entry_proto = tnl_type->tops_entry;
 
-	return mtk_tops_tnl_offload(skb, &tnl_params);
+	return mtk_tops_tnl_offload(skb, tnl_type, &tnl_params);
 }
 
 static struct net_device *mtk_tops_get_tnl_dev(int tnl_idx)
@@ -895,6 +1151,13 @@
 		return ret;
 	}
 
+	ret = mtk_tops_tnl_info_cls_tear_down(tnl_info);
+	if (ret) {
+		TOPS_ERR("tnl sync cls tear down faild: %d\n",
+			 ret);
+		return ret;
+	}
+
 	mtk_tops_tnl_info_free(tnl_info);
 
 	return ret;
@@ -936,10 +1199,18 @@
 {
 	int ret;
 
+	if (setup_pce) {
+		ret = mtk_tops_tnl_info_cls_setup(tnl_info);
+		if (ret) {
+			TOPS_ERR("tnl cls setup failed: %d\n", ret);
+			return ret;
+		}
+	}
+
 	ret = __mtk_tops_tnl_sync_param_update(tnl_info, is_new_tnl);
 	if (ret) {
 		TOPS_ERR("tnl sync failed: %d\n", ret);
-		return ret;
+		goto cls_tear_down;
 	}
 
 	tnl_info_sta_updated(tnl_info);
@@ -954,6 +1225,11 @@
 	}
 
 	return ret;
+
+cls_tear_down:
+	mtk_tops_tnl_info_cls_tear_down(tnl_info);
+
+	return ret;
 }
 
 static inline int mtk_tops_tnl_sync_param_new(struct tops_tnl_info *tnl_info,
@@ -1255,6 +1531,8 @@
 		mtk_tops_tnl_info_flush_ppe(tnl_info);
 
 		mtk_tops_tnl_info_dipfilter_tear_down(tnl_info);
+
+		mtk_tops_tnl_info_cls_tear_down(tnl_info);
 	}
 
 	spin_unlock_irqrestore(&tops_tnl.tbl_lock, flag);
@@ -1325,6 +1603,7 @@
 		return -EBUSY;
 	}
 
+	INIT_LIST_HEAD(&tnl_type->tcls_head);
 	tops_tnl.offload_tnl_types[tops_entry] = tnl_type;
 	tops_tnl.offload_tnl_type_num++;