Merge "[rdkb][common][app][Add MACsec service for platform HAL]"
diff --git a/recipes-connectivity/hostapd/files/patches/920-Add-hostapd-HEMU-SET-GET-control.patch b/recipes-connectivity/hostapd/files/patches/920-Add-hostapd-HEMU-SET-GET-control.patch
new file mode 100644
index 0000000..b31d521
--- /dev/null
+++ b/recipes-connectivity/hostapd/files/patches/920-Add-hostapd-HEMU-SET-GET-control.patch
@@ -0,0 +1,450 @@
+From f815c6befdd5b0185ae476d649e3eff58d6e9b69 Mon Sep 17 00:00:00 2001
+From: TomLiu <tomml.liu@mediatek.com>
+Date: Tue, 9 Aug 2022 10:23:44 -0700
+Subject: [PATCH] [PATCH-920]Add hostapd HEMU SET/GET control
+
+---
+ hostapd/config_file.c             |   9 +++
+ hostapd/ctrl_iface.c              |  62 +++++++++++++++++
+ hostapd/hostapd_cli.c             |  18 +++++
+ src/ap/ap_config.c                |   1 +
+ src/ap/ap_config.h                |   1 +
+ src/ap/ap_drv_ops.c               |  14 ++++
+ src/ap/ap_drv_ops.h               |   2 +
+ src/ap/hostapd.c                  |   2 +
+ src/common/mtk_vendor.h           |  15 ++++
+ src/drivers/driver.h              |  13 ++++
+ src/drivers/driver_nl80211.c      | 110 ++++++++++++++++++++++++++++++
+ src/drivers/driver_nl80211.h      |   1 +
+ src/drivers/driver_nl80211_capa.c |   3 +
+ 13 files changed, 251 insertions(+)
+
+diff --git a/hostapd/config_file.c b/hostapd/config_file.c
+index 19a2fd5..85d58cd 100644
+--- a/hostapd/config_file.c
++++ b/hostapd/config_file.c
+@@ -3655,6 +3655,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
+ 			return 1;
+ 		}
+ 		bss->unsol_bcast_probe_resp_interval = val;
++	} else if (os_strcmp(buf, "hemu_onoff") == 0) {
++		int val = atoi(pos);
++		if (val < 0 || val > 15) {
++			wpa_printf(MSG_ERROR,
++				   "Line %d: invalid hemu_onoff value",
++				   line);
++			return 1;
++		}
++		conf->hemu_onoff = val;
+ #endif /* CONFIG_IEEE80211AX */
+ 	} else if (os_strcmp(buf, "max_listen_interval") == 0) {
+ 		bss->max_listen_interval = atoi(pos);
+diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
+index 5e9af7f..5df5c84 100644
+--- a/hostapd/ctrl_iface.c
++++ b/hostapd/ctrl_iface.c
+@@ -3399,6 +3399,63 @@ hostapd_ctrl_iface_apply_edcca(struct hostapd_data *hapd, char *buf,
+ }
+ 
+ 
++static int
++hostapd_ctrl_iface_set_hemu(struct hostapd_data *hapd, char *cmd,
++					 char *buf, size_t buflen)
++{
++	char *pos, *config, *value;
++	config = cmd;
++	pos = os_strchr(config, ' ');
++	if (pos == NULL)
++		return -1;
++	*pos++ = '\0';
++
++	if(pos == NULL)
++		return -1;
++	value = pos;
++
++	if (os_strcmp(config, "onoff") == 0) {
++		int hemu = atoi(value);
++		if (hemu < 0 || hemu > 15) {
++			wpa_printf(MSG_ERROR, "Invalid value for hemu");
++			return -1;
++		}
++		hapd->iconf->hemu_onoff = (u8) hemu;
++	} else {
++		wpa_printf(MSG_ERROR,
++			"Unsupported parameter %s for SET_HEMU", config);
++		return -1;
++	}
++
++	if(hostapd_drv_hemu_ctrl(hapd) == 0) {
++		return os_snprintf(buf, buflen, "OK\n");
++	} else {
++		return -1;
++	}
++}
++
++
++static int
++hostapd_ctrl_iface_get_hemu(struct hostapd_data *hapd, char *buf,
++					 size_t buflen)
++{
++	u8 hemu_onoff;
++	char *pos, *end;
++
++	pos = buf;
++	end = buf + buflen;
++
++	if (hostapd_drv_hemu_dump(hapd, &hemu_onoff) == 0) {
++		hapd->iconf->hemu_onoff = hemu_onoff;
++		return os_snprintf(pos, end - pos, "[hostapd_cli] = UL MU-MIMO: %d, DL MU-MIMO: %d, UL OFDMA: %d, DL OFDMA: %d\n",
++			!!(hemu_onoff&BIT(3)), !!(hemu_onoff&BIT(2)), !!(hemu_onoff&BIT(1)), !!(hemu_onoff&BIT(0)));
++	} else {
++		wpa_printf(MSG_INFO, "ctrl iface failed to call");
++		return -1;
++	}
++}
++
++
+ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
+ 					      char *buf, char *reply,
+ 					      int reply_size,
+@@ -3939,6 +3996,11 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
+ 		reply_len = hostapd_ctrl_iface_get_edcca(hapd, reply, reply_size);
+ 	} else if (os_strncmp(buf, "APPLY_EDCCA", 11) == 0) {
+ 		reply_len = hostapd_ctrl_iface_apply_edcca(hapd, reply, reply_size);
++	} else if (os_strncmp(buf, "SET_HEMU ", 9) == 0) {
++		reply_len = hostapd_ctrl_iface_set_hemu(hapd, buf+9, reply,
++							  reply_size);
++	} else if (os_strncmp(buf, "GET_HEMU", 8) == 0) {
++		reply_len = hostapd_ctrl_iface_get_hemu(hapd, reply, reply_size);
+ 	} else {
+ 		os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
+ 		reply_len = 16;
+diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
+index 68133d4..41d244a 100644
+--- a/hostapd/hostapd_cli.c
++++ b/hostapd/hostapd_cli.c
+@@ -1380,6 +1380,20 @@ static int hostapd_cli_cmd_driver_flags(struct wpa_ctrl *ctrl, int argc,
+ }
+ 
+ 
++static int hostapd_cli_cmd_set_hemu(struct wpa_ctrl *ctrl, int argc,
++					   char *argv[])
++{
++	return hostapd_cli_cmd(ctrl, "SET_HEMU", 1, argc, argv);
++}
++
++
++static int hostapd_cli_cmd_get_hemu(struct wpa_ctrl *ctrl, int argc,
++					   char *argv[])
++{
++	return hostapd_cli_cmd(ctrl, "GET_HEMU", 0, NULL, NULL);
++}
++
++
+ #ifdef CONFIG_DPP
+ 
+ static int hostapd_cli_cmd_dpp_qr_code(struct wpa_ctrl *ctrl, int argc,
+@@ -1696,6 +1710,10 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
+ 	  " = send FTM range request"},
+ 	{ "driver_flags", hostapd_cli_cmd_driver_flags, NULL,
+ 	  " = show supported driver flags"},
++	{ "set_hemu", hostapd_cli_cmd_set_hemu, NULL,
++		"<value> [0-15] bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0)"},
++	{ "get_hemu", hostapd_cli_cmd_get_hemu, NULL,
++		" = show hemu onoff value in 0-15 bitmap"},
+ #ifdef CONFIG_DPP
+ 	{ "dpp_qr_code", hostapd_cli_cmd_dpp_qr_code, NULL,
+ 	  "report a scanned DPP URI from a QR Code" },
+diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
+index 427f16e..79e215f 100644
+--- a/src/ap/ap_config.c
++++ b/src/ap/ap_config.c
+@@ -280,6 +280,7 @@ struct hostapd_config * hostapd_config_defaults(void)
+ 	conf->he_6ghz_max_ampdu_len_exp = 7;
+ 	conf->he_6ghz_rx_ant_pat = 1;
+ 	conf->he_6ghz_tx_ant_pat = 1;
++	conf->hemu_onoff = 13;
+ #endif /* CONFIG_IEEE80211AX */
+ 
+ 	/* The third octet of the country string uses an ASCII space character
+diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
+index 9bbe7eb..737cc2f 100644
+--- a/src/ap/ap_config.h
++++ b/src/ap/ap_config.h
+@@ -1111,6 +1111,7 @@ struct hostapd_config {
+ 	u8 he_6ghz_rx_ant_pat;
+ 	u8 he_6ghz_tx_ant_pat;
+ 	u8 he_6ghz_reg_pwr_type;
++	u8 hemu_onoff;
+ #endif /* CONFIG_IEEE80211AX */
+ 
+ 	/* VHT enable/disable config from CHAN_SWITCH */
+diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
+index b8b98e4..3de6748 100644
+--- a/src/ap/ap_drv_ops.c
++++ b/src/ap/ap_drv_ops.c
+@@ -1021,3 +1021,17 @@ int hostapd_drv_configure_edcca_threshold(struct hostapd_data *hapd)
+ 				hapd->iconf->edcca_enable,
+ 				hapd->iconf->edcca_compensation);
+ }
++
++int hostapd_drv_hemu_ctrl(struct hostapd_data *hapd)
++{
++	if (!hapd->driver || !hapd->driver->hemu_ctrl)
++		return 0;
++	return hapd->driver->hemu_ctrl(hapd->drv_priv, hapd->iconf->hemu_onoff);
++}
++
++int hostapd_drv_hemu_dump(struct hostapd_data *hapd, u8 *hemu_onoff)
++{
++	if (!hapd->driver || !hapd->driver->hemu_dump)
++		return 0;
++	return hapd->driver->hemu_dump(hapd->drv_priv, hemu_onoff);
++}
+diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
+index f8fef19..5bcb225 100644
+--- a/src/ap/ap_drv_ops.h
++++ b/src/ap/ap_drv_ops.h
+@@ -139,6 +139,8 @@ int hostapd_drv_update_dh_ie(struct hostapd_data *hapd, const u8 *peer,
+ 			     u16 reason_code, const u8 *ie, size_t ielen);
+ int hostapd_drv_dpp_listen(struct hostapd_data *hapd, bool enable);
+ int hostapd_drv_configure_edcca_threshold(struct hostapd_data *hapd);
++int hostapd_drv_hemu_ctrl(struct hostapd_data *hapd);
++int hostapd_drv_hemu_dump(struct hostapd_data *hapd, u8 *hemu_onoff);
+ 
+ #include "drivers/driver.h"
+ 
+diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
+index c1edaab..3b752fc 100644
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -2297,6 +2297,8 @@ dfs_offload:
+ 
+ 	if (hostapd_drv_configure_edcca_threshold(hapd) < 0)
+ 		goto fail;
++	if (hostapd_drv_hemu_ctrl(hapd) < 0)
++		goto fail;
+ 
+ 	wpa_printf(MSG_DEBUG, "%s: Setup of interface done.",
+ 		   iface->bss[0]->conf->iface);
+diff --git a/src/common/mtk_vendor.h b/src/common/mtk_vendor.h
+index 528387f..5c8f179 100644
+--- a/src/common/mtk_vendor.h
++++ b/src/common/mtk_vendor.h
+@@ -10,6 +10,8 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL = 0xc2,
+ 	MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL = 0xc3,
+ 	MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL = 0xc4,
++	MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL = 0xc5,
++	MTK_NL80211_VENDOR_SUBCMD_PHY_CAPA_CTRL= 0xc6,
+ 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
+ };
+ 
+@@ -167,6 +169,19 @@ enum mtk_vendor_attr_rfeature_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL - 1
+ };
+ 
++enum mtk_vendor_attr_hemu_ctrl {
++	MTK_VENDOR_ATTR_HEMU_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF,
++	MTK_VENDOR_ATTR_HEMU_CTRL_DUMP,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_HEMU_CTRL,
++	MTK_VENDOR_ATTR_HEMU_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_HEMU_CTRL - 1
++};
++
++
+ #define CSI_MAX_COUNT 256
+ #define ETH_ALEN 6
+ 
+diff --git a/src/drivers/driver.h b/src/drivers/driver.h
+index fc96fef..6cc7e9b 100644
+--- a/src/drivers/driver.h
++++ b/src/drivers/driver.h
+@@ -1622,6 +1622,11 @@ struct wpa_driver_ap_params {
+ 	 * Unsolicited broadcast Probe Response template length
+ 	 */
+ 	size_t unsol_bcast_probe_resp_tmpl_len;
++
++	/**
++	 * hemu onoff=<val> (bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0))
++	 */
++	u8 hemu_onoff;
+ };
+ 
+ struct wpa_driver_mesh_bss_params {
+@@ -4675,6 +4680,14 @@ struct wpa_driver_ops {
+ #endif /* CONFIG_TESTING_OPTIONS */
+ 	int (*configure_edcca_threshold)(void *priv, const u8 edcca_enable,
+ 				  const s8 edcca_compensation);
++
++	/**
++	 * hemu_ctrl - ctrl on off for UL/DL MURU
++	 * @priv: Private driver interface data
++	 *
++	 */
++	 int (*hemu_ctrl)(void *priv, u8 hemu_onoff);
++	 int (*hemu_dump)(void *priv, u8 *hemu_onoff);
+ };
+ 
+ /**
+diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
+index b1e7b16..021a593 100644
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -12287,6 +12287,114 @@ fail:
+ }
+ 
+ 
++#ifdef CONFIG_IEEE80211AX
++static int nl80211_hemu_muruonoff(void *priv, u8 hemu_onoff)
++{
++	struct i802_bss *bss = priv;
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++	struct nl_msg *msg;
++	struct nlattr *data;
++	int ret;
++
++	if (!drv->mtk_hemu_vendor_cmd_avail) {
++		wpa_printf(MSG_INFO,
++			   "nl80211: Driver does not support setting hemu control");
++		return 0;
++	}
++
++	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL) ||
++		!(data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
++		nla_put_u8(msg, MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF, hemu_onoff)) {
++		nlmsg_free(msg);
++		return -ENOBUFS;
++	}
++	nla_nest_end(msg, data);
++	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
++	if(ret){
++		wpa_printf(MSG_ERROR, "Failed to set hemu_onoff. ret=%d (%s)", ret, strerror(-ret));
++	}
++	return ret;
++}
++
++
++static int hemu_dump_handler(struct nl_msg *msg, void *arg)
++{
++	u8 *hemu_onoff = (u8 *) arg;
++	struct nlattr *tb[NL80211_ATTR_MAX + 1];
++	struct nlattr *tb_vendor[MTK_VENDOR_ATTR_HEMU_CTRL_MAX + 1];
++	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
++	struct nlattr *nl_vend, *attr;
++
++	static const struct nla_policy
++	hemu_ctrl_policy[NUM_MTK_VENDOR_ATTRS_HEMU_CTRL + 1] = {
++		[MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF] = {.type = NLA_U8 },
++		[MTK_VENDOR_ATTR_HEMU_CTRL_DUMP] = {.type = NLA_U8 },
++	};
++
++	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
++			genlmsg_attrlen(gnlh, 0), NULL);
++
++	nl_vend = tb[NL80211_ATTR_VENDOR_DATA];
++	if (!nl_vend)
++		return NL_SKIP;
++
++	nla_parse(tb_vendor, MTK_VENDOR_ATTR_HEMU_CTRL_MAX,
++		  nla_data(nl_vend), nla_len(nl_vend), NULL);
++
++	attr = tb_vendor[MTK_VENDOR_ATTR_HEMU_CTRL_DUMP];
++	if (!attr) {
++		wpa_printf(MSG_ERROR, "nl80211: cannot find MTK_VENDOR_ATTR_HEMU_CTRL_DUMP");
++		return NL_SKIP;
++	}
++
++	*hemu_onoff = nla_get_u8(attr);
++	wpa_printf(MSG_DEBUG, "nla_get hemu_onoff: %d\n", *hemu_onoff);
++
++	return 0;
++}
++
++static int nl80211_hemu_dump(void *priv, u8 *hemu_onoff)
++{
++	struct i802_bss *bss = priv;
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++	struct nl_msg *msg;
++	struct nlattr *attr;
++	int ret;
++
++	if (!drv->mtk_hemu_vendor_cmd_avail) {
++		wpa_printf(MSG_INFO,
++			   "nl80211: Driver does not support setting hemu control");
++		return 0;
++	}
++
++	if (!(msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_VENDOR)) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL)) {
++		nlmsg_free(msg);
++		return -ENOBUFS;
++	}
++
++  attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
++	if (!attr) {
++		nlmsg_free(msg);
++		return -1;
++	}
++
++	nla_nest_end(msg, attr);
++
++	ret = send_and_recv_msgs(drv, msg, hemu_dump_handler, hemu_onoff, NULL, NULL);
++
++	if(ret){
++		wpa_printf(MSG_ERROR, "Failed to get hemu_onoff. ret=%d (%s)", ret, strerror(-ret));
++	}
++
++	return ret;
++}
++#endif /* CONFIG_IEEE80211AX */
++
++
+ #ifdef CONFIG_DPP
+ static int nl80211_dpp_listen(void *priv, bool enable)
+ {
+@@ -12531,6 +12639,8 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
+ 	.update_connect_params = nl80211_update_connection_params,
+ 	.send_external_auth_status = nl80211_send_external_auth_status,
+ 	.set_4addr_mode = nl80211_set_4addr_mode,
++	.hemu_ctrl = nl80211_hemu_muruonoff,
++	.hemu_dump = nl80211_hemu_dump,
+ #ifdef CONFIG_DPP
+ 	.dpp_listen = nl80211_dpp_listen,
+ #endif /* CONFIG_DPP */
+diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
+index b677907..62d9696 100644
+--- a/src/drivers/driver_nl80211.h
++++ b/src/drivers/driver_nl80211.h
+@@ -181,6 +181,7 @@ struct wpa_driver_nl80211_data {
+ 	unsigned int qca_do_acs:1;
+ 	unsigned int brcm_do_acs:1;
+ 	unsigned int mtk_edcca_vendor_cmd_avail:1;
++	unsigned int mtk_hemu_vendor_cmd_avail:1;
+ 
+ 	u64 vendor_scan_cookie;
+ 	u64 remain_on_chan_cookie;
+diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
+index 6c743bf..b5cd2c5 100644
+--- a/src/drivers/driver_nl80211_capa.c
++++ b/src/drivers/driver_nl80211_capa.c
+@@ -1050,6 +1050,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
+ 				case MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL :
+ 					drv->mtk_edcca_vendor_cmd_avail = 1;
+ 					break;
++				case MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL :
++					drv->mtk_hemu_vendor_cmd_avail = 1;
++					break;
+ 				}
+ 			}
+ 
+-- 
+2.32.0
+
diff --git a/recipes-connectivity/hostapd/files/patches/920-hostapd-hemu-onoff-ctrl.patch b/recipes-connectivity/hostapd/files/patches/920-hostapd-hemu-onoff-ctrl.patch
deleted file mode 100755
index 1e18c72..0000000
--- a/recipes-connectivity/hostapd/files/patches/920-hostapd-hemu-onoff-ctrl.patch
+++ /dev/null
@@ -1,218 +0,0 @@
-From 390f9431f5a6f8001f6822a3aea4c5e08743806d Mon Sep 17 00:00:00 2001
-From: TomLiu <tomml.liu@mediatek.com>
-Date: Thu, 14 Jul 2022 13:43:06 -0700
-Subject: [PATCH-920]Add hemu hostapd vendor command
-
----
- hostapd/config_file.c             |  9 ++++++++
- src/ap/ap_config.h                |  1 +
- src/ap/ap_drv_ops.c               |  7 ++++++
- src/ap/beacon.c                   |  2 ++
- src/common/mtk_vendor.h           | 13 +++++++++++
- src/drivers/driver.h              | 12 +++++++++++
- src/drivers/driver_nl80211.c      | 36 ++++++++++++++++++++++++++++++-
- src/drivers/driver_nl80211.h      |  1 +
- src/drivers/driver_nl80211_capa.c |  3 +++
- 9 files changed, 83 insertions(+), 1 deletion(-)
-
-diff --git a/hostapd/config_file.c b/hostapd/config_file.c
-index 19a2fd5..85d58cd 100644
---- a/hostapd/config_file.c
-+++ b/hostapd/config_file.c
-@@ -3655,6 +3655,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
- 			return 1;
- 		}
- 		bss->unsol_bcast_probe_resp_interval = val;
-+	} else if (os_strcmp(buf, "hemu_onoff") == 0) {
-+		int val = atoi(pos);
-+		if (val < 0 || val > 15) {
-+			wpa_printf(MSG_ERROR,
-+				   "Line %d: invalid hemu_onoff value",
-+				   line);
-+			return 1;
-+		}
-+		conf->hemu_onoff = val;
- #endif /* CONFIG_IEEE80211AX */
- 	} else if (os_strcmp(buf, "max_listen_interval") == 0) {
- 		bss->max_listen_interval = atoi(pos);
-diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
-index 9bbe7eb..737cc2f 100644
---- a/src/ap/ap_config.h
-+++ b/src/ap/ap_config.h
-@@ -1111,6 +1111,7 @@ struct hostapd_config {
- 	u8 he_6ghz_rx_ant_pat;
- 	u8 he_6ghz_tx_ant_pat;
- 	u8 he_6ghz_reg_pwr_type;
-+	u8 hemu_onoff;
- #endif /* CONFIG_IEEE80211AX */
-
- 	/* VHT enable/disable config from CHAN_SWITCH */
-diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
-index b8b98e4..517d2a5 100644
---- a/src/ap/ap_drv_ops.c
-+++ b/src/ap/ap_drv_ops.c
-@@ -1021,3 +1021,10 @@ int hostapd_drv_configure_edcca_threshold(struct hostapd_data *hapd)
- 				hapd->iconf->edcca_enable,
- 				hapd->iconf->edcca_compensation);
- }
-+
-+int hostapd_drv_hemu_ctrl(struct hostapd_data *hapd)
-+{
-+	if (!hapd->driver || !hapd->driver->hemu_ctrl)
-+		return 0;
-+	return hapd->driver->hemu_ctrl(hapd->drv_priv, hapd->iconf->hemu_onoff);
-+}
-diff --git a/src/ap/beacon.c b/src/ap/beacon.c
-index 575c92f..7170711 100644
---- a/src/ap/beacon.c
-+++ b/src/ap/beacon.c
-@@ -1976,6 +1976,8 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd)
- 		params.freq = &freq;
-
- 	res = hostapd_drv_set_ap(hapd, &params);
-+	if (hostapd_drv_hemu_ctrl(hapd) < 0)
-+		goto fail;
- 	hostapd_free_ap_extra_ies(hapd, beacon, proberesp, assocresp);
- 	if (res)
- 		wpa_printf(MSG_ERROR, "Failed to set beacon parameters");
-diff --git a/src/common/mtk_vendor.h b/src/common/mtk_vendor.h
-index 528387f..6a0e60a 100644
---- a/src/common/mtk_vendor.h
-+++ b/src/common/mtk_vendor.h
-@@ -10,6 +10,8 @@ enum mtk_nl80211_vendor_subcmds {
- 	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL = 0xc2,
- 	MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL = 0xc3,
- 	MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL = 0xc4,
-+	MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL = 0xc5,
-+	MTK_NL80211_VENDOR_SUBCMD_PHY_CAPA_CTRL= 0xc6,
- 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
- };
-
-@@ -167,6 +169,17 @@ enum mtk_vendor_attr_rfeature_ctrl {
- 		NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL - 1
- };
-
-+enum mtk_vendor_attr_hemu_ctrl {
-+	MTK_VENDOR_ATTR_HEMU_CTRL_UNSPEC,
-+
-+	MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF,
-+
-+	/* keep last */
-+	NUM_MTK_VENDOR_ATTRS_HEMU_CTRL,
-+	MTK_VENDOR_ATTR_HEMU_CTRL_MAX =
-+		NUM_MTK_VENDOR_ATTRS_HEMU_CTRL - 1
-+};
-+
- #define CSI_MAX_COUNT 256
- #define ETH_ALEN 6
-
-diff --git a/src/drivers/driver.h b/src/drivers/driver.h
-index fc96fef..298dbf3 100644
---- a/src/drivers/driver.h
-+++ b/src/drivers/driver.h
-@@ -1622,6 +1622,11 @@ struct wpa_driver_ap_params {
- 	 * Unsolicited broadcast Probe Response template length
- 	 */
- 	size_t unsol_bcast_probe_resp_tmpl_len;
-+
-+	/**
-+	 * hemu onoff=<val> (bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0))
-+	 */
-+	u8 hemu_onoff;
- };
-
- struct wpa_driver_mesh_bss_params {
-@@ -4675,6 +4680,13 @@ struct wpa_driver_ops {
- #endif /* CONFIG_TESTING_OPTIONS */
- 	int (*configure_edcca_threshold)(void *priv, const u8 edcca_enable,
- 				  const s8 edcca_compensation);
-+
-+	/**
-+	 * hemu_ctrl - ctrl on off for UL/DL MURU
-+	 * @priv: Private driver interface data
-+	 *
-+	 */
-+	int (*hemu_ctrl)(void *priv, u8 hemu_onoff);
- };
-
- /**
-diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
-index b1e7b16..089c24a 100644
---- a/src/drivers/driver_nl80211.c
-+++ b/src/drivers/driver_nl80211.c
-@@ -12287,6 +12287,39 @@ fail:
- }
-
-
-+#ifdef CONFIG_IEEE80211AX
-+static int nl80211_hemu_muruonoff(void *priv, u8 hemu_onoff)
-+{
-+	struct i802_bss *bss = priv;
-+	struct wpa_driver_nl80211_data *drv = bss->drv;
-+	struct nl_msg *msg;
-+	struct nlattr *data;
-+	int ret;
-+
-+	if (!drv->mtk_hemu_vendor_cmd_avail) {
-+		wpa_printf(MSG_INFO,
-+			   "nl80211: Driver does not support setting hemu control");
-+		return 0;
-+	}
-+
-+	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
-+		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
-+		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL) ||
-+		!(data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
-+		nla_put_u8(msg, MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF, hemu_onoff)) {
-+		nlmsg_free(msg);
-+		return -ENOBUFS;
-+	}
-+	nla_nest_end(msg, data);
-+	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
-+	if(ret){
-+		wpa_printf(MSG_ERROR, "Failed to set hemu_onoff. ret=%d (%s)", ret, strerror(-ret));
-+	}
-+	return ret;
-+}
-+#endif /* CONFIG_IEEE80211AX */
-+
-+
- #ifdef CONFIG_DPP
- static int nl80211_dpp_listen(void *priv, bool enable)
- {
-@@ -12531,6 +12564,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
- 	.update_connect_params = nl80211_update_connection_params,
- 	.send_external_auth_status = nl80211_send_external_auth_status,
- 	.set_4addr_mode = nl80211_set_4addr_mode,
-+	.hemu_ctrl = nl80211_hemu_muruonoff,
- #ifdef CONFIG_DPP
- 	.dpp_listen = nl80211_dpp_listen,
- #endif /* CONFIG_DPP */
-diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
-index b677907..62d9696 100644
---- a/src/drivers/driver_nl80211.h
-+++ b/src/drivers/driver_nl80211.h
-@@ -181,6 +181,7 @@ struct wpa_driver_nl80211_data {
- 	unsigned int qca_do_acs:1;
- 	unsigned int brcm_do_acs:1;
- 	unsigned int mtk_edcca_vendor_cmd_avail:1;
-+	unsigned int mtk_hemu_vendor_cmd_avail:1;
-
- 	u64 vendor_scan_cookie;
- 	u64 remain_on_chan_cookie;
-diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
-index 6c743bf..b5cd2c5 100644
---- a/src/drivers/driver_nl80211_capa.c
-+++ b/src/drivers/driver_nl80211_capa.c
-@@ -1050,6 +1050,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
- 				case MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL :
- 					drv->mtk_edcca_vendor_cmd_avail = 1;
- 					break;
-+				case MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL :
-+					drv->mtk_hemu_vendor_cmd_avail = 1;
-+					break;
- 				}
- 			}
-
---
-2.32.0
diff --git a/recipes-connectivity/hostapd/files/patches/patches.inc b/recipes-connectivity/hostapd/files/patches/patches.inc
index 415aac6..2ba3ff5 100644
--- a/recipes-connectivity/hostapd/files/patches/patches.inc
+++ b/recipes-connectivity/hostapd/files/patches/patches.inc
@@ -64,7 +64,7 @@
     file://914-Add-mtk_vendor.h.patch \
     file://915-Support-new-hostapd-configuration-edcca_enable-and-e.patch \
     file://916-Add-hostapd-command-handler-for-SET_EDCCA-GET_EDCCA-.patch \
-    file://920-hostapd-hemu-onoff-ctrl.patch \
+    file://920-Add-hostapd-HEMU-SET-GET-control.patch \
     file://990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch \
     file://991-fix-compile.patch \
     file://992-openssl-include-rsa.patch \
diff --git a/recipes-connectivity/wpa-supplicant/files/patches/920-Add-hostapd-HEMU-SET-GET-control.patch b/recipes-connectivity/wpa-supplicant/files/patches/920-Add-hostapd-HEMU-SET-GET-control.patch
new file mode 100644
index 0000000..b31d521
--- /dev/null
+++ b/recipes-connectivity/wpa-supplicant/files/patches/920-Add-hostapd-HEMU-SET-GET-control.patch
@@ -0,0 +1,450 @@
+From f815c6befdd5b0185ae476d649e3eff58d6e9b69 Mon Sep 17 00:00:00 2001
+From: TomLiu <tomml.liu@mediatek.com>
+Date: Tue, 9 Aug 2022 10:23:44 -0700
+Subject: [PATCH] [PATCH-920]Add hostapd HEMU SET/GET control
+
+---
+ hostapd/config_file.c             |   9 +++
+ hostapd/ctrl_iface.c              |  62 +++++++++++++++++
+ hostapd/hostapd_cli.c             |  18 +++++
+ src/ap/ap_config.c                |   1 +
+ src/ap/ap_config.h                |   1 +
+ src/ap/ap_drv_ops.c               |  14 ++++
+ src/ap/ap_drv_ops.h               |   2 +
+ src/ap/hostapd.c                  |   2 +
+ src/common/mtk_vendor.h           |  15 ++++
+ src/drivers/driver.h              |  13 ++++
+ src/drivers/driver_nl80211.c      | 110 ++++++++++++++++++++++++++++++
+ src/drivers/driver_nl80211.h      |   1 +
+ src/drivers/driver_nl80211_capa.c |   3 +
+ 13 files changed, 251 insertions(+)
+
+diff --git a/hostapd/config_file.c b/hostapd/config_file.c
+index 19a2fd5..85d58cd 100644
+--- a/hostapd/config_file.c
++++ b/hostapd/config_file.c
+@@ -3655,6 +3655,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
+ 			return 1;
+ 		}
+ 		bss->unsol_bcast_probe_resp_interval = val;
++	} else if (os_strcmp(buf, "hemu_onoff") == 0) {
++		int val = atoi(pos);
++		if (val < 0 || val > 15) {
++			wpa_printf(MSG_ERROR,
++				   "Line %d: invalid hemu_onoff value",
++				   line);
++			return 1;
++		}
++		conf->hemu_onoff = val;
+ #endif /* CONFIG_IEEE80211AX */
+ 	} else if (os_strcmp(buf, "max_listen_interval") == 0) {
+ 		bss->max_listen_interval = atoi(pos);
+diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
+index 5e9af7f..5df5c84 100644
+--- a/hostapd/ctrl_iface.c
++++ b/hostapd/ctrl_iface.c
+@@ -3399,6 +3399,63 @@ hostapd_ctrl_iface_apply_edcca(struct hostapd_data *hapd, char *buf,
+ }
+ 
+ 
++static int
++hostapd_ctrl_iface_set_hemu(struct hostapd_data *hapd, char *cmd,
++					 char *buf, size_t buflen)
++{
++	char *pos, *config, *value;
++	config = cmd;
++	pos = os_strchr(config, ' ');
++	if (pos == NULL)
++		return -1;
++	*pos++ = '\0';
++
++	if(pos == NULL)
++		return -1;
++	value = pos;
++
++	if (os_strcmp(config, "onoff") == 0) {
++		int hemu = atoi(value);
++		if (hemu < 0 || hemu > 15) {
++			wpa_printf(MSG_ERROR, "Invalid value for hemu");
++			return -1;
++		}
++		hapd->iconf->hemu_onoff = (u8) hemu;
++	} else {
++		wpa_printf(MSG_ERROR,
++			"Unsupported parameter %s for SET_HEMU", config);
++		return -1;
++	}
++
++	if(hostapd_drv_hemu_ctrl(hapd) == 0) {
++		return os_snprintf(buf, buflen, "OK\n");
++	} else {
++		return -1;
++	}
++}
++
++
++static int
++hostapd_ctrl_iface_get_hemu(struct hostapd_data *hapd, char *buf,
++					 size_t buflen)
++{
++	u8 hemu_onoff;
++	char *pos, *end;
++
++	pos = buf;
++	end = buf + buflen;
++
++	if (hostapd_drv_hemu_dump(hapd, &hemu_onoff) == 0) {
++		hapd->iconf->hemu_onoff = hemu_onoff;
++		return os_snprintf(pos, end - pos, "[hostapd_cli] = UL MU-MIMO: %d, DL MU-MIMO: %d, UL OFDMA: %d, DL OFDMA: %d\n",
++			!!(hemu_onoff&BIT(3)), !!(hemu_onoff&BIT(2)), !!(hemu_onoff&BIT(1)), !!(hemu_onoff&BIT(0)));
++	} else {
++		wpa_printf(MSG_INFO, "ctrl iface failed to call");
++		return -1;
++	}
++}
++
++
+ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
+ 					      char *buf, char *reply,
+ 					      int reply_size,
+@@ -3939,6 +3996,11 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
+ 		reply_len = hostapd_ctrl_iface_get_edcca(hapd, reply, reply_size);
+ 	} else if (os_strncmp(buf, "APPLY_EDCCA", 11) == 0) {
+ 		reply_len = hostapd_ctrl_iface_apply_edcca(hapd, reply, reply_size);
++	} else if (os_strncmp(buf, "SET_HEMU ", 9) == 0) {
++		reply_len = hostapd_ctrl_iface_set_hemu(hapd, buf+9, reply,
++							  reply_size);
++	} else if (os_strncmp(buf, "GET_HEMU", 8) == 0) {
++		reply_len = hostapd_ctrl_iface_get_hemu(hapd, reply, reply_size);
+ 	} else {
+ 		os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
+ 		reply_len = 16;
+diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
+index 68133d4..41d244a 100644
+--- a/hostapd/hostapd_cli.c
++++ b/hostapd/hostapd_cli.c
+@@ -1380,6 +1380,20 @@ static int hostapd_cli_cmd_driver_flags(struct wpa_ctrl *ctrl, int argc,
+ }
+ 
+ 
++static int hostapd_cli_cmd_set_hemu(struct wpa_ctrl *ctrl, int argc,
++					   char *argv[])
++{
++	return hostapd_cli_cmd(ctrl, "SET_HEMU", 1, argc, argv);
++}
++
++
++static int hostapd_cli_cmd_get_hemu(struct wpa_ctrl *ctrl, int argc,
++					   char *argv[])
++{
++	return hostapd_cli_cmd(ctrl, "GET_HEMU", 0, NULL, NULL);
++}
++
++
+ #ifdef CONFIG_DPP
+ 
+ static int hostapd_cli_cmd_dpp_qr_code(struct wpa_ctrl *ctrl, int argc,
+@@ -1696,6 +1710,10 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
+ 	  " = send FTM range request"},
+ 	{ "driver_flags", hostapd_cli_cmd_driver_flags, NULL,
+ 	  " = show supported driver flags"},
++	{ "set_hemu", hostapd_cli_cmd_set_hemu, NULL,
++		"<value> [0-15] bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0)"},
++	{ "get_hemu", hostapd_cli_cmd_get_hemu, NULL,
++		" = show hemu onoff value in 0-15 bitmap"},
+ #ifdef CONFIG_DPP
+ 	{ "dpp_qr_code", hostapd_cli_cmd_dpp_qr_code, NULL,
+ 	  "report a scanned DPP URI from a QR Code" },
+diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
+index 427f16e..79e215f 100644
+--- a/src/ap/ap_config.c
++++ b/src/ap/ap_config.c
+@@ -280,6 +280,7 @@ struct hostapd_config * hostapd_config_defaults(void)
+ 	conf->he_6ghz_max_ampdu_len_exp = 7;
+ 	conf->he_6ghz_rx_ant_pat = 1;
+ 	conf->he_6ghz_tx_ant_pat = 1;
++	conf->hemu_onoff = 13;
+ #endif /* CONFIG_IEEE80211AX */
+ 
+ 	/* The third octet of the country string uses an ASCII space character
+diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
+index 9bbe7eb..737cc2f 100644
+--- a/src/ap/ap_config.h
++++ b/src/ap/ap_config.h
+@@ -1111,6 +1111,7 @@ struct hostapd_config {
+ 	u8 he_6ghz_rx_ant_pat;
+ 	u8 he_6ghz_tx_ant_pat;
+ 	u8 he_6ghz_reg_pwr_type;
++	u8 hemu_onoff;
+ #endif /* CONFIG_IEEE80211AX */
+ 
+ 	/* VHT enable/disable config from CHAN_SWITCH */
+diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
+index b8b98e4..3de6748 100644
+--- a/src/ap/ap_drv_ops.c
++++ b/src/ap/ap_drv_ops.c
+@@ -1021,3 +1021,17 @@ int hostapd_drv_configure_edcca_threshold(struct hostapd_data *hapd)
+ 				hapd->iconf->edcca_enable,
+ 				hapd->iconf->edcca_compensation);
+ }
++
++int hostapd_drv_hemu_ctrl(struct hostapd_data *hapd)
++{
++	if (!hapd->driver || !hapd->driver->hemu_ctrl)
++		return 0;
++	return hapd->driver->hemu_ctrl(hapd->drv_priv, hapd->iconf->hemu_onoff);
++}
++
++int hostapd_drv_hemu_dump(struct hostapd_data *hapd, u8 *hemu_onoff)
++{
++	if (!hapd->driver || !hapd->driver->hemu_dump)
++		return 0;
++	return hapd->driver->hemu_dump(hapd->drv_priv, hemu_onoff);
++}
+diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
+index f8fef19..5bcb225 100644
+--- a/src/ap/ap_drv_ops.h
++++ b/src/ap/ap_drv_ops.h
+@@ -139,6 +139,8 @@ int hostapd_drv_update_dh_ie(struct hostapd_data *hapd, const u8 *peer,
+ 			     u16 reason_code, const u8 *ie, size_t ielen);
+ int hostapd_drv_dpp_listen(struct hostapd_data *hapd, bool enable);
+ int hostapd_drv_configure_edcca_threshold(struct hostapd_data *hapd);
++int hostapd_drv_hemu_ctrl(struct hostapd_data *hapd);
++int hostapd_drv_hemu_dump(struct hostapd_data *hapd, u8 *hemu_onoff);
+ 
+ #include "drivers/driver.h"
+ 
+diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
+index c1edaab..3b752fc 100644
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -2297,6 +2297,8 @@ dfs_offload:
+ 
+ 	if (hostapd_drv_configure_edcca_threshold(hapd) < 0)
+ 		goto fail;
++	if (hostapd_drv_hemu_ctrl(hapd) < 0)
++		goto fail;
+ 
+ 	wpa_printf(MSG_DEBUG, "%s: Setup of interface done.",
+ 		   iface->bss[0]->conf->iface);
+diff --git a/src/common/mtk_vendor.h b/src/common/mtk_vendor.h
+index 528387f..5c8f179 100644
+--- a/src/common/mtk_vendor.h
++++ b/src/common/mtk_vendor.h
+@@ -10,6 +10,8 @@ enum mtk_nl80211_vendor_subcmds {
+ 	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL = 0xc2,
+ 	MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL = 0xc3,
+ 	MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL = 0xc4,
++	MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL = 0xc5,
++	MTK_NL80211_VENDOR_SUBCMD_PHY_CAPA_CTRL= 0xc6,
+ 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
+ };
+ 
+@@ -167,6 +169,19 @@ enum mtk_vendor_attr_rfeature_ctrl {
+ 		NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL - 1
+ };
+ 
++enum mtk_vendor_attr_hemu_ctrl {
++	MTK_VENDOR_ATTR_HEMU_CTRL_UNSPEC,
++
++	MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF,
++	MTK_VENDOR_ATTR_HEMU_CTRL_DUMP,
++
++	/* keep last */
++	NUM_MTK_VENDOR_ATTRS_HEMU_CTRL,
++	MTK_VENDOR_ATTR_HEMU_CTRL_MAX =
++		NUM_MTK_VENDOR_ATTRS_HEMU_CTRL - 1
++};
++
++
+ #define CSI_MAX_COUNT 256
+ #define ETH_ALEN 6
+ 
+diff --git a/src/drivers/driver.h b/src/drivers/driver.h
+index fc96fef..6cc7e9b 100644
+--- a/src/drivers/driver.h
++++ b/src/drivers/driver.h
+@@ -1622,6 +1622,11 @@ struct wpa_driver_ap_params {
+ 	 * Unsolicited broadcast Probe Response template length
+ 	 */
+ 	size_t unsol_bcast_probe_resp_tmpl_len;
++
++	/**
++	 * hemu onoff=<val> (bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0))
++	 */
++	u8 hemu_onoff;
+ };
+ 
+ struct wpa_driver_mesh_bss_params {
+@@ -4675,6 +4680,14 @@ struct wpa_driver_ops {
+ #endif /* CONFIG_TESTING_OPTIONS */
+ 	int (*configure_edcca_threshold)(void *priv, const u8 edcca_enable,
+ 				  const s8 edcca_compensation);
++
++	/**
++	 * hemu_ctrl - ctrl on off for UL/DL MURU
++	 * @priv: Private driver interface data
++	 *
++	 */
++	 int (*hemu_ctrl)(void *priv, u8 hemu_onoff);
++	 int (*hemu_dump)(void *priv, u8 *hemu_onoff);
+ };
+ 
+ /**
+diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
+index b1e7b16..021a593 100644
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -12287,6 +12287,114 @@ fail:
+ }
+ 
+ 
++#ifdef CONFIG_IEEE80211AX
++static int nl80211_hemu_muruonoff(void *priv, u8 hemu_onoff)
++{
++	struct i802_bss *bss = priv;
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++	struct nl_msg *msg;
++	struct nlattr *data;
++	int ret;
++
++	if (!drv->mtk_hemu_vendor_cmd_avail) {
++		wpa_printf(MSG_INFO,
++			   "nl80211: Driver does not support setting hemu control");
++		return 0;
++	}
++
++	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL) ||
++		!(data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
++		nla_put_u8(msg, MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF, hemu_onoff)) {
++		nlmsg_free(msg);
++		return -ENOBUFS;
++	}
++	nla_nest_end(msg, data);
++	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
++	if(ret){
++		wpa_printf(MSG_ERROR, "Failed to set hemu_onoff. ret=%d (%s)", ret, strerror(-ret));
++	}
++	return ret;
++}
++
++
++static int hemu_dump_handler(struct nl_msg *msg, void *arg)
++{
++	u8 *hemu_onoff = (u8 *) arg;
++	struct nlattr *tb[NL80211_ATTR_MAX + 1];
++	struct nlattr *tb_vendor[MTK_VENDOR_ATTR_HEMU_CTRL_MAX + 1];
++	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
++	struct nlattr *nl_vend, *attr;
++
++	static const struct nla_policy
++	hemu_ctrl_policy[NUM_MTK_VENDOR_ATTRS_HEMU_CTRL + 1] = {
++		[MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF] = {.type = NLA_U8 },
++		[MTK_VENDOR_ATTR_HEMU_CTRL_DUMP] = {.type = NLA_U8 },
++	};
++
++	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
++			genlmsg_attrlen(gnlh, 0), NULL);
++
++	nl_vend = tb[NL80211_ATTR_VENDOR_DATA];
++	if (!nl_vend)
++		return NL_SKIP;
++
++	nla_parse(tb_vendor, MTK_VENDOR_ATTR_HEMU_CTRL_MAX,
++		  nla_data(nl_vend), nla_len(nl_vend), NULL);
++
++	attr = tb_vendor[MTK_VENDOR_ATTR_HEMU_CTRL_DUMP];
++	if (!attr) {
++		wpa_printf(MSG_ERROR, "nl80211: cannot find MTK_VENDOR_ATTR_HEMU_CTRL_DUMP");
++		return NL_SKIP;
++	}
++
++	*hemu_onoff = nla_get_u8(attr);
++	wpa_printf(MSG_DEBUG, "nla_get hemu_onoff: %d\n", *hemu_onoff);
++
++	return 0;
++}
++
++static int nl80211_hemu_dump(void *priv, u8 *hemu_onoff)
++{
++	struct i802_bss *bss = priv;
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++	struct nl_msg *msg;
++	struct nlattr *attr;
++	int ret;
++
++	if (!drv->mtk_hemu_vendor_cmd_avail) {
++		wpa_printf(MSG_INFO,
++			   "nl80211: Driver does not support setting hemu control");
++		return 0;
++	}
++
++	if (!(msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_VENDOR)) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
++		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL)) {
++		nlmsg_free(msg);
++		return -ENOBUFS;
++	}
++
++  attr = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA);
++	if (!attr) {
++		nlmsg_free(msg);
++		return -1;
++	}
++
++	nla_nest_end(msg, attr);
++
++	ret = send_and_recv_msgs(drv, msg, hemu_dump_handler, hemu_onoff, NULL, NULL);
++
++	if(ret){
++		wpa_printf(MSG_ERROR, "Failed to get hemu_onoff. ret=%d (%s)", ret, strerror(-ret));
++	}
++
++	return ret;
++}
++#endif /* CONFIG_IEEE80211AX */
++
++
+ #ifdef CONFIG_DPP
+ static int nl80211_dpp_listen(void *priv, bool enable)
+ {
+@@ -12531,6 +12639,8 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
+ 	.update_connect_params = nl80211_update_connection_params,
+ 	.send_external_auth_status = nl80211_send_external_auth_status,
+ 	.set_4addr_mode = nl80211_set_4addr_mode,
++	.hemu_ctrl = nl80211_hemu_muruonoff,
++	.hemu_dump = nl80211_hemu_dump,
+ #ifdef CONFIG_DPP
+ 	.dpp_listen = nl80211_dpp_listen,
+ #endif /* CONFIG_DPP */
+diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
+index b677907..62d9696 100644
+--- a/src/drivers/driver_nl80211.h
++++ b/src/drivers/driver_nl80211.h
+@@ -181,6 +181,7 @@ struct wpa_driver_nl80211_data {
+ 	unsigned int qca_do_acs:1;
+ 	unsigned int brcm_do_acs:1;
+ 	unsigned int mtk_edcca_vendor_cmd_avail:1;
++	unsigned int mtk_hemu_vendor_cmd_avail:1;
+ 
+ 	u64 vendor_scan_cookie;
+ 	u64 remain_on_chan_cookie;
+diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
+index 6c743bf..b5cd2c5 100644
+--- a/src/drivers/driver_nl80211_capa.c
++++ b/src/drivers/driver_nl80211_capa.c
+@@ -1050,6 +1050,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
+ 				case MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL :
+ 					drv->mtk_edcca_vendor_cmd_avail = 1;
+ 					break;
++				case MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL :
++					drv->mtk_hemu_vendor_cmd_avail = 1;
++					break;
+ 				}
+ 			}
+ 
+-- 
+2.32.0
+
diff --git a/recipes-connectivity/wpa-supplicant/files/patches/920-hostapd-hemu-onoff-ctrl.patch b/recipes-connectivity/wpa-supplicant/files/patches/920-hostapd-hemu-onoff-ctrl.patch
deleted file mode 100755
index 1e18c72..0000000
--- a/recipes-connectivity/wpa-supplicant/files/patches/920-hostapd-hemu-onoff-ctrl.patch
+++ /dev/null
@@ -1,218 +0,0 @@
-From 390f9431f5a6f8001f6822a3aea4c5e08743806d Mon Sep 17 00:00:00 2001
-From: TomLiu <tomml.liu@mediatek.com>
-Date: Thu, 14 Jul 2022 13:43:06 -0700
-Subject: [PATCH-920]Add hemu hostapd vendor command
-
----
- hostapd/config_file.c             |  9 ++++++++
- src/ap/ap_config.h                |  1 +
- src/ap/ap_drv_ops.c               |  7 ++++++
- src/ap/beacon.c                   |  2 ++
- src/common/mtk_vendor.h           | 13 +++++++++++
- src/drivers/driver.h              | 12 +++++++++++
- src/drivers/driver_nl80211.c      | 36 ++++++++++++++++++++++++++++++-
- src/drivers/driver_nl80211.h      |  1 +
- src/drivers/driver_nl80211_capa.c |  3 +++
- 9 files changed, 83 insertions(+), 1 deletion(-)
-
-diff --git a/hostapd/config_file.c b/hostapd/config_file.c
-index 19a2fd5..85d58cd 100644
---- a/hostapd/config_file.c
-+++ b/hostapd/config_file.c
-@@ -3655,6 +3655,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
- 			return 1;
- 		}
- 		bss->unsol_bcast_probe_resp_interval = val;
-+	} else if (os_strcmp(buf, "hemu_onoff") == 0) {
-+		int val = atoi(pos);
-+		if (val < 0 || val > 15) {
-+			wpa_printf(MSG_ERROR,
-+				   "Line %d: invalid hemu_onoff value",
-+				   line);
-+			return 1;
-+		}
-+		conf->hemu_onoff = val;
- #endif /* CONFIG_IEEE80211AX */
- 	} else if (os_strcmp(buf, "max_listen_interval") == 0) {
- 		bss->max_listen_interval = atoi(pos);
-diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
-index 9bbe7eb..737cc2f 100644
---- a/src/ap/ap_config.h
-+++ b/src/ap/ap_config.h
-@@ -1111,6 +1111,7 @@ struct hostapd_config {
- 	u8 he_6ghz_rx_ant_pat;
- 	u8 he_6ghz_tx_ant_pat;
- 	u8 he_6ghz_reg_pwr_type;
-+	u8 hemu_onoff;
- #endif /* CONFIG_IEEE80211AX */
-
- 	/* VHT enable/disable config from CHAN_SWITCH */
-diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
-index b8b98e4..517d2a5 100644
---- a/src/ap/ap_drv_ops.c
-+++ b/src/ap/ap_drv_ops.c
-@@ -1021,3 +1021,10 @@ int hostapd_drv_configure_edcca_threshold(struct hostapd_data *hapd)
- 				hapd->iconf->edcca_enable,
- 				hapd->iconf->edcca_compensation);
- }
-+
-+int hostapd_drv_hemu_ctrl(struct hostapd_data *hapd)
-+{
-+	if (!hapd->driver || !hapd->driver->hemu_ctrl)
-+		return 0;
-+	return hapd->driver->hemu_ctrl(hapd->drv_priv, hapd->iconf->hemu_onoff);
-+}
-diff --git a/src/ap/beacon.c b/src/ap/beacon.c
-index 575c92f..7170711 100644
---- a/src/ap/beacon.c
-+++ b/src/ap/beacon.c
-@@ -1976,6 +1976,8 @@ static int __ieee802_11_set_beacon(struct hostapd_data *hapd)
- 		params.freq = &freq;
-
- 	res = hostapd_drv_set_ap(hapd, &params);
-+	if (hostapd_drv_hemu_ctrl(hapd) < 0)
-+		goto fail;
- 	hostapd_free_ap_extra_ies(hapd, beacon, proberesp, assocresp);
- 	if (res)
- 		wpa_printf(MSG_ERROR, "Failed to set beacon parameters");
-diff --git a/src/common/mtk_vendor.h b/src/common/mtk_vendor.h
-index 528387f..6a0e60a 100644
---- a/src/common/mtk_vendor.h
-+++ b/src/common/mtk_vendor.h
-@@ -10,6 +10,8 @@ enum mtk_nl80211_vendor_subcmds {
- 	MTK_NL80211_VENDOR_SUBCMD_CSI_CTRL = 0xc2,
- 	MTK_NL80211_VENDOR_SUBCMD_RFEATURE_CTRL = 0xc3,
- 	MTK_NL80211_VENDOR_SUBCMD_WIRELESS_CTRL = 0xc4,
-+	MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL = 0xc5,
-+	MTK_NL80211_VENDOR_SUBCMD_PHY_CAPA_CTRL= 0xc6,
- 	MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL = 0xc7,
- };
-
-@@ -167,6 +169,17 @@ enum mtk_vendor_attr_rfeature_ctrl {
- 		NUM_MTK_VENDOR_ATTRS_RFEATURE_CTRL - 1
- };
-
-+enum mtk_vendor_attr_hemu_ctrl {
-+	MTK_VENDOR_ATTR_HEMU_CTRL_UNSPEC,
-+
-+	MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF,
-+
-+	/* keep last */
-+	NUM_MTK_VENDOR_ATTRS_HEMU_CTRL,
-+	MTK_VENDOR_ATTR_HEMU_CTRL_MAX =
-+		NUM_MTK_VENDOR_ATTRS_HEMU_CTRL - 1
-+};
-+
- #define CSI_MAX_COUNT 256
- #define ETH_ALEN 6
-
-diff --git a/src/drivers/driver.h b/src/drivers/driver.h
-index fc96fef..298dbf3 100644
---- a/src/drivers/driver.h
-+++ b/src/drivers/driver.h
-@@ -1622,6 +1622,11 @@ struct wpa_driver_ap_params {
- 	 * Unsolicited broadcast Probe Response template length
- 	 */
- 	size_t unsol_bcast_probe_resp_tmpl_len;
-+
-+	/**
-+	 * hemu onoff=<val> (bitmap- UL MU-MIMO(bit3), DL MU-MIMO(bit2), UL OFDMA(bit1), DL OFDMA(bit0))
-+	 */
-+	u8 hemu_onoff;
- };
-
- struct wpa_driver_mesh_bss_params {
-@@ -4675,6 +4680,13 @@ struct wpa_driver_ops {
- #endif /* CONFIG_TESTING_OPTIONS */
- 	int (*configure_edcca_threshold)(void *priv, const u8 edcca_enable,
- 				  const s8 edcca_compensation);
-+
-+	/**
-+	 * hemu_ctrl - ctrl on off for UL/DL MURU
-+	 * @priv: Private driver interface data
-+	 *
-+	 */
-+	int (*hemu_ctrl)(void *priv, u8 hemu_onoff);
- };
-
- /**
-diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
-index b1e7b16..089c24a 100644
---- a/src/drivers/driver_nl80211.c
-+++ b/src/drivers/driver_nl80211.c
-@@ -12287,6 +12287,39 @@ fail:
- }
-
-
-+#ifdef CONFIG_IEEE80211AX
-+static int nl80211_hemu_muruonoff(void *priv, u8 hemu_onoff)
-+{
-+	struct i802_bss *bss = priv;
-+	struct wpa_driver_nl80211_data *drv = bss->drv;
-+	struct nl_msg *msg;
-+	struct nlattr *data;
-+	int ret;
-+
-+	if (!drv->mtk_hemu_vendor_cmd_avail) {
-+		wpa_printf(MSG_INFO,
-+			   "nl80211: Driver does not support setting hemu control");
-+		return 0;
-+	}
-+
-+	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) ||
-+		nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_MTK) ||
-+		nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL) ||
-+		!(data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
-+		nla_put_u8(msg, MTK_VENDOR_ATTR_HEMU_CTRL_ONOFF, hemu_onoff)) {
-+		nlmsg_free(msg);
-+		return -ENOBUFS;
-+	}
-+	nla_nest_end(msg, data);
-+	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
-+	if(ret){
-+		wpa_printf(MSG_ERROR, "Failed to set hemu_onoff. ret=%d (%s)", ret, strerror(-ret));
-+	}
-+	return ret;
-+}
-+#endif /* CONFIG_IEEE80211AX */
-+
-+
- #ifdef CONFIG_DPP
- static int nl80211_dpp_listen(void *priv, bool enable)
- {
-@@ -12531,6 +12564,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
- 	.update_connect_params = nl80211_update_connection_params,
- 	.send_external_auth_status = nl80211_send_external_auth_status,
- 	.set_4addr_mode = nl80211_set_4addr_mode,
-+	.hemu_ctrl = nl80211_hemu_muruonoff,
- #ifdef CONFIG_DPP
- 	.dpp_listen = nl80211_dpp_listen,
- #endif /* CONFIG_DPP */
-diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
-index b677907..62d9696 100644
---- a/src/drivers/driver_nl80211.h
-+++ b/src/drivers/driver_nl80211.h
-@@ -181,6 +181,7 @@ struct wpa_driver_nl80211_data {
- 	unsigned int qca_do_acs:1;
- 	unsigned int brcm_do_acs:1;
- 	unsigned int mtk_edcca_vendor_cmd_avail:1;
-+	unsigned int mtk_hemu_vendor_cmd_avail:1;
-
- 	u64 vendor_scan_cookie;
- 	u64 remain_on_chan_cookie;
-diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
-index 6c743bf..b5cd2c5 100644
---- a/src/drivers/driver_nl80211_capa.c
-+++ b/src/drivers/driver_nl80211_capa.c
-@@ -1050,6 +1050,9 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg)
- 				case MTK_NL80211_VENDOR_SUBCMD_EDCCA_CTRL :
- 					drv->mtk_edcca_vendor_cmd_avail = 1;
- 					break;
-+				case MTK_NL80211_VENDOR_SUBCMD_HEMU_CTRL :
-+					drv->mtk_hemu_vendor_cmd_avail = 1;
-+					break;
- 				}
- 			}
-
---
-2.32.0
diff --git a/recipes-connectivity/wpa-supplicant/files/patches/patches.inc b/recipes-connectivity/wpa-supplicant/files/patches/patches.inc
index 415aac6..2ba3ff5 100644
--- a/recipes-connectivity/wpa-supplicant/files/patches/patches.inc
+++ b/recipes-connectivity/wpa-supplicant/files/patches/patches.inc
@@ -64,7 +64,7 @@
     file://914-Add-mtk_vendor.h.patch \
     file://915-Support-new-hostapd-configuration-edcca_enable-and-e.patch \
     file://916-Add-hostapd-command-handler-for-SET_EDCCA-GET_EDCCA-.patch \
