Merge "[rdkb][common][bsp][Refactor kernel part]"
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988-clkitg.dtsi b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988-clkitg.dtsi
index 65ec837..64f3c51 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988-clkitg.dtsi
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988-clkitg.dtsi
@@ -68,9 +68,9 @@
 			<&topckgen CK_TOP_CB_RTC_32P7K>,
 			<&topckgen CK_TOP_INFRA_F32K>,
 			<&topckgen CK_TOP_CKSQ_SRC>,
-			<&topckgen CK_TOP_NETSYS_2X>,
-			<&topckgen CK_TOP_NETSYS_GSW>,
-			<&topckgen CK_TOP_NETSYS_WED_MCU>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
 			<&topckgen CK_TOP_EIP197>,
 			<&topckgen CK_TOP_EMMC_250M>,
 			<&topckgen CK_TOP_EMMC_400M>,
@@ -94,13 +94,13 @@
 			<&topckgen CK_TOP_USB_REF>,
 			<&topckgen CK_TOP_USB_CK_P1>,
 			<&system_clk>,
-			<&topckgen CK_TOP_NETSYS_SEL>,
-			<&topckgen CK_TOP_NETSYS_500M_SEL>,
 			<&system_clk>,
 			<&system_clk>,
-			<&topckgen CK_TOP_ETH_GMII_SEL>,
-			<&topckgen CK_TOP_NETSYS_MCU_SEL>,
-			<&topckgen CK_TOP_NETSYS_PAO_2X_SEL>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
 			<&topckgen CK_TOP_EIP197_SEL>,
 			<&topckgen CK_TOP_AXI_INFRA_SEL>,
 			<&system_clk>,
@@ -135,14 +135,14 @@
 			<&topckgen CK_TOP_SGM_SBUS_0_SEL>,
 			<&system_clk>,
 			<&topckgen CK_TOP_SGM_SBUS_1_SEL>,
-			<&topckgen CK_TOP_XFI_PHY_0_XTAL_SEL>,
-			<&topckgen CK_TOP_XFI_PHY_1_XTAL_SEL>,
+			<&system_clk>,
+			<&system_clk>,
 			<&topckgen CK_TOP_SYSAXI_SEL>,
 			<&topckgen CK_TOP_SYSAPB_SEL>,
-			<&topckgen CK_TOP_ETH_REFCK_50M_SEL>,
-			<&topckgen CK_TOP_ETH_SYS_200M_SEL>,
-			<&topckgen CK_TOP_ETH_SYS_SEL>,
-			<&topckgen CK_TOP_ETH_XGMII_SEL>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
 			<&topckgen CK_TOP_DRAMC_SEL>,
 			<&topckgen CK_TOP_DRAMC_MD32_SEL>,
 			<&topckgen CK_TOP_INFRA_F26M_SEL>,
@@ -158,11 +158,11 @@
 			<&topckgen CK_TOP_DA_SELM_XTAL_SEL>,
 			<&topckgen CK_TOP_PEXTP_SEL>,
 			<&topckgen CK_TOP_MCUSYS_BACKUP_625M_SEL>,
-			<&topckgen CK_TOP_NETSYS_SYNC_250M_SEL>,
+			<&system_clk>,
 			<&topckgen CK_TOP_MACSEC_SEL>,
-			<&topckgen CK_TOP_NETSYS_PPEFB_250M_SEL>,
-			<&topckgen CK_TOP_NETSYS_WARP_SEL>,
-			<&topckgen CK_TOP_ETH_MII_SEL>,
+			<&system_clk>,
+			<&system_clk>,
+			<&system_clk>,
 			<&infracfg CK_INFRA_CK_F26M>,
 			<&system_clk>,
 			<&system_clk>,
@@ -317,9 +317,9 @@
 			<&system_clk>,
 			<&system_clk>,
 			<&system_clk>,
+			<&system_clk>,
-			<&ethwarp CK_ETHWARP_WOCPU2_EN>,
-			<&ethwarp CK_ETHWARP_WOCPU1_EN>,
-			<&ethwarp CK_ETHWARP_WOCPU0_EN>,
+			<&system_clk>,
+			<&system_clk>,
 			<&system_clk>,
 			<&system_clk>,
 			<&system_clk>,
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988.dtsi b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988.dtsi
index cec46ce..3dd999f 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988.dtsi
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988.dtsi
@@ -765,15 +765,42 @@
 			 <&sgmiisys0 CK_SGM0_RX_EN>,
 			 <&sgmiisys1 CK_SGM1_TX_EN>,
 			 <&sgmiisys1 CK_SGM1_RX_EN>,
+			 <&ethwarp CK_ETHWARP_WOCPU2_EN>,
+			 <&ethwarp CK_ETHWARP_WOCPU1_EN>,
+			 <&ethwarp CK_ETHWARP_WOCPU0_EN>,
 			 <&topckgen CK_TOP_USXGMII_SBUS_0_SEL>,
 			 <&topckgen CK_TOP_USXGMII_SBUS_1_SEL>,
 			 <&topckgen CK_TOP_SGM_0_SEL>,
-			 <&topckgen CK_TOP_SGM_1_SEL>;
+			 <&topckgen CK_TOP_SGM_1_SEL>,
+			 <&topckgen CK_TOP_XFI_PHY_0_XTAL_SEL>,
+			 <&topckgen CK_TOP_XFI_PHY_1_XTAL_SEL>,
+			 <&topckgen CK_TOP_ETH_GMII_SEL>,
+			 <&topckgen CK_TOP_ETH_REFCK_50M_SEL>,
+			 <&topckgen CK_TOP_ETH_SYS_200M_SEL>,
+			 <&topckgen CK_TOP_ETH_SYS_SEL>,
+			 <&topckgen CK_TOP_ETH_XGMII_SEL>,
+			 <&topckgen CK_TOP_ETH_MII_SEL>,
+			 <&topckgen CK_TOP_NETSYS_SEL>,
+			 <&topckgen CK_TOP_NETSYS_500M_SEL>,
+			 <&topckgen CK_TOP_NETSYS_PAO_2X_SEL>,
+			 <&topckgen CK_TOP_NETSYS_SYNC_250M_SEL>,
+			 <&topckgen CK_TOP_NETSYS_PPEFB_250M_SEL>,
+			 <&topckgen CK_TOP_NETSYS_WARP_SEL>;
 		clock-names = "xgp1", "xgp2", "xgp3", "fe", "gp2", "gp1",
 			      "gp3", "esw", "crypto", "sgmii_tx250m",
 			      "sgmii_rx250m", "sgmii2_tx250m", "sgmii2_rx250m",
-			      "usxgmii0_sel", "usxgmii1_sel",
-			      "sgm0_sel", "sgm1_sel";
+			      "ethwarp_wocpu2", "ethwarp_wocpu1",
+			      "ethwarp_wocpu0", "top_usxgmii0_sel",
+			      "top_usxgmii1_sel", "top_sgm0_sel",
+			      "top_sgm1_sel", "top_xfi_phy0_xtal_sel",
+			      "top_xfi_phy1_xtal_sel", "top_eth_gmii_sel",
+			      "top_eth_refck_50m_sel", "top_eth_sys_200m_sel",
+			      "top_eth_sys_sel", "top_eth_xgmii_sel",
+			      "top_eth_mii_sel", "top_netsys_sel",
+			      "top_netsys_500m_sel", "top_netsys_pao_2x_sel",
+			      "top_netsys_sync_250m_sel",
+			      "top_netsys_ppefb_250m_sel",
+			      "top_netsys_warp_sel";
 		assigned-clocks = <&topckgen CK_TOP_NETSYS_2X_SEL>,
 				  <&topckgen CK_TOP_NETSYS_GSW_SEL>,
 				  <&topckgen CK_TOP_USXGMII_SBUS_0_SEL>,
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index d9856f0..147f9b7 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -71,8 +71,15 @@
 	"xgp1", "xgp2", "xgp3", "crypto", "fe", "trgpll",
 	"sgmii_tx250m", "sgmii_rx250m", "sgmii_cdr_ref", "sgmii_cdr_fb",
 	"sgmii2_tx250m", "sgmii2_rx250m", "sgmii2_cdr_ref", "sgmii2_cdr_fb",
-	"sgmii_ck", "eth2pll", "wocpu0","wocpu1",
-	"usxgmii0_sel", "usxgmii1_sel", "sgm0_sel", "sgm1_sel",
+	"sgmii_ck", "eth2pll", "wocpu0", "wocpu1",
+	"ethwarp_wocpu2", "ethwarp_wocpu1", "ethwarp_wocpu0",
+	"top_usxgmii0_sel", "top_usxgmii1_sel", "top_sgm0_sel", "top_sgm1_sel",
+	"top_xfi_phy0_xtal_sel", "top_xfi_phy1_xtal_sel", "top_eth_gmii_sel",
+	"top_eth_refck_50m_sel", "top_eth_sys_200m_sel", "top_eth_sys_sel",
+	"top_eth_xgmii_sel", "top_eth_mii_sel", "top_netsys_sel",
+	"top_netsys_500m_sel", "top_netsys_pao_2x_sel",
+	"top_netsys_sync_250m_sel", "top_netsys_ppefb_250m_sel",
+	"top_netsys_warp_sel",
 };
 
 void mtk_w32(struct mtk_eth *eth, u32 val, unsigned reg)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
index dc02870..fe36102 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -1023,10 +1023,27 @@
 	MTK_CLK_ETH2PLL,
 	MTK_CLK_WOCPU0,
 	MTK_CLK_WOCPU1,
-	MTK_CLK_USXGMII0_SEL,
-	MTK_CLK_USXGMII1_SEL,
-	MTK_CLK_SGM0_SEL,
-	MTK_CLK_SGM1_SEL,
+	MTK_CLK_ETHWARP_WOCPU2,
+	MTK_CLK_ETHWARP_WOCPU1,
+	MTK_CLK_ETHWARP_WOCPU0,
+	MTK_CLK_TOP_USXGMII_SBUS_0_SEL,
+	MTK_CLK_TOP_USXGMII_SBUS_1_SEL,
+	MTK_CLK_TOP_SGM_0_SEL,
+	MTK_CLK_TOP_SGM_1_SEL,
+	MTK_CLK_TOP_XFI_PHY_0_XTAL_SEL,
+	MTK_CLK_TOP_XFI_PHY_1_XTAL_SEL,
+	MTK_CLK_TOP_ETH_GMII_SEL,
+	MTK_CLK_TOP_ETH_REFCK_50M_SEL,
+	MTK_CLK_TOP_ETH_SYS_200M_SEL,
+	MTK_CLK_TOP_ETH_SYS_SEL,
+	MTK_CLK_TOP_ETH_XGMII_SEL,
+	MTK_CLK_TOP_ETH_MII_SEL,
+	MTK_CLK_TOP_NETSYS_SEL,
+	MTK_CLK_TOP_NETSYS_500M_SEL,
+	MTK_CLK_TOP_NETSYS_PAO_2X_SEL,
+	MTK_CLK_TOP_NETSYS_SYNC_250M_SEL,
+	MTK_CLK_TOP_NETSYS_PPEFB_250M_SEL,
+	MTK_CLK_TOP_NETSYS_WARP_SEL,
 	MTK_CLK_MAX
 };
 
@@ -1090,10 +1107,27 @@
 				 BIT(MTK_CLK_SGMII_RX_250M) | \
 				 BIT(MTK_CLK_SGMII2_TX_250M) | \
 				 BIT(MTK_CLK_SGMII2_RX_250M) | \
