[][openwrt][mt7988][tops][support v1.2.0]

[Description]
Add support to TOPS v1.2.0 with latest driver and firmware
1. Fix L2TPv2 offload bugs
2. Add L2TPv2/IPsec offload feature
3. Add PPTP offload feature

[Release-log]
N/A

Change-Id: Id27778b1d88417293f306b2ea281777936545b70
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/8346642
diff --git a/package-21.02/kernel/tops/Makefile b/package-21.02/kernel/tops/Makefile
index b848646..2c7e0a9 100644
--- a/package-21.02/kernel/tops/Makefile
+++ b/package-21.02/kernel/tops/Makefile
@@ -54,6 +54,9 @@
 	kmod-tops \
 	+kmod-pce \
 	+@KERNEL_RELAY
+ifeq ($(CONFIG_MTK_TOPS_PPTP), y)
+  DEPENDS+= +kmod-pptp
+endif
 endef
 
 define KernelPackage/tops-release/description
diff --git a/package-21.02/kernel/tops/firmware/rebb/mt7988_mgmt/tops-mgmt.img b/package-21.02/kernel/tops/firmware/rebb/mt7988_mgmt/tops-mgmt.img
index 67f42dc..329dbb6 100644
--- a/package-21.02/kernel/tops/firmware/rebb/mt7988_mgmt/tops-mgmt.img
+++ b/package-21.02/kernel/tops/firmware/rebb/mt7988_mgmt/tops-mgmt.img
Binary files differ
diff --git a/package-21.02/kernel/tops/firmware/rebb/mt7988_offload/tops-offload.img b/package-21.02/kernel/tops/firmware/rebb/mt7988_offload/tops-offload.img
index 8738b5e..1a7bea5 100644
--- a/package-21.02/kernel/tops/firmware/rebb/mt7988_offload/tops-offload.img
+++ b/package-21.02/kernel/tops/firmware/rebb/mt7988_offload/tops-offload.img
Binary files differ
diff --git a/package-21.02/kernel/tops/src/Makefile b/package-21.02/kernel/tops/src/Makefile
index 86f860d..929d902 100644
--- a/package-21.02/kernel/tops/src/Makefile
+++ b/package-21.02/kernel/tops/src/Makefile
@@ -29,6 +29,7 @@
 tops-y += trm-debugfs.o
 tops-y += trm.o
 tops-y += wdt.o
+tops-y += seq_gen.o
 
 tops-y += protocol/mac/eth.o
 tops-y += protocol/mac/ppp.o
@@ -36,5 +37,7 @@
 tops-y += protocol/transport/udp.o
 tops-$(CONFIG_MTK_TOPS_GRETAP) += protocol/tunnel/gre/gretap.o
 tops-$(CONFIG_MTK_TOPS_L2TP_V2) += protocol/tunnel/l2tp/l2tpv2.o
+tops-$(CONFIG_MTK_TOPS_PPTP) += protocol/tunnel/pptp/pptp.o
+tops-$(CONFIG_MTK_TOPS_PPTP) += protocol/tunnel/pptp/pptp_seq.o
 
 include $(wildcard $(src)/*.mk)
diff --git a/package-21.02/kernel/tops/src/inc/tops/hwspinlock.h b/package-21.02/kernel/tops/src/inc/tops/hwspinlock.h
index 1ce5004..407c01a 100644
--- a/package-21.02/kernel/tops/src/inc/tops/hwspinlock.h
+++ b/package-21.02/kernel/tops/src/inc/tops/hwspinlock.h
@@ -8,6 +8,7 @@
 #ifndef _TOPS_HWSPIN_LOCK_H_
 #define _TOPS_HWSPIN_LOCK_H_
 
+#include <linux/platform_device.h>
 #include <linux/types.h>
 
 #define HWSPINLOCK_SLOT_MAX		16
@@ -55,7 +56,7 @@
 	HWSPINLOCK_CLUST_SLOT_8,
 	HWSPINLOCK_CLUST_SLOT_9,
 	HWSPINLOCK_CLUST_SLOT_10,
-	HWSPINLOCK_CLUST_SLOT_11,
+	HWSPINLOCK_CLUST_SLOT_PPTP_SEQ,
 	HWSPINLOCK_CLUST_SLOT_12,
 	HWSPINLOCK_CLUST_SLOT_13,
 	HWSPINLOCK_CLUST_SLOT_14,
diff --git a/package-21.02/kernel/tops/src/inc/tops/seq_gen.h b/package-21.02/kernel/tops/src/inc/tops/seq_gen.h
new file mode 100644
index 0000000..992cfb0
--- /dev/null
+++ b/package-21.02/kernel/tops/src/inc/tops/seq_gen.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Frank-zj Lin <frank-zj.lin@mediatek.com>
+ */
+
+#ifndef _TOPS_SEQ_GEN_H_
+#define _TOPS_SEQ_GEN_H_
+
+#include <linux/platform_device.h>
+
+#define TOPS_SEQ_GEN_BASE	0x880100	/* PKT_ID_GEN reg base */
+#define TOPS_SEQ_GEN_IDX_MAX	16		/* num of PKT_ID_GEN reg */
+
+void mtk_tops_seq_gen_set_16(int seq_gen_idx, u16 val);
+int mtk_tops_seq_gen_next_16(int seq_gen_idx, u16 *val);
+void mtk_tops_seq_gen_set_32(int seq_gen_idx, u32 val);
+int mtk_tops_seq_gen_next_32(int seq_gen_idx, u32 *val);
+int mtk_tops_seq_gen_alloc(int *seq_gen_idx);
+void mtk_tops_seq_gen_free(int seq_gen_idx);
+int mtk_tops_seq_gen_init(struct platform_device *pdev);
+#endif /* _TOPS_SEQ_GEN_H_ */
diff --git a/package-21.02/kernel/tops/src/inc/tops/tops_params.h b/package-21.02/kernel/tops/src/inc/tops/tops_params.h
index 0bdb9b5..057f20b 100644
--- a/package-21.02/kernel/tops/src/inc/tops/tops_params.h
+++ b/package-21.02/kernel/tops/src/inc/tops/tops_params.h
@@ -15,6 +15,7 @@
 #include "tops/protocol/network/ip_params.h"
 #include "tops/protocol/transport/udp_params.h"
 #include "tops/protocol/tunnel/l2tp/l2tp_params.h"
+#include "tops/protocol/tunnel/pptp/pptp_params.h"
 
 /* tunnel params flags */
 #define TNL_DECAP_ENABLE	(BIT(TNL_PARAMS_DECAP_ENABLE_BIT))
@@ -94,6 +95,7 @@
 struct tops_tunnel_params {
 	union {
 		struct tops_l2tp_params l2tp;
+		struct tops_pptp_params pptp;
 	};
 	enum tops_tunnel_type type;
 };
diff --git a/package-21.02/kernel/tops/src/init.c b/package-21.02/kernel/tops/src/init.c
index 478a45a..9944ea2 100644
--- a/package-21.02/kernel/tops/src/init.c
+++ b/package-21.02/kernel/tops/src/init.c
@@ -31,6 +31,7 @@
 #include "tops/trm.h"
 #include "tops/tunnel.h"
 #include "tops/wdt.h"
+#include "tops/seq_gen.h"
 
 #define EFUSE_TOPS_POWER_OFF		(0xD08)
 
@@ -145,6 +146,12 @@
 		goto err_netsys_deinit;
 	}
 
+	ret = mtk_tops_seq_gen_init(pdev);
+	if (ret) {
+		TOPS_ERR("sequence generator init failed: %d\n", ret);
+		goto err_tdma_deinit;
+	}
+
 	ret = mtk_tops_tnl_offload_init(pdev);
 	if (ret) {
 		TOPS_ERR("tunnel table init failed: %d\n", ret);
diff --git a/package-21.02/kernel/tops/src/protocol/gre/gretap.c b/package-21.02/kernel/tops/src/protocol/gre/gretap.c
deleted file mode 100644
index 9e937af..0000000
--- a/package-21.02/kernel/tops/src/protocol/gre/gretap.c
+++ /dev/null
@@ -1,193 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
- *
- * Author: Ren-Ting Wang <ren-ting.wang@mediatek.com>
- */
-
-#include <net/gre.h>
-
-#include <pce/cls.h>
-#include <pce/netsys.h>
-#include <pce/pce.h>
-
-#include "tunnel.h"
-
-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)
-{
-	struct gre_base_hdr *pgre;
-	struct gre_base_hdr greh;
-	struct ethhdr *eth;
-	struct ethhdr ethh;
-	struct iphdr *ip;
-	struct iphdr iph;
-	int ret = 0;
-
-	if (!skb->dev->rtnl_link_ops
-	    || strcmp(skb->dev->rtnl_link_ops->kind, "gretap"))
-		return -EAGAIN;
-
-	skb_push(skb, sizeof(struct gre_base_hdr));
-	pgre = skb_header_pointer(skb, 0, sizeof(struct gre_base_hdr), &greh);
-	if (unlikely(!pgre)) {
-		ret = -EINVAL;
-		goto restore_gre;
-	}
-
-	if (unlikely(ntohs(pgre->protocol) != ETH_P_TEB)) {
-		pr_notice("gre: %p protocol unmatched, proto: 0x%x\n",
-			pgre, ntohs(pgre->protocol));
-		ret = -EINVAL;
-		goto restore_gre;
-	}
-
-	/* TODO: store gre parameters? */
-
-	skb_push(skb, sizeof(struct iphdr));
-	ip = skb_header_pointer(skb, 0, sizeof(struct iphdr), &iph);
-	if (unlikely(!ip)) {
-		ret = -EINVAL;
-		goto restore_ip;
-	}
-
-	if (unlikely(ip->version != IPVERSION || ip->protocol != IPPROTO_GRE)) {
-		pr_notice("ip: %p version or protocol unmatched, ver: 0x%x, proto: 0x%x\n",
-			ip, ip->version, ip->protocol);
-		ret = -EINVAL;
-		goto restore_ip;
-	}
-
-	/* TODO: check ip options is support for us? */
-	/* TODO: store ip parameters? */
-	tnl_params->protocol = ip->protocol;
-	tnl_params->sip = ip->daddr;
-	tnl_params->dip = ip->saddr;
-
-	skb_push(skb, sizeof(struct ethhdr));
-	eth = skb_header_pointer(skb, 0, sizeof(struct ethhdr), &ethh);
-	if (unlikely(!eth)) {
-		ret = -EINVAL;
-		goto restore_eth;
-	}
-
-	if (unlikely(ntohs(eth->h_proto) != ETH_P_IP)) {
-		pr_notice("eth proto not support, proto: 0x%x\n",
-			ntohs(eth->h_proto));
-		ret = -EINVAL;
-		goto restore_eth;
-	}
-
-	memcpy(&tnl_params->saddr, eth->h_dest, sizeof(u8) * ETH_ALEN);
-	memcpy(&tnl_params->daddr, eth->h_source, sizeof(u8) * ETH_ALEN);
-
-restore_eth:
-	skb_pull(skb, sizeof(struct ethhdr));
-
-restore_ip:
-	skb_pull(skb, sizeof(struct iphdr));
-
-restore_gre:
-	skb_pull(skb, sizeof(struct gre_base_hdr));
-
-	return ret;
-}
-
-static int gretap_tnl_encap_param_setup(struct sk_buff *skb,
-					struct tops_tnl_params *tnl_params)
-{
-	struct ethhdr *eth = eth_hdr(skb);
-	struct iphdr *ip = ip_hdr(skb);
-
-	/*
-	 * ether type no need to check since it is even not constructed yet
-	 * currently not support gre without ipv4
-	 */
-	if (unlikely(ip->version != IPVERSION || ip->protocol != IPPROTO_GRE)) {
-		pr_notice("eth proto: 0x%x, ip ver: 0x%x, proto: 0x%x is not support\n",
-			  ntohs(eth->h_proto),
-			  ip->version,
-			  ip->protocol);
-		return -EINVAL;
-	}
-
-	memcpy(&tnl_params->saddr, eth->h_source, sizeof(u8) * ETH_ALEN);
-	memcpy(&tnl_params->daddr, eth->h_dest, sizeof(u8) * ETH_ALEN);
-	tnl_params->protocol = ip->protocol;
-	tnl_params->sip = ip->saddr;
-	tnl_params->dip = ip->daddr;
-
-	return 0;
-}
-
-static int gretap_tnl_debug_param_setup(const char *buf, int *ofs,
-					struct tops_tnl_params *tnl_params)
-{
-	tnl_params->protocol = IPPROTO_GRE;
-	return 0;
-}
-
-static bool gretap_tnl_info_match(struct tops_tnl_params *parms1,
-				  struct tops_tnl_params *parms2)
-{
-	if (parms1->sip == parms2->sip
-	    && parms1->dip == parms2->dip
-	    && !memcmp(parms1->saddr, parms2->saddr, sizeof(u8) * ETH_ALEN)
-	    && !memcmp(parms1->daddr, parms2->daddr, sizeof(u8) * ETH_ALEN)) {
-		return true;
-	}
-
-	return false;
-}
-
-static bool gretap_tnl_decap_offloadable(struct sk_buff *skb)
-{
-	struct iphdr *ip = ip_hdr(skb);
-
-	if (ip->protocol != IPPROTO_GRE)
-		return false;
-
-	return true;
-}
-
-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,
-	.tnl_info_match = gretap_tnl_info_match,
-	.tnl_decap_offloadable = gretap_tnl_decap_offloadable,
-	.tops_entry = TOPS_ENTRY_GRETAP,
-	.has_inner_eth = true,
-};
-
-int mtk_tops_gretap_init(void)
-{
-	return mtk_tops_tnl_type_register(&gretap_type);
-}
-
-void mtk_tops_gretap_deinit(void)
-{
-	mtk_tops_tnl_type_unregister(&gretap_type);
-}
diff --git a/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/l2tp/l2tp_params.h b/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/l2tp/l2tp_params.h
index dc266ef..62db153 100644
--- a/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/l2tp/l2tp_params.h
+++ b/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/l2tp/l2tp_params.h
@@ -19,8 +19,10 @@
 #define UDP_L2TP_PORT		1701
 
 struct tops_l2tp_params {
-	u16 tid; /* l2tp tunnel id */
-	u16 sid; /* l2tp session id */
+	u16 dl_tid; /* l2tp tunnel id for DL */
+	u16 dl_sid; /* l2tp session id for DL */
+	u16 ul_tid; /* l2tp tunnel id for UL */
+	u16 ul_sid; /* l2tp session id for UL */
 };
 
 /* Limited support: L2TPv2 only, no length field, no options */
