From: Felix Fietkau <nbd@nbd.name>
To: linux-wireless@vger.kernel.org
Subject: [PATCH mt76-next 12/25] wifi: mt76: mt7996: set up a vif link again on another radio
Date: Wed, 28 Oct 2026 13:23:50 +0100 [thread overview]
Message-ID: <20261028122403.796394-12-nbd@nbd.name> (raw)
In-Reply-To: <20261028122403.796394-1-nbd@nbd.name>
Since the link teardown moved to mt7996_remove_interface(), a link stays
in mvif->mt76.valid_links after its chanctx is unassigned. When the next
chanctx assignment puts the link on another radio, mt7996_vif_link_add()
takes the reuse branch and programs DEV_INFO and BSS_INFO on the new
radio with the OMAC index and the band of the old radio. The firmware
then holds the BSS of the link on the wrong band, and the STA_REC of a
station on that link is created under it. After the station
disconnects, the firmware keeps that record, so every later station
that gets the same WTBL index is refused:
mt7996e 0000:01:00.0: Message 001a0003 (seq 1) cid 0003 failed: firmware status 0x10003
hostapd then fails with "Could not set STA to kernel driver" for each
new station on that index until the firmware restarts. Examples are a
non-AP interface that used link 0 on 6 GHz and associates as an MLD
with link 0 on 2.4 GHz, and an AP that is stopped and started on
another band, both without an interface down.
Tear such a link down on its old radio and add it again on the new one.
If the add on the new radio fails, free the link, since it is no longer
a valid link and mt7996_remove_interface() destroys only valid links.
Keep the reuse branch for a link that stays on its band, and for
mt76_switch_vif_chanctx(), which moves a link to another radio during a
channel switch with its stations in place.
Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
.../net/wireless/mediatek/mt76/mt7996/main.c | 145 ++++++++++++------
1 file changed, 96 insertions(+), 49 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index d22abbd543b7..3384e46e7b05 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -305,6 +305,76 @@ mt7996_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
WARN_ON(mt7996_set_hw_key(hw, it->cmd, vif, sta, it->link_id, key));
}
+static void mt7996_vif_link_teardown(struct mt7996_phy *phy,
+ struct mt7996_vif_link *link,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *link_conf)
+{
+ struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
+ struct mt7996_sta_link *msta_link = &link->msta_link;
+ unsigned int link_id = msta_link->wcid.link_id;
+ struct mt76_vif_link *mlink = &link->mt76;
+ struct mt7996_dev *dev = phy->dev;
+ int idx = msta_link->wcid.idx;
+
+ if (!link_conf)
+ link_conf = &vif->bss_conf;
+
+ if (link->bmc_sta_added)
+ mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL,
+ CONN_STATE_DISCONNECT, false);
+ mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false);
+ mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, false);
+
+ rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
+
+ dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx);
+ dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx);
+ phy->omac_mask &= ~BIT_ULL(mlink->omac_idx);
+ if (!mlink->wcid->offchannel)
+ mvif->mt76.valid_links &= ~BIT(link_id);
+
+ spin_lock_bh(&dev->mt76.sta_poll_lock);
+ if (!list_empty(&msta_link->wcid.poll_list))
+ list_del_init(&msta_link->wcid.poll_list);
+ spin_unlock_bh(&dev->mt76.sta_poll_lock);
+
+ mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid);
+}
+
+static void mt7996_vif_link_free(struct mt7996_vif_link *link,
+ struct ieee80211_vif *vif)
+{
+ unsigned int link_id = link->msta_link.wcid.link_id;
+ struct mt76_vif_link *mlink = &link->mt76;
+
+ if (mlink == (struct mt76_vif_link *)vif->drv_priv ||
+ mlink->wcid->offchannel)
+ return;
+
+ rcu_assign_pointer(mlink->mvif->link[link_id], NULL);
+ kfree_rcu(mlink, rcu_head);
+}
+
+static int mt7996_vif_link_reuse(struct mt7996_phy *phy,
+ struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *link_conf,
+ struct mt7996_vif_link *link)
+{
+ struct mt76_vif_link *mlink = &link->mt76;
+ int ret;
+
+ ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true);
+ if (ret)
+ return ret;
+
+ if (vif->type == NL80211_IFTYPE_AP && ieee80211_vif_is_mld(vif))
+ return mt7996_mcu_mld_link_oper(phy->dev, link_conf, link, true);
+
+ return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink,
+ &link->msta_link, true);
+}
+
int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf,
struct mt76_vif_link *mlink)
@@ -320,33 +390,38 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif,
.link_id = link_conf->link_id,
};
int mld_idx, idx, ret;
+ bool moved = false;
if ((mvif->mt76.valid_links & BIT(link_conf->link_id)) &&
!mlink->offchannel) {
- /* update the link address */
- ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true);
- if (ret)
- return ret;
+ struct mt7996_phy *old_phy;
- if (vif->type == NL80211_IFTYPE_AP && ieee80211_vif_is_mld(vif))
- return mt7996_mcu_mld_link_oper(dev, link_conf, link,
- true);
+ /* mt76_switch_vif_chanctx() sets the new chanctx only after
+ * the link moved
+ */
+ if (mlink->band_idx == band_idx ||
+ mt76_vif_link_phy(mlink) != mphy)
+ return mt7996_vif_link_reuse(phy, vif, link_conf, link);
- return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink,
- msta_link, true);
+ old_phy = __mt7996_phy(dev, msta_link->wcid.phy_idx);
+ if (old_phy) {
+ mt7996_vif_link_teardown(old_phy, link, vif, link_conf);
+ moved = true;
+ }
}
+ ret = -ENOSPC;
mlink->idx = __ffs64(~dev->mt76.vif_mask);
if (mlink->idx >= mt7996_max_interface_num(dev))
- return -ENOSPC;
+ goto error;
idx = get_omac_idx(vif->type, phy->omac_mask);
if (idx < 0)
- return -ENOSPC;
+ goto error;
mld_idx = get_own_mld_idx(dev->mld_idx_mask, false);
if (mld_idx < 0)
- return -ENOSPC;
+ goto error;
link->mld_idx = mld_idx;
link->bmc_sta_added = false;
@@ -360,7 +435,7 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif,
ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true);
if (ret)
- return ret;
+ goto error;
dev->mt76.vif_mask |= BIT_ULL(mlink->idx);
dev->mld_idx_mask |= BIT_ULL(link->mld_idx);
@@ -426,6 +501,12 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif,
}
return 0;
+
+error:
+ if (moved)
+ mt7996_vif_link_free(link, vif);
+
+ return ret;
}
static void mt7996_vif_link_destroy(struct mt7996_phy *phy,
@@ -433,42 +514,8 @@ static void mt7996_vif_link_destroy(struct mt7996_phy *phy,
struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf)
{
- struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv;
- struct mt7996_sta_link *msta_link = &link->msta_link;
- unsigned int link_id = msta_link->wcid.link_id;
- struct mt76_vif_link *mlink = &link->mt76;
- struct mt7996_dev *dev = phy->dev;
- int idx = msta_link->wcid.idx;
-
- if (!link_conf)
- link_conf = &vif->bss_conf;
-
- if (link->bmc_sta_added)
- mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL,
- CONN_STATE_DISCONNECT, false);
- mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false);
- mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, false);
-
- rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
-
- dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx);
- dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx);
- phy->omac_mask &= ~BIT_ULL(mlink->omac_idx);
- if (!mlink->wcid->offchannel)
- mvif->mt76.valid_links &= ~BIT(link_id);
-
- spin_lock_bh(&dev->mt76.sta_poll_lock);
- if (!list_empty(&msta_link->wcid.poll_list))
- list_del_init(&msta_link->wcid.poll_list);
- spin_unlock_bh(&dev->mt76.sta_poll_lock);
-
- mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid);
-
- if (mlink != (struct mt76_vif_link *)vif->drv_priv &&
- !mlink->wcid->offchannel) {
- rcu_assign_pointer(mlink->mvif->link[link_id], NULL);
- kfree_rcu(mlink, rcu_head);
- }
+ mt7996_vif_link_teardown(phy, link, vif, link_conf);
+ mt7996_vif_link_free(link, vif);
}
static void
--
2.47.3
next prev parent reply other threads:[~2026-10-28 12:24 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 12:23 [PATCH mt76-next 01/25] wifi: mt76: mt7996: take the link of an 802.3 frame from its station Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 02/25] wifi: mt76: mt7996: do not update beacons while CSA is active Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 03/25] wifi: mt76: mt7996: only remove a BMC STA_REC that was added Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 04/25] wifi: mt76: mt7996: pass the puncturing bitmap to the firmware Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 05/25] wifi: mt76: abort a scan when the chanctx changes Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 06/25] wifi: mt76: set the channel again when the chanctx puncturing changes Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 07/25] wifi: mt76: mt7996: advertise preamble puncturing support Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 08/25] wifi: mt76: mt7996: keep scan and ROC links out of the MLD group Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 09/25] wifi: mt76: mt7996: factor the vif link teardown out of remove_interface Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 10/25] wifi: mt76: mt7996: destroy station links before the MLD group is freed Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 11/25] wifi: mt76: mt7996: send MLD link operations for an AP MLD only Felix Fietkau
2026-10-28 12:23 ` Felix Fietkau [this message]
2026-10-28 12:23 ` [PATCH mt76-next 13/25] wifi: mt76: mt7996: describe an MLD peer with the PEER_MLD command Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 14/25] wifi: mt76: mt7915: honour the valid flag in efuse block reads Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 15/25] wifi: mt76: mt7996: skip MLD address translation for encap offload frames Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 16/25] wifi: mt76: mt7921: honour the valid flag in efuse block reads Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 17/25] wifi: mt76: mt7915: upload the CSA beacon only once Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 18/25] wifi: mt76: read the chanctx of a link once Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 19/25] wifi: mt76: restart the beacon monitor of a link that gets a chanctx again Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 20/25] wifi: mt76: publish the beacon monitor interval after its timestamp Felix Fietkau
2026-10-28 12:23 ` [PATCH mt76-next 21/25] wifi: mt76: mt7996: keep rx header translation off while a monitor runs Felix Fietkau
2026-10-28 12:24 ` [PATCH mt76-next 22/25] wifi: mt76: mt7996: apply monitor mode again after a full reset Felix Fietkau
2026-10-28 12:24 ` [PATCH mt76-next 23/25] wifi: mt76: check the RX PN of an MLD peer once per peer Felix Fietkau
2026-10-28 12:24 ` [PATCH mt76-next 24/25] wifi: mt76: mt7996: send no MLD_SETUP when station links only go away Felix Fietkau
2026-10-28 12:24 ` [PATCH mt76-next 25/25] wifi: mt76: mt7996: keep a unicast management frame on its MLO link Felix Fietkau
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261028122403.796394-12-nbd@nbd.name \
--to=nbd@nbd.name \
--cc=linux-wireless@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox