[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
+