diff --git a/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/pptp/pptp.h b/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/pptp/pptp.h
new file mode 100644
index 0000000..950c984
--- /dev/null
+++ b/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/pptp/pptp.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Frank-zj Lin <frank-zj.lin@mediatek.com>
+ */
+
+#ifndef _TOPS_PPTP_H_
+#define _TOPS_PPTP_H_
+
+int mtk_tops_pptp_seq_get_seq_gen_idx(u16 call_id, int *seq_gen_idx);
+int mtk_tops_pptp_seq_alloc(u16 call_id, u32 start, int *seq_gen_idx);
+void mtk_tops_pptp_seq_free(u16 call_id);
+int mtk_tops_pptp_seq_init(void);
+void mtk_tops_pptp_seq_deinit(void);
+
+#if defined(CONFIG_MTK_TOPS_PPTP)
+int mtk_tops_pptp_init(void);
+void mtk_tops_pptp_deinit(void);
+#else /* !defined(CONFIG_MTK_TOPS_PPTP) */
+static inline int mtk_tops_pptp_init(void)
+{
+	return 0;
+}
+
+static inline void mtk_tops_pptp_deinit(void)
+{
+}
+#endif /* defined(CONFIG_MTK_TOPS_PPTP) */
+#endif /* _TOPS_PPTP_H_ */
diff --git a/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/pptp/pptp_params.h b/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/pptp/pptp_params.h
new file mode 100644
index 0000000..0ac4b9b
--- /dev/null
+++ b/package-21.02/kernel/tops/src/protocol/inc/tops/protocol/tunnel/pptp/pptp_params.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Frank-zj Lin <frank-zj.lin@mediatek.com>
+ */
+
+#ifndef _TOPS_PPTP_PARAMS_H_
+#define _TOPS_PPTP_PARAMS_H_
+
+#define PPTP_GRE_HDR_ACK_LEN	4
+
+struct tops_pptp_params {
+	u16 dl_call_id;	/* call id for download */
+	u16 ul_call_id;	/* call id for upload */
+	u8 seq_gen_idx;	/* seq generator idx */
+};
+#endif /* _TOPS_PPTP_PARAMS_H_ */
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
deleted file mode 100644
index 592271d..0000000
--- a/package-21.02/kernel/tops/src/protocol/l2tp/udp_l2tp_data.c
+++ /dev/null
@@ -1,324 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
- *
- * Author: Frank-zj Lin <rank-zj.lin@mediatek.com>
- *         Ren-Ting Wang <ren-ting.wang@mediatek.com>
- */
-
-#include <linux/if_ether.h>
-#include <linux/netdevice.h>
-#include <linux/ppp_defs.h>
-#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 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)
-{
-	u16 hdrflags = ntohs(l2tp->flag_ver);
-
-	return ((hdrflags & L2TP_HDR_VER_MASK) == L2TP_HDR_VER_2 &&
-		!(hdrflags & L2TP_HDRFLAG_T));
-}
-
-static inline bool ppp_offload_match(struct ppp_hdr *ppp)
-{
-	return (ppp->addr == PPP_ALLSTATIONS &&
-		ppp->ctrl == PPP_UI && ntohs(ppp->proto) == PPP_IP);
-}
-
-static int udp_l2tp_data_tnl_decap_param_setup(struct sk_buff *skb,
-					       struct tops_tnl_params *tnl_params)
-{
-	struct udp_l2tp_data_hdr *l2tp;
-	struct udp_l2tp_data_hdr l2tph;
-	struct ppp_hdr *ppp;
-	struct ppp_hdr ppph;
-	struct udphdr *udp;
-	struct udphdr udph;
-	struct ethhdr *eth;
-	struct ethhdr ethh;
-	struct iphdr *ip;
-	struct iphdr iph;
-	int ret = 0;
-
-	/* ppp */
-	skb_push(skb, sizeof(struct ppp_hdr));
-	ppp = skb_header_pointer(skb, 0, sizeof(struct ppp_hdr), &ppph);
-
-	if (unlikely(!ppp)) {
-		ret = -EINVAL;
-		goto restore_ppp;
-	}
-
-	if (unlikely(!ppp_offload_match(ppp))) {
-		pr_notice("ppp offload unmatched\n");
-		ret = -EINVAL;
-		goto restore_ppp;
-	}
-
-	/* l2tp */
-	skb_push(skb, sizeof(struct udp_l2tp_data_hdr));
-	l2tp = skb_header_pointer(skb, 0, sizeof(struct udp_l2tp_data_hdr), &l2tph);
-	if (unlikely(!l2tp)) {
-		ret = -EINVAL;
-		goto restore_l2tp;
-	}
-
-	if (unlikely(!l2tpv2_offload_match(l2tp))) {
-		ret = -EINVAL;
-		goto restore_l2tp;
-	}
-
-	tnl_params->priv.l2tp.tid = l2tp->tid;
-	tnl_params->priv.l2tp.sid = l2tp->sid;
-
-	/* udp */
-	skb_push(skb, sizeof(struct udphdr));
-	udp = skb_header_pointer(skb, 0, sizeof(struct udphdr), &udph);
-	if (unlikely(!udp)) {
-		ret = -EINVAL;
-		goto restore_udp;
-	}
-
-	if (unlikely(ntohs(udp->dest) != UDP_L2TP_PORT)) {
-		pr_notice("udp port 0x%x unmatched\n", ntohs(udp->dest));
-		ret = -EINVAL;
-		goto restore_udp;
-	}
-
-	tnl_params->sport = udp->dest;
-	tnl_params->dport = udp->source;
-
-	/* ip */
-	skb_push(skb, sizeof(struct iphdr));
-	ip = skb_header_pointer(skb, 0, sizeof(struct iphdr), &iph);
-	if (unlikely(!ip)) {
-		ret = -EINVAL;
-		goto restore_ip;
-	}
-
-	if (unlikely(ip->version != IPVERSION || ip->protocol != IPPROTO_UDP)) {
-		pr_notice("ip: %p version or protocol unmatched, ver: 0x%x, proto: 0x%x\n",
-			ip, ip->version, ip->protocol);
-		ret = -EINVAL;
-		goto restore_ip;
-	}
-
-	tnl_params->protocol = ip->protocol;
-	tnl_params->sip = ip->daddr;
-	tnl_params->dip = ip->saddr;
-
-	/* eth */
-	skb_push(skb, sizeof(struct ethhdr));
-	eth = skb_header_pointer(skb, 0, sizeof(struct ethhdr), &ethh);
-	if (unlikely(!eth)) {
-		ret = -EINVAL;
-		goto restore_eth;
-	}
-
-	if (unlikely(ntohs(eth->h_proto) != ETH_P_IP)) {
-		pr_notice("eth proto not supported, proto: 0x%x\n",
-			  ntohs(eth->h_proto));
-		ret = -EINVAL;
-		goto restore_eth;
-	}
-
-	memcpy(&tnl_params->saddr, eth->h_dest, sizeof(u8) * ETH_ALEN);
-	memcpy(&tnl_params->daddr, eth->h_source, sizeof(u8) * ETH_ALEN);
-
-restore_eth:
-	skb_pull(skb, sizeof(struct ethhdr));
-restore_ip:
-	skb_pull(skb, sizeof(struct iphdr));
-restore_udp:
-	skb_pull(skb, sizeof(struct udphdr));
-restore_l2tp:
-	skb_pull(skb, sizeof(struct udp_l2tp_data_hdr));
-restore_ppp:
-	skb_pull(skb, sizeof(struct ppp_hdr));
-
-	return ret;
-}
-
-static int udp_l2tp_data_tnl_encap_param_setup(struct sk_buff *skb,
-					       struct tops_tnl_params *tnl_params)
-{
-	struct ethhdr *eth = eth_hdr(skb);
-	struct iphdr *ip = ip_hdr(skb);
-	struct udp_l2tp_data_hdr *l2tp;
-	struct udp_l2tp_data_hdr l2tph;
-	struct udphdr *udp;
-	struct udphdr udph;
-	int ret = 0;
-
-	if (unlikely(ip->version != IPVERSION || ip->protocol != IPPROTO_UDP)) {
-		pr_notice("eth proto: 0x%x, ip ver: 0x%x, proto: 0x%x is not support\n",
-			  ntohs(eth->h_proto),
-			  ip->version,
-			  ip->protocol);
-		ret = -EINVAL;
-		goto out;
-	}
-
-	skb_pull(skb, sizeof(struct iphdr));
-	udp = skb_header_pointer(skb, 0, sizeof(struct udphdr), &udph);
-	if (unlikely(!udp)) {
-		ret = -EINVAL;
-		goto restore_ip;
-	}
-
-	if (unlikely(ntohs(udp->dest) != UDP_L2TP_PORT)) {
-		pr_notice("udp port 0x%x unmatched\n", ntohs(udp->dest));
-		ret = -EINVAL;
-		goto restore_ip;
-	}
-
-	skb_pull(skb, sizeof(struct udphdr));
-	l2tp = skb_header_pointer(skb, 0, sizeof(struct udp_l2tp_data_hdr), &l2tph);
-	if (unlikely(!l2tp)) {
-		ret = -EINVAL;
-		goto restore_udp;
-	}
-
-	if (unlikely(!l2tpv2_offload_match(l2tp))) {
-		ret = -EINVAL;
-		goto restore_udp;
-	}
-
-	memcpy(&tnl_params->saddr, eth->h_source, sizeof(u8) * ETH_ALEN);
-	memcpy(&tnl_params->daddr, eth->h_dest, sizeof(u8) * ETH_ALEN);
-	tnl_params->protocol = ip->protocol;
-	tnl_params->sip = ip->saddr;
-	tnl_params->dip = ip->daddr;
-	tnl_params->sport = udp->source;
-	tnl_params->dport = udp->dest;
-	tnl_params->priv.l2tp.tid = l2tp->tid;
-	tnl_params->priv.l2tp.sid = l2tp->sid;
-
-restore_udp:
-	skb_push(skb, sizeof(struct udphdr));
-restore_ip:
-	skb_push(skb, sizeof(struct iphdr));
-out:
-	return ret;
-}
-
-static int udp_l2tp_data_tnl_debug_param_setup(const char *buf, int *ofs,
-					struct tops_tnl_params *tnl_params)
-{
-	return -EPERM; //TODO: not implemented
-}
-
-static int udp_l2tp_data_tnl_l2_param_update(struct sk_buff *skb,
-					     struct tops_tnl_params *tnl_params)
-{
-	struct ethhdr *eth = eth_hdr(skb);
-
-	memcpy(&tnl_params->saddr, eth->h_source, sizeof(u8) * ETH_ALEN);
-	memcpy(&tnl_params->daddr, eth->h_dest, sizeof(u8) * ETH_ALEN);
-
-	return 1;
-}
-
-static bool udp_l2tp_data_tnl_info_match(struct tops_tnl_params *params1,
-					 struct tops_tnl_params *params2)
-{
-	if (params1->sip == params2->sip
-	    && params1->dip == params2->dip
-	    && params1->sport == params2->sport
-	    && params1->dport == params2->dport
-	    && params1->priv.l2tp.tid == params2->priv.l2tp.tid
-	    && params1->priv.l2tp.sid == params2->priv.l2tp.sid)
-		return true;
-
-	return false;
-}
-
-static bool udp_l2tp_data_tnl_decap_offloadable(struct sk_buff *skb)
-{
-	struct udp_l2tp_data_hdr *l2tp;
-	struct udp_l2tp_data_hdr l2tph;
-	struct ppp_hdr *ppp;
-	struct ppp_hdr ppph;
-	struct udphdr *udp;
-	struct iphdr *ip;
-
-	ip = ip_hdr(skb);
-	if (ip->protocol != IPPROTO_UDP)
-		return false;
-
-	udp = udp_hdr(skb);
-	if (ntohs(udp->dest) != UDP_L2TP_PORT)
-		return false;
-
-	l2tp = skb_header_pointer(skb, ip_hdr(skb)->ihl * 4 + sizeof(struct udphdr),
-				  sizeof(struct udp_l2tp_data_hdr), &l2tph);
-
-	if (unlikely(!l2tp))
-		return false;
-
-	if (unlikely(!l2tpv2_offload_match(l2tp)))
-		return false;
-
-	ppp = skb_header_pointer(skb, (ip_hdr(skb)->ihl * 4 +
-				       sizeof(struct udphdr) +
-				       sizeof(struct udp_l2tp_data_hdr)),
-				 sizeof(struct ppp_hdr), &ppph);
-
-	if (unlikely(!ppp))
-		return false;
-
-	if (unlikely(!ppp_offload_match(ppp)))
-		return false;
-
-	return true;
-}
-
-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,
-	.tnl_l2_param_update = udp_l2tp_data_tnl_l2_param_update,
-	.tnl_info_match = udp_l2tp_data_tnl_info_match,
-	.tnl_decap_offloadable = udp_l2tp_data_tnl_decap_offloadable,
-	.tops_entry = TOPS_ENTRY_UDP_L2TP_DATA,
-	.has_inner_eth = false,
-};
-
-int mtk_tops_udp_l2tp_data_init(void)
-{
-	return mtk_tops_tnl_type_register(&udp_l2tp_data_type);
-}
-
-void mtk_tops_udp_l2tp_data_deinit(void)
-{
-	mtk_tops_tnl_type_unregister(&udp_l2tp_data_type);
-}
diff --git a/package-21.02/kernel/tops/src/protocol/tunnel/gre/gretap.c b/package-21.02/kernel/tops/src/protocol/tunnel/gre/gretap.c
index 13618ce..584367f 100644
--- a/package-21.02/kernel/tops/src/protocol/tunnel/gre/gretap.c
+++ b/package-21.02/kernel/tops/src/protocol/tunnel/gre/gretap.c
@@ -87,10 +87,20 @@
 static bool gretap_tnl_decap_offloadable(struct sk_buff *skb)
 {
 	struct iphdr *ip = ip_hdr(skb);
+	struct gre_base_hdr *pgre;
+	struct gre_base_hdr greh;
 
 	if (ip->protocol != IPPROTO_GRE)
 		return false;
 
+	pgre = skb_header_pointer(skb, ip_hdr(skb)->ihl * 4,
+				  sizeof(struct gre_base_hdr), &greh);
+	if (unlikely(!pgre))
+		return false;
+
+	if (ntohs(pgre->protocol) != ETH_P_TEB)
+		return false;
+
 	return true;
 }
 