-    file://920-hostapd-hemu-onoff-ctrl.patch \
+    file://920-Add-hostapd-HEMU-SET-GET-control.patch \
     file://990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch \
     file://991-fix-compile.patch \
     file://992-openssl-include-rsa.patch \
diff --git a/recipes-kernel/linux-mt76/files/patches/1009-mt76-mt7915-add-fw_version-dump.patch b/recipes-kernel/linux-mt76/files/patches/1009-mt76-mt7915-add-fw_version-dump.patch
new file mode 100644
index 0000000..af4f56d
--- /dev/null
+++ b/recipes-kernel/linux-mt76/files/patches/1009-mt76-mt7915-add-fw_version-dump.patch
@@ -0,0 +1,100 @@
+From 611d781ff9e39b474e71a59ec0ea7761a4274456 Mon Sep 17 00:00:00 2001
+From: Evelyn Tsai <evelyn.tsai@mediatek.com>
+Date: Wed, 17 Aug 2022 13:40:24 +0800
+Subject: [PATCH] mt76: mt7915: add fw_version dump
+
+---
+ mt76.h               |  4 ++++
+ mt76_connac_mcu.c    |  9 +++++++++
+ mt7915/mtk_debugfs.c | 19 +++++++++++++++++++
+ 3 files changed, 32 insertions(+)
+
+diff --git a/mt76.h b/mt76.h
+index 8325409a..9a0f0708 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -803,6 +803,10 @@ struct mt76_dev {
+ 		struct mt76_usb usb;
+ 		struct mt76_sdio sdio;
+ 	};
++
++	struct mt76_connac2_patch_hdr *patch_hdr;
++	struct mt76_connac2_fw_trailer *wm_hdr;
++	struct mt76_connac2_fw_trailer *wa_hdr;
+ };
+ 
+ struct mt76_power_limits {
+diff --git a/mt76_connac_mcu.c b/mt76_connac_mcu.c
+index 261181dc..47b2bce6 100644
+--- a/mt76_connac_mcu.c
++++ b/mt76_connac_mcu.c
+@@ -2883,6 +2883,9 @@ int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm,
+ 		goto out;
+ 	}
+ 
++	dev->wm_hdr = devm_kzalloc(dev->dev, sizeof(*hdr), GFP_KERNEL);
++	memcpy(dev->wm_hdr, hdr, sizeof(*hdr));
++
+ 	release_firmware(fw);
+ 
+ 	if (!fw_wa)
+@@ -2908,6 +2911,9 @@ int mt76_connac2_load_ram(struct mt76_dev *dev, const char *fw_wm,
+ 		goto out;
+ 	}
+ 
++	dev->wa_hdr = devm_kzalloc(dev->dev, sizeof(*hdr), GFP_KERNEL);
++	memcpy(dev->wa_hdr, hdr, sizeof(*hdr));
++
+ 	snprintf(dev->hw->wiphy->fw_version,
+ 		 sizeof(dev->hw->wiphy->fw_version),
+ 		 "%.10s-%.15s", hdr->fw_ver, hdr->build_date);
+@@ -2978,6 +2984,9 @@ int mt76_connac2_load_patch(struct mt76_dev *dev, const char *fw_name)
+ 	dev_info(dev->dev, "HW/SW Version: 0x%x, Build Time: %.16s\n",
+ 		 be32_to_cpu(hdr->hw_sw_ver), hdr->build_date);
+ 
++	dev->patch_hdr = devm_kzalloc(dev->dev, sizeof(*hdr), GFP_KERNEL);
++	memcpy(dev->patch_hdr, hdr, sizeof(*hdr));
++
+ 	for (i = 0; i < be32_to_cpu(hdr->desc.n_region); i++) {
+ 		struct mt76_connac2_patch_sec *sec;
+ 		u32 len, addr, mode;
+diff --git a/mt7915/mtk_debugfs.c b/mt7915/mtk_debugfs.c
+index 222268fc..cfd0b303 100644
+--- a/mt7915/mtk_debugfs.c
++++ b/mt7915/mtk_debugfs.c
+@@ -2721,6 +2721,22 @@ static int mt7915_agginfo_read_band1(struct seq_file *s, void *data)
+ 	return 0;
+ }
+ 
++static int mt7915_dump_version(struct seq_file *s, void *data)
++{
++	struct mt7915_dev *dev = dev_get_drvdata(s->private);
++	struct mt76_dev *mdev = NULL;
++	seq_printf(s, "Version: 2.2.3.0\n");
++
++	if (!test_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state))
++		return 0;
++
++	mdev = &dev->mt76;
++	seq_printf(s, "Rom Patch Build Time: %.16s\n", mdev->patch_hdr->build_date);
++	seq_printf(s, "WM Patch Build Time: %.16s\n", mdev->wm_hdr->build_date);
++	seq_printf(s, "WA Patch Build Time: %.16s\n", mdev->wa_hdr->build_date);
++	return 0;
++}
++
+ /*usage: <en> <num> <len>
+ 	en: BIT(16) 0: sw amsdu  1: hw amsdu
+ 	num: GENMASK(15, 8) range 1-8
+@@ -2952,6 +2968,9 @@ int mt7915_mtk_init_debugfs(struct mt7915_phy *phy, struct dentry *dir)
+ 
+ 	debugfs_create_u8("sku_disable", 0600, dir, &dev->dbg.sku_disable);
+ 
++	debugfs_create_devm_seqfile(dev->mt76.dev, "fw_version", dir,
++				    mt7915_dump_version);
++
+ 	return 0;
+ }
+ #endif
+-- 
+2.36.1
+
diff --git a/recipes-kernel/linux-mt76/files/patches/3002-mt76-add-wed-rx-support.patch b/recipes-kernel/linux-mt76/files/patches/3002-mt76-add-wed-rx-support.patch
index 49992bc..92dc07b 100644
--- a/recipes-kernel/linux-mt76/files/patches/3002-mt76-add-wed-rx-support.patch
+++ b/recipes-kernel/linux-mt76/files/patches/3002-mt76-add-wed-rx-support.patch
@@ -19,7 +19,7 @@
  mt7915/dma.c      |  25 +++--
  mt7915/init.c     |   9 ++
  mt7915/mac.c      | 103 ++++++++++++++++++-
- mt7915/main.c     |  25 ++++-
+ mt7915/main.c     |  26 ++++-
  mt7915/mcu.c      |  14 ++-
  mt7915/mcu.h      |   1 +
  mt7915/mmio.c     |  26 ++++-
@@ -29,7 +29,7 @@
  mt7921/mt7921.h   |   4 +-
  mt7921/pci_mac.c  |   4 +-
  tx.c              |  34 +++++++
- 24 files changed, 504 insertions(+), 81 deletions(-)
+ 24 files changed, 505 insertions(+), 81 deletions(-)
 
 diff --git a/dma.c b/dma.c
 index 03ee9109..4d4d4046 100644
@@ -1016,7 +1016,7 @@
 +	bool wed_wds = false;
  
 -	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7915_WTBL_STA);
-+	if (mtk_wed_device_active(&mdev->mmio.wed))
++	if (mtk_wed_device_active(&mdev->mmio.wed) && is_mt7986(mdev))
 +		wed_wds = !!test_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags);
 +
 +	if (wed_wds)
@@ -1026,11 +1026,12 @@
  	if (idx < 0)
  		return -ENOSPC;
  
-@@ -1107,6 +1114,13 @@ static void mt7915_sta_set_4addr(struct ieee80211_hw *hw,
+@@ -1107,6 +1114,14 @@ static void mt7915_sta_set_4addr(struct ieee80211_hw *hw,
  	else
  		clear_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags);
  
 +	if (mtk_wed_device_active(&dev->mt76.mmio.wed) &&
++	    is_mt7986(&dev->mt76) &&
 +	    (msta->wcid.idx < MT7915_WTBL_WDS_START ||
 +	     msta->wcid.idx > MT7915_WTBL_WDS_END)) {
 +		mt7915_sta_remove(hw, vif, sta);
@@ -1040,7 +1041,7 @@
  	mt76_connac_mcu_wtbl_update_hdr_trans(&dev->mt76, vif, sta);
  }
  
-@@ -1449,9 +1463,12 @@ mt7915_net_fill_forward_path(struct ieee80211_hw *hw,
+@@ -1449,9 +1464,12 @@ mt7915_net_fill_forward_path(struct ieee80211_hw *hw,
  	/* fw will find the wcid by dest addr */
  	if(is_mt7915(&dev->mt76))
  		path->mtk_wdma.wcid = 0xff;
