[rdkb][common][bsp][Refactor and sync wifi from openwrt]

[Description]
ced8594f [MAC80211][WiFi6][Misc][Fix the MT76 WiFi6 Makefile]
7221999e [MAC80211][WiFi7][Misc][Correct the MAC80211 WiFi7 Makefile.]
9d87794a [MAC80211][WiFi7][Misc][Correct the MT76 WiFi7 Makefile.]
ff24e1b2 [openwrt-24][Mac80211][Fix patch conflict with upstream openwrt]
3a6c13e2 [mac80211][misc][fix patch fail due to openwrt update]
05763faa [MAC80211][WiFi7][misc][fix patch failed of wifi-scripts]
f34fd014 [mac80211][misc][fix patch fail due to openwrt update]
f6796660 [openwrt-24][Release][Fix build fail of Wi-Fi7 MT76]
7076d96c [MAC80211][WiFi7][Misc][Fix release build fail because of mt76 version upgradation]
1f748b17 [mac80211][misc][fix patch fail due to openwrt update]
95ba6722 [mac80211][misc][fix patch fail due to openwrt update]
17680d7f [MAC80211][WiFi7][misc][Rename eeprom of eFEM variants]
b97cefa1 [MAC80211][WiFi7][app][Add Griffin support for atenl/iwpriv]
6de718a4 [MAC80211][WiFi7][misc][fix wifi-scripts patch failed]
9f1ace86 [MAC80211][WiFi7][misc][fix hostapd Makefile patch]
e4d0d28e [MAC80211][Misc][Add MT7990 Firmware OpenWrt config]
f3a8a8f7 [MAC80211][Release][Fix build fail of Wi-Fi6 MT76]
dabe8eae [openwrt-24][common][bsp][Fix line ending]
6d438a9d [openwrt-24][common][bsp][Use zstd to compress rootfs debug symbols for unified autobuild]
c268e47e [openwrt][common][bsp][Change SMC ID of wdt nonrst reg of reset-boot-count to 0x570]
c6819fbc [openwrt-24][Release][Update release note for Filogic 880 alpha release]
6897b4de [openwrt-24][common][bsp][Adjust unified autobuild for internal build detection]
fb9b9762 [MAC80211][WiFi6/7][app][Add ext eeprom write back cmd support]
d42b42a3 [openwrt-24][common][bsp][Add kernel6.6 Filogic880 BE19000/BE14000]
3806f047 [MAC80211][misc][Add Bpi-R4 support]
ddbda753 [MAC80211][WiFi7][Misc][Fix build fail because of mt76 version upgradation]
90959b08 [MAC80211][WiFi6][mt76][Rebase mt76 pathes]
728a3362 [MAC80211][WiFi6][mt76][Refactor Qos Map]
b46277b5 [MAC80211][WiFi6][mt76][Fix add ba issue on tid not equal to zero]
c084ee8b [MAC80211][WiFi7][mt76][split mt76 Makefile patch]
bbaec094 [MAC80211][Release][Update Filogic 830/820/630 firmware]
5ce2eece [MAC80211][wifi6][MT76][Fix build fail]
5ac1121f [MAC80211][wifi6][MT76][Fix mt76 version to 2024-07-13]
485f92b1 [MAC80211][WiFi7][misc][synchronize PP bitmap when association]
84db8818 [MAC80211][WiFi6/7][app][Add ATETXNSS in iwpriv wrapper]
cc5a4605 [MAC80211][WiFi7][mt76][fix patch failed of Makefile]

[Release-log]

Change-Id: I06704c04c4b5571af4ffd189d636c1fc9f0567fd
diff --git a/recipes-wifi/linux-mt76/files/patches-3.x/0082-mtk-mt76-mt7996-support-multi-link-vif-links-and-MLO.patch b/recipes-wifi/linux-mt76/files/patches-3.x/0082-mtk-mt76-mt7996-support-multi-link-vif-links-and-MLO.patch
new file mode 100644
index 0000000..7d6ed45
--- /dev/null
+++ b/recipes-wifi/linux-mt76/files/patches-3.x/0082-mtk-mt76-mt7996-support-multi-link-vif-links-and-MLO.patch
@@ -0,0 +1,587 @@
+From 59199b2eabeb1d8ce11bf644c4ecd565a385c663 Mon Sep 17 00:00:00 2001
+From: Shayne Chen <shayne.chen@mediatek.com>
+Date: Thu, 23 Nov 2023 18:22:11 +0800
+Subject: [PATCH 082/223] mtk: mt76: mt7996: support multi-link vif links and
+ MLO bss callbacks
+
+Rework add/remove interface functions to add/remove bss_conf functions,
+and also switch to callbacks for MLO bss.
+This is a preliminary patch to add MLO support for mt7996 chipsets.
+
+Co-developed-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Bo Jiao <Bo.Jiao@mediatek.com>
+Signed-off-by: Shayne Chen <shayne.chen@mediatek.com>
+---
+ mt7996/main.c   | 295 +++++++++++++++++++++++++++++++++++++++---------
+ mt7996/mcu.c    |  29 +++--
+ mt7996/mt7996.h |   9 ++
+ 3 files changed, 267 insertions(+), 66 deletions(-)
+
+diff --git a/mt7996/main.c b/mt7996/main.c
+index 98afb057..dc7ee54c 100644
+--- a/mt7996/main.c
++++ b/mt7996/main.c
+@@ -205,6 +205,38 @@ static int get_omac_idx(enum nl80211_iftype type, u64 mask)
+ 	return -1;
+ }
+ 
++static int get_own_mld_idx(u64 mask, bool group_mld)
++{
++	u8 start, end;
++	int i;
++
++	if (group_mld) {
++		start = 0;
++		end = 15;
++	} else {
++		start = 16;
++		end = 63;
++	}
++
++	i = get_free_idx(mask, start, end);
++	if (i)
++		return i - 1;
++
++	return -1;
++}
++
++static int get_mld_remap_idx(u64 mask)
++{
++	u8 start = 0, end = 15;
++	int i;
++
++	i = get_free_idx(mask, start, end);
++	if (i)
++		return i - 1;
++
++	return -1;
++}
++
+ static void mt7996_init_bitrate_mask(struct mt7996_bss_conf *mconf)
+ {
+ 	int i;
+@@ -223,48 +255,108 @@ static void mt7996_init_bitrate_mask(struct mt7996_bss_conf *mconf)
+ 	}
+ }
+ 
+-static int mt7996_add_interface(struct ieee80211_hw *hw,
+-				struct ieee80211_vif *vif)
++static void mt7996_remove_bss_conf(struct ieee80211_vif *vif,
++				   struct ieee80211_bss_conf *conf,
++				   struct mt7996_bss_conf *mconf)
+ {
+-	struct ieee80211_bss_conf *conf = &vif->bss_conf;
++	struct mt7996_phy *phy = mconf->phy;
++	struct mt7996_dev *dev = phy->dev;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+-	struct mt7996_bss_conf *mconf = &mvif->deflink;
+-	struct mt7996_link_sta *mlink = &mvif->sta.deflink;
+-	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+-	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	u8 link_id = conf->link_id;
++	struct mt7996_link_sta *mlink =
++		mlink_dereference_protected(&mvif->sta, link_id);
++
++	if (!mlink)
++		return;
++
++	mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, false, false);
++	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, false);
++	mt7996_mcu_add_dev_info(phy, conf, mconf, false);
++
++	rcu_assign_pointer(dev->mt76.wcid[mlink->wcid.idx], NULL);
++	rcu_assign_pointer(mvif->link[link_id], NULL);
++	rcu_assign_pointer(mvif->sta.link[link_id], NULL);
++
++	dev->mt76.vif_mask &= ~BIT_ULL(mconf->mt76.idx);
++	dev->mld_id_mask &= ~BIT_ULL(mconf->own_mld_id);
++	phy->omac_mask &= ~BIT_ULL(mconf->mt76.omac_idx);
++
++	spin_lock_bh(&dev->mt76.sta_poll_lock);
++	if (!list_empty(&mlink->wcid.poll_list))
++		list_del_init(&mlink->wcid.poll_list);
++	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++
++	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
++
++	if (mlink != &mvif->sta.deflink)
++		kfree(mlink);
++
++	if (mconf != &mvif->deflink)
++		kfree(mconf);
++}
++
++static int mt7996_add_bss_conf(struct mt7996_phy *phy,
++			       struct ieee80211_vif *vif,
++			       struct ieee80211_bss_conf *conf)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_dev *dev = phy->dev;
++	struct mt7996_bss_conf *mconf;
++	struct mt7996_link_sta *mlink;
+ 	struct mt76_txq *mtxq;
+ 	u8 band_idx = phy->mt76->band_idx;
+-	int idx, ret = 0;
+-
+-	mutex_lock(&dev->mt76.mutex);
++	u8 link_id = conf->link_id;
++	int idx, ret;
+ 
+-	if (vif->type == NL80211_IFTYPE_MONITOR &&
+-	    is_zero_ether_addr(vif->addr))
+-		phy->monitor_vif = vif;
++	if (conf != &vif->bss_conf) {
++		mconf = kzalloc(sizeof(*mconf), GFP_KERNEL);
++		if (!mconf)
++			return -ENOMEM;
++	} else {
++		mconf = &mvif->deflink;
++	}
+ 
+ 	mconf->mt76.idx = __ffs64(~dev->mt76.vif_mask);
+ 	if (mconf->mt76.idx >= mt7996_max_interface_num(dev)) {
+ 		ret = -ENOSPC;
+-		goto out;
++		goto error;
+ 	}
+ 
+ 	idx = get_omac_idx(vif->type, phy->omac_mask);
+ 	if (idx < 0) {
+ 		ret = -ENOSPC;
+-		goto out;
++		goto error;
++	}
++
++	mconf->own_mld_id = get_own_mld_idx(dev->mld_id_mask, false);
++	if (mconf->own_mld_id < 0) {
++		ret = -ENOSPC;
++		goto error;
+ 	}
++
+ 	mconf->mt76.omac_idx = idx;
+ 	mconf->vif = mvif;
+ 	mconf->phy = phy;
+ 	mconf->mt76.band_idx = band_idx;
+ 	mconf->mt76.wmm_idx = vif->type == NL80211_IFTYPE_AP ? 0 : 3;
+-	mvif->dev = dev;
++	mconf->link_id = link_id;
+ 
+ 	ret = mt7996_mcu_add_dev_info(phy, conf, mconf, true);
+ 	if (ret)
+-		goto out;
++		goto error;
++
++	if (ieee80211_vif_is_mld(vif)) {
++		mlink = kzalloc(sizeof(*mlink), GFP_KERNEL);
++		if (!mlink) {
++			ret = -ENOMEM;
++			goto error;
++		}
++	} else {
++		mlink = &mvif->sta.deflink;
++	}
+ 
+ 	dev->mt76.vif_mask |= BIT_ULL(mconf->mt76.idx);
++	dev->mld_id_mask |= BIT_ULL(mconf->own_mld_id);
+ 	phy->omac_mask |= BIT_ULL(mconf->mt76.omac_idx);
+ 
+ 	idx = MT7996_WTBL_RESERVED - mconf->mt76.idx;
+@@ -275,6 +367,9 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	mlink->wcid.phy_idx = band_idx;
+ 	mlink->wcid.hw_key_idx = -1;
+ 	mlink->wcid.tx_info |= MT_WCID_TX_INFO_SET;
++	mlink->wcid.def_wcid = &mvif->sta.deflink.wcid;
++	mlink->wcid.link_id = link_id;
++	mlink->wcid.link_valid = ieee80211_vif_is_mld(vif);
+ 	mlink->sta = &mvif->sta;
+ 	mlink->sta->vif = mvif;
+ 	mt76_wcid_init(&mlink->wcid);
+@@ -296,7 +391,6 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 		mconf->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL + 4;
+ 	else
+ 		mconf->mt76.basic_rates_idx = MT7996_BASIC_RATES_TBL;
+-
+ 	mt7996_init_bitrate_mask(mconf);
+ 
+ 	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
+@@ -306,10 +400,32 @@ static int mt7996_add_interface(struct ieee80211_hw *hw,
+ 	if (vif->type != NL80211_IFTYPE_STATION)
+ 		mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, true);
+ 	rcu_assign_pointer(dev->mt76.wcid[idx], &mlink->wcid);
+-	rcu_assign_pointer(mvif->link[0], mconf);
+-	rcu_assign_pointer(mvif->sta.link[0], mlink);
++	rcu_assign_pointer(mvif->link[link_id], mconf);
++	rcu_assign_pointer(mvif->sta.link[link_id], mlink);
+ 
+-out:
++	return 0;
++error:
++	mt7996_remove_bss_conf(vif, conf, mconf);
++	return ret;
++}
++
++static int mt7996_add_interface(struct ieee80211_hw *hw,
++			        struct ieee80211_vif *vif)
++{
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	int ret = 0;
++
++	mutex_lock(&dev->mt76.mutex);
++	if (vif->type == NL80211_IFTYPE_MONITOR &&
++	    is_zero_ether_addr(vif->addr))
++		phy->monitor_vif = vif;
++
++	mvif->dev = dev;
++	mvif->sta.vif = mvif;
++
++	ret = mt7996_add_bss_conf(phy, vif, &vif->bss_conf);
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+ 	return ret;
+@@ -321,38 +437,23 @@ static void mt7996_remove_interface(struct ieee80211_hw *hw,
+ 	struct ieee80211_bss_conf *conf;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
+-	struct mt7996_link_sta *mlink = &mvif->sta.deflink;
+ 	struct mt7996_dev *dev = mt7996_hw_dev(hw);
+ 	struct mt7996_phy *phy = mt7996_hw_phy(hw);
+-	int idx = mlink->wcid.idx;
+ 
+ 	cancel_delayed_work_sync(&phy->scan_work);
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	conf = link_conf_dereference_protected(vif, 0);
+-	mconf = mconf_dereference_protected(mvif, 0);
+-	mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, false, false);
+-	mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, false);
++	if (test_bit(MT76_SCANNING, &phy->mt76->state))
++		mt7996_scan_complete(phy, true);
+ 
+ 	if (vif == phy->monitor_vif)
+ 		phy->monitor_vif = NULL;
+ 
+-	mt7996_mcu_add_dev_info(phy, conf, mconf, false);
+-
+-	rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
+-
+-	dev->mt76.vif_mask &= ~BIT_ULL(mconf->mt76.idx);
+-	phy->omac_mask &= ~BIT_ULL(mconf->mt76.omac_idx);
+-
+-	spin_lock_bh(&dev->mt76.sta_poll_lock);
+-	if (!list_empty(&mlink->wcid.poll_list))
+-		list_del_init(&mlink->wcid.poll_list);
+-	spin_unlock_bh(&dev->mt76.sta_poll_lock);
++	conf = link_conf_dereference_protected(vif, 0);
++	mconf = mconf_dereference_protected(mvif, 0);
+ 
+-	mt76_wcid_cleanup(&dev->mt76, &mlink->wcid);
+-	rcu_assign_pointer(mvif->link[0], NULL);
+-	rcu_assign_pointer(mvif->sta.link[0], NULL);
++	mt7996_remove_bss_conf(vif, conf, mconf);
+ 
+ 	mutex_unlock(&dev->mt76.mutex);
+ }
+@@ -714,10 +815,31 @@ mt7996_update_mu_group(struct ieee80211_hw *hw, struct ieee80211_bss_conf *conf,
+ 	mt76_wr(dev, MT_WF_PHYRX_BAND_GID_TAB_POS3(band), mu[3]);
+ }
+ 
+-static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+-				    struct ieee80211_vif *vif,
+-				    struct ieee80211_bss_conf *info,
+-				    u64 changed)
++static void mt7996_vif_cfg_changed(struct ieee80211_hw *hw,
++				   struct ieee80211_vif *vif, u64 changed)
++{
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++
++	mutex_lock(&dev->mt76.mutex);
++
++	if (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) {
++		struct ieee80211_bss_conf *conf = &vif->bss_conf;
++		struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++		struct mt7996_bss_conf *mconf = mconf_dereference_protected(mvif, 0);
++		struct mt7996_link_sta *mlink = mlink_dereference_protected(&mvif->sta, 0);
++
++		mt7996_mcu_add_bss_info(phy, conf, mconf, mlink, true);
++		mt7996_mcu_add_sta(dev, conf, mconf, NULL, mlink, true, false);
++	}
++
++	mutex_unlock(&dev->mt76.mutex);
++}
++
++static void mt7996_link_info_changed(struct ieee80211_hw *hw,
++				     struct ieee80211_vif *vif,
++				     struct ieee80211_bss_conf *info,
++				     u64 changed)
+ {
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
+@@ -733,7 +855,6 @@ static void mt7996_bss_info_changed(struct ieee80211_hw *hw,
+ 	 * and then peer references bss_info_rfch to set bandwidth cap.
+ 	 */
+ 	if ((changed & BSS_CHANGED_BSSID && !is_zero_ether_addr(info->bssid)) ||
+-	    (changed & BSS_CHANGED_ASSOC && vif->cfg.assoc) ||
+ 	    (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon)) {
+ 		mt7996_mcu_add_bss_info(phy, info, mconf, mlink, true);
+ 		mt7996_mcu_add_sta(dev, info, mconf, NULL, mlink, true,
+@@ -1081,7 +1202,7 @@ mt7996_get_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+ 	u64 ret;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	ret = __mt7996_get_tsf(hw, mconf);
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+@@ -1104,7 +1225,7 @@ mt7996_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+ 					       : mconf->mt76.omac_idx;
+ 	mt76_wr(dev, MT_LPON_UTTR0(phy->mt76->band_idx), tsf.t32[0]);
+@@ -1132,7 +1253,7 @@ mt7996_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	n = mconf->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0
+ 					       : mconf->mt76.omac_idx;
+ 	mt76_wr(dev, MT_LPON_UTTR0(phy->mt76->band_idx), tsf.t32[0]);
+@@ -1307,7 +1428,7 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	u32 changed = IEEE80211_RC_SUPP_RATES_CHANGED;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	mconf->bitrate_mask = *mask;
+ 	mutex_unlock(&dev->mt76.mutex);
+ 
+@@ -1527,7 +1648,7 @@ void mt7996_get_et_stats(struct ieee80211_hw *hw,
+ 	int i, ei = 0;
+ 
+ 	mutex_lock(&dev->mt76.mutex);
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, mvif->master_link_id);
+ 	wi.idx = mconf->mt76.idx,
+ 
+ 	mt7996_mac_update_stats(phy);
+@@ -1900,6 +2021,8 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	struct mt7996_phy *phy = ctx->phy;
+ 	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct mt7996_bss_conf *mconf;
++	u8 link_id = link_conf->link_id;
++	int ret;
+ 
+ 	wiphy_info(hw->wiphy, "Assign VIF (addr: %pM, type: %d, link_id: %d) to channel context: %d MHz\n",
+ 		    vif->addr, vif->type, link_conf->link_id,
+@@ -1907,10 +2030,24 @@ mt7996_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 
+ 	mutex_lock(&phy->dev->mt76.mutex);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	/* remove first */
++	if (rcu_access_pointer(mvif->link[link_id]))
++		mt7996_remove_bss_conf(vif, link_conf,
++				       mconf_dereference_protected(mvif, link_id));
++
++	ret = mt7996_add_bss_conf(phy, vif, link_conf);
++	if (ret) {
++		mutex_unlock(&phy->dev->mt76.mutex);
++		return ret;
++	}
++
++	mconf = mconf_dereference_protected(mvif, link_id);
+ 	mconf->chanctx = ctx;
+ 	ctx->nbss_assigned++;
+ 
++	if (mt7996_hw_phy(hw) == phy)
++		mvif->master_link_id = link_id;
++
+ 	mutex_unlock(&phy->dev->mt76.mutex);
+ 
+ 	return 0;
+@@ -1936,7 +2073,7 @@ mt7996_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ 	if (test_bit(MT76_SCANNING, &phy->mt76->state))
+ 		mt7996_scan_complete(phy, true);
+ 
+-	mconf = mconf_dereference_protected(mvif, 0);
++	mconf = mconf_dereference_protected(mvif, link_conf->link_id);
+ 	mconf->chanctx = NULL;
+ 	ctx->nbss_assigned--;
+ 
+@@ -1976,6 +2113,54 @@ mt7996_switch_vif_chanctx(struct ieee80211_hw *hw,
+ 	return mt7996_set_channel(phy, &new_ctx->chandef);
+ }
+ 
++static int
++mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
++			u16 old_links, u16 new_links,
++			struct ieee80211_bss_conf *old[IEEE80211_MLD_MAX_NUM_LINKS])
++{
++	struct mt7996_dev *dev = mt7996_hw_dev(hw);
++	struct mt7996_phy *phy = mt7996_hw_phy(hw);
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
++	unsigned long rem = old_links & ~new_links;
++	unsigned int link_id;
++	int ret = 0;
++
++	if (old_links == new_links)
++		return 0;
++
++	mutex_lock(&dev->mt76.mutex);
++
++	/* remove first */
++	for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
++		struct mt7996_bss_conf *mconf =
++			mconf_dereference_protected(mvif, link_id);
++
++		if (!mconf)
++			continue;
++
++		mt7996_remove_bss_conf(vif, old[link_id], mconf);
++	}
++
++	if (!old_links) {
++		mvif->group_mld_id = get_own_mld_idx(dev->mld_id_mask, true);
++		dev->mld_id_mask |= BIT_ULL(mvif->group_mld_id);
++
++		mvif->mld_remap_id = get_mld_remap_idx(dev->mld_remap_id_mask);
++		dev->mld_remap_id_mask |= BIT_ULL(mvif->mld_remap_id);
++	}
++
++	/* fallback to non-MLO interface */
++	if (!new_links) {
++		ret = mt7996_add_bss_conf(phy, vif, &vif->bss_conf);
++		dev->mld_id_mask &= ~BIT_ULL(mvif->group_mld_id);
++		dev->mld_remap_id_mask &= ~BIT_ULL(mvif->mld_remap_id);
++	}
++
++	mutex_unlock(&dev->mt76.mutex);
++
++	return ret;
++}
++
+ const struct ieee80211_ops mt7996_ops = {
+ 	.add_chanctx = ieee80211_emulate_add_chanctx,
+ 	.remove_chanctx = ieee80211_emulate_remove_chanctx,
+@@ -1989,7 +2174,8 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.config = mt7996_config,
+ 	.conf_tx = mt7996_conf_tx,
+ 	.configure_filter = mt7996_configure_filter,
+-	.bss_info_changed = mt7996_bss_info_changed,
++	.vif_cfg_changed = mt7996_vif_cfg_changed,
++	.link_info_changed = mt7996_link_info_changed,
+ 	.sta_state = mt76_sta_state,
+ 	.sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
+ 	.sta_rc_update = mt7996_sta_rc_update,
+@@ -2035,4 +2221,5 @@ const struct ieee80211_ops mt7996_ops = {
+ 	.assign_vif_chanctx = mt7996_assign_vif_chanctx,
+ 	.unassign_vif_chanctx = mt7996_unassign_vif_chanctx,
+ 	.switch_vif_chanctx = mt7996_switch_vif_chanctx,
++	.change_vif_links = mt7996_change_vif_links,
+ };
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index ced769b4..081e9ca9 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -1052,15 +1052,23 @@ static void
+ mt7996_mcu_bss_mld_tlv(struct sk_buff *skb, struct ieee80211_vif *vif,
+ 		       struct mt7996_bss_conf *mconf)
+ {
++	struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ 	struct bss_mld_tlv *mld;
+ 	struct tlv *tlv;
+ 
+ 	tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_MLD, sizeof(*mld));
+-
+ 	mld = (struct bss_mld_tlv *)tlv;
+-	mld->group_mld_id = 0xff;
+-	mld->own_mld_id = mconf->mt76.idx;
+-	mld->remap_idx = 0xff;
++
++	if (ieee80211_vif_is_mld(vif)) {
++		mld->group_mld_id = mvif->group_mld_id;
++		mld->remap_idx = mvif->mld_remap_id;
++		memcpy(mld->mac_addr, vif->addr, ETH_ALEN);
++	} else {
++		mld->group_mld_id = 0xff;
++		mld->remap_idx = 0xff;
++	}
++
++	mld->own_mld_id = mconf->own_mld_id;
+ }
+ 
+ static void
+@@ -1141,13 +1149,11 @@ mt7996_mcu_bss_ifs_timing_tlv(struct sk_buff *skb, struct mt7996_phy *phy)
+ }
+ 
+ static int
+-mt7996_mcu_bss_basic_tlv(struct sk_buff *skb,
+-			 struct ieee80211_bss_conf *conf,
+-			 struct mt7996_bss_conf *mconf,
+-			 struct ieee80211_sta *sta,
+-			 struct mt76_phy *phy, u16 wlan_idx,
+-			 bool enable)
++mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *conf,
++			 struct mt7996_bss_conf *mconf, struct ieee80211_sta *sta,
++			 u16 wlan_idx, bool enable)
+ {
++	struct mt76_phy *phy = mconf->phy->mt76;
+ 	struct ieee80211_vif *vif = conf->vif;
+ 	struct cfg80211_chan_def *chandef = &phy->chandef;
+ 	struct mt76_connac_bss_basic_tlv *bss;
+@@ -1259,8 +1265,7 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy,
+ 		return PTR_ERR(skb);
+ 
+ 	/* bss_basic must be first */
+-	mt7996_mcu_bss_basic_tlv(skb, conf, mconf, NULL, phy->mt76,
+-				 mlink->wcid.idx, enable);
++	mt7996_mcu_bss_basic_tlv(skb, conf, mconf, NULL, mlink->wcid.idx, enable);
+ 	mt7996_mcu_bss_sec_tlv(skb, mconf);
+ 
+ 	if (vif->type == NL80211_IFTYPE_MONITOR)
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index d86e24e3..9b9b1f8f 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -342,6 +342,9 @@ struct mt7996_bss_conf {
+ 	struct cfg80211_bitrate_mask bitrate_mask;
+ 
+ 	struct mt7996_chanctx *chanctx;
++
++	u8 link_id;
++	u8 own_mld_id;
+ };
+ 
+ struct mt7996_vif {
+@@ -350,6 +353,10 @@ struct mt7996_vif {
+ 
+ 	struct mt7996_sta sta;
+ 	struct mt7996_dev *dev;
++
++	u8 master_link_id;
++	u8 group_mld_id;
++	u8 mld_remap_id;
+ };
+ 
+ /* crash-dump */
+@@ -551,6 +558,8 @@ struct mt7996_dev {
+ 	u16 chainmask;
+ 	u8 chainshift[__MT_MAX_BAND];
+ 	u32 hif_idx;
++	u64 mld_id_mask;
++	u64 mld_remap_id_mask;
+ 
+ 	struct work_struct init_work;
+ 	struct work_struct rc_work;
+-- 
+2.45.2
+