diff --git a/package-21.02/kernel/tops/src/protocol/tunnel/l2tp/l2tpv2.c b/package-21.02/kernel/tops/src/protocol/tunnel/l2tp/l2tpv2.c
index d00d3c0..ac5a929 100644
--- a/package-21.02/kernel/tops/src/protocol/tunnel/l2tp/l2tpv2.c
+++ b/package-21.02/kernel/tops/src/protocol/tunnel/l2tp/l2tpv2.c
@@ -6,7 +6,11 @@
  *         Ren-Ting Wang <ren-ting.wang@mediatek.com>
  */
 
+#include <linux/if_pppox.h>
 #include <linux/netdevice.h>
+#include <linux/ppp_channel.h>
+
+#include <l2tp_core.h>
 
 #include <pce/cls.h>
 #include <pce/netsys.h>
@@ -37,6 +41,48 @@
 	return 0;
 }
 
+/* Helpers to obtain tunnel params from ppp netdev */
+static int l2tpv2_param_obtain_from_netdev(struct net_device *dev,
+					   struct tops_params *params)
+{
+	struct tops_l2tp_params *l2tpp;
+	struct l2tp_session *session;
+	struct l2tp_tunnel *tunnel;
+	struct sock *sk;
+	int ret = 0;
+
+	if (!dev || !params)
+		return -EINVAL;
+
+	sk = ppp_netdev_get_sock(dev);
+	if (IS_ERR(sk) || !sk)
+		return -EINVAL;
+
+	sock_hold(sk);
+	session = (struct l2tp_session *)(sk->sk_user_data);
+	if (!session) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (session->magic != L2TP_SESSION_MAGIC) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	tunnel = session->tunnel;
+
+	l2tpp = &params->tunnel.l2tp;
+	l2tpp->dl_tid = htons(tunnel->tunnel_id);
+	l2tpp->dl_sid = htons(session->session_id);
+	l2tpp->ul_tid = htons(tunnel->peer_tunnel_id);
+	l2tpp->ul_sid = htons(session->peer_session_id);
+out:
+	sock_put(sk);
+
+	return ret;
+}
+
 static inline bool l2tpv2_offload_valid(struct sk_buff *skb)
 {
 	struct udp_l2tp_data_hdr *l2tp;
@@ -56,9 +102,6 @@
 static int l2tpv2_tnl_decap_param_setup(struct sk_buff *skb,
 					struct tops_params *params)
 {
-	struct tops_l2tp_params *l2tpp;
-	struct udp_l2tp_data_hdr *l2tp;
-	struct udp_l2tp_data_hdr l2tph;
 	int ret = 0;
 
 	/* ppp */
@@ -75,17 +118,11 @@
 		goto restore_l2tp;
 	}
 
-	l2tp = skb_header_pointer(skb, 0, sizeof(struct udp_l2tp_data_hdr), &l2tph);
-	if (unlikely(!l2tp)) {
-		ret = -EINVAL;
-		goto restore_l2tp;
-	}
-
 	params->tunnel.type = TOPS_TUNNEL_L2TP_V2;
 
-	l2tpp = &params->tunnel.l2tp;
-	l2tpp->tid = l2tp->tid;
-	l2tpp->sid = l2tp->sid;
+	ret = l2tpv2_param_obtain_from_netdev(skb->dev, params);
+	if (ret)
+		goto restore_l2tp;
 
 	ret = mtk_tops_transport_decap_param_setup(skb, params);
 
@@ -115,8 +152,8 @@
 	params->tunnel.type = TOPS_TUNNEL_L2TP_V2;
 
 	l2tpp = &params->tunnel.l2tp;
-	l2tpp->tid = l2tp->tid;
-	l2tpp->sid = l2tp->sid;
+	l2tpp->ul_tid = l2tp->tid;
+	l2tpp->ul_sid = l2tp->sid;
 
 	return 0;
 }
@@ -125,20 +162,25 @@
 					struct tops_params *params)
 {
 	struct tops_l2tp_params *l2tpp;
+	u16 ul_tid = 0;
+	u16 ul_sid = 0;
+	u16 dl_tid = 0;
+	u16 dl_sid = 0;
 	int nchar = 0;
 	int ret;
-	u16 tid = 0;
-	u16 sid = 0;
 
 	params->tunnel.type = TOPS_TUNNEL_L2TP_V2;
 	l2tpp = &params->tunnel.l2tp;
 
-	ret = sscanf(buf + *ofs, "%hu %hu %n", &tid, &sid, &nchar);
+	ret = sscanf(buf + *ofs, "%hu %hu %hu %hu %n",
+		     &ul_tid, &ul_sid, &dl_tid, &dl_sid, &nchar);
 	if (ret != 2)
 		return -EINVAL;
 
-	l2tpp->tid = htons(tid);
-	l2tpp->sid = htons(sid);
+	l2tpp->ul_tid = htons(ul_tid);
+	l2tpp->ul_sid = htons(ul_sid);
+	l2tpp->dl_tid = htons(dl_tid);
+	l2tpp->dl_sid = htons(dl_sid);
 
 	*ofs += nchar;
 
@@ -189,13 +231,39 @@
 	return ret;
 }
 
+static void l2tpv2_tnl_param_restore(struct tops_params *old, struct tops_params *new)
+{
+	/* dl_tid and dl_sid are assigned at decap */
+	if (old->tunnel.l2tp.dl_tid)
+		new->tunnel.l2tp.dl_tid = old->tunnel.l2tp.dl_tid;
+	if (old->tunnel.l2tp.dl_sid)
+		new->tunnel.l2tp.dl_sid = old->tunnel.l2tp.dl_sid;
+
+	if (old->tunnel.l2tp.ul_tid)
+		new->tunnel.l2tp.ul_tid = old->tunnel.l2tp.ul_tid;
+	if (old->tunnel.l2tp.ul_sid)
+		new->tunnel.l2tp.ul_sid = old->tunnel.l2tp.ul_sid;
+}
+
+static bool l2tpv2_tnl_param_match(struct tops_params *p, struct tops_params *target)
+{
+	/*
+	 * Only UL params are guaranteed to be valid for comparison, DL params
+	 * may be left empty if no DL traffic had passed yet.
+	 */
+	return (p->tunnel.l2tp.ul_tid == target->tunnel.l2tp.ul_tid)
+	       && (p->tunnel.l2tp.ul_sid == target->tunnel.l2tp.ul_sid);
+}
+
 static void l2tpv2_tnl_param_dump(struct seq_file *s, struct tops_params *params)
 {
 	struct tops_l2tp_params *l2tpp = &params->tunnel.l2tp;
 
 	seq_puts(s, "\tTunnel Type: L2TPv2 ");
-	seq_printf(s, "tunnel ID: %05u session ID: %05u\n",
-		   ntohs(l2tpp->tid), ntohs(l2tpp->sid));
+	seq_printf(s, "DL tunnel ID: %05u DL session ID: %05u ",
+		   ntohs(l2tpp->dl_tid), ntohs(l2tpp->dl_sid));
+	seq_printf(s, "UL tunnel ID: %05u UL session ID: %05u\n",
+		   ntohs(l2tpp->ul_tid), ntohs(l2tpp->ul_sid));
 }
 
 static struct tops_tnl_type l2tpv2_type = {
@@ -206,6 +274,8 @@
 	.tnl_debug_param_setup = l2tpv2_tnl_debug_param_setup,
 	.tnl_decap_offloadable = l2tpv2_tnl_decap_offloadable,
 	.tnl_l2_param_update = l2tpv2_tnl_l2_param_update,
+	.tnl_param_restore = l2tpv2_tnl_param_restore,
+	.tnl_param_match = l2tpv2_tnl_param_match,
 	.tnl_param_dump = l2tpv2_tnl_param_dump,
 	.tnl_proto_type = TOPS_TUNNEL_L2TP_V2,
 	.has_inner_eth = false,
diff --git a/package-21.02/kernel/tops/src/protocol/tunnel/pptp/pptp.c b/package-21.02/kernel/tops/src/protocol/tunnel/pptp/pptp.c
new file mode 100644
index 0000000..8bc46fe
--- /dev/null
+++ b/package-21.02/kernel/tops/src/protocol/tunnel/pptp/pptp.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Frank-zj Lin <frank-zj.lin@mediatek.com>
+ */
+
+#include <linux/if_pppox.h>
+#include <linux/ppp_channel.h>
+
+#include <net/gre.h>
+#include <net/pptp.h>
+#include <net/sock.h>
+
+#include <pce/cls.h>
+#include <pce/netsys.h>
+#include <pce/pce.h>
+
+#include "tops/protocol/mac/ppp.h"
+#include "tops/protocol/tunnel/pptp/pptp.h"
+#include "tops/seq_gen.h"
+#include "tops/tunnel.h"
+
+static int pptp_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, 0x0000880B);
+
+	return 0;
+}
+
+/*
+ * If a sequence generator is already allocated for this tunnel (call_id),
+ * return with seq_gen_idx set. Otherwise, allocate a new sequence generator
+ * and set the starting sequence number.
+ */
+static int pptp_get_seq_gen_idx(uint16_t call_id, uint32_t seq_start,
+				int *seq_gen_idx)
+{
+	int ret;
+
+	ret = mtk_tops_pptp_seq_get_seq_gen_idx(call_id, seq_gen_idx);
+	if (ret)
+		ret = mtk_tops_pptp_seq_alloc(call_id, seq_start, seq_gen_idx);
+
+	return ret;
+}
+
+static inline bool pptp_gre_offload_valid(struct sk_buff *skb)
+{
+	struct pptp_gre_header *pptp_gre;
+	struct pptp_gre_header pptp_greh;
+
+	pptp_gre = skb_header_pointer(skb, 0, sizeof(struct pptp_gre_header), &pptp_greh);
+	if (unlikely(!pptp_gre))
+		return false;
+
+	if (pptp_gre->gre_hd.protocol != GRE_PROTO_PPP
+	    || pptp_gre->payload_len < sizeof(struct ppp_hdr)
+	    || GRE_IS_CSUM(pptp_gre->gre_hd.flags)	/* flag CSUM should be clear */
+	    || GRE_IS_ROUTING(pptp_gre->gre_hd.flags)	/* flag ROUTING should be clear */
+	    || !GRE_IS_KEY(pptp_gre->gre_hd.flags)	/* flag KEY should be set */
+	    || pptp_gre->gre_hd.flags & GRE_FLAGS)	/* flag Recursion Ctrl should be clear */
+		return false;
+
+	return true;
+}
+
+static inline int pptp_gre_len_evaluate(struct sk_buff *skb)
+{
+	static const int possible_greh_len[] = {
+		sizeof(struct pptp_gre_header) - PPTP_GRE_HDR_ACK_LEN,
+		sizeof(struct pptp_gre_header),
+	};
+	struct pptp_gre_header *pptp_gre;
+	int pptp_gre_len;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(possible_greh_len); i++) {
+		pptp_gre_len = possible_greh_len[i];
+
+		skb_push(skb, pptp_gre_len);
+		pptp_gre = (struct pptp_gre_header *)skb->data;
+		skb_pull(skb, pptp_gre_len);
+
+		if (pptp_gre->gre_hd.protocol == GRE_PROTO_PPP)
+			return pptp_gre_len;
+	}
+
+	return -EINVAL;
+}
+
+static int pptp_tnl_decap_param_setup(struct sk_buff *skb,
+				      struct tops_params *params)
+{
+	struct pptp_gre_header *pptp_gre;
+	struct pptp_gre_header pptp_greh;
+	struct tops_pptp_params *pptpp;
+	struct sock *sk;
+	int pptp_gre_len;
+	int ret = 0;
+
+	/* ppp */
+	skb_push(skb, sizeof(struct ppp_hdr));
+	if (unlikely(!mtk_tops_ppp_valid(skb))) {
+		ret = -EINVAL;
+		goto restore_ppp;
+	}
+
+	/* pptp_gre */
+	pptp_gre_len = pptp_gre_len_evaluate(skb);
+	if (pptp_gre_len < 0) {
+		ret = -EINVAL;
+		goto restore_ppp;
+	}
+
+	skb_push(skb, pptp_gre_len);
+	pptp_gre = skb_header_pointer(skb, 0, pptp_gre_len, &pptp_greh);
+	if (unlikely(!pptp_gre)) {
+		ret = -EINVAL;
+		goto restore_pptp_gre;
+	}
+
+	if (unlikely(!pptp_gre_offload_valid(skb))) {
+		ret = -EINVAL;
+		goto restore_pptp_gre;
+	}
+
+	/*
+	 * In decap setup, dl_call_id is fetched from the skb and ul_call_id is
+	 * fetched from socket struct of ppp device.
+	 */
+	sk = ppp_netdev_get_sock(skb->dev);
+	if (IS_ERR(sk)) {
+		ret = PTR_ERR(sk);
+		goto restore_pptp_gre;
+	}
+
+	params->tunnel.type = TOPS_TUNNEL_PPTP;
+	pptpp = &params->tunnel.pptp;
+	pptpp->dl_call_id = pptp_gre->call_id;
+	pptpp->ul_call_id = htons(pppox_sk(sk)->proto.pptp.dst_addr.call_id);
+
+	ret = mtk_tops_network_decap_param_setup(skb, params);
+
+restore_pptp_gre:
+	skb_pull(skb, pptp_gre_len);
+
+restore_ppp:
+	skb_pull(skb, sizeof(struct ppp_hdr));
+
+	return ret;
+}
+
+static int pptp_tnl_encap_param_setup(struct sk_buff *skb,
+				      struct tops_params *params)
+{
+	struct pptp_gre_header *pptp_gre;
+	struct pptp_gre_header pptp_greh;
+	struct tops_pptp_params *pptpp;
+	uint32_t pptp_gre_len;
+	int seq_gen_idx;
+	int ret = 0;
+
+	if (unlikely(!pptp_gre_offload_valid(skb)))
+		return -EINVAL;
+
+	pptp_gre = skb_header_pointer(skb, 0, sizeof(struct pptp_gre_header), &pptp_greh);
+	if (unlikely(!pptp_gre))
+		return -EINVAL;
+
+	pptp_gre_len = sizeof(*pptp_gre);
+	if (!(GRE_IS_ACK(pptp_gre->gre_hd.flags)))
+		pptp_gre_len -= sizeof(pptp_gre->ack);
+
+	skb_pull(skb, pptp_gre_len);
+
+	/* check ppp */
+	if (unlikely(!mtk_tops_ppp_valid(skb))) {
+		ret = -EINVAL;
+		goto restore_pptp_gre;
+	}
+
+	ret = pptp_get_seq_gen_idx(ntohs(pptp_gre->call_id),
+				   ntohl(pptp_gre->seq), &seq_gen_idx);
+	if (ret)
+		goto restore_pptp_gre;
+
+	params->tunnel.type = TOPS_TUNNEL_PPTP;
+	pptpp = &params->tunnel.pptp;
+	pptpp->seq_gen_idx = (u8)seq_gen_idx;
+	pptpp->ul_call_id = pptp_gre->call_id;
+
+restore_pptp_gre:
+	skb_push(skb, pptp_gre_len);
+
+	return ret;
+}
+
+static int pptp_debug_param_fetch_call_id(const char *buf, int *ofs, u16 *call_id)
+{
+	int nchar = 0;
+	int ret;
+	u16 c = 0;
+
+	ret = sscanf(buf + *ofs, "%hu %n", &c, &nchar);
+	if (ret != 1)
+		return -EPERM;
+
+	*call_id = htons(c);
+
+	*ofs += nchar;
+
+	return 0;
+}
+
+static int pptp_tnl_debug_param_setup(const char *buf, int *ofs,
+				      struct tops_params *params)
+{
+	struct tops_pptp_params *pptpp;
+	int seq_gen_idx;
+	int ret;
+
+	pptpp = &params->tunnel.pptp;
+
+	ret = pptp_debug_param_fetch_call_id(buf, ofs, &pptpp->ul_call_id);
+	if (ret)
+		return ret;
+
+	ret = pptp_debug_param_fetch_call_id(buf, ofs, &pptpp->dl_call_id);
+	if (ret)
+		return ret;
+
+	ret = pptp_get_seq_gen_idx(ntohs(pptpp->ul_call_id), 0, &seq_gen_idx);
+	if (ret)
+		return ret;
+
+	pptpp->seq_gen_idx = (u8)seq_gen_idx;
+
+	return 0;
+}
+
+static bool pptp_tnl_decap_offloadable(struct sk_buff *skb)
+{
+	struct pptp_gre_header *pptp_gre;
+	struct pptp_gre_header pptp_greh;
+	struct iphdr *ip;
+	int pptp_gre_len;
+	int ip_len;
+	bool ret = true;
+
+	/* check ip */
+	ip = ip_hdr(skb);
+	if (ip->protocol != IPPROTO_GRE)
+		return false;
+
+	ip_len = ip_hdr(skb)->ihl * 4;
+
+	skb_pull(skb, ip_len);
+
+	/* check gre */
+	if (!pptp_gre_offload_valid(skb)) {
+		ret = false;
+		goto restore_ip;
+	}
+
+	pptp_gre = skb_header_pointer(skb, 0, sizeof(struct pptp_gre_header), &pptp_greh);
+	if (unlikely(!pptp_gre)) {
+		ret = false;
+		goto restore_ip;
+	}
+
+	pptp_gre_len = sizeof(*pptp_gre);
+	if (!(GRE_IS_ACK(pptp_gre->gre_hd.flags)))
+		pptp_gre_len -= sizeof(pptp_gre->ack);
+
+	skb_pull(skb, pptp_gre_len);
+
+	/* check ppp */
+	if (unlikely(!mtk_tops_ppp_valid(skb))) {
+		ret = false;
+		goto restore_pptp_gre;
+	}
+
+restore_pptp_gre:
+	skb_push(skb, pptp_gre_len);
+
+restore_ip:
+	skb_push(skb, ip_len);
+
+	return ret;
+}
+
+static void pptp_tnl_param_restore(struct tops_params *old, struct tops_params *new)
+{
+	/* dl_call_id is assigned at decap */
+	if (old->tunnel.pptp.dl_call_id)
+		new->tunnel.pptp.dl_call_id = old->tunnel.pptp.dl_call_id;
+
+	if (old->tunnel.pptp.ul_call_id)
+		new->tunnel.pptp.ul_call_id = old->tunnel.pptp.ul_call_id;
+
+	/* seq_gen_idx is assigned at encap */
+	if (old->tunnel.pptp.seq_gen_idx)
+		new->tunnel.pptp.seq_gen_idx = old->tunnel.pptp.seq_gen_idx;
+}
+
+static bool pptp_tnl_param_match(struct tops_params *p, struct tops_params *target)
+{
+	/*
+	 * Only ul_call_id is guaranteed to be valid for comparison, dl_call_id
+	 * may be left empty if no DL traffic had passed yet.
+	 */
+	return p->tunnel.pptp.ul_call_id == target->tunnel.pptp.ul_call_id;
+}
+
+static void pptp_tnl_param_dump(struct seq_file *s, struct tops_params *params)
+{
+	struct tops_pptp_params *pptpp = &params->tunnel.pptp;
+
+	seq_puts(s, "\tTunnel Type: PPTP ");
+	seq_printf(s, "DL Call ID: %05u UL Call ID: %05u SEQ_GEN_IDX: %05u\n",
+		   ntohs(pptpp->dl_call_id), ntohs(pptpp->ul_call_id),
+		   pptpp->seq_gen_idx);
+}
+
+static struct tops_tnl_type pptp_type = {
+	.type_name = "pptp",
+	.cls_entry_setup = pptp_cls_entry_setup,
+	.tnl_decap_param_setup = pptp_tnl_decap_param_setup,
+	.tnl_encap_param_setup = pptp_tnl_encap_param_setup,
+	.tnl_debug_param_setup = pptp_tnl_debug_param_setup,
+	.tnl_decap_offloadable = pptp_tnl_decap_offloadable,
+	.tnl_param_restore = pptp_tnl_param_restore,
+	.tnl_param_match = pptp_tnl_param_match,
+	.tnl_param_dump = pptp_tnl_param_dump,
+	.tnl_proto_type = TOPS_TUNNEL_PPTP,
+	.has_inner_eth = false,
+};
+
+int mtk_tops_pptp_init(void)
+{
+	int ret = 0;
+
+	ret = mtk_tops_tnl_type_register(&pptp_type);
+	if (ret)
+		return ret;
+
+	mtk_tops_pptp_seq_init();
+
+	return ret;
+}
+
+void mtk_tops_pptp_deinit(void)
+{
+	mtk_tops_pptp_seq_deinit();
+
+	mtk_tops_tnl_type_unregister(&pptp_type);
+}
diff --git a/package-21.02/kernel/tops/src/protocol/tunnel/pptp/pptp_seq.c b/package-21.02/kernel/tops/src/protocol/tunnel/pptp/pptp_seq.c
new file mode 100644
index 0000000..ff61cc8
--- /dev/null
+++ b/package-21.02/kernel/tops/src/protocol/tunnel/pptp/pptp_seq.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Frank-zj Lin <frank-zj.lin@mediatek.com>
+ */
+
+#include <linux/hashtable.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+
+#include <net/pptp.h>
+
+#include "tops/hwspinlock.h"
+#include "tops/seq_gen.h"
+
+#define PPTP_SEQ_HT_BITS	4
+
+struct pptp_seq {
+	struct hlist_node hlist;
+	int seq_gen_idx;
+	uint16_t call_id;
+};
+
+static DEFINE_HASHTABLE(pptp_seq_ht, PPTP_SEQ_HT_BITS);
+static DEFINE_SPINLOCK(pptp_seq_ht_lock);
+
+static struct pptp_seq *mtk_tops_pptp_seq_find_no_lock(uint16_t call_id)
+{
+	struct pptp_seq *pptp_seq;
+
+	hash_for_each_possible(pptp_seq_ht, pptp_seq, hlist, call_id) {
+		if (pptp_seq->call_id == call_id)
+			return pptp_seq;
+	}
+
+	return ERR_PTR(-ENODEV);
+}
+
+static int mtk_tops_pptp_seq_alloc_no_lock(uint16_t call_id, uint32_t seq_start,
+					   int *seq_gen_idx)
+{
+	struct pptp_seq *pptp_seq;
+	int ret;
+
+	if (!IS_ERR(mtk_tops_pptp_seq_find_no_lock(call_id)))
+		return -EBUSY;
+
+	ret = mtk_tops_seq_gen_alloc(seq_gen_idx);
+	if (ret)
+		return ret;
+
+	pptp_seq = kzalloc(sizeof(struct pptp_seq), GFP_KERNEL);
+	if (!pptp_seq) {
+		mtk_tops_seq_gen_free(*seq_gen_idx);
+		return -ENOMEM;
+	}
+
+	pptp_seq->seq_gen_idx = *seq_gen_idx;
+	pptp_seq->call_id = call_id;
+	hash_add(pptp_seq_ht, &pptp_seq->hlist, pptp_seq->call_id);
+
+	mtk_tops_seq_gen_set_32(*seq_gen_idx, seq_start);
+
+	return 0;
+}
+
+int mtk_tops_pptp_seq_alloc(uint16_t call_id, uint32_t seq_start,
+			    int *seq_gen_idx)
+{
+	unsigned long flag;
+	int ret;
+
+	spin_lock_irqsave(&pptp_seq_ht_lock, flag);
+
+	ret = mtk_tops_pptp_seq_alloc_no_lock(call_id, seq_start, seq_gen_idx);
+
+	spin_unlock_irqrestore(&pptp_seq_ht_lock, flag);
+
+	return ret;
+}
+
+static void mtk_tops_pptp_seq_free_no_lock(uint16_t call_id)
+{
+	struct pptp_seq *pptp_seq;
+
+	pptp_seq = mtk_tops_pptp_seq_find_no_lock(call_id);
+	if (IS_ERR(pptp_seq))
+		return;
+
+	mtk_tops_seq_gen_free(pptp_seq->seq_gen_idx);
+	hash_del(&pptp_seq->hlist);
+	kfree(pptp_seq);
+}
+
+void mtk_tops_pptp_seq_free(uint16_t call_id)
+{
+	unsigned long flag;
+
+	spin_lock_irqsave(&pptp_seq_ht_lock, flag);
+
+	mtk_tops_pptp_seq_free_no_lock(call_id);
+
+	spin_unlock_irqrestore(&pptp_seq_ht_lock, flag);
+}
+
+static int mtk_tops_pptp_seq_next_no_lock(uint16_t call_id, uint32_t *val)
+{
+	struct pptp_seq *pptp_seq;
+
+	pptp_seq = mtk_tops_pptp_seq_find_no_lock(call_id);
+	if (IS_ERR(pptp_seq))
+		return -EINVAL;
+
+	return mtk_tops_seq_gen_next_32(pptp_seq->seq_gen_idx, val);
+}
+
+static int mtk_tops_pptp_seq_next(uint16_t call_id, uint32_t *val)
+{
+	unsigned long flag;
+	int ret;
+
+	spin_lock_irqsave(&pptp_seq_ht_lock, flag);
+
+	mtk_tops_hwspin_lock(HWSPINLOCK_GROUP_CLUST,
+			     HWSPINLOCK_CLUST_SLOT_PPTP_SEQ);
+
+	ret = mtk_tops_pptp_seq_next_no_lock(call_id, val);
+
+	mtk_tops_hwspin_unlock(HWSPINLOCK_GROUP_CLUST,
+			       HWSPINLOCK_CLUST_SLOT_PPTP_SEQ);
+
+	spin_unlock_irqrestore(&pptp_seq_ht_lock, flag);
+
+	return ret;
+}
+
+static int mtk_tops_pptp_seq_get_seq_gen_idx_no_lock(uint16_t call_id,
+						     int *seq_gen_idx)
+{
+	struct pptp_seq *pptp_seq;
+
+	pptp_seq = mtk_tops_pptp_seq_find_no_lock(call_id);
+	if (IS_ERR(pptp_seq))
+		return -EINVAL;
+
+	*seq_gen_idx = pptp_seq->seq_gen_idx;
+
+	return 0;
+}
+
+int mtk_tops_pptp_seq_get_seq_gen_idx(uint16_t call_id, int *seq_gen_idx)
+{
+	unsigned long flag;
+	int ret;
+
+	spin_lock_irqsave(&pptp_seq_ht_lock, flag);
+
+	ret = mtk_tops_pptp_seq_get_seq_gen_idx_no_lock(call_id, seq_gen_idx);
+
+	spin_unlock_irqrestore(&pptp_seq_ht_lock, flag);
+
+	return ret;
+}
+
+void mtk_tops_pptp_seq_init(void)
+{
+	mtk_pptp_seq_next = mtk_tops_pptp_seq_next;
+}
+
+void mtk_tops_pptp_seq_deinit(void)
+{
+	mtk_pptp_seq_next = NULL;
+}
diff --git a/package-21.02/kernel/tops/src/seq_gen.c b/package-21.02/kernel/tops/src/seq_gen.c
new file mode 100644
index 0000000..b9ac9e5
--- /dev/null
+++ b/package-21.02/kernel/tops/src/seq_gen.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Frank-zj Lin <frank-zj.lin@mediatek.com>
+ */
+
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "tops/internal.h"
+#include "tops/seq_gen.h"
+
+#define SEQ_GEN_L(idx)	(TOPS_SEQ_GEN_BASE + (idx) * 0x20)
+#define SEQ_GEN_H(idx)	(TOPS_SEQ_GEN_BASE + (idx) * 0x20 + 0x10)
+
+static void __iomem *base;
+
+static DECLARE_BITMAP(seq_gen_used, TOPS_SEQ_GEN_IDX_MAX);
+static DEFINE_SPINLOCK(seq_gen_used_lock);
+
+static inline u32 seq_gen_read(u32 reg)
+{
+	return readl(base + reg);
+}
+
+static inline void seq_gen_write(u32 reg, u32 val)
+{
+	writel(val, base + reg);
+}
+
+static inline int seq_gen_read_16(u32 seq_gen_idx, u16 *val)
+{
+	if (seq_gen_idx >= TOPS_SEQ_GEN_IDX_MAX)
+		return -EINVAL;
+
+	*val = (u16)seq_gen_read(SEQ_GEN_L(seq_gen_idx));
+
+	return 0;
+}
+
+static inline void seq_gen_write_16(u32 seq_gen_idx, u16 val)
+{
+	if (seq_gen_idx >= TOPS_SEQ_GEN_IDX_MAX)
+		return;
+
+	seq_gen_write(SEQ_GEN_L(seq_gen_idx), (u32)val);
+}
+
+static inline int seq_gen_read_32(u32 seq_gen_idx, u32 *val)
+{
+	u32 val_h, val_l;
+
+	if (seq_gen_idx >= TOPS_SEQ_GEN_IDX_MAX)
+		return -EINVAL;
+
+	val_l = seq_gen_read(SEQ_GEN_L(seq_gen_idx));
+	val_h = seq_gen_read(SEQ_GEN_H(seq_gen_idx));
+
+	if (val_l != 0xFFFF)
+		seq_gen_write(SEQ_GEN_H(seq_gen_idx), val_h);
+
+	*val = (val_h << 16) | val_l;
+
+	return 0;
+}
+
+static inline void seq_gen_write_32(u32 seq_gen_idx, u32 val)
+{
+	if (seq_gen_idx >= TOPS_SEQ_GEN_IDX_MAX)
+		return;
+
+	seq_gen_write(SEQ_GEN_L(seq_gen_idx), (val & 0xFFFF));
+	seq_gen_write(SEQ_GEN_H(seq_gen_idx), (val >> 16));
+}
+
+static void mtk_tops_seq_gen_set_16_no_lock(int seq_gen_idx, u16 val)
+{
+	if (unlikely(!test_bit(seq_gen_idx, seq_gen_used)))
+		return;
+
+	seq_gen_write_16(seq_gen_idx, val);
+}
+
+static void mtk_tops_seq_gen_set_32_no_lock(int seq_gen_idx, u32 val)
+{
+	if (unlikely(!test_bit(seq_gen_idx, seq_gen_used)))
+		return;
+
+	seq_gen_write_32(seq_gen_idx, val);
+}
+
+static int mtk_tops_seq_gen_next_16_no_lock(int seq_gen_idx, u16 *val)
+{
+	if (unlikely(!val || !test_bit(seq_gen_idx, seq_gen_used)))
+		return -EINVAL;
+
+	return seq_gen_read_16(seq_gen_idx, val);
+}
+
+static int mtk_tops_seq_gen_next_32_no_lock(int seq_gen_idx, u32 *val)
+{
+	if (unlikely(!val || !test_bit(seq_gen_idx, seq_gen_used)))
+		return -EINVAL;
+
+	return seq_gen_read_32(seq_gen_idx, val);
+}
+
+void mtk_tops_seq_gen_set_16(int seq_gen_idx, u16 val)
+{
+	unsigned long flag;
+
+	spin_lock_irqsave(&seq_gen_used_lock, flag);
+
+	mtk_tops_seq_gen_set_16_no_lock(seq_gen_idx, val);
+
+	spin_unlock_irqrestore(&seq_gen_used_lock, flag);
+}
+
+int mtk_tops_seq_gen_next_16(int seq_gen_idx, u16 *val)
+{
+	unsigned long flag;
+	int ret;
+
+	spin_lock_irqsave(&seq_gen_used_lock, flag);
+
+	ret = mtk_tops_seq_gen_next_16_no_lock(seq_gen_idx, val);
+
+	spin_unlock_irqrestore(&seq_gen_used_lock, flag);
+
+	return ret;
+}
+
+void mtk_tops_seq_gen_set_32(int seq_gen_idx, u32 val)
+{
+	unsigned long flag;
+
+	spin_lock_irqsave(&seq_gen_used_lock, flag);
+
+	mtk_tops_seq_gen_set_32_no_lock(seq_gen_idx, val);
+
+	spin_unlock_irqrestore(&seq_gen_used_lock, flag);
+}
+
+int mtk_tops_seq_gen_next_32(int seq_gen_idx, u32 *val)
+{
+	unsigned long flag;
+	int ret;
+
+	spin_lock_irqsave(&seq_gen_used_lock, flag);
+
+	ret = mtk_tops_seq_gen_next_32_no_lock(seq_gen_idx, val);
+
+	spin_unlock_irqrestore(&seq_gen_used_lock, flag);
+
+	return ret;
+}
+
+static int mtk_tops_seq_gen_alloc_no_lock(int *seq_gen_idx)
+{
+	if (!seq_gen_idx)
+		return -EINVAL;
+
+	*seq_gen_idx = find_first_zero_bit(seq_gen_used, TOPS_SEQ_GEN_IDX_MAX);
+	if (*seq_gen_idx == TOPS_SEQ_GEN_IDX_MAX) {
+		TOPS_NOTICE("Sequence generator exhausted\n");
+		return -ENOMEM;
+	}
+
+	set_bit(*seq_gen_idx, seq_gen_used);
+
+	return 0;
+}
+
+int mtk_tops_seq_gen_alloc(int *seq_gen_idx)
+{
+	unsigned long flag;
+	int ret;
+
+	spin_lock_irqsave(&seq_gen_used_lock, flag);
+
+	ret = mtk_tops_seq_gen_alloc_no_lock(seq_gen_idx);
+
+	spin_unlock_irqrestore(&seq_gen_used_lock, flag);
+
+	return ret;
+}
+
+static void mtk_tops_seq_gen_free_no_lock(int seq_gen_idx)
+{
+	clear_bit(seq_gen_idx, seq_gen_used);
+}
+
+void mtk_tops_seq_gen_free(int seq_gen_idx)
+{
+	unsigned long flag = 0;
+
+	spin_lock_irqsave(&seq_gen_used_lock, flag);
+
+	mtk_tops_seq_gen_free_no_lock(seq_gen_idx);
+
+	spin_unlock_irqrestore(&seq_gen_used_lock, flag);
+}
+
+int mtk_tops_seq_gen_init(struct platform_device *pdev)
+{
+	struct resource *res;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tops-base");
+	if (!res)
+		return -ENXIO;
+
+	base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+	if (!base)
+		return -ENOMEM;
+
+	return 0;
+}
diff --git a/package-21.02/kernel/tops/src/tnl_offload.c b/package-21.02/kernel/tops/src/tnl_offload.c
index 714139b..bc3057d 100644
--- a/package-21.02/kernel/tops/src/tnl_offload.c
+++ b/package-21.02/kernel/tops/src/tnl_offload.c
@@ -34,6 +34,7 @@
 #include "tops/netsys.h"
 #include "tops/protocol/tunnel/gre/gretap.h"
 #include "tops/protocol/tunnel/l2tp/l2tpv2.h"