diff --git a/recipes-kernel/linux-mt76/files/patches/3005-mt76-mt7915-add-statistic-for-HW-Tx-Path.patch b/recipes-kernel/linux-mt76/files/patches/3005-mt76-mt7915-add-statistic-for-HW-Tx-Path.patch
new file mode 100644
index 0000000..e99536a
--- /dev/null
+++ b/recipes-kernel/linux-mt76/files/patches/3005-mt76-mt7915-add-statistic-for-HW-Tx-Path.patch
@@ -0,0 +1,477 @@
+From c00e4e966cec137840f38cd0c7abf3f3237e9ea2 Mon Sep 17 00:00:00 2001
+From: Yi-Chia Hsieh <Yi-Chia.Hsieh@mediatek.com>
+Date: Thu, 21 Jul 2022 10:56:09 -0700
+Subject: [PATCH] mt76: mt7915: add statistic for H/W Tx Path
+
+Set PPDU_TXS2H_EN_B0/B1 to get PPDU txs.
+Add MT_PACKET_ID_WED for PPDU txs, and change MT_PACKET_ID_FIRST to 3
+to differentiate. We also need do byte cnt and pkt cnt for S/W path since
+we report it directly from mt7915_sta_statistics.
+
+---
+ mt76.h             | 49 ++++++++++++++++-------------
+ mt76_connac.h      |  5 +--
+ mt76_connac2_mac.h | 14 +++++++++
+ mt76_connac_mac.c  | 77 +++++++++++++++++++++++++++++-----------------
+ mt7915/mac.c       | 12 ++++----
+ mt7915/main.c      | 16 +++++++++-
+ mt7915/mmio.c      | 22 +++++++++++++
+ mt7915/mt7915.h    |  2 --
+ mt7915/regs.h      |  4 +++
+ mt7921/mt7921.h    |  1 -
+ 10 files changed, 141 insertions(+), 61 deletions(-)
+
+diff --git a/mt76.h b/mt76.h
+index 9162213..720a419 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -267,6 +267,30 @@ DECLARE_EWMA(signal, 10, 8);
+ #define MT_WCID_TX_INFO_TXPWR_ADJ	GENMASK(25, 18)
+ #define MT_WCID_TX_INFO_SET		BIT(31)
+ 
++enum mt76_phy_type {
++	MT_PHY_TYPE_CCK,
++	MT_PHY_TYPE_OFDM,
++	MT_PHY_TYPE_HT,
++	MT_PHY_TYPE_HT_GF,
++	MT_PHY_TYPE_VHT,
++	MT_PHY_TYPE_HE_SU = 8,
++	MT_PHY_TYPE_HE_EXT_SU,
++	MT_PHY_TYPE_HE_TB,
++	MT_PHY_TYPE_HE_MU,
++	__MT_PHY_TYPE_HE_MAX,
++};
++
++struct mt76_sta_stats {
++	u64 tx_mode[__MT_PHY_TYPE_HE_MAX];
++	u64 tx_bw[4];		/* 20, 40, 80, 160 */
++	u64 tx_nss[4];		/* 1, 2, 3, 4 */
++	u64 tx_mcs[16];		/* mcs idx */
++	u64 tx_bytes;
++	u32 tx_packets;
++	u32 tx_retries;
++	u32 tx_failed;
++};
++
+ struct mt76_wcid {
+ 	struct mt76_rx_tid __rcu *aggr[IEEE80211_NUM_TIDS];
+ 
+@@ -295,6 +319,8 @@ struct mt76_wcid {
+ 
+ 	struct list_head list;
+ 	struct idr pktid;
++
++	struct mt76_sta_stats stats;
+ };
+ 
+ struct mt76_txq {
+@@ -341,7 +367,8 @@ struct mt76_rx_tid {
+ #define MT_PACKET_ID_MASK		GENMASK(6, 0)
+ #define MT_PACKET_ID_NO_ACK		0
+ #define MT_PACKET_ID_NO_SKB		1
+-#define MT_PACKET_ID_FIRST		2
++#define MT_PACKET_ID_WED		2
++#define MT_PACKET_ID_FIRST		3
+ #define MT_PACKET_ID_HAS_RATE		BIT(7)
+ /* This is timer for when to give up when waiting for TXS callback,
+  * with starting time being the time at which the DMA_DONE callback
+@@ -861,26 +888,6 @@ struct mt76_power_limits {
+ 	s8 ru[7][12];
+ };
+ 
+-enum mt76_phy_type {
+-	MT_PHY_TYPE_CCK,
+-	MT_PHY_TYPE_OFDM,
+-	MT_PHY_TYPE_HT,
+-	MT_PHY_TYPE_HT_GF,
+-	MT_PHY_TYPE_VHT,
+-	MT_PHY_TYPE_HE_SU = 8,
+-	MT_PHY_TYPE_HE_EXT_SU,
+-	MT_PHY_TYPE_HE_TB,
+-	MT_PHY_TYPE_HE_MU,
+-	__MT_PHY_TYPE_HE_MAX,
+-};
+-
+-struct mt76_sta_stats {
+-	u64 tx_mode[__MT_PHY_TYPE_HE_MAX];
+-	u64 tx_bw[4];		/* 20, 40, 80, 160 */
+-	u64 tx_nss[4];		/* 1, 2, 3, 4 */
+-	u64 tx_mcs[16];		/* mcs idx */
+-};
+-
+ struct mt76_ethtool_worker_info {
+ 	u64 *data;
+ 	int idx;
+diff --git a/mt76_connac.h b/mt76_connac.h
+index c8d8680..8f78d12 100644
+--- a/mt76_connac.h
++++ b/mt76_connac.h
+@@ -352,9 +352,10 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
+ 				 struct sk_buff *skb, struct mt76_wcid *wcid,
+ 				 struct ieee80211_key_conf *key, int pid,
+ 				 enum mt76_txq_id qid, u32 changed);
++bool mt76_connac2_mac_fill_txs(struct mt76_dev *dev, struct mt76_wcid *wcid,
++				   __le32 *txs_data, struct mt76_sta_stats *stats);
+ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
+-				  int pid, __le32 *txs_data,
+-				  struct mt76_sta_stats *stats);
++				  int pid, __le32 *txs_data);
+ void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
+ 					 struct sk_buff *skb,
+ 					 __le32 *rxv, u32 mode);
+diff --git a/mt76_connac2_mac.h b/mt76_connac2_mac.h
+index 67ce216..c7064ba 100644
+--- a/mt76_connac2_mac.h
++++ b/mt76_connac2_mac.h
+@@ -123,6 +123,12 @@ enum {
+ /* VHT/HE only use bits 0-3 */
+ #define MT_TX_RATE_IDX			GENMASK(5, 0)
+ 
++enum {
++	MT_TXS_MPDU_FM0,
++	MT_TXS_MPDU_FM1,
++	MT_TXS_PPDU_FM
++};
++
+ #define MT_TXS0_FIXED_RATE		BIT(31)
+ #define MT_TXS0_BW			GENMASK(30, 29)
+ #define MT_TXS0_TID			GENMASK(28, 26)
+@@ -158,6 +164,14 @@ enum {
+ 
+ #define MT_TXS4_TIMESTAMP		GENMASK(31, 0)
+ 
++/* PPDU based */
++#define MT_TXS5_MPDU_TX_BYTE		GENMASK(22, 0)
++#define MT_TXS5_MPDU_TX_CNT		GENMASK(31, 23)
++
++#define MT_TXS6_MPDU_FAIL_CNT		GENMASK(31, 23)
++
++#define MT_TXS7_MPDU_RETRY_CNT		GENMASK(31, 23)
++
+ /* RXD DW1 */
+ #define MT_RXD1_NORMAL_WLAN_IDX		GENMASK(9, 0)
+ #define MT_RXD1_NORMAL_GROUP_1		BIT(11)
+diff --git a/mt76_connac_mac.c b/mt76_connac_mac.c
+index c1e8955..9e80cae 100644
+--- a/mt76_connac_mac.c
++++ b/mt76_connac_mac.c
+@@ -471,6 +471,9 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
+ 		p_fmt = mt76_is_mmio(dev) ? MT_TX_TYPE_CT : MT_TX_TYPE_SF;
+ 		q_idx = wmm_idx * MT76_CONNAC_MAX_WMM_SETS +
+ 			mt76_connac_lmac_mapping(skb_get_queue_mapping(skb));
++
++		wcid->stats.tx_bytes += skb->len;
++		wcid->stats.tx_packets++;
+ 	}
+ 
+ 	val = FIELD_PREP(MT_TXD0_TX_BYTES, skb->len + sz_txd) |
+@@ -539,35 +542,26 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
+ }
+ EXPORT_SYMBOL_GPL(mt76_connac2_mac_write_txwi);
+ 
+-bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
+-				  int pid, __le32 *txs_data,
+-				  struct mt76_sta_stats *stats)
++bool mt76_connac2_mac_fill_txs(struct mt76_dev *dev, struct mt76_wcid *wcid,
++				   __le32 *txs_data, struct mt76_sta_stats *stats)
+ {
+ 	struct ieee80211_supported_band *sband;
+ 	struct mt76_phy *mphy;
+-	struct ieee80211_tx_info *info;
+-	struct sk_buff_head list;
+ 	struct rate_info rate = {};
+-	struct sk_buff *skb;
+ 	bool cck = false;
+ 	u32 txrate, txs, mode;
+ 
+-	mt76_tx_status_lock(dev, &list);
+-	skb = mt76_tx_status_skb_get(dev, wcid, pid, &list);
+-	if (!skb)
+-		goto out;
+-
+ 	txs = le32_to_cpu(txs_data[0]);
+-
+-	info = IEEE80211_SKB_CB(skb);
+-	if (!(txs & MT_TXS0_ACK_ERROR_MASK))
+-		info->flags |= IEEE80211_TX_STAT_ACK;
+-
+-	info->status.ampdu_len = 1;
+-	info->status.ampdu_ack_len = !!(info->flags &
+-					IEEE80211_TX_STAT_ACK);
+-
+-	info->status.rates[0].idx = -1;
++	if (FIELD_GET(MT_TXS0_TXS_FORMAT, txs) == MT_TXS_PPDU_FM) {
++		stats->tx_bytes +=
++			le32_get_bits(txs_data[5], MT_TXS5_MPDU_TX_BYTE);
++		stats->tx_packets +=
++			le32_get_bits(txs_data[5], MT_TXS5_MPDU_TX_CNT);
++		stats->tx_failed +=
++			le32_get_bits(txs_data[6], MT_TXS6_MPDU_FAIL_CNT);
++		stats->tx_retries +=
++			le32_get_bits(txs_data[7], MT_TXS7_MPDU_RETRY_CNT);
++	}
+ 
+ 	txrate = FIELD_GET(MT_TXS0_TX_RATE, txs);
+ 
+@@ -602,7 +596,7 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
+ 	case MT_PHY_TYPE_HT:
+ 	case MT_PHY_TYPE_HT_GF:
+ 		if (rate.mcs > 31)
+-			goto out;
++			return false;
+ 
+ 		rate.flags = RATE_INFO_FLAGS_MCS;
+ 		if (wcid->rate.flags & RATE_INFO_FLAGS_SHORT_GI)
+@@ -610,7 +604,7 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
+ 		break;
+ 	case MT_PHY_TYPE_VHT:
+ 		if (rate.mcs > 9)
+-			goto out;
++			return false;
+ 
+ 		rate.flags = RATE_INFO_FLAGS_VHT_MCS;
+ 		break;
+@@ -619,14 +613,14 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
+ 	case MT_PHY_TYPE_HE_TB:
+ 	case MT_PHY_TYPE_HE_MU:
+ 		if (rate.mcs > 11)
+-			goto out;
++			return false;
+ 
+ 		rate.he_gi = wcid->rate.he_gi;
+ 		rate.he_dcm = FIELD_GET(MT_TX_RATE_DCM, txrate);
+ 		rate.flags = RATE_INFO_FLAGS_HE_MCS;
+ 		break;
+ 	default:
+-		goto out;
++		return false;
+ 	}
+ 
+ 	stats->tx_mode[mode]++;
+@@ -651,10 +645,37 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
+ 	}
+ 	wcid->rate = rate;
+ 
+-out:
+-	if (skb)
+-		mt76_tx_status_skb_done(dev, skb, &list);
++	return true;
++}
++EXPORT_SYMBOL_GPL(mt76_connac2_mac_fill_txs);
++
++bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
++				  int pid, __le32 *txs_data)
++{
++	struct mt76_sta_stats *stats = &wcid->stats;
++	struct sk_buff_head list;
++	struct sk_buff *skb;
++
++	if (pid < MT_PACKET_ID_FIRST)
++		return false;
+ 
++	mt76_tx_status_lock(dev, &list);
++	skb = mt76_tx_status_skb_get(dev, wcid, pid, &list);
++	if (skb) {
++		struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
++
++		if (!(le32_to_cpu(txs_data[0]) & MT_TXS0_ACK_ERROR_MASK))
++			info->flags |= IEEE80211_TX_STAT_ACK;
++
++		info->status.rates[0].idx = -1;
++		info->status.ampdu_len = 1;
++		info->status.ampdu_ack_len = !!(info->flags &
++						IEEE80211_TX_STAT_ACK);
++		stats->tx_failed += !(info->flags & IEEE80211_TX_STAT_ACK);
++
++		mt76_connac2_mac_fill_txs(dev, wcid, txs_data, stats);
++		mt76_tx_status_skb_done(dev, skb, &list);
++	}
+ 	mt76_tx_status_unlock(dev, &list);
+ 
+ 	return !!skb;
+diff --git a/mt7915/mac.c b/mt7915/mac.c
+index 1f8e123..5f42b46 100644
+--- a/mt7915/mac.c
++++ b/mt7915/mac.c
+@@ -1152,13 +1152,10 @@ static void mt7915_mac_add_txs(struct mt7915_dev *dev, void *data)
+ 	u16 wcidx;
+ 	u8 pid;
+ 
+-	if (le32_get_bits(txs_data[0], MT_TXS0_TXS_FORMAT) > 1)
+-		return;
+-
+ 	wcidx = le32_get_bits(txs_data[2], MT_TXS2_WCID);
+ 	pid = le32_get_bits(txs_data[3], MT_TXS3_PID);
+ 
+-	if (pid < MT_PACKET_ID_FIRST)
++	if (pid < MT_PACKET_ID_WED)
+ 		return;
+ 
+ 	if (wcidx >= mt7915_wtbl_size(dev))
+@@ -1172,8 +1169,11 @@ static void mt7915_mac_add_txs(struct mt7915_dev *dev, void *data)
+ 
+ 	msta = container_of(wcid, struct mt7915_sta, wcid);
+ 
+-	mt76_connac2_mac_add_txs_skb(&dev->mt76, wcid, pid, txs_data,
+-				     &msta->stats);
++	if (pid == MT_PACKET_ID_WED)
++		mt76_connac2_mac_fill_txs(&dev->mt76, wcid, txs_data,
++					  &msta->wcid.stats);
++	else
++		mt76_connac2_mac_add_txs_skb(&dev->mt76, wcid, pid, txs_data);
+ 	if (!wcid->sta)
+ 		goto out;
+ 
+diff --git a/mt7915/main.c b/mt7915/main.c
+index cfc522f..85d6be8 100644
+--- a/mt7915/main.c
++++ b/mt7915/main.c
+@@ -1042,6 +1042,20 @@ static void mt7915_sta_statistics(struct ieee80211_hw *hw,
+ 	}
+ 	sinfo->txrate.flags = txrate->flags;
+ 	sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE);
++
++	if (mtk_wed_device_active(&phy->dev->mt76.mmio.wed)) {
++		sinfo->tx_bytes = msta->wcid.stats.tx_bytes;
++		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES64);
++
++		sinfo->tx_packets = msta->wcid.stats.tx_packets;
++		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
++
++		sinfo->tx_failed = msta->wcid.stats.tx_failed;
++		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
++
++		sinfo->tx_retries = msta->wcid.stats.tx_retries;
++		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
++	}
+ }
+ 
+ static void mt7915_sta_rc_work(void *data, struct ieee80211_sta *sta)
+@@ -1256,7 +1270,7 @@ static void mt7915_ethtool_worker(void *wi_data, struct ieee80211_sta *sta)
+ 	if (msta->vif->mt76.idx != wi->idx)
+ 		return;
+ 
+-	mt76_ethtool_worker(wi, &msta->stats);
++	mt76_ethtool_worker(wi, &msta->wcid.stats);
+ }
+ 
+ static
+diff --git a/mt7915/mmio.c b/mt7915/mmio.c
+index 08ff556..080bfe7 100644
+--- a/mt7915/mmio.c
++++ b/mt7915/mmio.c
+@@ -92,6 +92,7 @@ static const u32 mt7915_offs[] = {
+ 	[AGG_AWSCR0]		= 0x05c,
+ 	[AGG_PCR0]		= 0x06c,
+ 	[AGG_ACR0]		= 0x084,
++	[AGG_ACR4]		= 0x08C,
+ 	[AGG_MRCR]		= 0x098,
+ 	[AGG_ATCR1]		= 0x0f0,
+ 	[AGG_ATCR3]		= 0x0f4,
+@@ -167,6 +168,7 @@ static const u32 mt7916_offs[] = {
+ 	[AGG_AWSCR0]		= 0x030,
+ 	[AGG_PCR0]		= 0x040,
+ 	[AGG_ACR0]		= 0x054,
++	[AGG_ACR4]		= 0x05C,
+ 	[AGG_MRCR]		= 0x068,
+ 	[AGG_ATCR1]		= 0x1a8,
+ 	[AGG_ATCR3]		= 0x080,
+@@ -668,9 +670,12 @@ irqreturn_t mt7915_irq_handler(int irq, void *dev_instance)
+ static int mt7915_wed_offload_enable(struct mtk_wed_device *wed)
+ {
+ 	struct mt7915_dev *dev;
++	struct mt7915_phy *phy;
+ 	int ret;
+ 
+ 	dev = container_of(wed, struct mt7915_dev, mt76.mmio.wed);
++	if (!dev)
++		return -EINVAL;
+ 
+ 	spin_lock_bh(&dev->mt76.token_lock);
+ 	dev->mt76.token_size = wed->wlan.token_start;
+@@ -681,18 +686,35 @@ static int mt7915_wed_offload_enable(struct mtk_wed_device *wed)
+ 	if (!ret)
+ 		return -EAGAIN;
+ 
++	phy = &dev->phy;
++	mt76_set(dev, MT_AGG_ACR4(phy->band_idx), MT_AGG_ACR_PPDU_TXS2H);
++
++	phy = dev->mt76.phy2 ? dev->mt76.phy2->priv : NULL;
++	if (phy)
++		mt76_set(dev, MT_AGG_ACR4(phy->band_idx), MT_AGG_ACR_PPDU_TXS2H);
++
+ 	return 0;
+ }
+ 
+ static void mt7915_wed_offload_disable(struct mtk_wed_device *wed)
+ {
+ 	struct mt7915_dev *dev;
++	struct mt7915_phy *phy;
+ 
+ 	dev = container_of(wed, struct mt7915_dev, mt76.mmio.wed);
++	if (!dev)
++		return;
+ 
+ 	spin_lock_bh(&dev->mt76.token_lock);
+ 	dev->mt76.token_size = wed->wlan.token_start;//MT7915_TOKEN_SIZE;
+ 	spin_unlock_bh(&dev->mt76.token_lock);
++
++	phy = &dev->phy;
++	mt76_clear(dev, MT_AGG_ACR4(phy->band_idx), MT_AGG_ACR_PPDU_TXS2H);
++
++	phy = dev->mt76.phy2 ? dev->mt76.phy2->priv : NULL;
++	if (phy)
++		mt76_clear(dev, MT_AGG_ACR4(phy->band_idx), MT_AGG_ACR_PPDU_TXS2H);
+ }
+ #endif
+ 
+diff --git a/mt7915/mt7915.h b/mt7915/mt7915.h
+index 22399cc..065c16c 100644
+--- a/mt7915/mt7915.h
++++ b/mt7915/mt7915.h
+@@ -139,8 +139,6 @@ struct mt7915_sta {
+ 	unsigned long jiffies;
+ 	unsigned long ampdu_state;
+ 
+-	struct mt76_sta_stats stats;
+-
+ 	struct mt76_connac_sta_key_conf bip;
+ 
+ 	struct {
+diff --git a/mt7915/regs.h b/mt7915/regs.h
+index 08bf84c..694cc56 100644
+--- a/mt7915/regs.h
++++ b/mt7915/regs.h
+@@ -58,6 +58,7 @@ enum offs_rev {
+ 	AGG_AWSCR0,
+ 	AGG_PCR0,
+ 	AGG_ACR0,
++	AGG_ACR4,
+ 	AGG_MRCR,
+ 	AGG_ATCR1,
+ 	AGG_ATCR3,
+@@ -495,6 +496,9 @@ enum offs_rev {
+ #define MT_AGG_ACR_CFEND_RATE		GENMASK(13, 0)
+ #define MT_AGG_ACR_BAR_RATE		GENMASK(29, 16)
+ 
++#define MT_AGG_ACR4(_band)		MT_WF_AGG(_band, __OFFS(AGG_ACR4))
++#define MT_AGG_ACR_PPDU_TXS2H		BIT(1)
++
+ #define MT_AGG_MRCR(_band)		MT_WF_AGG(_band, __OFFS(AGG_MRCR))
+ #define MT_AGG_MRCR_BAR_CNT_LIMIT		GENMASK(15, 12)
+ #define MT_AGG_MRCR_LAST_RTS_CTS_RN		BIT(6)
+diff --git a/mt7921/mt7921.h b/mt7921/mt7921.h
+index 4b2e974..f48295f 100644
+--- a/mt7921/mt7921.h
++++ b/mt7921/mt7921.h
+@@ -100,7 +100,6 @@ struct mt7921_sta {
+ 
+ 	unsigned long last_txs;
+ 	unsigned long ampdu_state;
+-	struct mt76_sta_stats stats;
+ 
+ 	struct mt76_connac_sta_key_conf bip;
+ };
+-- 
+2.32.0
+
diff --git a/recipes-kernel/linux-mt76/files/patches/3006-mt76-mt7915-add-statistic-for-HW-Rx-Path.patch b/recipes-kernel/linux-mt76/files/patches/3006-mt76-mt7915-add-statistic-for-HW-Rx-Path.patch
new file mode 100644
index 0000000..e91955f
--- /dev/null
+++ b/recipes-kernel/linux-mt76/files/patches/3006-mt76-mt7915-add-statistic-for-HW-Rx-Path.patch
@@ -0,0 +1,252 @@
+From 12b7c7a035359298fc7d14ae3a6dbc16ec0b70ad Mon Sep 17 00:00:00 2001
+From: Yi-Chia Hsieh <Yi-Chia.Hsieh@mediatek.com>
+Date: Fri, 5 Aug 2022 13:58:11 -0700
+Subject: [PATCH] mt76: mt7915: add statistic for H/W Rx Path
+
+Change-Id: Id94d663f08e91c83d296bd57e5e9b65a505ae1c7
+---
+ mt76.h            |  4 ++++
+ mt76_connac.h     |  5 +++++
+ mt76_connac_mcu.h | 35 +++++++++++++++++++++++++++++++++++
+ mt7915/mac.c      | 26 ++++++++++++++++++++++++++
+ mt7915/main.c     |  9 ++++++++-
+ mt7915/mcu.c      | 21 +++++++++++++++++++++
+ mt7915/mmio.c     |  6 ++++++
+ mt7915/mt7915.h   |  3 +++
+ 8 files changed, 108 insertions(+), 1 deletion(-)
+
+diff --git a/mt76.h b/mt76.h
+index a65e7a3..e56fd58 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -289,6 +289,10 @@ struct mt76_sta_stats {
+ 	u32 tx_packets;
+ 	u32 tx_retries;
+ 	u32 tx_failed;
++	u64 rx_bytes;
++	u32 rx_packets;
++	u32 rx_error;
++	u32 rx_drop;
+ };
+ 
+ struct mt76_wcid {
+diff --git a/mt76_connac.h b/mt76_connac.h
+index 8f78d12..41d6525 100644
+--- a/mt76_connac.h
++++ b/mt76_connac.h
+@@ -218,6 +218,11 @@ static inline bool is_mt76_fw_txp(struct mt76_dev *dev)
+ 	}
+ }
+ 
++static inline bool is_wo_mcu(struct mt76_dev *dev)
++{
++	return is_mt7986(dev);
++}
++
+ static inline u8 mt76_connac_chan_bw(struct cfg80211_chan_def *chandef)
+ {
+ 	static const u8 width_to_bw[] = {
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index ca68172..722e859 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1200,6 +1200,41 @@ enum {
+ 	MCU_CE_CMD_GET_TXPWR = 0xd0,
+ };
+ 
++enum wo_event_id {
++	WO_EVT_LOG_DUMP = 0x1,
++	WO_EVT_PROFILING = 0x2,
++	WO_EVT_RXCNT_INFO = 0x3
++};
++
++enum wo_cmd_id {
++	WO_CMD_WED_CFG = 0,
++	WO_CMD_WED_RX_STAT,
++	WO_CMD_RRO_SER,
++	WO_CMD_DBG_INFO,
++	WO_CMD_DEV_INFO,
++	WO_CMD_BSS_INFO,
++	WO_CMD_STA_REC,
++	WO_CMD_DEV_INFO_DUMP,
++	WO_CMD_BSS_INFO_DUMP,
++	WO_CMD_STA_REC_DUMP,
++	WO_CMD_BA_INFO_DUMP,
++	WO_CMD_FBCMD_Q_DUMP,
++	WO_CMD_FW_LOG_CTRL,
++	WO_CMD_LOG_FLUSH,
++	WO_CMD_CHANGE_STATE,
++	WO_CMD_CPU_STATS_ENABLE,
++	WO_CMD_CPU_STATS_DUMP,
++	WO_CMD_EXCEPTION_INIT,
++	WO_CMD_PROF_CTRL,
++	WO_CMD_STA_BA_DUMP,
++	WO_CMD_BA_CTRL_DUMP,
++	WO_CMD_RXCNT_CTRL,
++	WO_CMD_RXCNT_INFO,
++	WO_CMD_SET_CAP,
++	WO_CMD_CCIF_RING_DUMP,
++	WO_CMD_WED_END
++};
++
+ enum {
+ 	PATCH_SEM_RELEASE,
+ 	PATCH_SEM_GET
+diff --git a/mt7915/mac.c b/mt7915/mac.c
+index 5ee9bb2..9ce5a60 100644
+--- a/mt7915/mac.c
++++ b/mt7915/mac.c
+@@ -938,6 +938,31 @@ void mt7915_wed_trigger_ser(struct mtk_wed_device *wed)
+ 	return;
+ }
+ 
++void mt7915_wed_update_wo_rxcnt(struct mtk_wed_device *wed,
++				    struct wo_cmd_rxcnt_t *rxcnt)
++{
++	struct mt7915_dev *dev;
++	struct mt76_wcid *wcid;
++
++	dev = container_of(wed, struct mt7915_dev, mt76.mmio.wed);
++	if (rxcnt->wlan_idx >= mt7915_wtbl_size(dev))
++		return;
++
++	rcu_read_lock();
++
++	wcid = rcu_dereference(dev->mt76.wcid[rxcnt->wlan_idx]);
++	if (!wcid)
++		goto out;
++
++	wcid->stats.rx_bytes += rxcnt->rx_byte_cnt;
++	wcid->stats.rx_packets += rxcnt->rx_pkt_cnt;
++	wcid->stats.rx_error += rxcnt->rx_err_cnt;
++	wcid->stats.rx_drop += rxcnt->rx_drop_cnt;
++
++out:
++	rcu_read_unlock();
++}
++
+ static void
+ mt7915_tx_check_aggr(struct ieee80211_sta *sta, __le32 *txwi)
+ {
+@@ -1173,6 +1198,7 @@ static void mt7915_mac_add_txs(struct mt7915_dev *dev, void *data)
+ 					  &msta->wcid.stats);
+ 	else
+ 		mt76_connac2_mac_add_txs_skb(&dev->mt76, wcid, pid, txs_data);
++
+ 	if (!wcid->sta)
+ 		goto out;
+ 
+diff --git a/mt7915/main.c b/mt7915/main.c
+index 7935774..a73c488 100644
+--- a/mt7915/main.c
++++ b/mt7915/main.c
+@@ -1028,7 +1028,8 @@ static void mt7915_sta_statistics(struct ieee80211_hw *hw,
+ 	struct rate_info *txrate = &msta->wcid.rate;
+ 	struct rate_info rxrate = {};
+ 
+-	if (is_mt7915(&phy->dev->mt76) &&
++	if ((is_mt7915(&phy->dev->mt76) ||
++	    mtk_wed_device_active(&phy->dev->mt76.mmio.wed)) &&
+ 	    !mt7915_mcu_get_rx_rate(phy, vif, sta, &rxrate)) {
+ 		sinfo->rxrate = rxrate;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE);
+@@ -1062,6 +1063,12 @@ static void mt7915_sta_statistics(struct ieee80211_hw *hw,
+ 
+ 		sinfo->tx_retries = msta->wcid.stats.tx_retries;
+ 		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
++
++		sinfo->rx_bytes = msta->wcid.stats.rx_bytes;
++		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES64);
++
++		sinfo->rx_packets = msta->wcid.stats.rx_packets;
++		sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
+ 	}
+ }
+ 
+diff --git a/mt7915/mcu.c b/mt7915/mcu.c
+index 1272bee..8536b1e 100644
+--- a/mt7915/mcu.c
++++ b/mt7915/mcu.c
+@@ -293,6 +293,27 @@ int mt7915_mcu_wa_cmd(struct mt7915_dev *dev, int cmd, u32 a1, u32 a2, u32 a3)
+ 	return mt76_mcu_send_msg(&dev->mt76, cmd, &req, sizeof(req), false);
+ }
+ 
++int mt7915_mcu_wo_cmd(struct mt7915_dev *dev, int cmd, u32 a1, u32 a2)
++{
++	struct mtk_wed_device *wed = &dev->mt76.mmio.wed;
++	struct {
++		__le32 args[2];
++	} req = {
++		.args = {
++			cpu_to_le32(a1),
++			cpu_to_le32(a2),
++		},
++	};
++
++	if (!mtk_wed_device_active(wed))
++		return -1;
++
++	if (!is_wo_mcu(&dev->mt76))
++		return -1;
++
++	return mtk_soc_wed_ops->msg_update(wed, cmd, (void *)&req, sizeof(req));
++}
++
+ static void
+ mt7915_mcu_csa_finish(void *priv, u8 *mac, struct ieee80211_vif *vif)
+ {
+diff --git a/mt7915/mmio.c b/mt7915/mmio.c
+index f8dd553..bd80315 100644
+--- a/mt7915/mmio.c
++++ b/mt7915/mmio.c
+@@ -9,6 +9,7 @@
+ #include "mt7915.h"
+ #include "mac.h"
+ #include "../trace.h"
++#include "../mt76_connac_mcu.h"
+ 
+ static bool wed_enable = true;
+ module_param(wed_enable, bool, 0644);
+@@ -781,6 +782,8 @@ mt7915_pci_wed_init(struct mt7915_dev *dev, struct device *pdev, int *irq)
+ 
+ 	wed->wlan.ser_trigger = mt7915_wed_trigger_ser;
+ 
++	wed->wlan.update_wo_rxcnt = mt7915_wed_update_wo_rxcnt;
++
+ 	dev->mt76.rx_token_size = wed->wlan.rx_pkt;
+ 	if (mtk_wed_device_attach(wed) != 0)
+ 		return 0;
+@@ -792,6 +795,9 @@ mt7915_pci_wed_init(struct mt7915_dev *dev, struct device *pdev, int *irq)
+ 	if (ret)
+ 		return ret;
+ 
++	if (is_wo_mcu(&dev->mt76))
++		mt7915_mcu_wo_cmd(dev, WO_CMD_RXCNT_CTRL, 1, 6);
++
+ 	return 1;
+ #else
+ 	return 0;
+diff --git a/mt7915/mt7915.h b/mt7915/mt7915.h
+index b4d6b55..c02cc87 100644
+--- a/mt7915/mt7915.h
++++ b/mt7915/mt7915.h
+@@ -546,6 +546,8 @@ u32 mt7915_wed_init_rx_buf(struct mtk_wed_device *wed,
+ 				int pkt_num);
+ void mt7915_wed_release_rx_buf(struct mtk_wed_device *wed);
+ void mt7915_wed_trigger_ser(struct mtk_wed_device *wed);
++void mt7915_wed_update_wo_rxcnt(struct mtk_wed_device *wed,
++				struct wo_cmd_rxcnt_t *rxcnt);
+ int mt7915_register_device(struct mt7915_dev *dev);
+ void mt7915_unregister_device(struct mt7915_dev *dev);
+ int mt7915_eeprom_init(struct mt7915_dev *dev);
+@@ -630,6 +632,7 @@ int mt7915_mcu_rdd_background_enable(struct mt7915_phy *phy,
+ 				     struct cfg80211_chan_def *chandef);
+ int mt7915_mcu_rf_regval(struct mt7915_dev *dev, u32 regidx, u32 *val, bool set);
+ int mt7915_mcu_wa_cmd(struct mt7915_dev *dev, int cmd, u32 a1, u32 a2, u32 a3);
++int mt7915_mcu_wo_cmd(struct mt7915_dev *dev, int cmd, u32 a1, u32 a2);
+ int mt7915_mcu_fw_log_2_host(struct mt7915_dev *dev, u8 type, u8 ctrl);
+ int mt7915_mcu_fw_dbg_ctrl(struct mt7915_dev *dev, u32 module, u8 level);
+ void mt7915_mcu_rx_event(struct mt7915_dev *dev, struct sk_buff *skb);
+-- 
+2.32.0
+
diff --git a/recipes-kernel/linux-mt76/files/patches/patches.inc b/recipes-kernel/linux-mt76/files/patches/patches.inc
index cb34658..eba8ebf 100644
--- a/recipes-kernel/linux-mt76/files/patches/patches.inc
+++ b/recipes-kernel/linux-mt76/files/patches/patches.inc
@@ -18,6 +18,7 @@
     file://1006-mt76-mt7915-add-L0.5-system-error-recovery-support.patch \
     file://1007-mt76-mt7915-add-support-for-runtime-set-in-band-disc.patch \
     file://1008-mt76-mt7915-add-mt76-vendor-muru-onoff-command.patch \
+    file://1009-mt76-mt7915-add-fw_version-dump.patch \
     file://1111-mt76-testmode-additional-supports.patch \
     file://1112-mt76-mt7915-init-rssi-in-WTBL-when-add-station.patch \
     file://1113-mt76-mt7915-reduce-TWT-SP-sent-to-FW-for-cert.patch \
@@ -32,4 +33,6 @@
     file://3002-mt76-add-wed-rx-support.patch \
     file://3003-mt76-add-fill-receive-path-to-report-wed-idx.patch \
     file://3004-mt76-add-ser-spport-when-wed-on.patch \
+    file://3005-mt76-mt7915-add-statistic-for-HW-Tx-Path.patch \
+    file://3006-mt76-mt7915-add-statistic-for-HW-Rx-Path.patch \
     "
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7915_rom_patch.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7915_rom_patch.bin
index 1c69d39..7cca92e 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7915_rom_patch.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7915_rom_patch.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wa.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wa.bin
index 434e757..f87d182 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wa.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wa.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wm.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wm.bin
index fef6005..777bfac 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wm.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7915_wm.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7916_rom_patch.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7916_rom_patch.bin
index 07fe11a..a7c3de2 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7916_rom_patch.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7916_rom_patch.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wa.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wa.bin
index 27caa00..3f61a50 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wa.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wa.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wm.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wm.bin
index 7df01ea..f7f7616 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wm.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7916_wm.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch.bin
index 6bb033c..4074aa0 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch_mt7975.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch_mt7975.bin
index 936cfe8..58975ad 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch_mt7975.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_rom_patch_mt7975.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wa.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wa.bin
index 79ec406..0554c0e 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wa.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wa.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm.bin
index 002bece..87a7e3f 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm_mt7975.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm_mt7975.bin
index 972926c..783429b 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm_mt7975.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wm_mt7975.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_0.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_0.bin
index 19d607c..e567237 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_0.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_0.bin
Binary files differ
diff --git a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_1.bin b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_1.bin
index ec3b182..1708121 100644
--- a/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_1.bin
+++ b/recipes-kernel/linux-mt76/files/src/firmware/mt7986_wo_1.bin
Binary files differ
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/999-update-uapi-header-files-for-bridger.patch b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/999-update-uapi-header-files-for-bridger.patch
new file mode 100644
index 0000000..107cf49
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/999-update-uapi-header-files-for-bridger.patch
@@ -0,0 +1,194 @@
+diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
+index 1b3c2b6..00bbbf8 100644
+--- a/include/uapi/linux/if_bridge.h
++++ b/include/uapi/linux/if_bridge.h
+@@ -130,6 +130,7 @@ enum {
+ #define BRIDGE_VLAN_INFO_RANGE_BEGIN	(1<<3) /* VLAN is start of vlan range */
+ #define BRIDGE_VLAN_INFO_RANGE_END	(1<<4) /* VLAN is end of vlan range */
+ #define BRIDGE_VLAN_INFO_BRENTRY	(1<<5) /* Global bridge VLAN entry */
++#define BRIDGE_VLAN_INFO_ONLY_OPTS	(1<<6) /* Skip create/delete/flags */
+ 
+ struct bridge_vlan_info {
+ 	__u16 flags;
+@@ -156,6 +157,112 @@ struct bridge_vlan_xstats {
+ 	__u32 pad2;
+ };
+ 
++/* Bridge vlan RTM header */
++struct br_vlan_msg {
++	__u8 family;
++	__u8 reserved1;
++	__u16 reserved2;
++	__u32 ifindex;
++};
++
++enum {
++	BRIDGE_VLANDB_DUMP_UNSPEC,
++	BRIDGE_VLANDB_DUMP_FLAGS,
++	__BRIDGE_VLANDB_DUMP_MAX,
++};
++#define BRIDGE_VLANDB_DUMP_MAX (__BRIDGE_VLANDB_DUMP_MAX - 1)
++
++/* flags used in BRIDGE_VLANDB_DUMP_FLAGS attribute to affect dumps */
++#define BRIDGE_VLANDB_DUMPF_STATS	(1 << 0) /* Include stats in the dump */
++#define BRIDGE_VLANDB_DUMPF_GLOBAL	(1 << 1) /* Dump global vlan options only */
++
++/* Bridge vlan RTM attributes
++ * [BRIDGE_VLANDB_ENTRY] = {
++ *     [BRIDGE_VLANDB_ENTRY_INFO]
++ *     ...
++ * }
++ * [BRIDGE_VLANDB_GLOBAL_OPTIONS] = {
++ *     [BRIDGE_VLANDB_GOPTS_ID]
++ *     ...
++ * }
++ */
++enum {
++	BRIDGE_VLANDB_UNSPEC,
++	BRIDGE_VLANDB_ENTRY,
++	BRIDGE_VLANDB_GLOBAL_OPTIONS,
++	__BRIDGE_VLANDB_MAX,
++};
++#define BRIDGE_VLANDB_MAX (__BRIDGE_VLANDB_MAX - 1)
++
++enum {
++	BRIDGE_VLANDB_ENTRY_UNSPEC,
++	BRIDGE_VLANDB_ENTRY_INFO,
++	BRIDGE_VLANDB_ENTRY_RANGE,
++	BRIDGE_VLANDB_ENTRY_STATE,
++	BRIDGE_VLANDB_ENTRY_TUNNEL_INFO,
++	BRIDGE_VLANDB_ENTRY_STATS,
++	BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
++	__BRIDGE_VLANDB_ENTRY_MAX,
++};
++#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1)
++
++/* [BRIDGE_VLANDB_ENTRY] = {
++ *     [BRIDGE_VLANDB_ENTRY_TUNNEL_INFO] = {
++ *         [BRIDGE_VLANDB_TINFO_ID]
++ *         ...
++ *     }
++ * }
++ */
++enum {
++	BRIDGE_VLANDB_TINFO_UNSPEC,
++	BRIDGE_VLANDB_TINFO_ID,
++	BRIDGE_VLANDB_TINFO_CMD,
++	__BRIDGE_VLANDB_TINFO_MAX,
++};
++#define BRIDGE_VLANDB_TINFO_MAX (__BRIDGE_VLANDB_TINFO_MAX - 1)
++
++/* [BRIDGE_VLANDB_ENTRY] = {
++ *     [BRIDGE_VLANDB_ENTRY_STATS] = {
++ *         [BRIDGE_VLANDB_STATS_RX_BYTES]
++ *         ...
++ *     }
++ *     ...
++ * }
++ */
++enum {
++	BRIDGE_VLANDB_STATS_UNSPEC,
++	BRIDGE_VLANDB_STATS_RX_BYTES,
++	BRIDGE_VLANDB_STATS_RX_PACKETS,
++	BRIDGE_VLANDB_STATS_TX_BYTES,
++	BRIDGE_VLANDB_STATS_TX_PACKETS,
++	BRIDGE_VLANDB_STATS_PAD,
++	__BRIDGE_VLANDB_STATS_MAX,
++};
++#define BRIDGE_VLANDB_STATS_MAX (__BRIDGE_VLANDB_STATS_MAX - 1)
++
++enum {
++	BRIDGE_VLANDB_GOPTS_UNSPEC,
++	BRIDGE_VLANDB_GOPTS_ID,
++	BRIDGE_VLANDB_GOPTS_RANGE,
++	BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING,
++	BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION,
++	BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
++	BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT,
++	BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT,
++	BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
++	BRIDGE_VLANDB_GOPTS_PAD,
++	BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERIER,
++	BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS,
++	BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE,
++	__BRIDGE_VLANDB_GOPTS_MAX
++};
++#define BRIDGE_VLANDB_GOPTS_MAX (__BRIDGE_VLANDB_GOPTS_MAX - 1)
++
+ /* Bridge multicast database attributes
+  * [MDBA_MDB] = {
+  *     [MDBA_MDB_ENTRY] = {
+diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
+index a6aa466..6abd5a1 100644
+--- a/include/uapi/linux/pkt_cls.h
++++ b/include/uapi/linux/pkt_cls.h
+@@ -16,9 +16,37 @@ enum {
+ 	TCA_ACT_STATS,
+ 	TCA_ACT_PAD,
+ 	TCA_ACT_COOKIE,
++	TCA_ACT_FLAGS,
++	TCA_ACT_HW_STATS,
++	TCA_ACT_USED_HW_STATS,
+ 	__TCA_ACT_MAX
+ };
+ 
++/* See other TCA_ACT_FLAGS_ * flags in include/net/act_api.h. */
++#define TCA_ACT_FLAGS_NO_PERCPU_STATS 1 /* Don't use percpu allocator for
++					 * actions stats.
++					 */
++
++/* tca HW stats type
++ * When user does not pass the attribute, he does not care.
++ * It is the same as if he would pass the attribute with
++ * all supported bits set.
++ * In case no bits are set, user is not interested in getting any HW statistics.
++ */
++#define TCA_ACT_HW_STATS_IMMEDIATE (1 << 0) /* Means that in dump, user
++					     * gets the current HW stats
++					     * state from the device
++					     * queried at the dump time.
++					     */
++#define TCA_ACT_HW_STATS_DELAYED (1 << 1) /* Means that in dump, user gets
++					   * HW stats that might be out of date
++					   * for some time, maybe couple of
++					   * seconds. This is the case when
++					   * driver polls stats updates
++					   * periodically or when it gets async
++					   * stats update from the device.
++					   */
++
+ #define TCA_ACT_MAX __TCA_ACT_MAX
+ #define TCA_OLD_COMPAT (TCA_ACT_MAX+1)
+ #define TCA_ACT_MAX_PRIO 32
+diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
+index 96eca6e..ff43cb9 100644
+--- a/include/uapi/linux/rtnetlink.h
++++ b/include/uapi/linux/rtnetlink.h
+@@ -164,6 +164,13 @@ enum {
+ 	RTM_GETNEXTHOP,
+ #define RTM_GETNEXTHOP	RTM_GETNEXTHOP
+ 
++	RTM_NEWVLAN = 112,
++#define RTM_NEWNVLAN	RTM_NEWVLAN
++	RTM_DELVLAN,
++#define RTM_DELVLAN	RTM_DELVLAN
++	RTM_GETVLAN,
++#define RTM_GETVLAN	RTM_GETVLAN
++
+ 	__RTM_MAX,
+ #define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
+ };
+@@ -717,6 +724,8 @@ enum rtnetlink_groups {
+ #define RTNLGRP_IPV6_MROUTE_R	RTNLGRP_IPV6_MROUTE_R
+ 	RTNLGRP_NEXTHOP,
+ #define RTNLGRP_NEXTHOP		RTNLGRP_NEXTHOP
++	RTNLGRP_BRVLAN,
++#define RTNLGRP_BRVLAN		RTNLGRP_BRVLAN
+ 	__RTNLGRP_MAX
+ };
+ #define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc
index 6d84bd1..4f141ed 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc
+++ b/recipes-kernel/linux/linux-mediatek-5.4/generic/backport-5.4/backport-5.4.inc
@@ -277,4 +277,5 @@
     file://900-v5.9-0001-dt-bindings-Add-multicolor-class-dt-bindings-documen.patch \
     file://900-v5.9-0002-leds-Add-multicolor-ID-to-the-color-ID-list.patch \
     file://900-v5.9-0003-leds-add-RGB-color-option-as-that-is-different-from-.patch \
+    file://999-update-uapi-header-files-for-bridger.patch \
     "
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts
index a050c88..40c41b6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-emmc-rfb.dts
@@ -68,7 +68,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -102,9 +102,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -112,33 +109,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -179,12 +167,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts
index 4c4f987..925a852 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-gsw-spim-nand-rfb.dts
@@ -93,6 +93,7 @@
 			pause;
 			link-gpio = <&pio 47 0>;
 			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -100,35 +101,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts
index 6af58aa..32f9320 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-sd-rfb.dts
@@ -59,7 +59,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -93,9 +93,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -103,33 +100,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -170,12 +158,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts
index e5024b5..61b68af 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nand-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts
index 15e9773..629d509 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986a-2500wan-spim-nor-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts
index be89622..09e41f6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-emmc-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts
index 470e9f3..1603714 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-gsw-spim-nand-rfb.dts
@@ -62,6 +62,9 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
+			link-gpio = <&pio 47 0>;
+			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -69,33 +72,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts
index a56da0d..c074239 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-sd-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts
index 4fe4946..dd02baf 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-snfi-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts
index 187a8a5..1dc1551 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts
index f9fefa0..4cbfe41 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm/boot/dts/mt7986b-2500wan-spim-nor-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts
index a050c88..40c41b6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-emmc-rfb.dts
@@ -68,7 +68,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -102,9 +102,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -112,33 +109,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -179,12 +167,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts
index 4c4f987..925a852 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-gsw-spim-nand-rfb.dts
@@ -93,6 +93,7 @@
 			pause;
 			link-gpio = <&pio 47 0>;
 			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -100,35 +101,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts
index 6af58aa..32f9320 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-sd-rfb.dts
@@ -59,7 +59,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -93,9 +93,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -103,33 +100,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -170,12 +158,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts
index e5024b5..61b68af 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nand-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts
index 15e9773..629d509 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986a-2500wan-spim-nor-rfb.dts
@@ -50,7 +50,7 @@
 &uart2 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart2_pins>;
-	status = "disabled";
+	status = "okay";
 };
 
 &i2c0 {
@@ -84,9 +84,6 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
-			link-gpio = <&pio 47 0>;
-			phy-handle = <&phy5>;
-			label = "lan5";
 		};
 	};
 
@@ -94,33 +91,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-			link-gpio = <&pio 46 0>;
-			phy-handle = <&phy6>;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -161,12 +149,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts
index be89622..09e41f6 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-emmc-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts
index 470e9f3..1603714 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-gsw-spim-nand-rfb.dts
@@ -62,6 +62,9 @@
 			speed = <2500>;
 			full-duplex;
 			pause;
+			link-gpio = <&pio 47 0>;
+			phy-handle = <&phy5>;
+			label = "lan5";
 		};
 	};
 
@@ -69,33 +72,25 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
 			compatible = "ethernet-phy-id67c9.de0a";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
-
 	};
 };
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts
index a56da0d..c074239 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-sd-rfb.dts
@@ -71,31 +71,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -136,12 +129,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts
index 4fe4946..dd02baf 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-snfi-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts
index 187a8a5..1dc1551 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nand-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts
index f9fefa0..4cbfe41 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7986b-2500wan-spim-nor-rfb.dts
@@ -62,31 +62,24 @@
 		compatible = "mediatek,eth-mac";
 		reg = <1>;
 		phy-mode = "2500base-x";
-
-		fixed-link {
-			speed = <2500>;
-			full-duplex;
-			pause;
-		};
+		phy-handle = <&phy6>;
 	};
 
 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
 
+		reset-gpios = <&pio 6 1>;
+		reset-delay-us = <600>;
+
 		phy5: phy@5 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <5>;
-			reset-gpios = <&pio 6 1>;
-			reset-assert-us = <600>;
-			reset-deassert-us = <20000>;
-			phy-mode = "2500base-x";
 		};
 
 		phy6: phy@6 {
-			compatible = "ethernet-phy-id67c9.de0a";
+			compatible = "ethernet-phy-ieee802.3-c45";
 			reg = <6>;
-			phy-mode = "2500base-x";
 		};
 
 		switch@0 {
@@ -127,12 +120,7 @@
 					reg = <5>;
 					label = "lan5";
 					phy-mode = "2500base-x";
-
-					fixed-link {
-						speed = <2500>;
-						full-duplex;
-						pause;
-					};
+					phy-handle = <&phy5>;
 				};
 
 				port@6 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c
index 1756ff7..f1812bb 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/mtd/mtk-snand/mtk-snand-ids.c
@@ -133,7 +133,7 @@
 		   &snand_cap_program_load_x4),
 	SNAND_INFO("GD5F2GQ5UExxG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x52),
 		   SNAND_MEMORG_2G_2K_128,
-		   &snand_cap_read_from_cache_quad_q2d,
+		   &snand_cap_read_from_cache_quad_a8d,
 		   &snand_cap_program_load_x4),
 	SNAND_INFO("GD5F4GQ4UCxIG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0xb4),
 		   SNAND_MEMORG_4G_4K_256,
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 bd70441..6955aad 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
@@ -188,6 +188,16 @@
 	return _mtk_mdio_read(eth, phy_addr, phy_reg);
 }
 