-				 BIT(MTK_CLK_USXGMII0_SEL) | \
-				 BIT(MTK_CLK_USXGMII1_SEL) | \
-				 BIT(MTK_CLK_SGM0_SEL) | \
-				 BIT(MTK_CLK_SGM1_SEL))
+				 BIT(MTK_CLK_ETHWARP_WOCPU2) | \
+				 BIT(MTK_CLK_ETHWARP_WOCPU1) | \
+				 BIT(MTK_CLK_ETHWARP_WOCPU0) | \
+				 BIT(MTK_CLK_TOP_USXGMII_SBUS_0_SEL) | \
+				 BIT(MTK_CLK_TOP_USXGMII_SBUS_1_SEL) | \
+				 BIT(MTK_CLK_TOP_SGM_0_SEL) | \
+				 BIT(MTK_CLK_TOP_SGM_1_SEL) | \
+				 BIT(MTK_CLK_TOP_XFI_PHY_0_XTAL_SEL) | \
+				 BIT(MTK_CLK_TOP_XFI_PHY_1_XTAL_SEL) | \
+				 BIT(MTK_CLK_TOP_ETH_GMII_SEL) | \
+				 BIT(MTK_CLK_TOP_ETH_REFCK_50M_SEL) | \
+				 BIT(MTK_CLK_TOP_ETH_SYS_200M_SEL) | \
+				 BIT(MTK_CLK_TOP_ETH_SYS_SEL) | \
+				 BIT(MTK_CLK_TOP_ETH_XGMII_SEL) | \
+				 BIT(MTK_CLK_TOP_ETH_MII_SEL) | \
+				 BIT(MTK_CLK_TOP_NETSYS_SEL) | \
+				 BIT(MTK_CLK_TOP_NETSYS_500M_SEL) | \
+				 BIT(MTK_CLK_TOP_NETSYS_PAO_2X_SEL) | \
+				 BIT(MTK_CLK_TOP_NETSYS_SYNC_250M_SEL) | \
+				 BIT(MTK_CLK_TOP_NETSYS_PPEFB_250M_SEL) | \
+				 BIT(MTK_CLK_TOP_NETSYS_WARP_SEL))
 
 enum mtk_dev_state {
 	MTK_HW_INIT,
@@ -1452,7 +1486,7 @@
 struct mtk_soc_data {
 	u32             ana_rgc3;
 	u64		caps;
-	u32		required_clks;
+	u64		required_clks;
 	bool		required_pctl;
 	netdev_features_t hw_features;
 	bool		has_sram;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/640-netfilter-nf_flow_table-add-hardware-offload-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/640-netfilter-nf_flow_table-add-hardware-offload-support.patch
new file mode 100644
index 0000000..4d6a4ac
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/640-netfilter-nf_flow_table-add-hardware-offload-support.patch
@@ -0,0 +1,564 @@
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Thu, 11 Jan 2018 16:32:00 +0100
+Subject: [PATCH] netfilter: nf_flow_table: add hardware offload support
+
+This patch adds the infrastructure to offload flows to hardware, in case
+the nic/switch comes with built-in flow tables capabilities.
+
+If the hardware comes with no hardware flow tables or they have
+limitations in terms of features, the existing infrastructure falls back
+to the software flow table implementation.
+
+The software flow table garbage collector skips entries that resides in
+the hardware, so the hardware will be responsible for releasing this
+flow table entry too via flow_offload_dead().
+
+Hardware configuration, either to add or to delete entries, is done from
+the hardware offload workqueue, to ensure this is done from user context
+given that we may sleep when grabbing the mdio mutex.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+ create mode 100644 net/netfilter/nf_flow_table_hw.c
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -922,6 +922,13 @@ struct devlink;
+ struct tlsdev_ops;
+ 
+ 
++struct flow_offload;
++
++enum flow_offload_type {
++	FLOW_OFFLOAD_ADD	= 0,
++	FLOW_OFFLOAD_DEL,
++};
++
+ /*
+  * This structure defines the management hooks for network devices.
+  * The following hooks can be defined; unless noted otherwise, they are
+@@ -1154,6 +1161,10 @@ struct tlsdev_ops;
+  * int (*ndo_bridge_dellink)(struct net_device *dev, struct nlmsghdr *nlh,
+  *			     u16 flags);
+  *
++ * int (*ndo_flow_offload)(enum flow_offload_type type,
++ *			   struct flow_offload *flow);
++ *	Adds/deletes flow entry to/from net device flowtable.
++ *
+  * int (*ndo_change_carrier)(struct net_device *dev, bool new_carrier);
+  *	Called to change device carrier. Soft-devices (like dummy, team, etc)
+  *	which do not represent real hardware may define this to allow their
+@@ -1401,6 +1412,8 @@ struct net_device_ops {
+ 	int			(*ndo_bridge_dellink)(struct net_device *dev,
+ 						      struct nlmsghdr *nlh,
+ 						      u16 flags);
++	int			(*ndo_flow_offload)(enum flow_offload_type type,
++						    struct flow_offload *flow);
+ 	int			(*ndo_change_carrier)(struct net_device *dev,
+ 						      bool new_carrier);
+ 	int			(*ndo_get_phys_port_id)(struct net_device *dev,
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -21,11 +21,17 @@ struct nf_flowtable_type {
+ 	struct module			*owner;
+ };
+ 
++enum nf_flowtable_flags {
++	NF_FLOWTABLE_F_HW		= 0x1,
++};
++
+ struct nf_flowtable {
+ 	struct list_head		list;
+ 	struct rhashtable		rhashtable;
+ 	const struct nf_flowtable_type	*type;
++	u32				flags;
+ 	struct delayed_work		gc_work;
++	possible_net_t			ft_net;
+ };
+ 
+ enum flow_offload_tuple_dir {
+@@ -71,6 +77,7 @@ struct flow_offload_tuple_rhash {
+ #define FLOW_OFFLOAD_DNAT	0x2
+ #define FLOW_OFFLOAD_DYING	0x4
+ #define FLOW_OFFLOAD_TEARDOWN	0x8
++#define FLOW_OFFLOAD_HW		0x10
+ 
+ struct flow_offload {
+ 	struct flow_offload_tuple_rhash		tuplehash[FLOW_OFFLOAD_DIR_MAX];
+@@ -123,6 +130,22 @@ unsigned int nf_flow_offload_ip_hook(voi
+ unsigned int nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
+ 				       const struct nf_hook_state *state);
+ 
++void nf_flow_offload_hw_add(struct net *net, struct flow_offload *flow,
++			    struct nf_conn *ct);
++void nf_flow_offload_hw_del(struct net *net, struct flow_offload *flow);
++
++struct nf_flow_table_hw {
++	struct module	*owner;
++	void		(*add)(struct net *net, struct flow_offload *flow,
++			       struct nf_conn *ct);
++	void		(*del)(struct net *net, struct flow_offload *flow);
++};
++
++int nf_flow_table_hw_register(const struct nf_flow_table_hw *offload);
++void nf_flow_table_hw_unregister(const struct nf_flow_table_hw *offload);
++
++extern struct work_struct nf_flow_offload_hw_work;
++
+ #define MODULE_ALIAS_NF_FLOWTABLE(family)	\
+ 	MODULE_ALIAS("nf-flowtable-" __stringify(family))
+ 
+--- a/include/uapi/linux/netfilter/nf_tables.h
++++ b/include/uapi/linux/netfilter/nf_tables.h
+@@ -1516,6 +1516,7 @@ enum nft_object_attributes {
+  * @NFTA_FLOWTABLE_HOOK: netfilter hook configuration(NLA_U32)
+  * @NFTA_FLOWTABLE_USE: number of references to this flow table (NLA_U32)
+  * @NFTA_FLOWTABLE_HANDLE: object handle (NLA_U64)
++ * @NFTA_FLOWTABLE_FLAGS: flags (NLA_U32)
+  */
+ enum nft_flowtable_attributes {
+ 	NFTA_FLOWTABLE_UNSPEC,
+@@ -1525,6 +1526,7 @@ enum nft_flowtable_attributes {
+ 	NFTA_FLOWTABLE_USE,
+ 	NFTA_FLOWTABLE_HANDLE,
+ 	NFTA_FLOWTABLE_PAD,
++	NFTA_FLOWTABLE_FLAGS,
+ 	__NFTA_FLOWTABLE_MAX
+ };
+ #define NFTA_FLOWTABLE_MAX	(__NFTA_FLOWTABLE_MAX - 1)
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -710,6 +710,15 @@ config NF_FLOW_TABLE
+ 
+ 	  To compile it as a module, choose M here.
+ 
++config NF_FLOW_TABLE_HW
++	tristate "Netfilter flow table hardware offload module"
++	depends on NF_FLOW_TABLE
++	help
++	  This option adds hardware offload support for the flow table core
++	  infrastructure.
++
++	  To compile it as a module, choose M here.
++
+ config NETFILTER_XTABLES
+ 	tristate "Netfilter Xtables support (required for ip_tables)"
+ 	default m if NETFILTER_ADVANCED=n
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -123,6 +123,7 @@ obj-$(CONFIG_NF_FLOW_TABLE)	+= nf_flow_t
+ nf_flow_table-objs := nf_flow_table_core.o nf_flow_table_ip.o
+ 
+ obj-$(CONFIG_NF_FLOW_TABLE_INET) += nf_flow_table_inet.o
++obj-$(CONFIG_NF_FLOW_TABLE_HW)	+= nf_flow_table_hw.o
+ 
+ # generic X tables
+ obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -261,10 +261,16 @@ static inline bool nf_flow_has_expired(c
+ 	return nf_flow_timeout_delta(flow->timeout) <= 0;
+ }
+ 
++static inline bool nf_flow_in_hw(const struct flow_offload *flow)
++{
++	return flow->flags & FLOW_OFFLOAD_HW;
++}
++
+ static void flow_offload_del(struct nf_flowtable *flow_table,
+ 			     struct flow_offload *flow)
+ {
+ 	struct flow_offload_entry *e;
++	struct net *net = read_pnet(&flow_table->ft_net);
+ 
+ 	rhashtable_remove_fast(&flow_table->rhashtable,
+ 			       &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].node,
+@@ -284,6 +290,9 @@ static void flow_offload_del(struct nf_f
+ 	if (!(flow->flags & FLOW_OFFLOAD_TEARDOWN))
+ 		flow_offload_fixup_ct_state(e->ct);
+ 
++	if (nf_flow_in_hw(flow))
++		nf_flow_offload_hw_del(net, flow);
++
+ 	flow_offload_free(flow);
+ }
+ 
+@@ -374,6 +383,9 @@ static void nf_flow_offload_gc_step(stru
+ 	if (!teardown)
+ 		nf_ct_offload_timeout(flow);
+ 
++	if (nf_flow_in_hw(flow) && !teardown)
++		return;
++
+ 	if (nf_flow_has_expired(flow) || teardown)
+ 		flow_offload_del(flow_table, flow);
+ }
+@@ -503,10 +515,43 @@ int nf_flow_dnat_port(const struct flow_
+ }
+ EXPORT_SYMBOL_GPL(nf_flow_dnat_port);
+ 
++static const struct nf_flow_table_hw __rcu *nf_flow_table_hw_hook __read_mostly;
++
++static int nf_flow_offload_hw_init(struct nf_flowtable *flow_table)
++{
++	const struct nf_flow_table_hw *offload;
++
++	if (!rcu_access_pointer(nf_flow_table_hw_hook))
++		request_module("nf-flow-table-hw");
++
++	rcu_read_lock();
++	offload = rcu_dereference(nf_flow_table_hw_hook);
++	if (!offload)
++		goto err_no_hw_offload;
++
++	if (!try_module_get(offload->owner))
++		goto err_no_hw_offload;
++
++	rcu_read_unlock();
++
++	return 0;
++
++err_no_hw_offload:
++	rcu_read_unlock();
++
++	return -EOPNOTSUPP;
++}
++
+ int nf_flow_table_init(struct nf_flowtable *flowtable)
+ {
+ 	int err;
+ 
++	if (flowtable->flags & NF_FLOWTABLE_F_HW) {
++		err = nf_flow_offload_hw_init(flowtable);
++		if (err)
++			return err;
++	}
++
+ 	INIT_DEFERRABLE_WORK(&flowtable->gc_work, nf_flow_offload_work_gc);
+ 
+ 	err = rhashtable_init(&flowtable->rhashtable,
+@@ -547,6 +592,8 @@ static void nf_flow_table_iterate_cleanu
+ {
+ 	nf_flow_table_iterate(flowtable, nf_flow_table_do_cleanup, dev);
+ 	flush_delayed_work(&flowtable->gc_work);
++	if (flowtable->flags & NF_FLOWTABLE_F_HW)
++		flush_work(&nf_flow_offload_hw_work);
+ }
+ 
+ void nf_flow_table_cleanup(struct net_device *dev)
+@@ -560,6 +607,26 @@ void nf_flow_table_cleanup(struct net_de
+ }
+ EXPORT_SYMBOL_GPL(nf_flow_table_cleanup);
+ 
++struct work_struct nf_flow_offload_hw_work;
++EXPORT_SYMBOL_GPL(nf_flow_offload_hw_work);
++
++/* Give the hardware workqueue the chance to remove entries from hardware.*/
++static void nf_flow_offload_hw_free(struct nf_flowtable *flowtable)
++{
++	const struct nf_flow_table_hw *offload;
++
++	flush_work(&nf_flow_offload_hw_work);
++
++	rcu_read_lock();
++	offload = rcu_dereference(nf_flow_table_hw_hook);
++	if (!offload) {
++		rcu_read_unlock();
++		return;
++	}
++	module_put(offload->owner);
++	rcu_read_unlock();
++}
++
+ void nf_flow_table_free(struct nf_flowtable *flow_table)
+ {
+ 	mutex_lock(&flowtable_lock);
+@@ -569,9 +636,58 @@ void nf_flow_table_free(struct nf_flowta
+ 	nf_flow_table_iterate(flow_table, nf_flow_table_do_cleanup, NULL);
+ 	nf_flow_table_iterate(flow_table, nf_flow_offload_gc_step, flow_table);
+ 	rhashtable_destroy(&flow_table->rhashtable);
++	if (flow_table->flags & NF_FLOWTABLE_F_HW)
++		nf_flow_offload_hw_free(flow_table);
+ }
+ EXPORT_SYMBOL_GPL(nf_flow_table_free);
+ 
++/* Must be called from user context. */
++void nf_flow_offload_hw_add(struct net *net, struct flow_offload *flow,
++			    struct nf_conn *ct)
++{
++	const struct nf_flow_table_hw *offload;
++
++	rcu_read_lock();
++	offload = rcu_dereference(nf_flow_table_hw_hook);
++	if (offload)
++		offload->add(net, flow, ct);
++	rcu_read_unlock();
++}
++EXPORT_SYMBOL_GPL(nf_flow_offload_hw_add);
++
++/* Must be called from user context. */
++void nf_flow_offload_hw_del(struct net *net, struct flow_offload *flow)
++{
++	const struct nf_flow_table_hw *offload;
++
++	rcu_read_lock();
++	offload = rcu_dereference(nf_flow_table_hw_hook);
++	if (offload)
++		offload->del(net, flow);
++	rcu_read_unlock();
++}
++EXPORT_SYMBOL_GPL(nf_flow_offload_hw_del);
++
++int nf_flow_table_hw_register(const struct nf_flow_table_hw *offload)
++{
++	if (rcu_access_pointer(nf_flow_table_hw_hook))
++		return -EBUSY;
++
++	rcu_assign_pointer(nf_flow_table_hw_hook, offload);
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(nf_flow_table_hw_register);
++
++void nf_flow_table_hw_unregister(const struct nf_flow_table_hw *offload)
++{
++	WARN_ON(rcu_access_pointer(nf_flow_table_hw_hook) != offload);
++	rcu_assign_pointer(nf_flow_table_hw_hook, NULL);
++
++	synchronize_rcu();
++}
++EXPORT_SYMBOL_GPL(nf_flow_table_hw_unregister);
++
+ static int nf_flow_table_netdev_event(struct notifier_block *this,
+ 				      unsigned long event, void *ptr)
+ {
+--- /dev/null
++++ b/net/netfilter/nf_flow_table_hw.c
+@@ -0,0 +1,169 @@
++#include <linux/kernel.h>
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/netfilter.h>
++#include <linux/rhashtable.h>
++#include <linux/netdevice.h>
++#include <net/netfilter/nf_flow_table.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_core.h>
++#include <net/netfilter/nf_conntrack_tuple.h>
++
++static DEFINE_SPINLOCK(flow_offload_hw_pending_list_lock);
++static LIST_HEAD(flow_offload_hw_pending_list);
++
++static DEFINE_MUTEX(nf_flow_offload_hw_mutex);
++
++struct flow_offload_hw {
++	struct list_head	list;
++	enum flow_offload_type	type;
++	struct flow_offload	*flow;
++	struct nf_conn		*ct;
++	possible_net_t		flow_hw_net;
++};
++
++static int do_flow_offload_hw(struct net *net, struct flow_offload *flow,
++			      int type)
++{
++	struct net_device *indev;
++	int ret, ifindex;
++
++	ifindex = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.iifidx;
++	indev = dev_get_by_index(net, ifindex);
++	if (WARN_ON(!indev))
++		return 0;
++
++	mutex_lock(&nf_flow_offload_hw_mutex);
++	ret = indev->netdev_ops->ndo_flow_offload(type, flow);
++	mutex_unlock(&nf_flow_offload_hw_mutex);
++
++	dev_put(indev);
++
++	return ret;
++}
++
++static void flow_offload_hw_work_add(struct flow_offload_hw *offload)
++{
++	struct net *net;
++	int ret;
++
++	if (nf_ct_is_dying(offload->ct))
++		return;
++
++	net = read_pnet(&offload->flow_hw_net);
++	ret = do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_ADD);
++	if (ret >= 0)
++		offload->flow->flags |= FLOW_OFFLOAD_HW;
++}
++
++static void flow_offload_hw_work_del(struct flow_offload_hw *offload)
++{
++	struct net *net = read_pnet(&offload->flow_hw_net);
++
++	do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_DEL);
++}
++
++static void flow_offload_hw_work(struct work_struct *work)
++{
++	struct flow_offload_hw *offload, *next;
++	LIST_HEAD(hw_offload_pending);
++
++	spin_lock_bh(&flow_offload_hw_pending_list_lock);
++	list_replace_init(&flow_offload_hw_pending_list, &hw_offload_pending);
++	spin_unlock_bh(&flow_offload_hw_pending_list_lock);
++
++	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
++		switch (offload->type) {
++		case FLOW_OFFLOAD_ADD:
++			flow_offload_hw_work_add(offload);
++			break;
++		case FLOW_OFFLOAD_DEL:
++			flow_offload_hw_work_del(offload);
++			break;
++		}
++		if (offload->ct)
++			nf_conntrack_put(&offload->ct->ct_general);
++		list_del(&offload->list);
++		kfree(offload);
++	}
++}
++
++static void flow_offload_queue_work(struct flow_offload_hw *offload)
++{
++	spin_lock_bh(&flow_offload_hw_pending_list_lock);
++	list_add_tail(&offload->list, &flow_offload_hw_pending_list);
++	spin_unlock_bh(&flow_offload_hw_pending_list_lock);
++
++	schedule_work(&nf_flow_offload_hw_work);
++}
++
++static void flow_offload_hw_add(struct net *net, struct flow_offload *flow,
++				struct nf_conn *ct)
++{
++	struct flow_offload_hw *offload;
++
++	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
++	if (!offload)
++		return;
++
++	nf_conntrack_get(&ct->ct_general);
++	offload->type = FLOW_OFFLOAD_ADD;
++	offload->ct = ct;
++	offload->flow = flow;
++	write_pnet(&offload->flow_hw_net, net);
++
++	flow_offload_queue_work(offload);
++}
++
++static void flow_offload_hw_del(struct net *net, struct flow_offload *flow)
++{
++	struct flow_offload_hw *offload;
++
++	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
++	if (!offload)
++		return;
++
++	offload->type = FLOW_OFFLOAD_DEL;
++	offload->ct = NULL;
++	offload->flow = flow;
++	write_pnet(&offload->flow_hw_net, net);
++
++	flow_offload_queue_work(offload);
++}
++
++static const struct nf_flow_table_hw flow_offload_hw = {
++	.add	= flow_offload_hw_add,
++	.del	= flow_offload_hw_del,
++	.owner	= THIS_MODULE,
++};
++
++static int __init nf_flow_table_hw_module_init(void)
++{
++	INIT_WORK(&nf_flow_offload_hw_work, flow_offload_hw_work);
++	nf_flow_table_hw_register(&flow_offload_hw);
++
++	return 0;
++}
++
++static void __exit nf_flow_table_hw_module_exit(void)
++{
++	struct flow_offload_hw *offload, *next;
++	LIST_HEAD(hw_offload_pending);
++
++	nf_flow_table_hw_unregister(&flow_offload_hw);
++	cancel_work_sync(&nf_flow_offload_hw_work);
++
++	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
++		if (offload->ct)
++			nf_conntrack_put(&offload->ct->ct_general);
++		list_del(&offload->list);
++		kfree(offload);
++	}
++}
++
++module_init(nf_flow_table_hw_module_init);
++module_exit(nf_flow_table_hw_module_exit);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
++MODULE_ALIAS("nf-flow-table-hw");
+--- a/net/netfilter/nf_tables_api.c
++++ b/net/netfilter/nf_tables_api.c
+@@ -5758,6 +5758,13 @@ static int nf_tables_flowtable_parse_hoo
+ 	if (err < 0)
+ 		return err;
+ 
++	for (i = 0; i < n; i++) {
++		if (flowtable->data.flags & NF_FLOWTABLE_F_HW &&
++		    !dev_array[i]->netdev_ops->ndo_flow_offload) {
++			return -EOPNOTSUPP;
++		}
++	}
++
+ 	ops = kcalloc(n, sizeof(struct nf_hook_ops), GFP_KERNEL);
+ 	if (!ops)
+ 		return -ENOMEM;
+@@ -5888,10 +5895,19 @@ static int nf_tables_newflowtable(struct
+ 	}
+ 
+ 	flowtable->data.type = type;
++	write_pnet(&flowtable->data.ft_net, net);
++
+ 	err = type->init(&flowtable->data);
+ 	if (err < 0)
+ 		goto err3;
+ 
++	if (nla[NFTA_FLOWTABLE_FLAGS]) {
++		flowtable->data.flags =
++			ntohl(nla_get_be32(nla[NFTA_FLOWTABLE_FLAGS]));
++		if (flowtable->data.flags & ~NF_FLOWTABLE_F_HW)
++			goto err4;
++	}
++
+ 	err = nf_tables_flowtable_parse_hook(&ctx, nla[NFTA_FLOWTABLE_HOOK],
+ 					     flowtable);
+ 	if (err < 0)
+@@ -6017,7 +6033,8 @@ static int nf_tables_fill_flowtable_info
+ 	    nla_put_string(skb, NFTA_FLOWTABLE_NAME, flowtable->name) ||
+ 	    nla_put_be32(skb, NFTA_FLOWTABLE_USE, htonl(flowtable->use)) ||
+ 	    nla_put_be64(skb, NFTA_FLOWTABLE_HANDLE, cpu_to_be64(flowtable->handle),
+-			 NFTA_FLOWTABLE_PAD))
++			 NFTA_FLOWTABLE_PAD) ||
++	    nla_put_be32(skb, NFTA_FLOWTABLE_FLAGS, htonl(flowtable->data.flags)))
+ 		goto nla_put_failure;
+ 
+ 	nest = nla_nest_start_noflag(skb, NFTA_FLOWTABLE_HOOK);
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -128,6 +128,9 @@ static void nft_flow_offload_eval(const
+ 	if (ret < 0)
+ 		goto err_flow_add;
+ 
++	if (flowtable->flags & NF_FLOWTABLE_F_HW)
++		nf_flow_offload_hw_add(nft_net(pkt), flow, ct);
++
+ 	dst_release(route.tuple[!dir].dst);
+ 	return;
+ 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch
new file mode 100644
index 0000000..ae9f7f0
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/641-netfilter-nf_flow_table-support-hw-offload-through-v.patch
@@ -0,0 +1,306 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 15 Mar 2018 20:46:31 +0100
+Subject: [PATCH] netfilter: nf_flow_table: support hw offload through
+ virtual interfaces
+
+There are hardware offload devices that support offloading VLANs and
+PPPoE devices. Additionally, it is useful to be able to offload packets
+routed through bridge interfaces as well.
+Add support for finding the path to the offload device through these
+virtual interfaces, while collecting useful parameters for the offload
+device, like VLAN ID/protocol, PPPoE session and Ethernet MAC address.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/netdevice.h
++++ b/include/linux/netdevice.h
+@@ -923,6 +923,7 @@ struct tlsdev_ops;
+ 
+ 
+ struct flow_offload;
++struct flow_offload_hw_path;
+ 
+ enum flow_offload_type {
+ 	FLOW_OFFLOAD_ADD	= 0,
+@@ -1161,8 +1162,15 @@ enum flow_offload_type {
+  * int (*ndo_bridge_dellink)(struct net_device *dev, struct nlmsghdr *nlh,
+  *			     u16 flags);
+  *
++ * int (*ndo_flow_offload_check)(struct flow_offload_hw_path *path);
++ *	For virtual devices like bridges, vlan, and pppoe, fill in the
++ *	underlying network device that can be used for offloading connections.
++ *	Return an error if offloading is not supported.
++ *
+  * int (*ndo_flow_offload)(enum flow_offload_type type,
+- *			   struct flow_offload *flow);
++ *			   struct flow_offload *flow,
++ *			   struct flow_offload_hw_path *src,
++ *			   struct flow_offload_hw_path *dest);
+  *	Adds/deletes flow entry to/from net device flowtable.
+  *
+  * int (*ndo_change_carrier)(struct net_device *dev, bool new_carrier);
+@@ -1412,8 +1420,11 @@ struct net_device_ops {
+ 	int			(*ndo_bridge_dellink)(struct net_device *dev,
+ 						      struct nlmsghdr *nlh,
+ 						      u16 flags);
++	int			(*ndo_flow_offload_check)(struct flow_offload_hw_path *path);
+ 	int			(*ndo_flow_offload)(enum flow_offload_type type,
+-						    struct flow_offload *flow);
++						    struct flow_offload *flow,
++						    struct flow_offload_hw_path *src,
++						    struct flow_offload_hw_path *dest);
+ 	int			(*ndo_change_carrier)(struct net_device *dev,
+ 						      bool new_carrier);
+ 	int			(*ndo_get_phys_port_id)(struct net_device *dev,
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -88,6 +88,21 @@ struct flow_offload {
+ 	};
+ };
+ 
++#define FLOW_OFFLOAD_PATH_ETHERNET	BIT(0)
++#define FLOW_OFFLOAD_PATH_VLAN		BIT(1)
++#define FLOW_OFFLOAD_PATH_PPPOE		BIT(2)
++
++struct flow_offload_hw_path {
++	struct net_device *dev;
++	u32 flags;
++
++	u8 eth_src[ETH_ALEN];
++	u8 eth_dest[ETH_ALEN];
++	u16 vlan_proto;
++	u16 vlan_id;
++	u16 pppoe_sid;
++};
++
+ #define NF_FLOW_TIMEOUT (30 * HZ)
+ 
+ struct nf_flow_route {
+--- a/net/netfilter/nf_flow_table_hw.c
++++ b/net/netfilter/nf_flow_table_hw.c
+@@ -19,48 +19,77 @@ struct flow_offload_hw {
+ 	enum flow_offload_type	type;
+ 	struct flow_offload	*flow;
+ 	struct nf_conn		*ct;
+-	possible_net_t		flow_hw_net;
++
++	struct flow_offload_hw_path src;
++	struct flow_offload_hw_path dest;
+ };
+ 
+-static int do_flow_offload_hw(struct net *net, struct flow_offload *flow,
+-			      int type)
++static void flow_offload_check_ethernet(struct flow_offload_tuple *tuple,
++					struct dst_entry *dst,
++					struct flow_offload_hw_path *path)
+ {
+-	struct net_device *indev;
+-	int ret, ifindex;
++	struct net_device *dev = path->dev;
++	struct neighbour *n;
+ 
+-	ifindex = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.iifidx;
+-	indev = dev_get_by_index(net, ifindex);
+-	if (WARN_ON(!indev))
+-		return 0;
+-
+-	mutex_lock(&nf_flow_offload_hw_mutex);
+-	ret = indev->netdev_ops->ndo_flow_offload(type, flow);
+-	mutex_unlock(&nf_flow_offload_hw_mutex);
++	if (dev->type != ARPHRD_ETHER)
++		return;
+ 
+-	dev_put(indev);
++	memcpy(path->eth_src, path->dev->dev_addr, ETH_ALEN);
++	n = dst_neigh_lookup(dst, &tuple->src_v4);
++	if (!n)
++		return;
+ 
+-	return ret;
++	memcpy(path->eth_dest, n->ha, ETH_ALEN);
++	path->flags |= FLOW_OFFLOAD_PATH_ETHERNET;
++	neigh_release(n);
+ }
+ 
+-static void flow_offload_hw_work_add(struct flow_offload_hw *offload)
++static int flow_offload_check_path(struct net *net,
++				   struct flow_offload_tuple *tuple,
++				   struct dst_entry *dst,
++				   struct flow_offload_hw_path *path)
+ {
+-	struct net *net;
+-	int ret;
++	struct net_device *dev;
+ 
+-	if (nf_ct_is_dying(offload->ct))
+-		return;
++	dev = dev_get_by_index_rcu(net, tuple->iifidx);
++	if (!dev)
++		return -ENOENT;
++
++	path->dev = dev;
++	flow_offload_check_ethernet(tuple, dst, path);
+ 
+-	net = read_pnet(&offload->flow_hw_net);
+-	ret = do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_ADD);
+-	if (ret >= 0)
+-		offload->flow->flags |= FLOW_OFFLOAD_HW;
++	if (dev->netdev_ops->ndo_flow_offload_check)
++		return dev->netdev_ops->ndo_flow_offload_check(path);
++
++	return 0;
+ }
+ 
+-static void flow_offload_hw_work_del(struct flow_offload_hw *offload)
++static int do_flow_offload_hw(struct flow_offload_hw *offload)
+ {
+-	struct net *net = read_pnet(&offload->flow_hw_net);
++	struct net_device *src_dev = offload->src.dev;
++	struct net_device *dest_dev = offload->dest.dev;
++	int ret;
++
++	ret = src_dev->netdev_ops->ndo_flow_offload(offload->type,
++						    offload->flow,
++						    &offload->src,
++						    &offload->dest);
++
++	/* restore devices in case the driver mangled them */
++	offload->src.dev = src_dev;
++	offload->dest.dev = dest_dev;
+ 
+-	do_flow_offload_hw(net, offload->flow, FLOW_OFFLOAD_DEL);
++	return ret;
++}
++
++static void flow_offload_hw_free(struct flow_offload_hw *offload)
++{
++	dev_put(offload->src.dev);
++	dev_put(offload->dest.dev);
++	if (offload->ct)
++		nf_conntrack_put(&offload->ct->ct_general);
++	list_del(&offload->list);
++	kfree(offload);
+ }
+ 
+ static void flow_offload_hw_work(struct work_struct *work)
+@@ -73,18 +102,22 @@ static void flow_offload_hw_work(struct
+ 	spin_unlock_bh(&flow_offload_hw_pending_list_lock);
+ 
+ 	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
++		mutex_lock(&nf_flow_offload_hw_mutex);
+ 		switch (offload->type) {
+ 		case FLOW_OFFLOAD_ADD:
+-			flow_offload_hw_work_add(offload);
++			if (nf_ct_is_dying(offload->ct))
++				break;
++
++			if (do_flow_offload_hw(offload) >= 0)
++				offload->flow->flags |= FLOW_OFFLOAD_HW;
+ 			break;
+ 		case FLOW_OFFLOAD_DEL:
+-			flow_offload_hw_work_del(offload);
++			do_flow_offload_hw(offload);
+ 			break;
+ 		}
+-		if (offload->ct)
+-			nf_conntrack_put(&offload->ct->ct_general);
+-		list_del(&offload->list);
+-		kfree(offload);
++		mutex_unlock(&nf_flow_offload_hw_mutex);
++
++		flow_offload_hw_free(offload);
+ 	}
+ }
+ 
+@@ -97,20 +130,56 @@ static void flow_offload_queue_work(stru
+ 	schedule_work(&nf_flow_offload_hw_work);
+ }
+ 
++static struct flow_offload_hw *
++flow_offload_hw_prepare(struct net *net, struct flow_offload *flow)
++{
++	struct flow_offload_hw_path src = {};
++	struct flow_offload_hw_path dest = {};
++	struct flow_offload_tuple *tuple_s, *tuple_d;
++	struct flow_offload_hw *offload = NULL;
++
++	rcu_read_lock_bh();
++
++	tuple_s = &flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple;
++	tuple_d = &flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple;
++
++	if (flow_offload_check_path(net, tuple_s, tuple_d->dst_cache, &src))
++		goto out;
++
++	if (flow_offload_check_path(net, tuple_d, tuple_s->dst_cache, &dest))
++		goto out;
++
++	if (!src.dev->netdev_ops->ndo_flow_offload)
++		goto out;
++
++	offload = kzalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
++	if (!offload)
++		goto out;
++
++	dev_hold(src.dev);
++	dev_hold(dest.dev);
++	offload->src = src;
++	offload->dest = dest;
++	offload->flow = flow;
++
++out:
++	rcu_read_unlock_bh();
++
++	return offload;
++}
++
+ static void flow_offload_hw_add(struct net *net, struct flow_offload *flow,
+ 				struct nf_conn *ct)
+ {
+ 	struct flow_offload_hw *offload;
+ 
+-	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
++	offload = flow_offload_hw_prepare(net, flow);
+ 	if (!offload)
+ 		return;
+ 
+ 	nf_conntrack_get(&ct->ct_general);
+ 	offload->type = FLOW_OFFLOAD_ADD;
+ 	offload->ct = ct;
+-	offload->flow = flow;
+-	write_pnet(&offload->flow_hw_net, net);
+ 
+ 	flow_offload_queue_work(offload);
+ }
+@@ -119,14 +188,11 @@ static void flow_offload_hw_del(struct n
+ {
+ 	struct flow_offload_hw *offload;
+ 
+-	offload = kmalloc(sizeof(struct flow_offload_hw), GFP_ATOMIC);
++	offload = flow_offload_hw_prepare(net, flow);
+ 	if (!offload)
+ 		return;
+ 
+ 	offload->type = FLOW_OFFLOAD_DEL;
+-	offload->ct = NULL;
+-	offload->flow = flow;
+-	write_pnet(&offload->flow_hw_net, net);
+ 
+ 	flow_offload_queue_work(offload);
+ }
+@@ -153,12 +219,8 @@ static void __exit nf_flow_table_hw_modu
+ 	nf_flow_table_hw_unregister(&flow_offload_hw);
+ 	cancel_work_sync(&nf_flow_offload_hw_work);
+ 
+-	list_for_each_entry_safe(offload, next, &hw_offload_pending, list) {
+-		if (offload->ct)
+-			nf_conntrack_put(&offload->ct->ct_general);
+-		list_del(&offload->list);
+-		kfree(offload);
+-	}
++	list_for_each_entry_safe(offload, next, &hw_offload_pending, list)
++		flow_offload_hw_free(offload);
+ }
+ 
+ module_init(nf_flow_table_hw_module_init);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/642-net-8021q-support-hardware-flow-table-offload.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/642-net-8021q-support-hardware-flow-table-offload.patch
new file mode 100644
index 0000000..cfcc28a
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/642-net-8021q-support-hardware-flow-table-offload.patch
@@ -0,0 +1,61 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 15 Mar 2018 20:49:58 +0100
+Subject: [PATCH] net: 8021q: support hardware flow table offload
+
+Add the VLAN ID and protocol information
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/8021q/vlan_dev.c
++++ b/net/8021q/vlan_dev.c
+@@ -27,6 +27,11 @@
+ #include <linux/phy.h>
+ #include <net/arp.h>
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
++
+ #include "vlan.h"
+ #include "vlanproc.h"
+ #include <linux/if_vlan.h>
+@@ -747,6 +752,27 @@ static int vlan_dev_get_iflink(const str
+ 	return real_dev->ifindex;
+ }
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int vlan_dev_flow_offload_check(struct flow_offload_hw_path *path)
++{
++	struct net_device *dev = path->dev;
++	struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
++
++	if (path->flags & FLOW_OFFLOAD_PATH_VLAN)
++		return -EEXIST;
++
++	path->flags |= FLOW_OFFLOAD_PATH_VLAN;
++	path->vlan_proto = vlan->vlan_proto;
++	path->vlan_id = vlan->vlan_id;
++	path->dev = vlan->real_dev;
++
++	if (vlan->real_dev->netdev_ops->ndo_flow_offload_check)
++		return vlan->real_dev->netdev_ops->ndo_flow_offload_check(path);
++
++	return 0;
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ static const struct ethtool_ops vlan_ethtool_ops = {
+ 	.get_link_ksettings	= vlan_ethtool_get_link_ksettings,
+ 	.get_drvinfo	        = vlan_ethtool_get_drvinfo,
+@@ -785,6 +811,9 @@ static const struct net_device_ops vlan_
+ #endif
+ 	.ndo_fix_features	= vlan_dev_fix_features,
+ 	.ndo_get_iflink		= vlan_dev_get_iflink,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.ndo_flow_offload_check = vlan_dev_flow_offload_check,
++#endif
+ };
+ 
+ static void vlan_dev_free(struct net_device *dev)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/643-net-bridge-support-hardware-flow-table-offload.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/643-net-bridge-support-hardware-flow-table-offload.patch
new file mode 100644
index 0000000..d47482d
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/643-net-bridge-support-hardware-flow-table-offload.patch
@@ -0,0 +1,61 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 15 Mar 2018 20:50:37 +0100
+Subject: [PATCH] net: bridge: support hardware flow table offload
+
+Look up the real device and pass it on
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/net/bridge/br_device.c
++++ b/net/bridge/br_device.c
+@@ -14,6 +14,10 @@
+ #include <linux/ethtool.h>
+ #include <linux/list.h>
+ #include <linux/netfilter_bridge.h>
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
+ 
+ #include <linux/uaccess.h>
+ #include "br_private.h"
+@@ -382,6 +386,28 @@ static const struct ethtool_ops br_ethto
+ 	.get_link	= ethtool_op_get_link,
+ };
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int br_flow_offload_check(struct flow_offload_hw_path *path)
++{
++	struct net_device *dev = path->dev;
++	struct net_bridge *br = netdev_priv(dev);
++	struct net_bridge_fdb_entry *dst;
++
++	if (!(path->flags & FLOW_OFFLOAD_PATH_ETHERNET))
++		return -EINVAL;
++
++	dst = br_fdb_find_rcu(br, path->eth_dest, path->vlan_id);
++	if (!dst || !dst->dst)
++		return -ENOENT;
++
++	path->dev = dst->dst->dev;
++	if (path->dev->netdev_ops->ndo_flow_offload_check)
++		return path->dev->netdev_ops->ndo_flow_offload_check(path);
++
++	return 0;
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ static const struct net_device_ops br_netdev_ops = {
+ 	.ndo_open		 = br_dev_open,
+ 	.ndo_stop		 = br_dev_stop,
+@@ -410,6 +436,9 @@ static const struct net_device_ops br_ne
+ 	.ndo_bridge_setlink	 = br_setlink,
+ 	.ndo_bridge_dellink	 = br_dellink,
+ 	.ndo_features_check	 = passthru_features_check,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.ndo_flow_offload_check	 = br_flow_offload_check,
++#endif
+ };
+ 
+ static struct device_type br_type = {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/644-net-pppoe-support-hardware-flow-table-offload.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/644-net-pppoe-support-hardware-flow-table-offload.patch
new file mode 100644
index 0000000..b09b3bc
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/644-net-pppoe-support-hardware-flow-table-offload.patch
@@ -0,0 +1,125 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 15 Mar 2018 21:15:00 +0100
+Subject: [PATCH] net: pppoe: support hardware flow table offload
+
+Pass on the PPPoE session ID and the remote MAC address
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/drivers/net/ppp/ppp_generic.c
++++ b/drivers/net/ppp/ppp_generic.c
+@@ -53,6 +53,11 @@
+ #include <net/net_namespace.h>
+ #include <net/netns/generic.h>
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
++
+ #define PPP_VERSION	"2.4.2"
+ 
+ /*
+@@ -1378,12 +1383,37 @@ static void ppp_dev_priv_destructor(stru
+ 		ppp_destroy_interface(ppp);
+ }
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int ppp_flow_offload_check(struct flow_offload_hw_path *path)
++{
++	struct ppp *ppp = netdev_priv(path->dev);
++	struct ppp_channel *chan;
++	struct channel *pch;
++
++	if (ppp->flags & SC_MULTILINK)
++		return -EOPNOTSUPP;
++
++	if (list_empty(&ppp->channels))
++		return -ENODEV;
++
++	pch = list_first_entry(&ppp->channels, struct channel, clist);
++	chan = pch->chan;
++	if (!chan->ops->flow_offload_check)
++		return -EOPNOTSUPP;
++
++	return chan->ops->flow_offload_check(chan, path);
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ static const struct net_device_ops ppp_netdev_ops = {
+ 	.ndo_init	 = ppp_dev_init,
+ 	.ndo_uninit      = ppp_dev_uninit,
+ 	.ndo_start_xmit  = ppp_start_xmit,
+ 	.ndo_do_ioctl    = ppp_net_ioctl,
+ 	.ndo_get_stats64 = ppp_get_stats64,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.ndo_flow_offload_check = ppp_flow_offload_check,
++#endif
+ };
+ 
+ static struct device_type ppp_type = {
+--- a/drivers/net/ppp/pppoe.c
++++ b/drivers/net/ppp/pppoe.c
+@@ -73,6 +73,11 @@
+ #include <linux/proc_fs.h>
+ #include <linux/seq_file.h>
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
++
+ #include <linux/nsproxy.h>
+ #include <net/net_namespace.h>
+ #include <net/netns/generic.h>
+@@ -974,8 +979,36 @@ static int pppoe_xmit(struct ppp_channel
+ 	return __pppoe_xmit(sk, skb);
+ }
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int pppoe_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);
++	struct net_device *dev = po->pppoe_dev;
++
++	if (sock_flag(sk, SOCK_DEAD) ||
++	    !(sk->sk_state & PPPOX_CONNECTED) || !dev)
++		return -ENODEV;
++
++	path->dev = po->pppoe_dev;
++	path->flags |= FLOW_OFFLOAD_PATH_PPPOE;
++	memcpy(path->eth_src, po->pppoe_dev->dev_addr, ETH_ALEN);
++	memcpy(path->eth_dest, po->pppoe_pa.remote, ETH_ALEN);
++	path->pppoe_sid = be16_to_cpu(po->num);
++
++	if (path->dev->netdev_ops->ndo_flow_offload_check)
++		return path->dev->netdev_ops->ndo_flow_offload_check(path);
++
++	return 0;
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ static const struct ppp_channel_ops pppoe_chan_ops = {
+ 	.start_xmit = pppoe_xmit,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.flow_offload_check = pppoe_flow_offload_check,
++#endif
+ };
+ 
+ static int pppoe_recvmsg(struct socket *sock, struct msghdr *m,
+--- a/include/linux/ppp_channel.h
++++ b/include/linux/ppp_channel.h
+@@ -28,6 +28,10 @@ struct ppp_channel_ops {
+ 	int	(*start_xmit)(struct ppp_channel *, struct sk_buff *);
+ 	/* Handle an ioctl call that has come in via /dev/ppp. */
+ 	int	(*ioctl)(struct ppp_channel *, unsigned int, unsigned long);
++
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	int	(*flow_offload_check)(struct ppp_channel *, struct flow_offload_hw_path *);
++#endif
+ };
+ 
+ struct ppp_channel {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch
new file mode 100644
index 0000000..f4efbcd
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/645-netfilter-nf_flow_table-rework-hardware-offload-time.patch
@@ -0,0 +1,37 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Sun, 25 Mar 2018 21:10:55 +0200
+Subject: [PATCH] netfilter: nf_flow_table: rework hardware offload timeout
+ handling
+
+Some offload implementations send keepalive packets + explicit
+notifications of TCP FIN/RST packets. In this case it is more convenient
+to simply let the driver update flow->timeout handling and use the
+regular flow offload gc step.
+
+For drivers that manage their own lifetime, a separate flag can be set
+to avoid gc timeouts.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -78,6 +78,7 @@ struct flow_offload_tuple_rhash {
+ #define FLOW_OFFLOAD_DYING	0x4
+ #define FLOW_OFFLOAD_TEARDOWN	0x8
+ #define FLOW_OFFLOAD_HW		0x10
++#define FLOW_OFFLOAD_KEEP	0x20
+ 
+ struct flow_offload {
+ 	struct flow_offload_tuple_rhash		tuplehash[FLOW_OFFLOAD_DIR_MAX];
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -383,7 +383,7 @@ static void nf_flow_offload_gc_step(stru
+ 	if (!teardown)
+ 		nf_ct_offload_timeout(flow);
+ 
+-	if (nf_flow_in_hw(flow) && !teardown)
++	if ((flow->flags & FLOW_OFFLOAD_KEEP) && !teardown)
+ 		return;
+ 
+ 	if (nf_flow_has_expired(flow) || teardown)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/646-netfilter-nf_flow_table-rework-private-driver-data.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/646-netfilter-nf_flow_table-rework-private-driver-data.patch
new file mode 100644
index 0000000..235f2a3
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/646-netfilter-nf_flow_table-rework-private-driver-data.patch
@@ -0,0 +1,25 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 27 Apr 2018 14:42:14 +0200
+Subject: [PATCH] netfilter: nf_flow_table: rework private driver data
+
+Move the timeout out of the union, since it can be shared between the
+driver and the stack. Add a private pointer that the driver can use to
+point to its own data structures
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -83,9 +83,10 @@ struct flow_offload_tuple_rhash {
+ struct flow_offload {
+ 	struct flow_offload_tuple_rhash		tuplehash[FLOW_OFFLOAD_DIR_MAX];
+ 	u32					flags;
++	u32					timeout;
+ 	union {
+ 		/* Your private driver data here. */
+-		u32		timeout;
++		void *priv;
+ 	};
+ };
+ 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/647-net-dsa-support-hardware-flow-table-offload.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/647-net-dsa-support-hardware-flow-table-offload.patch
new file mode 100644
index 0000000..ff41a17
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/647-net-dsa-support-hardware-flow-table-offload.patch
@@ -0,0 +1,78 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Thu, 17 Sep 2020 18:41:23 +0200
+Subject: [PATCH] net: dsa: support hardware flow table offload
+
+Look up the master device and the port id
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -93,6 +93,7 @@ struct flow_offload {
+ #define FLOW_OFFLOAD_PATH_ETHERNET	BIT(0)
+ #define FLOW_OFFLOAD_PATH_VLAN		BIT(1)
+ #define FLOW_OFFLOAD_PATH_PPPOE		BIT(2)
++#define FLOW_OFFLOAD_PATH_DSA		BIT(3)
+ 
+ struct flow_offload_hw_path {
+ 	struct net_device *dev;
+@@ -103,6 +104,7 @@ struct flow_offload_hw_path {
+ 	u16 vlan_proto;
+ 	u16 vlan_id;
+ 	u16 pppoe_sid;
++	u16 dsa_port;
+ };
+ 
+ #define NF_FLOW_TIMEOUT (30 * HZ)
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -19,6 +19,10 @@
+ #include <linux/if_bridge.h>
+ #include <linux/netpoll.h>
+ #include <linux/ptp_classify.h>
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
+ 
+ #include "dsa_priv.h"
+ 
+@@ -1224,6 +1228,27 @@ static struct devlink_port *dsa_slave_ge
+ 	return dp->ds->devlink ? &dp->devlink_port : NULL;
+ }
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int dsa_flow_offload_check(struct flow_offload_hw_path *path)
++{
++	struct net_device *dev = path->dev;
++	struct dsa_port *dp;
++
++	if (!(path->flags & FLOW_OFFLOAD_PATH_ETHERNET))
++		return -EINVAL;
++
++	dp = dsa_slave_to_port(dev);
++	path->dsa_port = dp->index;
++	path->dev = dsa_slave_to_master(dev);
++	path->flags |= FLOW_OFFLOAD_PATH_DSA;
++
++	if (path->dev->netdev_ops->ndo_flow_offload_check)
++		return path->dev->netdev_ops->ndo_flow_offload_check(path);
++
++	return 0;
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ static const struct net_device_ops dsa_slave_netdev_ops = {
+ 	.ndo_open	 	= dsa_slave_open,
+ 	.ndo_stop		= dsa_slave_close,
+@@ -1248,6 +1273,9 @@ static const struct net_device_ops dsa_s
+ 	.ndo_vlan_rx_add_vid	= dsa_slave_vlan_rx_add_vid,
+ 	.ndo_vlan_rx_kill_vid	= dsa_slave_vlan_rx_kill_vid,
+ 	.ndo_get_devlink_port	= dsa_slave_get_devlink_port,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.ndo_flow_offload_check	 = dsa_flow_offload_check,
++#endif
+ };
+ 
+ static struct device_type dsa_type = {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/647-netfilter-flow-acct.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/647-netfilter-flow-acct.patch
new file mode 100644
index 0000000..9f7ca61
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/647-netfilter-flow-acct.patch
@@ -0,0 +1,70 @@
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -163,6 +163,8 @@ struct nf_flow_table_hw {
+ int nf_flow_table_hw_register(const struct nf_flow_table_hw *offload);
+ void nf_flow_table_hw_unregister(const struct nf_flow_table_hw *offload);
+ 
++void nf_flow_table_acct(struct flow_offload *flow, struct sk_buff *skb, int dir);
++
+ extern struct work_struct nf_flow_offload_hw_work;
+ 
+ #define MODULE_ALIAS_NF_FLOWTABLE(family)	\
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -13,6 +13,7 @@
+ #include <net/netfilter/nf_conntrack_core.h>
+ #include <net/netfilter/nf_conntrack_l4proto.h>
+ #include <net/netfilter/nf_conntrack_tuple.h>
++#include <net/netfilter/nf_conntrack_acct.h>
+ 
+ struct flow_offload_entry {
+ 	struct flow_offload	flow;
+@@ -177,6 +178,22 @@ void flow_offload_free(struct flow_offlo
+ }
+ EXPORT_SYMBOL_GPL(flow_offload_free);
+ 
++void nf_flow_table_acct(struct flow_offload *flow, struct sk_buff *skb, int dir)
++{
++	struct flow_offload_entry *entry;
++	struct nf_conn_acct *acct;
++
++	entry = container_of(flow, struct flow_offload_entry, flow);
++	acct = nf_conn_acct_find(entry->ct);
++	if (acct) {
++		struct nf_conn_counter *counter = acct->counter;
++
++		atomic64_inc(&counter[dir].packets);
++		atomic64_add(skb->len, &counter[dir].bytes);
++	}
++}
++EXPORT_SYMBOL_GPL(nf_flow_table_acct);
++
+ static u32 flow_offload_hash(const void *data, u32 len, u32 seed)
+ {
+ 	const struct flow_offload_tuple *tuple = data;
+--- a/net/netfilter/nf_flow_table_ip.c
++++ b/net/netfilter/nf_flow_table_ip.c
+@@ -12,6 +12,7 @@
+ #include <net/ip6_route.h>
+ #include <net/neighbour.h>
+ #include <net/netfilter/nf_flow_table.h>
++
+ /* For layer 4 checksum field offset. */
+ #include <linux/tcp.h>
+ #include <linux/udp.h>
+@@ -288,6 +289,7 @@ nf_flow_offload_ip_hook(void *priv, stru
+ 	skb->dev = outdev;
+ 	nexthop = rt_nexthop(rt, flow->tuplehash[!dir].tuple.src_v4.s_addr);
+ 	skb_dst_set_noref(skb, &rt->dst);
++	nf_flow_table_acct(flow, skb, dir);
+ 	neigh_xmit(NEIGH_ARP_TABLE, outdev, &nexthop, skb);
+ 
+ 	return NF_STOLEN;
+@@ -518,6 +520,7 @@ nf_flow_offload_ipv6_hook(void *priv, st
+ 	skb->dev = outdev;
+ 	nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
+ 	skb_dst_set_noref(skb, &rt->dst);
++	nf_flow_table_acct(flow, skb, dir);
+ 	neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
+ 
+ 	return NF_STOLEN;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/650-netfilter-add-xt_OFFLOAD-target.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/650-netfilter-add-xt_OFFLOAD-target.patch
new file mode 100644
index 0000000..8d9fec0
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/650-netfilter-add-xt_OFFLOAD-target.patch
@@ -0,0 +1,589 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Tue, 20 Feb 2018 15:56:02 +0100
+Subject: [PATCH] netfilter: add xt_OFFLOAD target
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+ create mode 100644 net/netfilter/xt_OFFLOAD.c
+
+--- a/net/ipv4/netfilter/Kconfig
++++ b/net/ipv4/netfilter/Kconfig
+@@ -56,8 +56,6 @@ config NF_TABLES_ARP
+ 	help
+ 	  This option enables the ARP support for nf_tables.
+ 
+-endif # NF_TABLES
+-
+ config NF_FLOW_TABLE_IPV4
+ 	tristate "Netfilter flow table IPv4 module"
+ 	depends on NF_FLOW_TABLE
+@@ -66,6 +64,8 @@ config NF_FLOW_TABLE_IPV4
+ 
+ 	  To compile it as a module, choose M here.
+ 
++endif # NF_TABLES
++
+ config NF_DUP_IPV4
+ 	tristate "Netfilter IPv4 packet duplication to alternate destination"
+ 	depends on !NF_CONNTRACK || NF_CONNTRACK
+--- a/net/ipv6/netfilter/Kconfig
++++ b/net/ipv6/netfilter/Kconfig
+@@ -45,7 +45,6 @@ config NFT_FIB_IPV6
+ 	  multicast or blackhole.
+ 
+ endif # NF_TABLES_IPV6
+-endif # NF_TABLES
+ 
+ config NF_FLOW_TABLE_IPV6
+ 	tristate "Netfilter flow table IPv6 module"
+@@ -55,6 +54,8 @@ config NF_FLOW_TABLE_IPV6
+ 
+ 	  To compile it as a module, choose M here.
+ 
++endif # NF_TABLES
++
+ config NF_DUP_IPV6
+ 	tristate "Netfilter IPv6 packet duplication to alternate destination"
+ 	depends on !NF_CONNTRACK || NF_CONNTRACK
+--- a/net/netfilter/Kconfig
++++ b/net/netfilter/Kconfig
+@@ -689,8 +689,6 @@ config NFT_FIB_NETDEV
+ 
+ endif # NF_TABLES_NETDEV
+ 
+-endif # NF_TABLES
+-
+ config NF_FLOW_TABLE_INET
+ 	tristate "Netfilter flow table mixed IPv4/IPv6 module"
+ 	depends on NF_FLOW_TABLE
+@@ -699,11 +697,12 @@ config NF_FLOW_TABLE_INET
+ 
+ 	  To compile it as a module, choose M here.
+ 
++endif # NF_TABLES
++
+ config NF_FLOW_TABLE
+ 	tristate "Netfilter flow table module"
+ 	depends on NETFILTER_INGRESS
+ 	depends on NF_CONNTRACK
+-	depends on NF_TABLES
+ 	help
+ 	  This option adds the flow table core infrastructure.
+ 
+@@ -992,6 +991,15 @@ config NETFILTER_XT_TARGET_NOTRACK
+ 	depends on NETFILTER_ADVANCED
+ 	select NETFILTER_XT_TARGET_CT
+ 
++config NETFILTER_XT_TARGET_FLOWOFFLOAD
++	tristate '"FLOWOFFLOAD" target support'
++	depends on NF_FLOW_TABLE
++	depends on NETFILTER_INGRESS
++	help
++	  This option adds a `FLOWOFFLOAD' target, which uses the nf_flow_offload
++	  module to speed up processing of packets by bypassing the usual
++	  netfilter chains
++
+ config NETFILTER_XT_TARGET_RATEEST
+ 	tristate '"RATEEST" target support'
+ 	depends on NETFILTER_ADVANCED
+--- a/net/netfilter/Makefile
++++ b/net/netfilter/Makefile
+@@ -141,6 +141,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_CLASSIF
+ obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_CT) += xt_CT.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_DSCP) += xt_DSCP.o
++obj-$(CONFIG_NETFILTER_XT_TARGET_FLOWOFFLOAD) += xt_FLOWOFFLOAD.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HL) += xt_HL.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_HMARK) += xt_HMARK.o
+ obj-$(CONFIG_NETFILTER_XT_TARGET_LED) += xt_LED.o
+--- /dev/null
++++ b/net/netfilter/xt_FLOWOFFLOAD.c
+@@ -0,0 +1,427 @@
++/*
++ * Copyright (C) 2018 Felix Fietkau <nbd@nbd.name>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/netfilter.h>
++#include <linux/netfilter/xt_FLOWOFFLOAD.h>
++#include <net/ip.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_extend.h>
++#include <net/netfilter/nf_conntrack_helper.h>
++#include <net/netfilter/nf_flow_table.h>
++
++static struct nf_flowtable nf_flowtable;
++static HLIST_HEAD(hooks);
++static DEFINE_SPINLOCK(hooks_lock);
++static struct delayed_work hook_work;
++
++struct xt_flowoffload_hook {
++	struct hlist_node list;
++	struct nf_hook_ops ops;
++	struct net *net;
++	bool registered;
++	bool used;
++};
++
++static unsigned int
++xt_flowoffload_net_hook(void *priv, struct sk_buff *skb,
++			  const struct nf_hook_state *state)
++{
++	switch (skb->protocol) {
++	case htons(ETH_P_IP):
++		return nf_flow_offload_ip_hook(priv, skb, state);
++	case htons(ETH_P_IPV6):
++		return nf_flow_offload_ipv6_hook(priv, skb, state);
++	}
++
++	return NF_ACCEPT;
++}
++
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
++			   void (*iter)(struct flow_offload *flow, void *data),
++			   void *data);
++
++static int
++xt_flowoffload_create_hook(struct net_device *dev)
++{
++	struct xt_flowoffload_hook *hook;
++	struct nf_hook_ops *ops;
++
++	hook = kzalloc(sizeof(*hook), GFP_ATOMIC);
++	if (!hook)
++		return -ENOMEM;
++
++	ops = &hook->ops;
++	ops->pf = NFPROTO_NETDEV;
++	ops->hooknum = NF_NETDEV_INGRESS;
++	ops->priority = 10;
++	ops->priv = &nf_flowtable;
++	ops->hook = xt_flowoffload_net_hook;
++	ops->dev = dev;
++
++	hlist_add_head(&hook->list, &hooks);
++	mod_delayed_work(system_power_efficient_wq, &hook_work, 0);
++
++	return 0;
++}
++
++static struct xt_flowoffload_hook *
++flow_offload_lookup_hook(struct net_device *dev)
++{
++	struct xt_flowoffload_hook *hook;
++
++	hlist_for_each_entry(hook, &hooks, list) {
++		if (hook->ops.dev == dev)
++			return hook;
++	}
++
++	return NULL;
++}
++
++static void
++xt_flowoffload_check_device(struct net_device *dev)
++{
++	struct xt_flowoffload_hook *hook;
++
++	spin_lock_bh(&hooks_lock);
++	hook = flow_offload_lookup_hook(dev);
++	if (hook)
++		hook->used = true;
++	else
++		xt_flowoffload_create_hook(dev);
++	spin_unlock_bh(&hooks_lock);
++}
++
++static void
++xt_flowoffload_register_hooks(void)
++{
++	struct xt_flowoffload_hook *hook;
++
++restart:
++	hlist_for_each_entry(hook, &hooks, list) {
++		if (hook->registered)
++			continue;
++
++		hook->registered = true;
++		hook->net = dev_net(hook->ops.dev);
++		spin_unlock_bh(&hooks_lock);
++		nf_register_net_hook(hook->net, &hook->ops);
++		spin_lock_bh(&hooks_lock);
++		goto restart;
++	}
++
++}
++
++static void
++xt_flowoffload_cleanup_hooks(void)
++{
++	struct xt_flowoffload_hook *hook;
++
++restart:
++	hlist_for_each_entry(hook, &hooks, list) {
++		if (hook->used || !hook->registered)
++			continue;
++
++		hlist_del(&hook->list);
++		spin_unlock_bh(&hooks_lock);
++		nf_unregister_net_hook(hook->net, &hook->ops);
++		kfree(hook);
++		spin_lock_bh(&hooks_lock);
++		goto restart;
++	}
++
++}
++
++static void
++xt_flowoffload_check_hook(struct flow_offload *flow, void *data)
++{
++	struct flow_offload_tuple *tuple = &flow->tuplehash[0].tuple;
++	struct xt_flowoffload_hook *hook;
++	bool *found = data;
++	struct rtable *rt = (struct rtable *)tuple->dst_cache;
++
++	spin_lock_bh(&hooks_lock);
++	hlist_for_each_entry(hook, &hooks, list) {
++		if (hook->ops.dev->ifindex != tuple->iifidx &&
++		    hook->ops.dev->ifindex != rt->dst.dev->ifindex)
++			continue;
++
++		hook->used = true;
++		*found = true;
++	}
++	spin_unlock_bh(&hooks_lock);
++}
++
++static void
++xt_flowoffload_hook_work(struct work_struct *work)
++{
++	struct xt_flowoffload_hook *hook;
++	bool found = false;
++	int err;
++
++	spin_lock_bh(&hooks_lock);
++	xt_flowoffload_register_hooks();
++	hlist_for_each_entry(hook, &hooks, list)
++		hook->used = false;
++	spin_unlock_bh(&hooks_lock);
++
++	err = nf_flow_table_iterate(&nf_flowtable, xt_flowoffload_check_hook,
++				    &found);
++	if (err && err != -EAGAIN)
++	    goto out;
++
++	spin_lock_bh(&hooks_lock);
++	xt_flowoffload_cleanup_hooks();
++	spin_unlock_bh(&hooks_lock);
++
++out:
++	if (found)
++		queue_delayed_work(system_power_efficient_wq, &hook_work, HZ);
++}
++
++static bool
++xt_flowoffload_skip(struct sk_buff *skb, int family)
++{
++	if (skb_sec_path(skb))
++		return true;
++
++	if (family == NFPROTO_IPV4) {
++		const struct ip_options *opt = &(IPCB(skb)->opt);
++
++		if (unlikely(opt->optlen))
++			return true;
++	}
++
++	return false;
++}
++
++static struct dst_entry *
++xt_flowoffload_dst(const struct nf_conn *ct, enum ip_conntrack_dir dir,
++		   const struct xt_action_param *par, int ifindex)
++{
++	struct dst_entry *dst = NULL;
++	struct flowi fl;
++
++	memset(&fl, 0, sizeof(fl));
++	switch (xt_family(par)) {
++	case NFPROTO_IPV4:
++		fl.u.ip4.daddr = ct->tuplehash[dir].tuple.src.u3.ip;
++		fl.u.ip4.flowi4_oif = ifindex;
++		break;
++	case NFPROTO_IPV6:
++		fl.u.ip6.saddr = ct->tuplehash[dir].tuple.dst.u3.in6;
++		fl.u.ip6.daddr = ct->tuplehash[dir].tuple.src.u3.in6;
++		fl.u.ip6.flowi6_oif = ifindex;
++		break;
++	}
++
++	nf_route(xt_net(par), &dst, &fl, false, xt_family(par));
++
++	return dst;
++}
++
++static int
++xt_flowoffload_route(struct sk_buff *skb, const struct nf_conn *ct,
++		   const struct xt_action_param *par,
++		   struct nf_flow_route *route, enum ip_conntrack_dir dir)
++{
++	struct dst_entry *this_dst, *other_dst;
++
++	this_dst = xt_flowoffload_dst(ct, !dir, par, xt_out(par)->ifindex);
++	other_dst = xt_flowoffload_dst(ct, dir, par, xt_in(par)->ifindex);
++
++	route->tuple[dir].dst		= this_dst;
++	route->tuple[!dir].dst		= other_dst;
++
++	if (!this_dst || !other_dst)
++		return -ENOENT;
++
++	if (dst_xfrm(this_dst) || dst_xfrm(other_dst))
++		return -EINVAL;
++
++	return 0;
++}
++
++static unsigned int
++flowoffload_tg(struct sk_buff *skb, const struct xt_action_param *par)
++{
++	const struct xt_flowoffload_target_info *info = par->targinfo;
++	struct tcphdr _tcph, *tcph = NULL;
++	enum ip_conntrack_info ctinfo;
++	enum ip_conntrack_dir dir;
++	struct nf_flow_route route;
++	struct flow_offload *flow = NULL;
++	struct nf_conn *ct;
++	struct net *net;
++
++	if (xt_flowoffload_skip(skb, xt_family(par)))
++		return XT_CONTINUE;
++
++	ct = nf_ct_get(skb, &ctinfo);
++	if (ct == NULL)
++		return XT_CONTINUE;
++
++	switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
++	case IPPROTO_TCP:
++		if (ct->proto.tcp.state != TCP_CONNTRACK_ESTABLISHED)
++			return XT_CONTINUE;
++
++		tcph = skb_header_pointer(skb, par->thoff,
++					  sizeof(_tcph), &_tcph);
++		if (unlikely(!tcph || tcph->fin || tcph->rst))
++			return XT_CONTINUE;
++		break;
++	case IPPROTO_UDP:
++		break;
++	default:
++		return XT_CONTINUE;
++	}
++
++	if (nf_ct_ext_exist(ct, NF_CT_EXT_HELPER) ||
++	    ct->status & IPS_SEQ_ADJUST)
++		return XT_CONTINUE;
++
++	if (!nf_ct_is_confirmed(ct))
++		return XT_CONTINUE;
++
++	if (!xt_in(par) || !xt_out(par))
++		return XT_CONTINUE;
++
++	if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status))
++		return XT_CONTINUE;
++
++	dir = CTINFO2DIR(ctinfo);
++
++	if (xt_flowoffload_route(skb, ct, par, &route, dir) == 0)
++		flow = flow_offload_alloc(ct, &route);
++
++	dst_release(route.tuple[dir].dst);
++	dst_release(route.tuple[!dir].dst);
++
++	if (!flow)
++		goto err_flow_route;
++
++	if (tcph) {
++		ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
++		ct->proto.tcp.seen[1].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
++	}
++
++	if (flow_offload_add(&nf_flowtable, flow) < 0)
++		goto err_flow_add;
++
++	xt_flowoffload_check_device(xt_in(par));
++	xt_flowoffload_check_device(xt_out(par));
++
++	net = read_pnet(&nf_flowtable.ft_net);
++	if (!net)
++		write_pnet(&nf_flowtable.ft_net, xt_net(par));
++
++	if (info->flags & XT_FLOWOFFLOAD_HW)
++		nf_flow_offload_hw_add(xt_net(par), flow, ct);
++
++	return XT_CONTINUE;
++
++err_flow_add:
++	flow_offload_free(flow);
++err_flow_route:
++	clear_bit(IPS_OFFLOAD_BIT, &ct->status);
++	return XT_CONTINUE;
++}
++
++
++static int flowoffload_chk(const struct xt_tgchk_param *par)
++{
++	struct xt_flowoffload_target_info *info = par->targinfo;
++
++	if (info->flags & ~XT_FLOWOFFLOAD_MASK)
++		return -EINVAL;
++
++	return 0;
++}
++
++static struct xt_target offload_tg_reg __read_mostly = {
++	.family		= NFPROTO_UNSPEC,
++	.name		= "FLOWOFFLOAD",
++	.revision	= 0,
++	.targetsize	= sizeof(struct xt_flowoffload_target_info),
++	.usersize	= sizeof(struct xt_flowoffload_target_info),
++	.checkentry	= flowoffload_chk,
++	.target		= flowoffload_tg,
++	.me		= THIS_MODULE,
++};
++
++static int xt_flowoffload_table_init(struct nf_flowtable *table)
++{
++	table->flags = NF_FLOWTABLE_F_HW;
++	nf_flow_table_init(table);
++	return 0;
++}
++
++static void xt_flowoffload_table_cleanup(struct nf_flowtable *table)
++{
++	nf_flow_table_free(table);
++}
++
++static int flow_offload_netdev_event(struct notifier_block *this,
++				     unsigned long event, void *ptr)
++{
++	struct xt_flowoffload_hook *hook = NULL;
++	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++
++	if (event != NETDEV_UNREGISTER)
++		return NOTIFY_DONE;
++
++	spin_lock_bh(&hooks_lock);
++	hook = flow_offload_lookup_hook(dev);
++	if (hook) {
++		hlist_del(&hook->list);
++	}
++	spin_unlock_bh(&hooks_lock);
++	if (hook) {
++		nf_unregister_net_hook(hook->net, &hook->ops);
++		kfree(hook);
++	}
++
++	nf_flow_table_cleanup(dev);
++
++	return NOTIFY_DONE;
++}
++
++static struct notifier_block flow_offload_netdev_notifier = {
++	.notifier_call	= flow_offload_netdev_event,
++};
++
++static int __init xt_flowoffload_tg_init(void)
++{
++	int ret;
++
++	register_netdevice_notifier(&flow_offload_netdev_notifier);
++
++	INIT_DELAYED_WORK(&hook_work, xt_flowoffload_hook_work);
++
++	ret = xt_flowoffload_table_init(&nf_flowtable);
++	if (ret)
++		return ret;
++
++	ret = xt_register_target(&offload_tg_reg);
++	if (ret)
++		xt_flowoffload_table_cleanup(&nf_flowtable);
++
++	return ret;
++}
++
++static void __exit xt_flowoffload_tg_exit(void)
++{
++	xt_unregister_target(&offload_tg_reg);
++	xt_flowoffload_table_cleanup(&nf_flowtable);
++	unregister_netdevice_notifier(&flow_offload_netdev_notifier);
++}
++
++MODULE_LICENSE("GPL");
++module_init(xt_flowoffload_tg_init);
++module_exit(xt_flowoffload_tg_exit);
+--- a/net/netfilter/nf_flow_table_core.c
++++ b/net/netfilter/nf_flow_table_core.c
+@@ -7,7 +7,6 @@
+ #include <linux/netdevice.h>
+ #include <net/ip.h>
+ #include <net/ip6_route.h>
+-#include <net/netfilter/nf_tables.h>
+ #include <net/netfilter/nf_flow_table.h>
+ #include <net/netfilter/nf_conntrack.h>
+ #include <net/netfilter/nf_conntrack_core.h>
+@@ -351,8 +350,7 @@ flow_offload_lookup(struct nf_flowtable
+ }
+ EXPORT_SYMBOL_GPL(flow_offload_lookup);
+ 
+-static int
+-nf_flow_table_iterate(struct nf_flowtable *flow_table,
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
+ 		      void (*iter)(struct flow_offload *flow, void *data),
+ 		      void *data)
+ {
+@@ -385,6 +383,7 @@ nf_flow_table_iterate(struct nf_flowtabl
+ 
+ 	return err;
+ }
++EXPORT_SYMBOL_GPL(nf_flow_table_iterate);
+ 
+ static void nf_flow_offload_gc_step(struct flow_offload *flow, void *data)
+ {
+--- /dev/null
++++ b/include/uapi/linux/netfilter/xt_FLOWOFFLOAD.h
+@@ -0,0 +1,17 @@
++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
++#ifndef _XT_FLOWOFFLOAD_H
++#define _XT_FLOWOFFLOAD_H
++
++#include <linux/types.h>
++
++enum {
++	XT_FLOWOFFLOAD_HW	= 1 << 0,
++
++	XT_FLOWOFFLOAD_MASK	= XT_FLOWOFFLOAD_HW
++};
++
++struct xt_flowoffload_target_info {
++	__u32 flags;
++};
++
++#endif /* _XT_FLOWOFFLOAD_H */
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -133,6 +133,10 @@ static inline void flow_offload_dead(str
+ 	flow->flags |= FLOW_OFFLOAD_DYING;
+ }
+ 
++int nf_flow_table_iterate(struct nf_flowtable *flow_table,
++                      void (*iter)(struct flow_offload *flow, void *data),
++                      void *data);
++
+ int nf_flow_snat_port(const struct flow_offload *flow,
+ 		      struct sk_buff *skb, unsigned int thoff,
+ 		      u8 protocol, enum flow_offload_tuple_dir dir);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/699-1002-mtkhnat-add-support-for-virtual-interface-acceleration.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/699-1002-mtkhnat-add-support-for-virtual-interface-acceleration.patch
new file mode 100644
index 0000000..150087a
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/nf_hnat/699-1002-mtkhnat-add-support-for-virtual-interface-acceleration.patch
@@ -0,0 +1,127 @@
+diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
+index 3d73c0c..960ade1 100644
+--- a/include/net/netfilter/nf_flow_table.h
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -92,9 +92,12 @@ struct flow_offload {
+ #define FLOW_OFFLOAD_PATH_VLAN		BIT(1)
+ #define FLOW_OFFLOAD_PATH_PPPOE		BIT(2)
+ #define FLOW_OFFLOAD_PATH_DSA		BIT(3)
++#define FLOW_OFFLOAD_PATH_DSLITE	BIT(4)
++#define FLOW_OFFLOAD_PATH_6RD		BIT(5)
+ 
+ struct flow_offload_hw_path {
+ 	struct net_device *dev;
++	struct net_device *virt_dev;
+ 	u32 flags;
+ 
+ 	u8 eth_src[ETH_ALEN];
+diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c
+index be6801524..c51af70f6 100644
+--- a/net/8021q/vlan_dev.c
++++ b/net/8021q/vlan_dev.c
+@@ -761,6 +761,7 @@ static int vlan_dev_flow_offload_check(struct flow_offload_hw_path *path)
+ 	path->flags |= FLOW_OFFLOAD_PATH_VLAN;
+ 	path->vlan_proto = vlan->vlan_proto;
+ 	path->vlan_id = vlan->vlan_id;
++	path->virt_dev = dev;
+ 	path->dev = vlan->real_dev;
+ 
+ 	if (vlan->real_dev->netdev_ops->ndo_flow_offload_check)
+diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
+index 1b7e3141c..da4e34f74 100644
+--- a/net/ipv6/ip6_tunnel.c
++++ b/net/ipv6/ip6_tunnel.c
+@@ -57,6 +57,11 @@
+ #include <net/netns/generic.h>
+ #include <net/dst_metadata.h>
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
++
+ MODULE_AUTHOR("Ville Nuorvala");
+ MODULE_DESCRIPTION("IPv6 tunneling device");
+ MODULE_LICENSE("GPL");
+@@ -1880,6 +1885,22 @@ int ip6_tnl_get_iflink(const struct net_device *dev)
+ }
+ EXPORT_SYMBOL(ip6_tnl_get_iflink);
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int ipip6_dev_flow_offload_check(struct flow_offload_hw_path *path)
++{
++	struct net_device *dev = path->dev;
++	struct ip6_tnl *tnl = netdev_priv(dev);
++
++	if (path->flags & FLOW_OFFLOAD_PATH_DSLITE)
++		return -EEXIST;
++
++	path->flags |= FLOW_OFFLOAD_PATH_DSLITE;
++	path->dev = tnl->dev;
++
++	return 0;
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ int ip6_tnl_encap_add_ops(const struct ip6_tnl_encap_ops *ops,
+ 			  unsigned int num)
+ {
+@@ -1941,6 +1962,9 @@ static const struct net_device_ops ip6_tnl_netdev_ops = {
+ 	.ndo_change_mtu = ip6_tnl_change_mtu,
+ 	.ndo_get_stats	= ip6_get_stats,
+ 	.ndo_get_iflink = ip6_tnl_get_iflink,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.ndo_flow_offload_check = ipip6_dev_flow_offload_check,
++#endif
+ };
+ 
+ #define IPXIPX_FEATURES (NETIF_F_SG |		\
+diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
+index 98954830c..42b6e8c4c 100644
+--- a/net/ipv6/sit.c
++++ b/net/ipv6/sit.c
+@@ -52,6 +52,11 @@
+ #include <net/net_namespace.h>
+ #include <net/netns/generic.h>
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++#include <linux/netfilter.h>
++#include <net/netfilter/nf_flow_table.h>
++#endif
++
+ /*
+    This version of net/ipv6/sit.c is cloned of net/ipv4/ip_gre.c
+ 
+@@ -1345,6 +1350,22 @@ ipip6_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+ 	return err;
+ }
+ 
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++static int ipip6_dev_flow_offload_check(struct flow_offload_hw_path *path)
++{
++	struct net_device *dev = path->dev;
++	struct ip_tunnel *tnl = netdev_priv(dev);
++
++	if (path->flags & FLOW_OFFLOAD_PATH_6RD)
++		return -EEXIST;
++
++	path->flags |= FLOW_OFFLOAD_PATH_6RD;
++	path->dev = tnl->dev;
++
++	return 0;
++}
++#endif /* CONFIG_NF_FLOW_TABLE */
++
+ static const struct net_device_ops ipip6_netdev_ops = {
+ 	.ndo_init	= ipip6_tunnel_init,
+ 	.ndo_uninit	= ipip6_tunnel_uninit,
+@@ -1352,6 +1373,9 @@ static const struct net_device_ops ipip6_netdev_ops = {
+ 	.ndo_do_ioctl	= ipip6_tunnel_ioctl,
+ 	.ndo_get_stats64 = ip_tunnel_get_stats64,
+ 	.ndo_get_iflink = ip_tunnel_get_iflink,
++#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
++	.ndo_flow_offload_check = ipip6_dev_flow_offload_check,
++#endif
+ };
+ 
+ static void ipip6_dev_free(struct net_device *dev)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/rdkb_cfg/nf_hnat.cfg b/recipes-kernel/linux/linux-mediatek-5.4/rdkb_cfg/nf_hnat.cfg
new file mode 100644
index 0000000..ad3e7d8
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/rdkb_cfg/nf_hnat.cfg
@@ -0,0 +1,11 @@
+CONFIG_CRYPTO_AES=y
+CONFIG_CRYPTO_CCM=y
+CONFIG_CRYPTO_GCM=y
+CONFIG_CRYPTO_CMAC=y
+CONFIG_NF_FLOW_TABLE=y
+CONFIG_NETFILTER_XT_TARGET_FLOWOFFLOAD=y
+CONFIG_NETFILTER_INGRESS=y
+CONFIG_NF_CONNTRACK_MARK=y
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NET_MEDIATEK_HNAT=m
+CONFIG_NF_FLOW_TABLE_HW=m
\ No newline at end of file
diff --git a/recipes-kernel/linux/linux-mediatek_5.4.bb b/recipes-kernel/linux/linux-mediatek_5.4.bb
index 323be62..e471312 100644
--- a/recipes-kernel/linux/linux-mediatek_5.4.bb
+++ b/recipes-kernel/linux/linux-mediatek_5.4.bb
@@ -4,6 +4,7 @@
 FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}/generic/hack-5.4:"
 FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}/mediatek/patches-5.4:"
 FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}/mediatek/flow_patch:"
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}/mediatek/nf_hnat:"
 
 KBRANCH ?= "linux-5.4.y"
 
@@ -38,7 +39,7 @@
     file://rdkb_cfg/iptables.cfg \
     file://rdkb_cfg/turris_rdkb.cfg \
     file://rdkb_cfg/openvswitch.cfg \
-    file://rdkb_cfg/mac80211.cfg \
+    ${@bb.utils.contains('DISTRO_FEATURES', 'mt76', 'file://rdkb_cfg/mac80211.cfg', 'file://rdkb_cfg/nf_hnat.cfg', d)} \
     file://rdkb_cfg/prplmesh.cfg \
     file://rdkb_cfg/filogic_rdkb.cfg \
 "
@@ -68,6 +69,7 @@
 do_filogic_patches() {
     cd ${S}
     DISTRO_FlowBlock_ENABLED="${@bb.utils.contains('DISTRO_FEATURES','flow_offload','true','false',d)}"
+    DISTRO_logan_ENABLED="${@bb.utils.contains('DISTRO_FEATURES','logan','true','false',d)}"
         if [ ! -e patch_applied ]; then
             patch -p1 < ${WORKDIR}/001-rdkb-eth-mtk-change-ifname-for.patch
             patch -p1 < ${WORKDIR}/003-rdkb-mtd-kernel-ubi-relayout.patch
@@ -79,8 +81,13 @@
             if [ $DISTRO_FlowBlock_ENABLED = 'true' ]; then
                 for i in ${WORKDIR}/mediatek/flow_patch/*.patch; do patch -p1 < $i; done
             fi
+            if [ $DISTRO_logan_ENABLED = 'true' ]; then
+                for i in ${WORKDIR}/mediatek/nf_hnat/*.patch; do patch -p1 < $i; done
+            fi
             touch patch_applied
         fi
 }
 
 addtask filogic_patches after do_patch before do_compile
+
+KERNEL_MODULE_AUTOLOAD += "${@bb.utils.contains('DISTRO_FEATURES','logan','mtkhnat nf_flow_table_hw','',d)}"
\ No newline at end of file