+#include "tops/protocol/tunnel/pptp/pptp.h"
 #include "tops/tunnel.h"
 
 #define TOPS_PPE_ENTRY_BUCKETS		(64)
@@ -1707,14 +1708,18 @@
 
 	mtk_tops_l2tpv2_init();
 
+	mtk_tops_pptp_init();
+
 	return 0;
 }
 
 void mtk_tops_tnl_offload_proto_teardown(struct platform_device *pdev)
 {
-	mtk_tops_gretap_deinit();
+	mtk_tops_pptp_deinit();
 
 	mtk_tops_l2tpv2_deinit();
+
+	mtk_tops_gretap_deinit();
 }
 
 struct tops_tnl_type *mtk_tops_tnl_type_get_by_name(const char *name)
diff --git a/package-21.02/kernel/tops/tops.mk b/package-21.02/kernel/tops/tops.mk
index 09d2a98..e8ccd39 100644
--- a/package-21.02/kernel/tops/tops.mk
+++ b/package-21.02/kernel/tops/tops.mk
@@ -10,6 +10,7 @@
 	CONFIG_MTK_TOPS_GRETAP=$(CONFIG_MTK_TOPS_GRETAP) \
 	CONFIG_MTK_TOPS_L2TP=$(CONFIG_MTK_TOPS_L2TP) \
 	CONFIG_MTK_TOPS_L2TP_V2=$(CONFIG_MTK_TOPS_L2TP_V2) \