+static int mtk_mdio_reset(struct mii_bus *bus)
+{
+	/* The mdiobus_register will trigger a reset pulse when enabling Bus reset,
+	 * we just need to wait until device ready.
+	 */
+	mdelay(20);
+
+	return 0;
+}
+
 static int mt7621_gmac0_rgmii_adjust(struct mtk_eth *eth,
 				     phy_interface_t interface)
 {
@@ -626,6 +636,7 @@
 	eth->mii_bus->name = "mdio";
 	eth->mii_bus->read = mtk_mdio_read;
 	eth->mii_bus->write = mtk_mdio_write;
+	eth->mii_bus->reset = mtk_mdio_reset;
 	eth->mii_bus->priv = eth;
 	eth->mii_bus->parent = eth->dev;
 
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 60939f2..54674ad 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
@@ -581,7 +581,7 @@
 /* Mac control registers */
 #define MTK_MAC_MCR(x)		(0x10100 + (x * 0x100))
 #define MAC_MCR_MAX_RX_1536	BIT(24)
-#define MAC_MCR_IPG_CFG		(BIT(18) | BIT(16))
+#define MAC_MCR_IPG_CFG		(BIT(18) | BIT(16) | BIT(12))
 #define MAC_MCR_FORCE_MODE	BIT(15)
 #define MAC_MCR_TX_EN		BIT(14)
 #define MAC_MCR_RX_EN		BIT(13)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c
index 1540bf8..6b937d5 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat_nf_hook.c
@@ -1571,7 +1571,8 @@
 
 	if (IS_HQOS_MODE || skb->mark >= MAX_PPPQ_PORT_NUM)
 		qid = skb->mark & (MTK_QDMA_TX_MASK);
-	else if (IS_PPPQ_MODE && (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev)))
+	else if (IS_PPPQ_MODE && (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev) ||
+		 (FROM_WED(skb) && IS_DSA_LAN(dev))))
 		qid = port_id & MTK_QDMA_TX_MASK;
 	else
 		qid = 0;