+	CONFIG_MTK_TOPS_PPTP=$(CONFIG_MTK_TOPS_PPTP) \
 	CONFIG_MTK_TOPS_SECURE_FW=$(CONFIG_MTK_TOPS_SECURE_FW)
 
 EXTRA_CFLAGS+= \
@@ -19,6 +20,7 @@
 EXTRA_CFLAGS+= \
 	-I$(LINUX_DIR)/drivers/net/ethernet/mediatek/ \
 	-I$(LINUX_DIR)/drivers/dma/ \
+	-I$(LINUX_DIR)/net/l2tp/ \
 	-I$(KERNEL_BUILD_DIR)/pce/inc/ \
 	-DCONFIG_TOPS_TNL_NUM=$(CONFIG_TOPS_TNL_NUM) \
 	-DCONFIG_TOPS_TNL_MAP_BIT=$(CONFIG_TOPS_TNL_MAP_BIT) \
diff --git a/target/linux/mediatek/patches-5.4/999-4500-mtk-ppp-offload-support.patch b/target/linux/mediatek/patches-5.4/999-4500-mtk-ppp-offload-support.patch
new file mode 100644
index 0000000..1409f9f
--- /dev/null
+++ b/target/linux/mediatek/patches-5.4/999-4500-mtk-ppp-offload-support.patch
@@ -0,0 +1,65 @@
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -296,6 +296,9 @@ static void unit_put(struct idr *p, int
+ static void *unit_find(struct idr *p, int n);
+ static void ppp_setup(struct net_device *dev);
+ 
++struct sock *ppp_netdev_get_sock(struct net_device *dev);
++EXPORT_SYMBOL(ppp_netdev_get_sock);
++
+ static const struct net_device_ops ppp_netdev_ops;
+ 
+ static struct class *ppp_class;
+@@ -1660,6 +1663,40 @@ ppp_send_frame(struct ppp *ppp, struct s
+ 	++ppp->dev->stats.tx_errors;
+ }
+ 
++struct sock *ppp_netdev_get_sock(struct net_device *dev)
++{
++	struct list_head *list;
++	struct channel *pch;
++	struct ppp *ppp;
++	struct sock *sk;
++
++	if (!dev)
++		return ERR_PTR(-EINVAL);
++
++	ppp = netdev_priv(dev);
++
++	list = &ppp->channels;
++	if (list_empty(list))
++		 /* nowhere to send the packet */
++		return ERR_PTR(-EINVAL);
++
++	if (ppp->flags & SC_MULTILINK)
++		/* not doing multilink: send it down the first channel */
++		return ERR_PTR(-EPERM);
++
++	list = list->next;
++	pch = list_entry(list, struct channel, clist);
++
++	spin_lock(&pch->downl);
++	if (pch->chan)
++		sk = (struct sock *)pch->chan->private;
++	else
++		sk = ERR_PTR(-EINVAL);
++	spin_unlock(&pch->downl);
++
++	return sk;
++}
++
+ /*
+  * Try to send the frame in xmit_pending.
+  * The caller should have the xmit path locked.
+--- a/include/linux/ppp_channel.h
++++ b/include/linux/ppp_channel.h
+@@ -75,6 +75,9 @@ extern int ppp_unit_number(struct ppp_ch
+ /* Get the device name associated with a channel, or NULL if none */
+ extern char *ppp_dev_name(struct ppp_channel *);
+ 
++/* Get the socket structure of a given ppp netdev */
++extern struct sock *ppp_netdev_get_sock(struct net_device *dev);
++
+ /*
+  * SMP locking notes:
+  * The channel code must ensure that when it calls ppp_unregister_channel,
diff --git a/target/linux/mediatek/patches-5.4/999-4500-mtk-l2tp-offload-support.patch b/target/linux/mediatek/patches-5.4/999-4501-mtk-l2tp-offload-support.patch
similarity index 100%
rename from target/linux/mediatek/patches-5.4/999-4500-mtk-l2tp-offload-support.patch
rename to target/linux/mediatek/patches-5.4/999-4501-mtk-l2tp-offload-support.patch
diff --git a/target/linux/mediatek/patches-5.4/999-4501-mtk-pptp-offload-support.patch b/target/linux/mediatek/patches-5.4/999-4501-mtk-pptp-offload-support.patch
new file mode 100644
index 0000000..ec218e1
--- /dev/null
+++ b/target/linux/mediatek/patches-5.4/999-4501-mtk-pptp-offload-support.patch
@@ -0,0 +1,100 @@
+--- a/drivers/net/ppp/pptp.c
++++ b/drivers/net/ppp/pptp.c
+@@ -33,6 +33,7 @@
+ #include <net/route.h>
+ #include <net/gre.h>
+ #include <net/pptp.h>
++#include <net/netfilter/nf_flow_table.h>
+ 
+ #include <linux/uaccess.h>
+ 
+@@ -40,6 +41,9 @@
+ 
+ #define MAX_CALLID 65535
+ 
++int (*mtk_pptp_seq_next)(u16 call_id, u32 *val) = NULL;
++EXPORT_SYMBOL(mtk_pptp_seq_next);
++
+ static DECLARE_BITMAP(callid_bitmap, MAX_CALLID + 1);
+ static struct pppox_sock __rcu **callid_sock;
+ 
+@@ -128,6 +132,26 @@ static void del_chan(struct pppox_sock *
+ 	spin_unlock(&chan_lock);
+ }
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int pptp_flow_offload_check(struct ppp_channel *chan,
++				   struct flow_offload_hw_path *path)
++{
++	struct sock *sk = (struct sock *)chan->private;
++	struct pppox_sock *po = pppox_sk(sk);
++
++	if (path->flags & FLOW_OFFLOAD_PATH_TNL)
++		return -EEXIST;
++
++	if (sk_pppox(po)->sk_state & PPPOX_DEAD)
++		return -EINVAL;
++
++	path->flags |= FLOW_OFFLOAD_PATH_TNL;
++	path->tnl_type = FLOW_OFFLOAD_TNL_PPTP;
++
++	return 0;
++}
++#endif /* IS_ENABLED(CONFIG_NF_FLOW_TABLE) */
++
+ static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+ {
+ 	struct sock *sk = (struct sock *) chan->private;
+@@ -140,6 +164,7 @@ static int pptp_xmit(struct ppp_channel
+ 	int islcp;
+ 	int len;
+ 	unsigned char *data;
++	u32 seq_sent_hw;
+ 	__u32 seq_recv;
+ 
+ 
+@@ -204,7 +229,14 @@ static int pptp_xmit(struct ppp_channel
+ 	hdr->gre_hd.protocol = GRE_PROTO_PPP;
+ 	hdr->call_id = htons(opt->dst_addr.call_id);
+ 
+-	hdr->seq = htonl(++opt->seq_sent);
++	if (mtk_pptp_seq_next && !mtk_pptp_seq_next(opt->dst_addr.call_id,
++						    &seq_sent_hw)) {
++		opt->seq_sent = seq_sent_hw;
++		hdr->seq = htonl(opt->seq_sent);
++	} else {
++		hdr->seq = htonl(++opt->seq_sent);
++	}
++
+ 	if (opt->ack_sent != seq_recv)	{
+ 		/* send ack with this message */
+ 		hdr->gre_hd.flags |= GRE_ACK;
+@@ -598,6 +630,9 @@ static int pptp_ppp_ioctl(struct ppp_cha
+ static const struct ppp_channel_ops pptp_chan_ops = {
+ 	.start_xmit = pptp_xmit,
+ 	.ioctl      = pptp_ppp_ioctl,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.flow_offload_check = pptp_flow_offload_check,
++#endif /* IS_ENABLED(CONFIG_NF_FLOW_TABLE) */
+ };
+ 
+ static struct proto pptp_sk_proto __read_mostly = {
+--- a/include/net/pptp.h
++++ b/include/net/pptp.h
+@@ -2,6 +2,8 @@
+ #ifndef _NET_PPTP_H
+ #define _NET_PPTP_H
+ 
++#include <net/gre.h>
++
+ #define PPP_LCP_ECHOREQ 0x09
+ #define PPP_LCP_ECHOREP 0x0A
+ #define SC_RCV_BITS     (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
+@@ -20,5 +22,7 @@ struct pptp_gre_header {
+ 	__be32 ack;
+ } __packed;
+ 
++/* symbol exported from linux kernel driver/net/ppp/pptp.c */
++extern int (*mtk_pptp_seq_next)(uint16_t call_id, uint32_t *val);
+ 
+ #endif