@@ -1607,7 +1608,8 @@
 			else
 				entry.ipv4_hnapt.iblk2.fqos =
 					(!IS_PPPQ_MODE || (IS_PPPQ_MODE &&
-					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev))));
+					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev) ||
+					 (FROM_WED(skb) && IS_DSA_LAN(dev)))));
 		} else {
 			entry.ipv4_hnapt.iblk2.fqos = 0;
 		}
@@ -1641,7 +1643,8 @@
 			else
 				entry.ipv6_5t_route.iblk2.fqos =
 					(!IS_PPPQ_MODE || (IS_PPPQ_MODE &&
-					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev))));
+					 (IS_DSA_1G_LAN(dev) || IS_DSA_WAN(dev) ||
+					 (FROM_WED(skb) && IS_DSA_LAN(dev)))));
 		} else {
 			entry.ipv6_5t_route.iblk2.fqos = 0;
 		}
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
index c0abdc7..146f15f 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
@@ -725,6 +725,11 @@
 	ret = upper_ret-lower_ret;
 	if (ret == 1) {
 		ret = 0;
+		/* Make sure we use upper_idx in our calibration system */
+		cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RXADC_CTRL_RG9,
+			MTK_PHY_DA_RX_PSBN_TBT_MASK | MTK_PHY_DA_RX_PSBN_HBT_MASK |
+			MTK_PHY_DA_RX_PSBN_GBE_MASK | MTK_PHY_DA_RX_PSBN_LP_MASK,
+			upper_idx << 12 | upper_idx << 8 | upper_idx << 4 | upper_idx);
 		dev_info(&phydev->mdio.dev, "TX-VCM SW cal result: 0x%x\n", upper_idx);
 	} else if (lower_idx == TXRESERVE_MIN && upper_ret == 1 && lower_ret == 1) {
 		ret = 0;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h
index 70db398..a790c62 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-declaration.h
@@ -13,6 +13,7 @@
  DEVICE_ATTR_DECLARED(RG_USB20_HSTX_SRCTRL);
  DEVICE_ATTR_DECLARED(RG_USB20_DISCTH);
  DEVICE_ATTR_DECLARED(RG_CHGDT_EN);
+ DEVICE_ATTR_DECLARED(RG_USB20_PHY_REV);
  DEVICE_ATTR_DECLARED(reg);
 
  #define HQA_INFORMACTION_COLLECTS() do {\
@@ -22,5 +23,6 @@
 	ECHO_HQA(USB20_PHY_USBPHYACR5, RG_USB20_HSTX_SRCTRL, 3); \
 	ECHO_HQA(USB20_PHY_USBPHYACR6, RG_USB20_DISCTH, 4); \
 	ECHO_HQA(USB20_PHY_U2PHYBC12C, RG_CHGDT_EN, 1); \
+	ECHO_HQA(USB20_PHY_USBPHYACR6, RG_USB20_PHY_REV, 2); \
 	} while (0)
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h
index e898a26..80b786a 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/unusual-statement.h
@@ -13,5 +13,6 @@
 UNUSUAL_DEVICE_ATTR(RG_USB20_HSTX_SRCTRL),
 UNUSUAL_DEVICE_ATTR(RG_USB20_DISCTH),
 UNUSUAL_DEVICE_ATTR(RG_CHGDT_EN),
+UNUSUAL_DEVICE_ATTR(RG_USB20_PHY_REV),
 UNUSUAL_DEVICE_ATTR(reg),
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-preemphasic.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-preemphasic.c
new file mode 100644
index 0000000..5041dbc
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-preemphasic.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * xHCI host controller toolkit driver for pre-emphasic
+ *
+ * Copyright (C) 2021  MediaTek Inc.
+ *
+ *  Author: Zhanyong Wang <zhanyong.wang@mediatek.com>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include "xhci-mtk.h"
+#include "xhci-mtk-test.h"
+#include "xhci-mtk-unusual.h"
+
+static ssize_t RG_USB20_PHY_REV_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
+	struct usb_hcd *hcd = mtk->hcd;
+	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+	struct device_node  *node = dev->of_node;
+	ssize_t cnt = 0;
+	void __iomem *addr;
+	u32 val;
+	u32 i;
+	int ports;
+	char str[32];
+	int index = 0;
+	u32 io, length;
+	int ret;
+
+	ports = mtk->num_u3_ports + mtk->num_u2_ports;
+	cnt += sprintf(buf + cnt, " RG_USB20_PHY_REV usage:\n");
+	cnt += sprintf(buf + cnt,
+                "   echo u2p index 2b00 > RG_USB20_PHY_REV\n");
+	if (mtk->num_u3_ports + 1 != ports)
+		cnt += sprintf(buf + cnt, "	parameter: u2p: %i ~ %i\n",
+					mtk->num_u3_ports + 1, ports);
+	else
+		cnt += sprintf(buf + cnt, "	parameter: u2p: %i\n",
+					mtk->num_u3_ports + 1);
+
+	if (mtk->num_u2_ports > 1)
+		cnt += sprintf(buf + cnt, "	parameter: index: 0 ~ %i\n",
+			       mtk->num_u2_ports);
+	else
+		cnt += sprintf(buf + cnt, "	parameter: index: 0\n");
+
+	cnt += sprintf(buf + cnt, " e.g.: echo 2 0 2b10 > RG_USB20_PHY_REV\n");
+	cnt += sprintf(buf + cnt,
+		"  port2 binding phy 0, enable 2b'10 as RG_USB20_PHY_REV\n");
+
+	cnt += sprintf(buf + cnt,
+			"\n=========current HQA setting check=========\n");
+	for (i = 1; i <= ports; i++) {
+		addr = &xhci->op_regs->port_status_base +
+			NUM_PORT_REGS * ((i - 1) & 0xff);
+		val = readl(addr);
+		if (i <= mtk->num_u3_ports) {
+			cnt += sprintf(buf + cnt,
+				       "USB30 Port%i: 0x%08X\n", i, val);
+		} else {
+			cnt += sprintf(buf + cnt,
+				       "USB20 Port%i: 0x%08X\n", i, val);
+
+			ret = query_phy_addr(node,
+					 &index, &io, &length, PHY_TYPE_USB2);
+			if (ret && ret != -EACCES) {
+				if (ret == -EPERM)
+					cnt += sprintf(buf + cnt,
+					"USB20 Port%i (Phy%i: absent)\n",
+					i, index);
+				else
+					cnt += sprintf(buf + cnt,
+					"USB20 Port%i (Phy%i) failure %i\n",
+						 i, index, ret);
+				continue;
+			}
+
+			cnt += sprintf(buf + cnt,
+				"USB20 Port%i (Phy%i:%sable): 0x%08X 0x%08X\n",
+				i, index, ret ? " dis" : " en", io, length);
+
+			addr   = ioremap_nocache(io, length);
+			addr  += (length != 0x100) ? 0x300 : 0;
+
+			HQA_INFORMACTION_COLLECTS();
+
+			iounmap(addr);
+			index ++;
+		}
+	}
+
+	if (mtk->hqa_pos) {
+		cnt += sprintf(buf + cnt, "%s", mtk->hqa_buf);
+		mtk->hqa_pos = 0;
+	}
+
+	return cnt;
+}
+
+static ssize_t RG_USB20_PHY_REV_store(struct device *dev,
+                        struct device_attribute *attr,
+                        const char *buf, size_t n)
+{
+	u32 val;
+	u32 io;
+	u32 length;
+	int ports;
+	int words;
+	int port;
+	int index;
+	int ret;
+	char *str = NULL;
+	void __iomem *addr;
+	struct xhci_hcd_mtk *mtk  = dev_get_drvdata(dev);
+	struct device_node  *node = dev->of_node;
+
+	ports = mtk->num_u3_ports + mtk->num_u2_ports;
+	mtk->hqa_pos = 0;
+
+	memset(mtk->hqa_buf, 0, mtk->hqa_size);
+
+	str = kzalloc(n, GFP_ATOMIC);
+
+	hqa_info(mtk, "RG_USB20_PHY_REV(%lu): %s\n", n, buf);
+
+	words = sscanf(buf, "%i %i 2b%2[0,1]", &port, &index, str);
+	if ((words != 3) ||
+	    (port < mtk->num_u3_ports || port > ports)) {
+		hqa_info(mtk, "Check params(%i):\" %i %i %s\", Please!\n",
+			words, port, index, str);
+
+		ret = -EINVAL;
+		goto error;
+	}
+
+	hqa_info(mtk, " params: %i %i %s\n",
+		port, index, str);
+
+	ret = query_phy_addr(node, &index, &io, &length, PHY_TYPE_USB2);
+	if (ret && ret != -EACCES)
+		goto error;
+
+	io += (length != 0x100) ? 0x300 : 0;
+	io += USB20_PHY_USBPHYACR6;
+
+	addr = ioremap_nocache(io, 4);
+	val = binary_write_width2(addr, SHFT_RG_USB20_PHY_REV, str);
+	hqa_info(mtk, "Port%i(Phy%i)[0x%08X]: 0x%08X but 0x%08X\n",
+		port, index, io, val, readl(addr));
+
+	iounmap(addr);
+	ret = n;
+
+error:
+	kfree(str);
+	return ret;
+}
+DEVICE_ATTR_RW(RG_USB20_PHY_REV);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c
index 01029fb..b02720d 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.c
@@ -35,6 +35,27 @@
 	return val;
 }
 
+u32 binary_write_width2(u32 __iomem *addr, u32 shift, const char *buf)
+{
+	u32 val = 0;
+
+	if (!strncmp(buf, STRNG_0_WIDTH_2, BIT_WIDTH_2))
+		val = 0;
+	else if (!strncmp(buf, STRNG_1_WIDTH_2, BIT_WIDTH_2))
+		val = 1;
+	else if (!strncmp(buf, STRNG_2_WIDTH_2, BIT_WIDTH_2))
+		val = 2;
+	else if (!strncmp(buf, STRNG_3_WIDTH_2, BIT_WIDTH_2))
+		val = 3;
+	else
+		val = 0xFFFFFFFF;
+
+	if (val <= 3)
+		val = usb20hqa_write(addr, shift, MSK_WIDTH_2, val);
+
+	return val;
+}
+
 u32 binary_write_width3(u32 __iomem *addr, u32 shift, const char *buf)
 {
 	u32 val = 0;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h
index 0bc6dd8..3850ccf 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/usb/host/xhci-mtk-unusual.h
@@ -92,6 +92,11 @@
 #define SHFT_RG_CHGDT_EN		0
 #define BV_RG_CHGDT_EN			BIT(0)
 
+#define NAME_RG_USB20_PHY_REV		"RG_USB20_PHY_REV"
+/* #define USB20_PHY_USBPHYACR6		0x18 */
+#define SHFT_RG_USB20_PHY_REV		30
+#define BV_RG_USB20_PHY_REV		GENMASK(31, 30)
+
 #define ECHO_HQA(reg, _bd, _bw)  do {\
 	val = usb20hqa_read(addr + (reg), \
 		 SHFT_##_bd, \
@@ -127,6 +132,8 @@
 
 u32 binary_write_width1(u32 __iomem *addr,
 				u32 shift, const char *buf);
+u32 binary_write_width2(u32 __iomem *addr,
+				u32 shift, const char *buf);
 u32 binary_write_width3(u32 __iomem *addr,
 				u32 shift, const char *buf);
 u32 binary_write_width4(u32 __iomem *addr,
@@ -168,6 +175,11 @@
 {
 	return 0;
 };
+static inline u32 binary_write_width2(u32 __iomem *addr,
+					u32 shift, const char *buf)
+{
+	return 0;
+};
 static inline u32 binary_write_width3(u32 __iomem *addr,
 					u32 shift, const char *buf)
 {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
index 47c1ab3..5da593d 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9997-add-wed-rx-support-for-mt7896.patch
@@ -1,4 +1,4 @@
-From bc8244ada5c668374813f7f9b73d990bf2695aaf Mon Sep 17 00:00:00 2001
+From 7c81104d65728fb1c0f156c46e3cfc5dec24b119 Mon Sep 17 00:00:00 2001
 From: Sujuan Chen <sujuan.chen@mediatek.com>
 Date: Wed, 15 Jun 2022 14:38:54 +0800
 Subject: [PATCH 8/8] 9997-add-wed-rx-support-for-mt7896
@@ -13,13 +13,13 @@
  drivers/net/ethernet/mediatek/mtk_wed_ccif.c  | 133 ++++
  drivers/net/ethernet/mediatek/mtk_wed_ccif.h  |  45 ++
  .../net/ethernet/mediatek/mtk_wed_debugfs.c   |  90 +++
- drivers/net/ethernet/mediatek/mtk_wed_mcu.c   | 561 ++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_wed_mcu.c   | 586 ++++++++++++++++
  drivers/net/ethernet/mediatek/mtk_wed_mcu.h   | 125 ++++
  drivers/net/ethernet/mediatek/mtk_wed_regs.h  | 145 +++-
- drivers/net/ethernet/mediatek/mtk_wed_wo.c    | 588 ++++++++++++++++
- drivers/net/ethernet/mediatek/mtk_wed_wo.h    | 336 ++++++++++
- include/linux/soc/mediatek/mtk_wed.h          |  64 +-
- 14 files changed, 2643 insertions(+), 69 deletions(-)
+ drivers/net/ethernet/mediatek/mtk_wed_wo.c    | 581 ++++++++++++++++
+ drivers/net/ethernet/mediatek/mtk_wed_wo.h    | 327 +++++++++
+ include/linux/soc/mediatek/mtk_wed.h          |  75 ++-
+ 14 files changed, 2796 insertions(+), 75 deletions(-)
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ccif.c
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ccif.h
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_mcu.c
@@ -28,7 +28,7 @@
  create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_wo.h
 
 diff --git a/arch/arm64/boot/dts/mediatek/mt7986a.dtsi b/arch/arm64/boot/dts/mediatek/mt7986a.dtsi
-index 644255b35..ddcc0b809 100644
+index 87d2b11a9..6abc06db8 100644
 --- a/arch/arm64/boot/dts/mediatek/mt7986a.dtsi
 +++ b/arch/arm64/boot/dts/mediatek/mt7986a.dtsi
 @@ -65,6 +65,12 @@
@@ -170,7 +170,7 @@
  		resets = <&ethsysrst 0>;
  		reset-names = "wocpu_rst";
 diff --git a/drivers/net/ethernet/mediatek/Makefile b/drivers/net/ethernet/mediatek/Makefile
-index 3528f1b..0c724a5 100644
+index 3528f1b3c..0c724a55c 100644
 --- a/drivers/net/ethernet/mediatek/Makefile
 +++ b/drivers/net/ethernet/mediatek/Makefile
 @@ -10,5 +10,5 @@ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o
@@ -181,7 +181,7 @@
 +obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o mtk_wed_wo.o mtk_wed_mcu.o mtk_wed_ccif.o
  obj-$(CONFIG_NET_MEDIATEK_HNAT)			+= mtk_hnat/
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c
-index 48b0353..0750def 100644
+index 48b0353bb..75527956b 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed.c
 +++ b/drivers/net/ethernet/mediatek/mtk_wed.c
 @@ -13,11 +13,19 @@
@@ -338,7 +338,7 @@
  	for (i = 0, page_idx = 0; i < dev->buf_ring.size; i += MTK_WED_BUF_PER_PAGE) {
  		void *page = page_list[page_idx++];
  
-@@ -205,6 +316,42 @@ free_pagelist:
+@@ -205,6 +316,42 @@ mtk_wed_free_buffer(struct mtk_wed_device *dev)
  	kfree(page_list);
  }
  
@@ -1128,7 +1128,7 @@
  
  	mtk_wed_hw_add_debugfs(hw);
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed.h b/drivers/net/ethernet/mediatek/mtk_wed.h
-index 9b17b74..8ef5253 100644
+index 9b17b7405..8ef5253ca 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed.h
 +++ b/drivers/net/ethernet/mediatek/mtk_wed.h
 @@ -13,6 +13,7 @@
@@ -1232,7 +1232,7 @@
  #endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_ccif.c b/drivers/net/ethernet/mediatek/mtk_wed_ccif.c
 new file mode 100644
-index 0000000..22ef337
+index 000000000..22ef337d0
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_ccif.c
 @@ -0,0 +1,133 @@
@@ -1371,7 +1371,7 @@
 +}
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_ccif.h b/drivers/net/ethernet/mediatek/mtk_wed_ccif.h
 new file mode 100644
-index 0000000..68ade44
+index 000000000..68ade449c
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_ccif.h
 @@ -0,0 +1,45 @@
@@ -1421,7 +1421,7 @@
 +
 +#endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
-index f420f18..4a9e684 100644
+index f420f187e..4a9e684ed 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
 @@ -2,6 +2,7 @@
@@ -1563,10 +1563,10 @@
  }
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
 new file mode 100644
-index 0000000..bd1ab95
+index 000000000..723bdfd55
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
-@@ -0,0 +1,561 @@
+@@ -0,0 +1,586 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +
 +#include <linux/skbuff.h>
@@ -1744,8 +1744,10 @@
 +void
 +mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo, struct sk_buff *skb)
 +{
++	struct mtk_wed_device *wed = wo->hw->wed_dev;
 +	struct wed_cmd_hdr *hdr = (struct wed_cmd_hdr *)skb->data;
 +	struct wed_wo_log *record;
++	struct wo_cmd_rxcnt_t *rxcnt;
 +	char *msg = (char *)(skb->data + sizeof(struct wed_cmd_hdr));
 +	u16 msg_len = skb->len - sizeof(struct wed_cmd_hdr);
 +	u32 i, cnt = 0;
@@ -1771,7 +1773,14 @@
 +				 record[i].mod);
 +		}
 +		break;
++	case WO_EVT_RXCNT_INFO:
++		cnt = *(u32 *)msg;
++		rxcnt = (struct wo_cmd_rxcnt_t *)((u32 *)msg+1);
 +
++		for (i = 0; i < cnt; i++)
++			if (wed->wlan.update_wo_rxcnt)
++				wed->wlan.update_wo_rxcnt(wed, rxcnt);
++		break;
 +	default:
 +		break;
 +	}
@@ -1942,7 +1951,10 @@
 +wo_mcu_parse_response(struct mtk_wed_wo *wo, int cmd,
 +			  struct sk_buff *skb, int seq)
 +{
++	struct mtk_wed_device *wed = wo->hw->wed_dev;
 +	struct wed_cmd_hdr  *hdr;
++	struct wo_cmd_rxcnt_t *rxcnt = NULL;
++	u32 i, cnt = 0;
 +
 +	if (!skb) {
 +		dev_err(wo->hw->dev, "Message %08x (seq %d) timeout\n",
@@ -1957,7 +1969,20 @@
 +		return -EAGAIN;
 +	}
 +
-+	//skb_pull(skb, sizeof(struct wed_cmd_hdr));
++	skb_pull(skb, sizeof(struct wed_cmd_hdr));
++
++	switch (cmd) {
++	case WO_CMD_RXCNT_INFO:
++		cnt = *(u32 *)skb->data;
++		rxcnt = (struct wo_cmd_rxcnt_t *)((u32 *)skb->data+1);
++
++		for (i = 0; i < cnt; i++)
++			if (wed->wlan.update_wo_rxcnt)
++				wed->wlan.update_wo_rxcnt(wed, rxcnt);
++		break;
++	default:
++		break;
++	}
 +
 +	return 0;
 +}
@@ -2130,7 +2155,7 @@
 +
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.h b/drivers/net/ethernet/mediatek/mtk_wed_mcu.h
 new file mode 100644
-index 0000000..6a5ac76
+index 000000000..6a5ac7672
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.h
 @@ -0,0 +1,125 @@
@@ -2260,7 +2285,7 @@
 +
 +#endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_regs.h b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
-index e107de7..9d021e2 100644
+index e107de7ba..9d021e2da 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
 @@ -4,6 +4,8 @@
@@ -2524,7 +2549,7 @@
  #endif
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_wo.c b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
 new file mode 100644
-index 0000000..e101f17
+index 000000000..67dcffb26
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
 @@ -0,0 +1,581 @@
@@ -3111,10 +3136,10 @@
 +}
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed_wo.h b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
 new file mode 100644
-index 0000000..d962e3a
+index 000000000..d962e3a33
 --- /dev/null
 +++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
-@@ -0,0 +1,336 @@
+@@ -0,0 +1,327 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
 +
@@ -3259,15 +3284,6 @@
 +	bool done:1;
 +};
 +
-+struct wo_cmd_rxcnt_t {
-+	u16 wlan_idx;
-+	u16 tid;
-+	u32 rx_pkt_cnt;
-+	u32 rx_byte_cnt;
-+	u32 rx_err_cnt;
-+	u32 rx_drop_cnt;
-+};
-+
 +struct wo_cmd_query {
 +	u32 query0;
 +	u32 query1;
@@ -3452,7 +3468,7 @@
 +#endif
 +
 diff --git a/include/linux/soc/mediatek/mtk_wed.h b/include/linux/soc/mediatek/mtk_wed.h
-index ffd547a..9a9cc1b 100644
+index ffd547a4c..c74dd4aad 100644
 --- a/include/linux/soc/mediatek/mtk_wed.h
 +++ b/include/linux/soc/mediatek/mtk_wed.h
 @@ -7,6 +7,9 @@
@@ -3465,7 +3481,7 @@
  
  enum {
  	MTK_NO_WED,
-@@ -33,6 +36,24 @@ struct mtk_wed_ring {
+@@ -33,6 +36,33 @@ struct mtk_wed_ring {
  	void __iomem *wpdma;
  };
  
@@ -3487,10 +3503,19 @@
 +	dma_addr_t desc_phys;
 +};
 +
++struct wo_cmd_rxcnt_t {
++	u16 wlan_idx;
++	u16 tid;
++	u32 rx_pkt_cnt;
++	u32 rx_byte_cnt;
++	u32 rx_err_cnt;
++	u32 rx_drop_cnt;
++};
++
  struct mtk_wed_device {
  #ifdef CONFIG_NET_MEDIATEK_SOC_WED
  	const struct mtk_wed_ops *ops;
-@@ -42,39 +63,57 @@ struct mtk_wed_device {
+@@ -42,39 +63,59 @@ struct mtk_wed_device {
  	int wdma_idx;
  	int irq;
  	u8 ver;
@@ -3550,10 +3575,12 @@
 +		u32 (*init_rx_buf)(struct mtk_wed_device *wed,
 +				   int pkt_num);
 +		void (*release_rx_buf)(struct mtk_wed_device *wed);
++		void (*update_wo_rxcnt)(struct  mtk_wed_device *wed,
++				struct wo_cmd_rxcnt_t *rxcnt);
  	} wlan;
  #endif
  };
-@@ -85,6 +124,10 @@ struct mtk_wed_ops {
+@@ -85,6 +126,10 @@ struct mtk_wed_ops {
  			     void __iomem *regs);
  	int (*txfree_ring_setup)(struct mtk_wed_device *dev,
  				 void __iomem *regs);
@@ -3564,7 +3591,7 @@
  	void (*detach)(struct mtk_wed_device *dev);
  
  	void (*stop)(struct mtk_wed_device *dev);
-@@ -96,6 +139,8 @@ struct mtk_wed_ops {
+@@ -96,6 +141,8 @@ struct mtk_wed_ops {
  
  	u32 (*irq_get)(struct mtk_wed_device *dev, u32 mask);
  	void (*irq_set_mask)(struct mtk_wed_device *dev, u32 mask);
@@ -3573,7 +3600,7 @@
  };
  
  extern const struct mtk_wed_ops __rcu *mtk_soc_wed_ops;
-@@ -128,6 +173,10 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
+@@ -128,6 +175,10 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
  	(_dev)->ops->tx_ring_setup(_dev, _ring, _regs)
  #define mtk_wed_device_txfree_ring_setup(_dev, _regs) \
  	(_dev)->ops->txfree_ring_setup(_dev, _regs)
@@ -3584,7 +3611,7 @@
  #define mtk_wed_device_reg_read(_dev, _reg) \
  	(_dev)->ops->reg_read(_dev, _reg)
  #define mtk_wed_device_reg_write(_dev, _reg, _val) \
-@@ -136,6 +185,8 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
+@@ -136,6 +187,8 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
  	(_dev)->ops->irq_get(_dev, _mask)
  #define mtk_wed_device_irq_set_mask(_dev, _mask) \
  	(_dev)->ops->irq_set_mask(_dev, _mask)
@@ -3593,7 +3620,7 @@
  #else
  static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
  {
-@@ -145,10 +196,13 @@ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
+@@ -145,10 +198,13 @@ static inline bool mtk_wed_device_active(struct mtk_wed_device *dev)
  #define mtk_wed_device_start(_dev, _mask) do {} while (0)
  #define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) -ENODEV
  #define mtk_wed_device_txfree_ring_setup(_dev, _ring, _regs) -ENODEV
@@ -3608,5 +3635,5 @@
  
  #endif
 -- 
-2.18.0
+2.32.0
 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch
index 6442853..32daccb 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-add-wed-ser-support.patch
@@ -703,10 +703,12 @@
 index 9a9cc1b..31f4a26 100644
 --- a/include/linux/soc/mediatek/mtk_wed.h
 +++ b/include/linux/soc/mediatek/mtk_wed.h
-@@ -114,23 +114,27 @@ struct mtk_wed_device {
+@@ -114,25 +114,29 @@ struct mtk_wed_device {
  		u32 (*init_rx_buf)(struct mtk_wed_device *wed,
  				   int pkt_num);
  		void (*release_rx_buf)(struct mtk_wed_device *wed);
+ 		void (*update_wo_rxcnt)(struct  mtk_wed_device *wed,
+ 				struct wo_cmd_rxcnt_t *rxcnt);
 +		void (*ser_trigger)(struct mtk_wed_device *wed);
  	} wlan;
 +	struct completion fe_reset_done;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-update-net-bridge-for-bridger.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-update-net-bridge-for-bridger.patch
new file mode 100644
index 0000000..6fe3733
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-update-net-bridge-for-bridger.patch
@@ -0,0 +1,1823 @@
+diff --git a/include/net/switchdev.h b/include/net/switchdev.h
+index 191dc34..d4d71d9 100644
+--- a/include/net/switchdev.h
++++ b/include/net/switchdev.h
+@@ -77,6 +77,7 @@ struct switchdev_obj {
+ struct switchdev_obj_port_vlan {
+ 	struct switchdev_obj obj;
+ 	u16 flags;
++	u16 vid;
+ 	u16 vid_begin;
+ 	u16 vid_end;
+ };
+@@ -117,6 +118,7 @@ enum switchdev_notifier_type {
+ struct switchdev_notifier_info {
+ 	struct net_device *dev;
+ 	struct netlink_ext_ack *extack;
++	const void *ctx;
+ };
+ 
+ struct switchdev_notifier_fdb_info {
+diff --git a/net/bridge/Makefile b/net/bridge/Makefile
+index ac9ef33..49da7ae 100644
+--- a/net/bridge/Makefile
++++ b/net/bridge/Makefile
+@@ -20,7 +20,7 @@ obj-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
+ 
+ bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
+ 
+-bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o
++bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o
+ 
+ bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
+ 
+diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c
+index da5ed4c..eeabfbc 100644
+--- a/net/bridge/br_mdb.c
++++ b/net/bridge/br_mdb.c
+@@ -16,7 +16,37 @@
+ 
+ #include "br_private.h"
+ 
+-static int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
++static size_t __br_rports_one_size(void)
++{
++	return nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PORT */
++	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_TIMER */
++	       nla_total_size(sizeof(u8)) +  /* MDBA_ROUTER_PATTR_TYPE */
++	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET_TIMER */
++	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET6_TIMER */
++	       nla_total_size(sizeof(u32));  /* MDBA_ROUTER_PATTR_VID */
++}
++
++size_t br_rports_size(const struct net_bridge_mcast *brmctx)
++{
++	struct net_bridge_mcast_port *pmctx;
++	size_t size = nla_total_size(0); /* MDBA_ROUTER */
++
++	rcu_read_lock();
++	hlist_for_each_entry_rcu(pmctx, &brmctx->ip4_mc_router_list,
++				 ip4_rlist)
++		size += __br_rports_one_size();
++
++#if IS_ENABLED(CONFIG_IPV6)
++	hlist_for_each_entry_rcu(pmctx, &brmctx->ip6_mc_router_list,
++				 ip6_rlist)
++		size += __br_rports_one_size();
++#endif
++	rcu_read_unlock();
++
++	return size;
++}
++
++int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
+ 			       struct net_device *dev)
+ {
+ 	struct net_bridge *br = netdev_priv(dev);
+diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
+index cbcbc19..887e767 100644
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -562,7 +562,7 @@ static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
+ 	return err;
+ }
+ 
+-static int br_process_vlan_info(struct net_bridge *br,
++int br_process_vlan_info(struct net_bridge *br,
+ 				struct net_bridge_port *p, int cmd,
+ 				struct bridge_vlan_info *vinfo_curr,
+ 				struct bridge_vlan_info **vinfo_last,
+@@ -1578,7 +1578,7 @@ static int br_fill_linkxstats(struct sk_buff *skb,
+ 		pvid = br_get_pvid(vg);
+ 		list_for_each_entry(v, &vg->vlan_list, vlist) {
+ 			struct bridge_vlan_xstats vxi;
+-			struct br_vlan_stats stats;
++			struct pcpu_sw_netstats stats;
+ 
+ 			if (++vl_idx < *prividx)
+ 				continue;
+@@ -1652,6 +1652,7 @@ int __init br_netlink_init(void)
+ 	int err;
+ 
+ 	br_mdb_init();
++	br_vlan_rtnl_init();
+ 	rtnl_af_register(&br_af_ops);
+ 
+ 	err = rtnl_link_register(&br_link_ops);
+@@ -1669,6 +1670,7 @@ int __init br_netlink_init(void)
+ void br_netlink_fini(void)
+ {
+ 	br_mdb_uninit();
++	br_vlan_rtnl_uninit();
+ 	rtnl_af_unregister(&br_af_ops);
+ 	rtnl_link_unregister(&br_link_ops);
+ }
+diff --git a/net/bridge/br_netlink_tunnel.c b/net/bridge/br_netlink_tunnel.c
+index afee292..3bbbd66 100644
+--- a/net/bridge/br_netlink_tunnel.c
++++ b/net/bridge/br_netlink_tunnel.c
+@@ -26,7 +26,7 @@ static size_t __get_vlan_tinfo_size(void)
+ 		  nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */
+ }
+ 
+-static bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
++bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
+ 			       struct net_bridge_vlan *v_last)
+ {
+ 	__be32 tunid_curr = tunnel_id_to_key32(v_curr->tinfo.tunnel_id);
+@@ -193,7 +193,7 @@ static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX +
+ 	[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 },
+ };
+ 
+-static int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
++int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
+ 			       u16 vid, u32 tun_id, bool *changed)
+ {
+ 	int err = 0;
+diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
+index 4bd9e9b..4620f70 100644
+--- a/net/bridge/br_private.h
++++ b/net/bridge/br_private.h
+@@ -95,6 +95,60 @@ struct br_vlan_stats {
+ 	struct u64_stats_sync syncp;
+ };
+ 
++/* net_bridge_mcast_port must be always defined due to forwarding stubs */
++struct net_bridge_mcast_port {
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	struct net_bridge_port		*port;
++	struct net_bridge_vlan		*vlan;
++
++	struct bridge_mcast_own_query	ip4_own_query;
++	struct timer_list		ip4_mc_router_timer;
++	struct hlist_node		ip4_rlist;
++#if IS_ENABLED(CONFIG_IPV6)
++	struct bridge_mcast_own_query	ip6_own_query;
++	struct timer_list		ip6_mc_router_timer;
++	struct hlist_node		ip6_rlist;
++#endif /* IS_ENABLED(CONFIG_IPV6) */
++	unsigned char			multicast_router;
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
++};
++
++/* net_bridge_mcast must be always defined due to forwarding stubs */
++struct net_bridge_mcast {
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	struct net_bridge		*br;
++	struct net_bridge_vlan		*vlan;
++
++	u32				multicast_last_member_count;
++	u32				multicast_startup_query_count;
++
++	u8				multicast_querier;
++	u8				multicast_igmp_version;
++	u8				multicast_router;
++#if IS_ENABLED(CONFIG_IPV6)
++	u8				multicast_mld_version;
++#endif
++	unsigned long			multicast_last_member_interval;
++	unsigned long			multicast_membership_interval;
++	unsigned long			multicast_querier_interval;
++	unsigned long			multicast_query_interval;
++	unsigned long			multicast_query_response_interval;
++	unsigned long			multicast_startup_query_interval;
++	struct hlist_head		ip4_mc_router_list;
++	struct timer_list		ip4_mc_router_timer;
++	struct bridge_mcast_other_query	ip4_other_query;
++	struct bridge_mcast_own_query	ip4_own_query;
++	struct bridge_mcast_querier	ip4_querier;
++#if IS_ENABLED(CONFIG_IPV6)
++	struct hlist_head		ip6_mc_router_list;
++	struct timer_list		ip6_mc_router_timer;
++	struct bridge_mcast_other_query	ip6_other_query;
++	struct bridge_mcast_own_query	ip6_own_query;
++	struct bridge_mcast_querier	ip6_querier;
++#endif /* IS_ENABLED(CONFIG_IPV6) */
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
++};
++
+ struct br_tunnel_info {
+ 	__be64				tunnel_id;
+ 	struct metadata_dst __rcu	*tunnel_dst;
+@@ -104,6 +158,8 @@ struct br_tunnel_info {
+ enum {
+ 	BR_VLFLAG_PER_PORT_STATS = BIT(0),
+ 	BR_VLFLAG_ADDED_BY_SWITCHDEV = BIT(1),
++	BR_VLFLAG_MCAST_ENABLED = BIT(2),
++	BR_VLFLAG_GLOBAL_MCAST_ENABLED = BIT(3),
+ };
+ 
+ /**
+@@ -113,12 +169,16 @@ enum {
+  * @vid: VLAN id
+  * @flags: bridge vlan flags
+  * @priv_flags: private (in-kernel) bridge vlan flags
++ * @state: STP state (e.g. blocking, learning, forwarding)
+  * @stats: per-cpu VLAN statistics
+  * @br: if MASTER flag set, this points to a bridge struct
+  * @port: if MASTER flag unset, this points to a port struct
+  * @refcnt: if MASTER flag set, this is bumped for each port referencing it
+  * @brvlan: if MASTER flag unset, this points to the global per-VLAN context
+  *          for this VLAN entry
++ * @br_mcast_ctx: if MASTER flag set, this is the global vlan multicast context
++ * @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast
++ *                  context
+  * @vlist: sorted list of VLAN entries
+  * @rcu: used for entry destruction
+  *
+@@ -133,7 +193,8 @@ struct net_bridge_vlan {
+ 	u16				vid;
+ 	u16				flags;
+ 	u16				priv_flags;
+-	struct br_vlan_stats __percpu	*stats;
++	u8				state;
++	struct pcpu_sw_netstats __percpu *stats;
+ 	union {
+ 		struct net_bridge	*br;
+ 		struct net_bridge_port	*port;
+@@ -145,6 +206,11 @@ struct net_bridge_vlan {
+ 
+ 	struct br_tunnel_info		tinfo;
+ 
++	union {
++		struct net_bridge_mcast		br_mcast_ctx;
++		struct net_bridge_mcast_port	port_mcast_ctx;
++	};
++
+ 	struct list_head		vlist;
+ 
+ 	struct rcu_head			rcu;
+@@ -170,6 +236,7 @@ struct net_bridge_vlan_group {
+ 	struct list_head		vlan_list;
+ 	u16				num_vlans;
+ 	u16				pvid;
++	u8				pvid_state;
+ };
+ 
+ struct net_bridge_fdb_key {
+@@ -497,6 +564,67 @@ static inline bool br_vlan_should_use(const struct net_bridge_vlan *v)
+ 	return true;
+ }
+ 
++static inline bool br_vlan_valid_id(u16 vid, struct netlink_ext_ack *extack)
++{
++	bool ret = vid > 0 && vid < VLAN_VID_MASK;
++
++	if (!ret)
++		NL_SET_ERR_MSG_MOD(extack, "Vlan id is invalid");
++
++	return ret;
++}
++
++static inline bool br_vlan_valid_range(const struct bridge_vlan_info *cur,
++				       const struct bridge_vlan_info *last,
++				       struct netlink_ext_ack *extack)
++{
++	/* pvid flag is not allowed in ranges */
++	if (cur->flags & BRIDGE_VLAN_INFO_PVID) {
++		NL_SET_ERR_MSG_MOD(extack, "Pvid isn't allowed in a range");
++		return false;
++	}
++
++	/* when cur is the range end, check if:
++	 *  - it has range start flag
++	 *  - range ids are invalid (end is equal to or before start)
++	 */
++	if (last) {
++		if (cur->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
++			NL_SET_ERR_MSG_MOD(extack, "Found a new vlan range start while processing one");
++			return false;
++		} else if (!(cur->flags & BRIDGE_VLAN_INFO_RANGE_END)) {
++			NL_SET_ERR_MSG_MOD(extack, "Vlan range end flag is missing");
++			return false;
++		} else if (cur->vid <= last->vid) {
++			NL_SET_ERR_MSG_MOD(extack, "End vlan id is less than or equal to start vlan id");
++			return false;
++		}
++	}
++
++	/* check for required range flags */
++	if (!(cur->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
++			    BRIDGE_VLAN_INFO_RANGE_END))) {
++		NL_SET_ERR_MSG_MOD(extack, "Both vlan range flags are missing");
++		return false;
++	}
++
++	return true;
++}
++
++static inline u8 br_vlan_multicast_router(const struct net_bridge_vlan *v)
++{
++	u8 mcast_router = MDB_RTR_TYPE_DISABLED;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	if (!br_vlan_is_master(v))
++		mcast_router = v->port_mcast_ctx.multicast_router;
++	else
++		mcast_router = v->br_mcast_ctx.multicast_router;
++#endif
++
++	return mcast_router;
++}
++
+ static inline int br_opt_get(const struct net_bridge *br,
+ 			     enum net_bridge_opts opt)
+ {
+@@ -676,8 +804,10 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
+ 			struct sk_buff *skb, bool local_rcv, bool local_orig);
+ int br_multicast_set_router(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
++int br_multicast_set_vlan_router(struct net_bridge_vlan *v, u8 mcast_router);
+ int br_multicast_toggle(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
++
+ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
+ #if IS_ENABLED(CONFIG_IPV6)
+@@ -708,6 +838,17 @@ void br_mdb_init(void);
+ void br_mdb_uninit(void);
+ void br_multicast_host_join(struct net_bridge_mdb_entry *mp, bool notify);
+ void br_multicast_host_leave(struct net_bridge_mdb_entry *mp, bool notify);
++int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
++			       struct net_device *dev);
++int br_multicast_dump_querier_state(struct sk_buff *skb,
++				    const struct net_bridge_mcast *brmctx,
++				    int nest_attr);
++size_t br_multicast_querier_state_size(void);
++size_t br_rports_size(const struct net_bridge_mcast *brmctx);
++void br_multicast_set_query_intvl(struct net_bridge_mcast *brmctx,
++				  unsigned long val);
++void br_multicast_set_startup_query_intvl(struct net_bridge_mcast *brmctx,
++					  unsigned long val);
+ 
+ #define mlock_dereference(X, br) \
+ 	rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock))
+@@ -760,6 +901,49 @@ static inline int br_multicast_igmp_type(const struct sk_buff *skb)
+ {
+ 	return BR_INPUT_SKB_CB(skb)->igmp;
+ }
++static inline bool
++br_rports_have_mc_router(const struct net_bridge_mcast *brmctx)
++{
++#if IS_ENABLED(CONFIG_IPV6)
++	return !hlist_empty(&brmctx->ip4_mc_router_list) ||
++	       !hlist_empty(&brmctx->ip6_mc_router_list);
++#else
++	return !hlist_empty(&brmctx->ip4_mc_router_list);
++#endif
++}
++
++static inline bool
++br_multicast_ctx_options_equal(const struct net_bridge_mcast *brmctx1,
++			       const struct net_bridge_mcast *brmctx2)
++{
++	return brmctx1->multicast_igmp_version ==
++	       brmctx2->multicast_igmp_version &&
++	       brmctx1->multicast_last_member_count ==
++	       brmctx2->multicast_last_member_count &&
++	       brmctx1->multicast_startup_query_count ==
++	       brmctx2->multicast_startup_query_count &&
++	       brmctx1->multicast_last_member_interval ==
++	       brmctx2->multicast_last_member_interval &&
++	       brmctx1->multicast_membership_interval ==
++	       brmctx2->multicast_membership_interval &&
++	       brmctx1->multicast_querier_interval ==
++	       brmctx2->multicast_querier_interval &&
++	       brmctx1->multicast_query_interval ==
++	       brmctx2->multicast_query_interval &&
++	       brmctx1->multicast_query_response_interval ==
++	       brmctx2->multicast_query_response_interval &&
++	       brmctx1->multicast_startup_query_interval ==
++	       brmctx2->multicast_startup_query_interval &&
++	       brmctx1->multicast_querier == brmctx2->multicast_querier &&
++	       brmctx1->multicast_router == brmctx2->multicast_router &&
++	       !br_rports_have_mc_router(brmctx1) &&
++	       !br_rports_have_mc_router(brmctx2) &&
++#if IS_ENABLED(CONFIG_IPV6)
++	       brmctx1->multicast_mld_version ==
++	       brmctx2->multicast_mld_version &&
++#endif
++	       true;
++}
+ #else
+ static inline int br_multicast_rcv(struct net_bridge *br,
+ 				   struct net_bridge_port *port,
+@@ -907,10 +1091,21 @@ void nbp_vlan_flush(struct net_bridge_port *port);
+ int nbp_vlan_init(struct net_bridge_port *port, struct netlink_ext_ack *extack);
+ int nbp_get_num_vlan_infos(struct net_bridge_port *p, u32 filter_mask);
+ void br_vlan_get_stats(const struct net_bridge_vlan *v,
+-		       struct br_vlan_stats *stats);
++		       struct pcpu_sw_netstats *stats);
+ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event);
+ int br_vlan_bridge_event(struct net_device *dev, unsigned long event,
+ 			 void *ptr);
++void br_vlan_rtnl_init(void);
++void br_vlan_rtnl_uninit(void);
++void br_vlan_notify(const struct net_bridge *br,
++		    const struct net_bridge_port *p,
++		    u16 vid, u16 vid_range,
++		    int cmd);
++int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
++		   const void *ctx, bool adding, struct notifier_block *nb,
++		   struct netlink_ext_ack *extack);
++bool br_vlan_can_enter_range(struct net_bridge_vlan *v_curr,
++			     struct net_bridge_vlan *range_end);
+ 
+ void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+ 				    struct net_device_path_ctx *ctx,
+@@ -969,6 +1164,10 @@ static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)
+ 	return vg->pvid;
+ }
+ 
++static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
++{
++	return v->vid == pvid ? v->flags | BRIDGE_VLAN_INFO_PVID : v->flags;
++}
+ #else
+ static inline bool br_allowed_ingress(const struct net_bridge *br,
+ 				      struct net_bridge_vlan_group *vg,
+@@ -1111,7 +1310,7 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu(
+ }
+ 
+ static inline void br_vlan_get_stats(const struct net_bridge_vlan *v,
+-				     struct br_vlan_stats *stats)
++				     struct pcpu_sw_netstats *stats)
+ {
+ }
+ 
+@@ -1125,6 +1324,88 @@ static inline int br_vlan_bridge_event(struct net_device *dev,
+ {
+ 	return 0;
+ }
++
++static inline void br_vlan_rtnl_init(void)
++{
++}
++
++static inline void br_vlan_rtnl_uninit(void)
++{
++}
++
++static inline void br_vlan_notify(const struct net_bridge *br,
++				  const struct net_bridge_port *p,
++				  u16 vid, u16 vid_range,
++				  int cmd)
++{
++}
++
++static inline bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
++					   const struct net_bridge_vlan *range_end)
++{
++	return true;
++}
++
++static inline int br_vlan_replay(struct net_device *br_dev,
++				 struct net_device *dev, const void *ctx,
++				 bool adding, struct notifier_block *nb,
++				 struct netlink_ext_ack *extack)
++{
++	return -EOPNOTSUPP;
++}
++#endif
++
++/* br_vlan_options.c */
++#ifdef CONFIG_BRIDGE_VLAN_FILTERING
++bool br_vlan_opts_eq_range(struct net_bridge_vlan *v_curr,
++			   struct net_bridge_vlan *range_end);
++bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v);
++size_t br_vlan_opts_nl_size(void);
++int br_vlan_process_options(const struct net_bridge *br,
++			    struct net_bridge_port *p,
++			    struct net_bridge_vlan *range_start,
++			    struct net_bridge_vlan *range_end,
++			    struct nlattr **tb,
++			    struct netlink_ext_ack *extack);
++bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
++					 const struct net_bridge_vlan *r_end);
++bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
++			      const struct net_bridge_vlan *v_opts);
++
++/* vlan state manipulation helpers using *_ONCE to annotate lock-free access */
++static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
++{
++	return READ_ONCE(v->state);
++}
++
++static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
++{
++	WRITE_ONCE(v->state, state);
++}
++
++static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
++{
++	return READ_ONCE(vg->pvid_state);
++}
++
++static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,
++					  u8 state)
++{
++	WRITE_ONCE(vg->pvid_state, state);
++}
++
++/* learn_allow is true at ingress and false at egress */
++static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
++{
++	switch (state) {
++	case BR_STATE_LEARNING:
++		return learn_allow;
++	case BR_STATE_FORWARDING:
++		return true;
++	default:
++		return false;
++	}
++}
+ #endif
+ 
+ struct nf_br_ops {
+@@ -1196,6 +1477,12 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
+ int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);
+ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq, struct net_device *dev,
+ 	       u32 filter_mask, int nlflags);
++int br_process_vlan_info(struct net_bridge *br,
++			 struct net_bridge_port *p, int cmd,
++			 struct bridge_vlan_info *vinfo_curr,
++			 struct bridge_vlan_info **vinfo_last,
++			 bool *changed,
++			 struct netlink_ext_ack *extack);
+ 
+ #ifdef CONFIG_SYSFS
+ /* br_sysfs_if.c */
+diff --git a/net/bridge/br_private_tunnel.h b/net/bridge/br_private_tunnel.h
+index 2bdef2e..25be963 100644
+--- a/net/bridge/br_private_tunnel.h
++++ b/net/bridge/br_private_tunnel.h
+@@ -42,6 +42,10 @@ int br_handle_ingress_vlan_tunnel(struct sk_buff *skb,
+ 				  struct net_bridge_vlan_group *vg);
+ int br_handle_egress_vlan_tunnel(struct sk_buff *skb,
+ 				 struct net_bridge_vlan *vlan);
++bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
++			struct net_bridge_vlan *v_last);
++int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
++			u16 vid, u32 tun_id, bool *changed);
+ #else
+ static inline int vlan_tunnel_init(struct net_bridge_vlan_group *vg)
+ {
+diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
+index bcfd169..2b5950c 100644
+--- a/net/bridge/br_vlan.c
++++ b/net/bridge/br_vlan.c
+@@ -34,13 +34,15 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
+ 	return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
+ }
+ 
+-static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid)
++static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg,
++			    const struct net_bridge_vlan *v)
+ {
+-	if (vg->pvid == vid)
++	if (vg->pvid == v->vid)
+ 		return false;
+ 
+ 	smp_wmb();
+-	vg->pvid = vid;
++	br_vlan_set_pvid_state(vg, v->state);
++	vg->pvid = v->vid;
+ 
+ 	return true;
+ }
+@@ -69,7 +71,7 @@ static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
+ 		vg = nbp_vlan_group(v->port);
+ 
+ 	if (flags & BRIDGE_VLAN_INFO_PVID)
+-		ret = __vlan_add_pvid(vg, v->vid);
++		ret = __vlan_add_pvid(vg, v);
+ 	else
+ 		ret = __vlan_delete_pvid(vg, v->vid);
+ 
+@@ -257,6 +259,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
+ 					  &changed, extack);
+ 			if (err)
+ 				goto out_filt;
++
++			if (changed)
++				br_vlan_notify(br, NULL, v->vid, 0,
++					       RTM_NEWVLAN);
+ 		}
+ 
+ 		masterv = br_vlan_get_master(br, v->vid, extack);
+@@ -266,7 +272,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
+ 		}
+ 		v->brvlan = masterv;
+ 		if (br_opt_get(br, BROPT_VLAN_STATS_PER_PORT)) {
+-			v->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
++			v->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ 			if (!v->stats) {
+ 				err = -ENOMEM;
+ 				goto out_filt;
+@@ -382,13 +388,31 @@ static void __vlan_group_free(struct net_bridge_vlan_group *vg)
+ 	kfree(vg);
+ }
+ 
+-static void __vlan_flush(struct net_bridge_vlan_group *vg)
++static void __vlan_flush(const struct net_bridge *br,
++			 const struct net_bridge_port *p,
++			 struct net_bridge_vlan_group *vg)
+ {
+ 	struct net_bridge_vlan *vlan, *tmp;
++	u16 v_start = 0, v_end = 0;
+ 
+ 	__vlan_delete_pvid(vg, vg->pvid);
+-	list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist)
++	list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) {
++		/* take care of disjoint ranges */
++		if (!v_start) {
++			v_start = vlan->vid;
++		} else if (vlan->vid - v_end != 1) {
++			/* found range end, notify and start next one */
++			br_vlan_notify(br, p, v_start, v_end, RTM_DELVLAN);
++			v_start = vlan->vid;
++		}
++		v_end = vlan->vid;
++
+ 		__vlan_del(vlan);
++	}
++
++	/* notify about the last/whole vlan range */
++	if (v_start)
++		br_vlan_notify(br, p, v_start, v_end, RTM_DELVLAN);
+ }
+ 
+ struct sk_buff *br_handle_vlan(struct net_bridge *br,
+@@ -396,7 +420,7 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
+ 			       struct net_bridge_vlan_group *vg,
+ 			       struct sk_buff *skb)
+ {
+-	struct br_vlan_stats *stats;
++	struct pcpu_sw_netstats *stats;
+ 	struct net_bridge_vlan *v;
+ 	u16 vid;
+ 
+@@ -448,7 +472,7 @@ static bool __allowed_ingress(const struct net_bridge *br,
+ 			      struct net_bridge_vlan_group *vg,
+ 			      struct sk_buff *skb, u16 *vid)
+ {
+-	struct br_vlan_stats *stats;
++	struct pcpu_sw_netstats *stats;
+ 	struct net_bridge_vlan *v;
+ 	bool tagged;
+ 
+@@ -666,7 +690,7 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,
+ 	if (!vlan)
+ 		return -ENOMEM;
+ 
+-	vlan->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
++	vlan->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ 	if (!vlan->stats) {
+ 		kfree(vlan);
+ 		return -ENOMEM;
+@@ -718,7 +742,7 @@ void br_vlan_flush(struct net_bridge *br)
+ 	ASSERT_RTNL();
+ 
+ 	vg = br_vlan_group(br);
+-	__vlan_flush(vg);
++	__vlan_flush(br, NULL, vg);
+ 	RCU_INIT_POINTER(br->vlgrp, NULL);
+ 	synchronize_rcu();
+ 	__vlan_group_free(vg);
+@@ -927,12 +951,15 @@ static void br_vlan_disable_default_pvid(struct net_bridge *br)
+ 	/* Disable default_pvid on all ports where it is still
+ 	 * configured.
+ 	 */
+-	if (vlan_default_pvid(br_vlan_group(br), pvid))
+-		br_vlan_delete(br, pvid);
++	if (vlan_default_pvid(br_vlan_group(br), pvid)) {
++		if (!br_vlan_delete(br, pvid))
++			br_vlan_notify(br, NULL, pvid, 0, RTM_DELVLAN);
++	}
+ 
+ 	list_for_each_entry(p, &br->port_list, list) {
+-		if (vlan_default_pvid(nbp_vlan_group(p), pvid))
+-			nbp_vlan_delete(p, pvid);
++		if (vlan_default_pvid(nbp_vlan_group(p), pvid) &&
++		    !nbp_vlan_delete(p, pvid))
++			br_vlan_notify(br, p, pvid, 0, RTM_DELVLAN);
+ 	}
+ 
+ 	br->default_pvid = 0;
+@@ -974,7 +1001,10 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ 				  &vlchange, extack);
+ 		if (err)
+ 			goto out;
+-		br_vlan_delete(br, old_pvid);
++
++		if (br_vlan_delete(br, old_pvid))
++			br_vlan_notify(br, NULL, old_pvid, 0, RTM_DELVLAN);
++		br_vlan_notify(br, NULL, pvid, 0, RTM_NEWVLAN);
+ 		set_bit(0, changed);
+ 	}
+ 
+@@ -994,7 +1024,9 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ 				   &vlchange, extack);
+ 		if (err)
+ 			goto err_port;
+-		nbp_vlan_delete(p, old_pvid);
++		if (nbp_vlan_delete(p, old_pvid))
++			br_vlan_notify(br, p, old_pvid, 0, RTM_DELVLAN);
++		br_vlan_notify(p->br, p, pvid, 0, RTM_NEWVLAN);
+ 		set_bit(p->port_no, changed);
+ 	}
+ 
+@@ -1009,22 +1041,28 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ 		if (!test_bit(p->port_no, changed))
+ 			continue;
+ 
+-		if (old_pvid)
++		if (old_pvid) {
+ 			nbp_vlan_add(p, old_pvid,
+ 				     BRIDGE_VLAN_INFO_PVID |
+ 				     BRIDGE_VLAN_INFO_UNTAGGED,
+ 				     &vlchange, NULL);
++			br_vlan_notify(p->br, p, old_pvid, 0, RTM_NEWVLAN);
++		}
+ 		nbp_vlan_delete(p, pvid);
++		br_vlan_notify(br, p, pvid, 0, RTM_DELVLAN);
+ 	}
+ 
+ 	if (test_bit(0, changed)) {
+-		if (old_pvid)
++		if (old_pvid) {
+ 			br_vlan_add(br, old_pvid,
+ 				    BRIDGE_VLAN_INFO_PVID |
+ 				    BRIDGE_VLAN_INFO_UNTAGGED |
+ 				    BRIDGE_VLAN_INFO_BRENTRY,
+ 				    &vlchange, NULL);
++			br_vlan_notify(br, NULL, old_pvid, 0, RTM_NEWVLAN);
++		}
+ 		br_vlan_delete(br, pvid);
++		br_vlan_notify(br, NULL, pvid, 0, RTM_DELVLAN);
+ 	}
+ 	goto out;
+ }
+@@ -1117,6 +1155,7 @@ int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)
+ 				   &changed, extack);
+ 		if (ret)
+ 			goto err_vlan_add;
++		br_vlan_notify(p->br, p, p->br->default_pvid, 0, RTM_NEWVLAN);
+ 	}
+ out:
+ 	return ret;
+@@ -1198,21 +1237,21 @@ void nbp_vlan_flush(struct net_bridge_port *port)
+ 	ASSERT_RTNL();
+ 
+ 	vg = nbp_vlan_group(port);
+-	__vlan_flush(vg);
++	__vlan_flush(port->br, port, vg);
+ 	RCU_INIT_POINTER(port->vlgrp, NULL);
+ 	synchronize_rcu();
+ 	__vlan_group_free(vg);
+ }
+ 
+ void br_vlan_get_stats(const struct net_bridge_vlan *v,
+-		       struct br_vlan_stats *stats)
++		       struct pcpu_sw_netstats *stats)
+ {
+ 	int i;
+ 
+ 	memset(stats, 0, sizeof(*stats));
+ 	for_each_possible_cpu(i) {
+ 		u64 rxpackets, rxbytes, txpackets, txbytes;
+-		struct br_vlan_stats *cpu_stats;
++		struct pcpu_sw_netstats *cpu_stats;
+ 		unsigned int start;
+ 
+ 		cpu_stats = per_cpu_ptr(v->stats, i);
+@@ -1526,8 +1565,8 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ {
+ 	struct netdev_notifier_changeupper_info *info;
+ 	struct net_bridge *br = netdev_priv(dev);
+-	bool changed;
+-	int ret = 0;
++	int vlcmd = 0, ret = 0;
++	bool changed = false;
+ 
+ 	switch (event) {
+ 	case NETDEV_REGISTER:
+@@ -1535,9 +1574,11 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ 				  BRIDGE_VLAN_INFO_PVID |
+ 				  BRIDGE_VLAN_INFO_UNTAGGED |
+ 				  BRIDGE_VLAN_INFO_BRENTRY, &changed, NULL);
++		vlcmd = RTM_NEWVLAN;
+ 		break;
+ 	case NETDEV_UNREGISTER:
+-		br_vlan_delete(br, br->default_pvid);
++		changed = !br_vlan_delete(br, br->default_pvid);
++		vlcmd = RTM_DELVLAN;
+ 		break;
+ 	case NETDEV_CHANGEUPPER:
+ 		info = ptr;
+@@ -1551,6 +1592,8 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ 		br_vlan_link_state_change(dev, br);
+ 		break;
+ 	}
++	if (changed)
++		br_vlan_notify(br, NULL, br->default_pvid, 0, vlcmd);
+ 
+ 	return ret;
+ }
+@@ -1569,3 +1612,608 @@ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
+ 		break;
+ 	}
+ }
++
++static bool br_vlan_stats_fill(struct sk_buff *skb,
++			       const struct net_bridge_vlan *v)
++{
++	struct pcpu_sw_netstats stats;
++	struct nlattr *nest;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_STATS);
++	if (!nest)
++		return false;
++
++	br_vlan_get_stats(v, &stats);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_RX_BYTES, stats.rx_bytes,
++			      BRIDGE_VLANDB_STATS_PAD) ||
++	    nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_RX_PACKETS,
++			      stats.rx_packets, BRIDGE_VLANDB_STATS_PAD) ||
++	    nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_TX_BYTES, stats.tx_bytes,
++			      BRIDGE_VLANDB_STATS_PAD) ||
++	    nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_TX_PACKETS,
++			      stats.tx_packets, BRIDGE_VLANDB_STATS_PAD))
++		goto out_err;
++
++	nla_nest_end(skb, nest);
++
++	return true;
++
++out_err:
++	nla_nest_cancel(skb, nest);
++	return false;
++}
++
++/* v_opts is used to dump the options which must be equal in the whole range */
++static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 vid_range,
++			      const struct net_bridge_vlan *v_opts,
++			      u16 flags,
++			      bool dump_stats)
++{
++	struct bridge_vlan_info info;
++	struct nlattr *nest;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY);
++	if (!nest)
++		return false;
++
++	memset(&info, 0, sizeof(info));
++	info.vid = vid;
++	if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
++		info.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
++	if (flags & BRIDGE_VLAN_INFO_PVID)
++		info.flags |= BRIDGE_VLAN_INFO_PVID;
++
++	if (nla_put(skb, BRIDGE_VLANDB_ENTRY_INFO, sizeof(info), &info))
++		goto out_err;
++
++	if (vid_range && vid < vid_range &&
++	    !(flags & BRIDGE_VLAN_INFO_PVID) &&
++	    nla_put_u16(skb, BRIDGE_VLANDB_ENTRY_RANGE, vid_range))
++		goto out_err;
++
++	if (v_opts) {
++		if (!br_vlan_opts_fill(skb, v_opts))
++			goto out_err;
++
++		if (dump_stats && !br_vlan_stats_fill(skb, v_opts))
++			goto out_err;
++	}
++
++	nla_nest_end(skb, nest);
++
++	return true;
++
++out_err:
++	nla_nest_cancel(skb, nest);
++	return false;
++}
++
++static size_t rtnl_vlan_nlmsg_size(void)
++{
++	return NLMSG_ALIGN(sizeof(struct br_vlan_msg))
++		+ nla_total_size(0) /* BRIDGE_VLANDB_ENTRY */
++		+ nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_ENTRY_RANGE */
++		+ nla_total_size(sizeof(struct bridge_vlan_info)) /* BRIDGE_VLANDB_ENTRY_INFO */
++		+ br_vlan_opts_nl_size(); /* bridge vlan options */
++}
++
++void br_vlan_notify(const struct net_bridge *br,
++		    const struct net_bridge_port *p,
++		    u16 vid, u16 vid_range,
++		    int cmd)
++{
++	struct net_bridge_vlan_group *vg;
++	struct net_bridge_vlan *v = NULL;
++	struct br_vlan_msg *bvm;
++	struct nlmsghdr *nlh;
++	struct sk_buff *skb;
++	int err = -ENOBUFS;
++	struct net *net;
++	u16 flags = 0;
++	int ifindex;
++
++	/* right now notifications are done only with rtnl held */
++	ASSERT_RTNL();
++
++	if (p) {
++		ifindex = p->dev->ifindex;
++		vg = nbp_vlan_group(p);
++		net = dev_net(p->dev);
++	} else {
++		ifindex = br->dev->ifindex;
++		vg = br_vlan_group(br);
++		net = dev_net(br->dev);
++	}
++
++	skb = nlmsg_new(rtnl_vlan_nlmsg_size(), GFP_KERNEL);
++	if (!skb)
++		goto out_err;
++
++	err = -EMSGSIZE;
++	nlh = nlmsg_put(skb, 0, 0, cmd, sizeof(*bvm), 0);
++	if (!nlh)
++		goto out_err;
++	bvm = nlmsg_data(nlh);
++	memset(bvm, 0, sizeof(*bvm));
++	bvm->family = AF_BRIDGE;
++	bvm->ifindex = ifindex;
++
++	switch (cmd) {
++	case RTM_NEWVLAN:
++		/* need to find the vlan due to flags/options */
++		v = br_vlan_find(vg, vid);
++		if (!v || !br_vlan_should_use(v))
++			goto out_kfree;
++
++		flags = v->flags;
++		if (br_get_pvid(vg) == v->vid)
++			flags |= BRIDGE_VLAN_INFO_PVID;
++		break;
++	case RTM_DELVLAN:
++		break;
++	default:
++		goto out_kfree;
++	}
++
++	if (!br_vlan_fill_vids(skb, vid, vid_range, v, flags, false))
++		goto out_err;
++
++	nlmsg_end(skb, nlh);
++	rtnl_notify(skb, net, 0, RTNLGRP_BRVLAN, NULL, GFP_KERNEL);
++	return;
++
++out_err:
++	rtnl_set_sk_err(net, RTNLGRP_BRVLAN, err);
++out_kfree:
++	kfree_skb(skb);
++}
++
++static int br_vlan_replay_one(struct notifier_block *nb,
++			      struct net_device *dev,
++			      struct switchdev_obj_port_vlan *vlan,
++			      const void *ctx, unsigned long action,
++			      struct netlink_ext_ack *extack)
++{
++	struct switchdev_notifier_port_obj_info obj_info = {
++		.info = {
++			.dev = dev,
++			.extack = extack,
++			.ctx = ctx,
++		},
++		.obj = &vlan->obj,
++	};
++	int err;
++
++	err = nb->notifier_call(nb, action, &obj_info);
++	return notifier_to_errno(err);
++}
++
++int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
++		   const void *ctx, bool adding, struct notifier_block *nb,
++		   struct netlink_ext_ack *extack)
++{
++	struct net_bridge_vlan_group *vg;
++	struct net_bridge_vlan *v;
++	struct net_bridge_port *p;
++	struct net_bridge *br;
++	unsigned long action;
++	int err = 0;
++	u16 pvid;
++
++	ASSERT_RTNL();
++
++	if (!nb)
++		return 0;
++
++	if (!netif_is_bridge_master(br_dev))
++		return -EINVAL;
++
++	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
++		return -EINVAL;
++
++	if (netif_is_bridge_master(dev)) {
++		br = netdev_priv(dev);
++		vg = br_vlan_group(br);
++		p = NULL;
++	} else {
++		p = br_port_get_rtnl(dev);
++		if (WARN_ON(!p))
++			return -EINVAL;
++		vg = nbp_vlan_group(p);
++		br = p->br;
++	}
++
++	if (!vg)
++		return 0;
++
++	if (adding)
++		action = SWITCHDEV_PORT_OBJ_ADD;
++	else
++		action = SWITCHDEV_PORT_OBJ_DEL;
++
++	pvid = br_get_pvid(vg);
++
++	list_for_each_entry(v, &vg->vlan_list, vlist) {
++		struct switchdev_obj_port_vlan vlan = {
++			.obj.orig_dev = dev,
++			.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
++			.flags = br_vlan_flags(v, pvid),
++			.vid = v->vid,
++		};
++
++		if (!br_vlan_should_use(v))
++			continue;
++
++		err = br_vlan_replay_one(nb, dev, &vlan, ctx, action, extack);
++		if (err)
++			return err;
++	}
++
++	return err;
++}
++
++/* check if v_curr can enter a range ending in range_end */
++bool br_vlan_can_enter_range(struct net_bridge_vlan *v_curr,
++			     struct net_bridge_vlan *range_end)
++{
++	return v_curr->vid - range_end->vid == 1 &&
++	       range_end->flags == v_curr->flags &&
++	       br_vlan_opts_eq_range(v_curr, range_end);
++}
++
++static int br_vlan_dump_dev(const struct net_device *dev,
++			    struct sk_buff *skb,
++			    struct netlink_callback *cb,
++			    u32 dump_flags)
++{
++	struct net_bridge_vlan *v, *range_start = NULL, *range_end = NULL;
++	bool dump_global = !!(dump_flags & BRIDGE_VLANDB_DUMPF_GLOBAL);
++	bool dump_stats = !!(dump_flags & BRIDGE_VLANDB_DUMPF_STATS);
++	struct net_bridge_vlan_group *vg;
++	int idx = 0, s_idx = cb->args[1];
++	struct nlmsghdr *nlh = NULL;
++	struct net_bridge_port *p;
++	struct br_vlan_msg *bvm;
++	struct net_bridge *br;
++	int err = 0;
++	u16 pvid;
++
++	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
++		return -EINVAL;
++
++	if (netif_is_bridge_master(dev)) {
++		br = netdev_priv(dev);
++		vg = br_vlan_group_rcu(br);
++		p = NULL;
++	} else {
++		/* global options are dumped only for bridge devices */
++		if (dump_global)
++			return 0;
++
++		p = br_port_get_rcu(dev);
++		if (WARN_ON(!p))
++			return -EINVAL;
++		vg = nbp_vlan_group_rcu(p);
++		br = p->br;
++	}
++
++	if (!vg)
++		return 0;
++
++	nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
++			RTM_NEWVLAN, sizeof(*bvm), NLM_F_MULTI);
++	if (!nlh)
++		return -EMSGSIZE;
++	bvm = nlmsg_data(nlh);
++	memset(bvm, 0, sizeof(*bvm));
++	bvm->family = PF_BRIDGE;
++	bvm->ifindex = dev->ifindex;
++	pvid = br_get_pvid(vg);
++
++	/* idx must stay at range's beginning until it is filled in */
++	list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
++		if (!dump_global && !br_vlan_should_use(v))
++			continue;
++		if (idx < s_idx) {
++			idx++;
++			continue;
++		}
++
++		if (!range_start) {
++			range_start = v;
++			range_end = v;
++			continue;
++		}
++
++		if (dump_global) {
++			if (br_vlan_global_opts_can_enter_range(v, range_end))
++				goto update_end;
++			if (!br_vlan_global_opts_fill(skb, range_start->vid,
++						      range_end->vid,
++						      range_start)) {
++				err = -EMSGSIZE;
++				break;
++			}
++			/* advance number of filled vlans */
++			idx += range_end->vid - range_start->vid + 1;
++
++			range_start = v;
++		} else if (dump_stats || v->vid == pvid ||
++			   !br_vlan_can_enter_range(v, range_end)) {
++			u16 vlan_flags = br_vlan_flags(range_start, pvid);
++
++			if (!br_vlan_fill_vids(skb, range_start->vid,
++					       range_end->vid, range_start,
++					       vlan_flags, dump_stats)) {
++				err = -EMSGSIZE;
++				break;
++			}
++			/* advance number of filled vlans */
++			idx += range_end->vid - range_start->vid + 1;
++
++			range_start = v;
++		}
++update_end:
++		range_end = v;
++	}
++
++	/* err will be 0 and range_start will be set in 3 cases here:
++	 * - first vlan (range_start == range_end)
++	 * - last vlan (range_start == range_end, not in range)
++	 * - last vlan range (range_start != range_end, in range)
++	 */
++	if (!err && range_start) {
++		if (dump_global &&
++		    !br_vlan_global_opts_fill(skb, range_start->vid,
++					      range_end->vid, range_start))
++			err = -EMSGSIZE;
++		else if (!dump_global &&
++			 !br_vlan_fill_vids(skb, range_start->vid,
++					    range_end->vid, range_start,
++					    br_vlan_flags(range_start, pvid),
++					    dump_stats))
++			err = -EMSGSIZE;
++	}
++
++	cb->args[1] = err ? idx : 0;
++
++	nlmsg_end(skb, nlh);
++
++	return err;
++}
++
++static const struct nla_policy br_vlan_db_dump_pol[BRIDGE_VLANDB_DUMP_MAX + 1] = {
++	[BRIDGE_VLANDB_DUMP_FLAGS] = { .type = NLA_U32 },
++};
++
++static int br_vlan_rtm_dump(struct sk_buff *skb, struct netlink_callback *cb)
++{
++	struct nlattr *dtb[BRIDGE_VLANDB_DUMP_MAX + 1];
++	int idx = 0, err = 0, s_idx = cb->args[0];
++	struct net *net = sock_net(skb->sk);
++	struct br_vlan_msg *bvm;
++	struct net_device *dev;
++	u32 dump_flags = 0;
++
++	err = nlmsg_parse(cb->nlh, sizeof(*bvm), dtb, BRIDGE_VLANDB_DUMP_MAX,
++			  br_vlan_db_dump_pol, cb->extack);
++	if (err < 0)
++		return err;
++
++	bvm = nlmsg_data(cb->nlh);
++	if (dtb[BRIDGE_VLANDB_DUMP_FLAGS])
++		dump_flags = nla_get_u32(dtb[BRIDGE_VLANDB_DUMP_FLAGS]);
++
++	rcu_read_lock();
++	if (bvm->ifindex) {
++		dev = dev_get_by_index_rcu(net, bvm->ifindex);
++		if (!dev) {
++			err = -ENODEV;
++			goto out_err;
++		}
++		err = br_vlan_dump_dev(dev, skb, cb, dump_flags);
++		/* if the dump completed without an error we return 0 here */
++		if (err != -EMSGSIZE)
++			goto out_err;
++	} else {
++		for_each_netdev_rcu(net, dev) {
++			if (idx < s_idx)
++				goto skip;
++
++			err = br_vlan_dump_dev(dev, skb, cb, dump_flags);
++			if (err == -EMSGSIZE)
++				break;
++skip:
++			idx++;
++		}
++	}
++	cb->args[0] = idx;
++	rcu_read_unlock();
++
++	return skb->len;
++
++out_err:
++	rcu_read_unlock();
++
++	return err;
++}
++
++static const struct nla_policy br_vlan_db_policy[BRIDGE_VLANDB_ENTRY_MAX + 1] = {
++	[BRIDGE_VLANDB_ENTRY_INFO]	=
++		NLA_POLICY_EXACT_LEN(sizeof(struct bridge_vlan_info)),
++	[BRIDGE_VLANDB_ENTRY_RANGE]	= { .type = NLA_U16 },
++	[BRIDGE_VLANDB_ENTRY_STATE]	= { .type = NLA_U8 },
++	[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO] = { .type = NLA_NESTED },
++	[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]	= { .type = NLA_U8 },
++};
++
++static int br_vlan_rtm_process_one(struct net_device *dev,
++				   const struct nlattr *attr,
++				   int cmd, struct netlink_ext_ack *extack)
++{
++	struct bridge_vlan_info *vinfo, vrange_end, *vinfo_last = NULL;
++	struct nlattr *tb[BRIDGE_VLANDB_ENTRY_MAX + 1];
++	bool changed = false, skip_processing = false;
++	struct net_bridge_vlan_group *vg;
++	struct net_bridge_port *p = NULL;
++	int err = 0, cmdmap = 0;
++	struct net_bridge *br;
++
++	if (netif_is_bridge_master(dev)) {
++		br = netdev_priv(dev);
++		vg = br_vlan_group(br);
++	} else {
++		p = br_port_get_rtnl(dev);
++		if (WARN_ON(!p))
++			return -ENODEV;
++		br = p->br;
++		vg = nbp_vlan_group(p);
++	}
++
++	if (WARN_ON(!vg))
++		return -ENODEV;
++
++	err = nla_parse_nested(tb, BRIDGE_VLANDB_ENTRY_MAX, attr,
++			       br_vlan_db_policy, extack);
++	if (err)
++		return err;
++
++	if (!tb[BRIDGE_VLANDB_ENTRY_INFO]) {
++		NL_SET_ERR_MSG_MOD(extack, "Missing vlan entry info");
++		return -EINVAL;
++	}
++	memset(&vrange_end, 0, sizeof(vrange_end));
++
++	vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
++	if (vinfo->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
++			    BRIDGE_VLAN_INFO_RANGE_END)) {
++		NL_SET_ERR_MSG_MOD(extack, "Old-style vlan ranges are not allowed when using RTM vlan calls");
++		return -EINVAL;
++	}
++	if (!br_vlan_valid_id(vinfo->vid, extack))
++		return -EINVAL;
++
++	if (tb[BRIDGE_VLANDB_ENTRY_RANGE]) {
++		vrange_end.vid = nla_get_u16(tb[BRIDGE_VLANDB_ENTRY_RANGE]);
++		/* validate user-provided flags without RANGE_BEGIN */
++		vrange_end.flags = BRIDGE_VLAN_INFO_RANGE_END | vinfo->flags;
++		vinfo->flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
++
++		/* vinfo_last is the range start, vinfo the range end */
++		vinfo_last = vinfo;
++		vinfo = &vrange_end;
++
++		if (!br_vlan_valid_id(vinfo->vid, extack) ||
++		    !br_vlan_valid_range(vinfo, vinfo_last, extack))
++			return -EINVAL;
++	}
++
++	switch (cmd) {
++	case RTM_NEWVLAN:
++		cmdmap = RTM_SETLINK;
++		skip_processing = !!(vinfo->flags & BRIDGE_VLAN_INFO_ONLY_OPTS);
++		break;
++	case RTM_DELVLAN:
++		cmdmap = RTM_DELLINK;
++		break;
++	}
++
++	if (!skip_processing) {
++		struct bridge_vlan_info *tmp_last = vinfo_last;
++
++		/* br_process_vlan_info may overwrite vinfo_last */
++		err = br_process_vlan_info(br, p, cmdmap, vinfo, &tmp_last,
++					   &changed, extack);
++
++		/* notify first if anything changed */
++		if (changed)
++			br_ifinfo_notify(cmdmap, br, p);
++
++		if (err)
++			return err;
++	}
++
++	/* deal with options */
++	if (cmd == RTM_NEWVLAN) {
++		struct net_bridge_vlan *range_start, *range_end;
++
++		if (vinfo_last) {
++			range_start = br_vlan_find(vg, vinfo_last->vid);
++			range_end = br_vlan_find(vg, vinfo->vid);
++		} else {
++			range_start = br_vlan_find(vg, vinfo->vid);
++			range_end = range_start;
++		}
++
++		err = br_vlan_process_options(br, p, range_start, range_end,
++					      tb, extack);
++	}
++
++	return err;
++}
++
++static int br_vlan_rtm_process(struct sk_buff *skb, struct nlmsghdr *nlh,
++			       struct netlink_ext_ack *extack)
++{
++	struct net *net = sock_net(skb->sk);
++	struct br_vlan_msg *bvm;
++	struct net_device *dev;
++	struct nlattr *attr;
++	int err, vlans = 0;
++	int rem;
++
++	/* this should validate the header and check for remaining bytes */
++	err = nlmsg_parse(nlh, sizeof(*bvm), NULL, BRIDGE_VLANDB_MAX, NULL,
++			  extack);
++	if (err < 0)
++		return err;
++
++	bvm = nlmsg_data(nlh);
++	dev = __dev_get_by_index(net, bvm->ifindex);
++	if (!dev)
++		return -ENODEV;
++
++	if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev)) {
++		NL_SET_ERR_MSG_MOD(extack, "The device is not a valid bridge or bridge port");
++		return -EINVAL;
++	}
++
++	nlmsg_for_each_attr(attr, nlh, sizeof(*bvm), rem) {
++		switch (nla_type(attr)) {
++		case BRIDGE_VLANDB_ENTRY:
++			err = br_vlan_rtm_process_one(dev, attr,
++						      nlh->nlmsg_type,
++						      extack);
++			break;
++		default:
++			continue;
++		}
++
++		vlans++;
++		if (err)
++			break;
++	}
++	if (!vlans) {
++		NL_SET_ERR_MSG_MOD(extack, "No vlans found to process");
++		err = -EINVAL;
++	}
++
++	return err;
++}
++
++void br_vlan_rtnl_init(void)
++{
++	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_GETVLAN, NULL,
++			     br_vlan_rtm_dump, 0);
++	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_NEWVLAN,
++			     br_vlan_rtm_process, NULL, 0);
++	rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_DELVLAN,
++			     br_vlan_rtm_process, NULL, 0);
++}
++
++void br_vlan_rtnl_uninit(void)
++{
++	rtnl_unregister(PF_BRIDGE, RTM_GETVLAN);
++	rtnl_unregister(PF_BRIDGE, RTM_NEWVLAN);
++	rtnl_unregister(PF_BRIDGE, RTM_DELVLAN);
++}
+diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
+new file mode 100644
+index 0000000..5e48c29
+--- /dev/null
++++ b/net/bridge/br_vlan_options.c
+@@ -0,0 +1,346 @@
++// SPDX-License-Identifier: GPL-2.0-only
++// Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/rtnetlink.h>
++#include <linux/slab.h>
++#include <net/ip_tunnels.h>
++
++#include "br_private.h"
++#include "br_private_tunnel.h"
++
++static bool __vlan_tun_put(struct sk_buff *skb, const struct net_bridge_vlan *v)
++{
++	__be32 tid = tunnel_id_to_key32(v->tinfo.tunnel_id);
++	struct nlattr *nest;
++
++	if (!v->tinfo.tunnel_dst)
++		return true;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_TUNNEL_INFO);
++	if (!nest)
++		return false;
++	if (nla_put_u32(skb, BRIDGE_VLANDB_TINFO_ID, be32_to_cpu(tid))) {
++		nla_nest_cancel(skb, nest);
++		return false;
++	}
++	nla_nest_end(skb, nest);
++
++	return true;
++}
++
++static bool __vlan_tun_can_enter_range(struct net_bridge_vlan *v_curr,
++				       struct net_bridge_vlan *range_end)
++{
++	return (!v_curr->tinfo.tunnel_dst && !range_end->tinfo.tunnel_dst) ||
++	       vlan_tunid_inrange(v_curr, range_end);
++}
++
++/* check if the options' state of v_curr allow it to enter the range */
++bool br_vlan_opts_eq_range(struct net_bridge_vlan *v_curr,
++			   struct net_bridge_vlan *range_end)
++{
++	u8 range_mc_rtr = br_vlan_multicast_router(range_end);
++	u8 curr_mc_rtr = br_vlan_multicast_router(v_curr);
++
++	return v_curr->state == range_end->state &&
++	       __vlan_tun_can_enter_range(v_curr, range_end) &&
++	       curr_mc_rtr == range_mc_rtr;
++}
++
++bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
++{
++	if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE, br_vlan_get_state(v)) ||
++	    !__vlan_tun_put(skb, v))
++		return false;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
++		       br_vlan_multicast_router(v)))
++		return false;
++#endif
++
++	return true;
++}
++
++size_t br_vlan_opts_nl_size(void)
++{
++	return nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_STATE */
++	       + nla_total_size(0) /* BRIDGE_VLANDB_ENTRY_TUNNEL_INFO */
++	       + nla_total_size(sizeof(u32)) /* BRIDGE_VLANDB_TINFO_ID */
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	       + nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_MCAST_ROUTER */
++#endif
++	       + 0;
++}
++
++static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
++				struct net_bridge_vlan *v,
++				u8 state,
++				bool *changed,
++				struct netlink_ext_ack *extack)
++{
++	struct net_bridge *br;
++
++	ASSERT_RTNL();
++
++	if (state > BR_STATE_BLOCKING) {
++		NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state");
++		return -EINVAL;
++	}
++
++	if (br_vlan_is_brentry(v))
++		br = v->br;
++	else
++		br = v->port->br;
++
++	if (br->stp_enabled == BR_KERNEL_STP) {
++		NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP");
++		return -EBUSY;
++	}
++
++	if (v->state == state)
++		return 0;
++
++	if (v->vid == br_get_pvid(vg))
++		br_vlan_set_pvid_state(vg, state);
++
++	br_vlan_set_state(v, state);
++	*changed = true;
++
++	return 0;
++}
++
++static const struct nla_policy br_vlandb_tinfo_pol[BRIDGE_VLANDB_TINFO_MAX + 1] = {
++	[BRIDGE_VLANDB_TINFO_ID]	= { .type = NLA_U32 },
++	[BRIDGE_VLANDB_TINFO_CMD]	= { .type = NLA_U32 },
++};
++
++static int br_vlan_modify_tunnel(struct net_bridge_port *p,
++				 struct net_bridge_vlan *v,
++				 struct nlattr **tb,
++				 bool *changed,
++				 struct netlink_ext_ack *extack)
++{
++	struct nlattr *tun_tb[BRIDGE_VLANDB_TINFO_MAX + 1], *attr;
++	struct bridge_vlan_info *vinfo;
++	u32 tun_id = 0;
++	int cmd, err;
++
++	if (!p) {
++		NL_SET_ERR_MSG_MOD(extack, "Can't modify tunnel mapping of non-port vlans");
++		return -EINVAL;
++	}
++	if (!(p->flags & BR_VLAN_TUNNEL)) {
++		NL_SET_ERR_MSG_MOD(extack, "Port doesn't have tunnel flag set");
++		return -EINVAL;
++	}
++
++	attr = tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO];
++	err = nla_parse_nested(tun_tb, BRIDGE_VLANDB_TINFO_MAX, attr,
++			       br_vlandb_tinfo_pol, extack);
++	if (err)
++		return err;
++
++	if (!tun_tb[BRIDGE_VLANDB_TINFO_CMD]) {
++		NL_SET_ERR_MSG_MOD(extack, "Missing tunnel command attribute");
++		return -ENOENT;
++	}
++	cmd = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_CMD]);
++	switch (cmd) {
++	case RTM_SETLINK:
++		if (!tun_tb[BRIDGE_VLANDB_TINFO_ID]) {
++			NL_SET_ERR_MSG_MOD(extack, "Missing tunnel id attribute");
++			return -ENOENT;
++		}
++		/* when working on vlan ranges this is the starting tunnel id */
++		tun_id = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_ID]);
++		/* vlan info attr is guaranteed by br_vlan_rtm_process_one */
++		vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
++		/* tunnel ids are mapped to each vlan in increasing order,
++		 * the starting vlan is in BRIDGE_VLANDB_ENTRY_INFO and v is the
++		 * current vlan, so we compute: tun_id + v - vinfo->vid
++		 */
++		tun_id += v->vid - vinfo->vid;
++		break;
++	case RTM_DELLINK:
++		break;
++	default:
++		NL_SET_ERR_MSG_MOD(extack, "Unsupported tunnel command");
++		return -EINVAL;
++	}
++
++	return br_vlan_tunnel_info(p, cmd, v->vid, tun_id, changed);
++}
++
++static int br_vlan_process_one_opts(const struct net_bridge *br,
++				    struct net_bridge_port *p,
++				    struct net_bridge_vlan_group *vg,
++				    struct net_bridge_vlan *v,
++				    struct nlattr **tb,
++				    bool *changed,
++				    struct netlink_ext_ack *extack)
++{
++	int err;
++
++	*changed = false;
++	if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
++		u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
++
++		err = br_vlan_modify_state(vg, v, state, changed, extack);
++		if (err)
++			return err;
++	}
++	if (tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO]) {
++		err = br_vlan_modify_tunnel(p, v, tb, changed, extack);
++		if (err)
++			return err;
++	}
++
++	return 0;
++}
++
++int br_vlan_process_options(const struct net_bridge *br,
++			    struct net_bridge_port *p,
++			    struct net_bridge_vlan *range_start,
++			    struct net_bridge_vlan *range_end,
++			    struct nlattr **tb,
++			    struct netlink_ext_ack *extack)
++{
++	struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
++	struct net_bridge_vlan_group *vg;
++	int vid, err = 0;
++	u16 pvid;
++
++	if (p)
++		vg = nbp_vlan_group(p);
++	else
++		vg = br_vlan_group(br);
++
++	if (!range_start || !br_vlan_should_use(range_start)) {
++		NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options");
++		return -ENOENT;
++	}
++	if (!range_end || !br_vlan_should_use(range_end)) {
++		NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options");
++		return -ENOENT;
++	}
++
++	pvid = br_get_pvid(vg);
++	for (vid = range_start->vid; vid <= range_end->vid; vid++) {
++		bool changed = false;
++
++		v = br_vlan_find(vg, vid);
++		if (!v || !br_vlan_should_use(v)) {
++			NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options");
++			err = -ENOENT;
++			break;
++		}
++
++		err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
++					       extack);
++		if (err)
++			break;
++
++		if (changed) {
++			/* vlan options changed, check for range */
++			if (!curr_start) {
++				curr_start = v;
++				curr_end = v;
++				continue;
++			}
++
++			if (v->vid == pvid ||
++			    !br_vlan_can_enter_range(v, curr_end)) {
++				br_vlan_notify(br, p, curr_start->vid,
++					       curr_end->vid, RTM_NEWVLAN);
++				curr_start = v;
++			}
++			curr_end = v;
++		} else {
++			/* nothing changed and nothing to notify yet */
++			if (!curr_start)
++				continue;
++
++			br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
++				       RTM_NEWVLAN);
++			curr_start = NULL;
++			curr_end = NULL;
++		}
++	}
++	if (curr_start)
++		br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
++			       RTM_NEWVLAN);
++
++	return err;
++}
++
++bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
++					 const struct net_bridge_vlan *r_end)
++{
++	return v_curr->vid - r_end->vid == 1 &&
++	       ((v_curr->priv_flags ^ r_end->priv_flags) &
++		BR_VLFLAG_GLOBAL_MCAST_ENABLED) == 0 &&
++		br_multicast_ctx_options_equal(&v_curr->br_mcast_ctx,
++					       &r_end->br_mcast_ctx);
++}
++
++bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
++			      const struct net_bridge_vlan *v_opts)
++{
++	struct nlattr *nest2 __maybe_unused;
++	u64 clockval __maybe_unused;
++	struct nlattr *nest;
++
++	nest = nla_nest_start(skb, BRIDGE_VLANDB_GLOBAL_OPTIONS);
++	if (!nest)
++		return false;
++
++	if (nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_ID, vid))
++		goto out_err;
++
++	if (vid_range && vid < vid_range &&
++	    nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_RANGE, vid_range))
++		goto out_err;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_last_member_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_membership_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_querier_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_query_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_query_response_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++	clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_startup_query_interval);
++	if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
++			      clockval, BRIDGE_VLANDB_GOPTS_PAD))
++		goto out_err;
++
++#if IS_ENABLED(CONFIG_IPV6)
++	if (nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
++		       v_opts->br_mcast_ctx.multicast_mld_version))
++		goto out_err;
++#endif
++#endif
++
++	nla_nest_end(skb, nest);
++
++	return true;
++
++out_err:
++	nla_nest_cancel(skb, nest);
++	return false;
++}
+diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
+index dbc9b2f..706b207 100644
+--- a/net/core/rtnetlink.c
++++ b/net/core/rtnetlink.c
+@@ -1996,6 +1996,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
+ 				goto cont;
+ 			if (idx < s_idx)
+ 				goto cont;
++
+ 			err = rtnl_fill_ifinfo(skb, dev, net,
+ 					       RTM_NEWLINK,
+ 					       NETLINK_CB(cb->skb).portid,
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 2dfaa1e..a60a26c 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1495,8 +1495,19 @@ int dsa_slave_create(struct dsa_port *port)
+ 		goto out_phy;
+ 	}
+ 
++	rtnl_lock();
++
++	ret = netdev_upper_dev_link(master, slave_dev, NULL);
++
++	rtnl_unlock();
++
++	if (ret)
++		goto out_unregister;
++
+ 	return 0;
+ 
++out_unregister:
++	unregister_netdev(slave_dev);
+ out_phy:
+ 	rtnl_lock();
+ 	phylink_disconnect_phy(p->dp->pl);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg
index b4eef30..eb9070e 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7986.cfg
@@ -293,6 +293,7 @@
 CONFIG_LZO_DECOMPRESS=y
 CONFIG_MAGIC_SYSRQ=y
 CONFIG_MAGIC_SYSRQ_SERIAL=y
+CONFIG_MAXLINEAR_GPHY=y
 CONFIG_MD=y
 CONFIG_MDIO_BUS=y
 CONFIG_MDIO_DEVICE=y
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
index 26bef5f..6812f0c 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/746-mxl-gpy-phy-support.patch
@@ -32,7 +32,7 @@
 index 0000000..7304278
 --- /dev/null
 +++ b/drivers/net/phy/mxl-gpy.c
-@@ -0,0 +1,738 @@
+@@ -0,0 +1,749 @@
 +// SPDX-License-Identifier: GPL-2.0+
 +/* Copyright (C) 2021 Maxlinear Corporation
 + * Copyright (C) 2020 Intel Corporation
@@ -98,6 +98,7 @@
 +#define VSPEC1_SGMII_CTRL_ANRS	BIT(9)		/* Restart Aneg */
 +#define VSPEC1_SGMII_ANEN_ANRS	(VSPEC1_SGMII_CTRL_ANEN | \
 +				 VSPEC1_SGMII_CTRL_ANRS)
++#define VSPEC1_SGMII_FIXED_2G5	BIT(5)
 +
 +/* WoL */
 +#define VPSPEC2_WOL_CTL		0x0E06
@@ -300,13 +301,23 @@
 +	case SPEED_2500:
 +		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
 +		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
-+				     VSPEC1_SGMII_CTRL_ANEN, 0);
++				     VSPEC1_SGMII_CTRL_ANEN | VSPEC1_SGMII_FIXED_2G5,
++				     0);
 +		if (ret < 0)
 +			phydev_err(phydev,
 +				   "Error: Disable of SGMII ANEG failed: %d\n",
 +				   ret);
 +		break;
 +	case SPEED_1000:
++		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
++		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
++				     VSPEC1_SGMII_CTRL_ANEN | VSPEC1_SGMII_FIXED_2G5,
++				     VSPEC1_SGMII_FIXED_2G5);
++		if (ret < 0)
++			phydev_err(phydev,
++				   "Error: Disable of SGMII ANEG failed: %d\n",
++				   ret);
++		break;
 +	case SPEED_100:
 +	case SPEED_10:
 +		phydev->interface = PHY_INTERFACE_MODE_SGMII;
@@ -316,7 +327,7 @@
 +		 * if ANEG is disabled (in 2500-BaseX mode).
 +		 */
 +		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_SGMII_CTRL,
-+				     VSPEC1_SGMII_ANEN_ANRS,
++				     VSPEC1_SGMII_ANEN_ANRS | VSPEC1_SGMII_FIXED_2G5,
 +				     VSPEC1_SGMII_ANEN_ANRS);
 +		if (ret < 0)
 +			phydev_err(phydev,
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch
index 452b237..0b3bf3c 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9001-PATCH-1-2-xHCI-MT7986-USB-2.0-USBIF-compliance-toolkit.patch
@@ -1,19 +1,19 @@
-From 9deb29cc86b8fdee6702f8d575f08f9a214cf90a Mon Sep 17 00:00:00 2001
+From 4d19c233a01598a28fdc528ebaeb7a1b4bb6884f Mon Sep 17 00:00:00 2001
 From: Zhanyong Wang <zhanyong.wang@mediatek.com>
-Date: Thu, 27 May 2021 11:44:17 +0800
-Subject: [PATCH 1/2] xHCI: MT7986 USB 2.0 USBIF compliance toolkit
+Date: Mon, 15 Aug 2022 12:40:22 +0800
+Subject: [PATCH 2/3] xHCI: MT79xx USB 2.0 USBIF compliance toolkit
 
-MT7986 USB 2.0 USBIF compliance toolkit
+MT79xx USB 2.0 USBIF compliance toolkit
 
 Signed-off-by: Zhanyong Wang <zhanyong.wang@mediatek.com>
 ---
- drivers/usb/host/Kconfig    | 9 +++++++++
- drivers/usb/host/Makefile   | 9 +++++++++
- drivers/usb/host/xhci-mtk.c | 5 ++++-
- drivers/usb/host/xhci-mtk.h | 7 +++++++
- drivers/usb/host/xhci.c     | 2 +-
- drivers/usb/host/xhci.h     | 1 +
- 6 files changed, 31 insertions(+), 2 deletions(-)
+ drivers/usb/host/Kconfig    |  9 +++++++++
+ drivers/usb/host/Makefile   | 10 ++++++++++
+ drivers/usb/host/xhci-mtk.c |  5 ++++-
+ drivers/usb/host/xhci-mtk.h |  7 +++++++
+ drivers/usb/host/xhci.c     |  2 +-
+ drivers/usb/host/xhci.h     |  1 +
+ 6 files changed, 32 insertions(+), 2 deletions(-)
 
 diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
 index 79b2e79dddd0..12b1bf9aa043 100644
@@ -36,10 +36,10 @@
  	tristate "xHCI support for Marvell Armada 375/38x/37xx"
  	select USB_XHCI_PLATFORM
 diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
-index b191361257cc..612c855adfa1 100644
+index b191361257cc..f064f836db2b 100644
 --- a/drivers/usb/host/Makefile
 +++ b/drivers/usb/host/Makefile
-@@ -21,6 +21,15 @@ endif
+@@ -21,6 +21,16 @@ endif
  
  ifneq ($(CONFIG_USB_XHCI_MTK), )
  	xhci-hcd-y += xhci-mtk-sch.o
@@ -52,14 +52,15 @@
 +	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-discth.o
 +	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-chgdt-en.o
 +	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-reg.o
++	xhci-hcd-$(CONFIG_USB_XHCI_MTK_DEBUGFS) += xhci-mtk-preemphasic.o
  endif
  
  xhci-plat-hcd-y := xhci-plat.o
 diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
-index 5c0eb35cd007..8bd4c95a5435 100644
+index 104296fdd03e..d4345657945d 100644
 --- a/drivers/usb/host/xhci-mtk.c
 +++ b/drivers/usb/host/xhci-mtk.c
-@@ -18,9 +18,10 @@
+@@ -19,9 +19,10 @@
  #include <linux/pm_runtime.h>
  #include <linux/regmap.h>
  #include <linux/regulator/consumer.h>
@@ -71,7 +72,7 @@
  
  /* ip_pw_ctrl0 register */
  #define CTRL0_IP_SW_RST	BIT(0)
-@@ -570,6 +571,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
+@@ -581,6 +582,7 @@ static int xhci_mtk_probe(struct platform_device *pdev)
  	ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED);
  	if (ret)
  		goto dealloc_usb2_hcd;
@@ -79,7 +80,7 @@
  
  	return 0;
  
-@@ -604,6 +606,7 @@ static int xhci_mtk_remove(struct platform_device *dev)
+@@ -615,6 +617,7 @@ static int xhci_mtk_remove(struct platform_device *dev)
  	struct usb_hcd	*hcd = mtk->hcd;
  	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
  	struct usb_hcd  *shared_hcd = xhci->shared_hcd;
@@ -106,10 +107,10 @@
  
  static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd)
 diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
-index 1c8070023161..cf004950bc00 100644
+index 9fe35bb67731..4f62eddce6ab 100644
 --- a/drivers/usb/host/xhci.c
 +++ b/drivers/usb/host/xhci.c
-@@ -713,7 +713,7 @@ EXPORT_SYMBOL_GPL(xhci_run);
+@@ -711,7 +711,7 @@ EXPORT_SYMBOL_GPL(xhci_run);
   * Disable device contexts, disable IRQs, and quiesce the HC.
   * Reset the HC, finish any completed transactions, and cleanup memory.
   */
@@ -119,12 +120,12 @@
  	u32 temp;
  	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
 diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
-index 02df309e4409..3af400068324 100644
+index a9031f494984..b54be4833ef7 100644
 --- a/drivers/usb/host/xhci.h
 +++ b/drivers/usb/host/xhci.h
-@@ -2067,6 +2067,7 @@ int xhci_halt(struct xhci_hcd *xhci);
+@@ -2070,6 +2070,7 @@ int xhci_halt(struct xhci_hcd *xhci);
  int xhci_start(struct xhci_hcd *xhci);
- int xhci_reset(struct xhci_hcd *xhci);
+ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us);
  int xhci_run(struct usb_hcd *hcd);
 +void xhci_stop(struct usb_hcd *hcd);
  int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch
index 14c32a8..c4970eb 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/9002-PATCH-1-1-usb-add-embedded-Host-feature-support.patch
@@ -1,7 +1,7 @@
-From 5b28b61fb9c88e3d2f7c7057929d55e54bc17966 Mon Sep 17 00:00:00 2001
+From be6839b4144867c7ea6ffbedb6c6a2a42976e16d Mon Sep 17 00:00:00 2001
 From: Zhanyong Wang <zhanyong.wang@mediatek.com>
 Date: Thu, 17 Jun 2021 16:09:04 +0800
-Subject: [PATCH 2/2] usb: add embedded Host feature support
+Subject: [PATCH 3/3] usb: add embedded Host feature support
 
 add EH(Embedded Host) feature for PET authentication
 1. need CONFIG_USB_OTG_WHITELIST enable
@@ -107,10 +107,10 @@
  	}
  
 diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
-index 8bd4c95a5435..876e134a01b4 100644
+index d4345657945d..2a4b73a658f9 100644
 --- a/drivers/usb/host/xhci-mtk.c
 +++ b/drivers/usb/host/xhci-mtk.c
-@@ -560,6 +560,8 @@ static int xhci_mtk_probe(struct platform_device *pdev)
+@@ -571,6 +571,8 @@ static int xhci_mtk_probe(struct platform_device *pdev)
  		goto disable_device_wakeup;
  	}