* [PATCH mt76 1/5] wifi: mt76: mt7996: program a link again if the driver holds it
@ 2026-08-18 12:58 Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 2/5] wifi: mt76: check the owner of a remain-on-channel request Felix Fietkau
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Felix Fietkau @ 2026-08-18 12:58 UTC (permalink / raw)
To: linux-wireless
The firmware needs the driver to remove active links from the BSSINFO table
when it destroys the interface. A link therefore stays registered from its
first chanctx assignment until the interface goes away. If the driver holds
the link already, a second add does nothing.
mac80211 gives a non-AP MLD a new address each time it sets a link up. A link
that comes back after a failed attempt thus carries an address that the
hardware does not know, on a BSS that the driver never programs again. The
station then authenticates from an address that nothing answers. The failure
repeats until the driver tears the interface down for good.
Program the address and the BSS again. This is the pair that the initial add
sets up. For a station it does what mt7996_mcu_mld_link_oper() does for an AP.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index e218856b0c45..d17d1dbb2038 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -310,7 +310,14 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif,
if (vif->type == NL80211_IFTYPE_AP)
return mt7996_mcu_mld_link_oper(dev, link_conf, link,
true);
- return 0;
+
+ /* update the link address */
+ ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true);
+ if (ret)
+ return ret;
+
+ return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink,
+ msta_link, true);
}
mlink->idx = __ffs64(~dev->mt76.vif_mask);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH mt76 2/5] wifi: mt76: check the owner of a remain-on-channel request
2026-08-18 12:58 [PATCH mt76 1/5] wifi: mt76: mt7996: program a link again if the driver holds it Felix Fietkau
@ 2026-08-18 12:58 ` Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 3/5] wifi: mt76: mt7996: take over connection monitoring Felix Fietkau
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Felix Fietkau @ 2026-08-18 12:58 UTC (permalink / raw)
To: linux-wireless
mvif->roc_phy points to the phy of the request that started last for the
interface. That request can end, and the phy can start a request for a
different interface. The driver does not always clear the pointer.
Two callers trust the pointer:
- mt76_vif_cleanup() aborts the request on that phy when the driver removes
the interface.
- mt76_cancel_remain_on_channel() aborts the request on that phy for the
interface.
If the phy holds a request for a different interface, both callers abort the
wrong request. The request of the interface stays active. Its work then runs
after the driver removes the interface. The work tears the link down through
the freed bss_conf.
Compare phy->roc_vif with the interface in both callers before the abort.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/channel.c | 2 +-
drivers/net/wireless/mediatek/mt76/mac80211.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c
index 28ad7bcaffd4..1d2635672ac3 100644
--- a/drivers/net/wireless/mediatek/mt76/channel.c
+++ b/drivers/net/wireless/mediatek/mt76/channel.c
@@ -423,7 +423,7 @@ int mt76_cancel_remain_on_channel(struct ieee80211_hw *hw,
struct mt76_vif_data *mvif = mlink->mvif;
struct mt76_phy *phy = mvif->roc_phy;
- if (!phy)
+ if (!phy || phy->roc_vif != vif)
return 0;
mt76_abort_roc(phy);
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index abbe65cbcd89..adda8b92ebe9 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -2107,7 +2107,7 @@ void mt76_vif_cleanup(struct mt76_dev *dev, struct ieee80211_vif *vif)
rcu_assign_pointer(mvif->link[0], NULL);
mt76_abort_scan(dev);
- if (mvif->roc_phy)
+ if (mvif->roc_phy && mvif->roc_phy->roc_vif == vif)
mt76_abort_roc(mvif->roc_phy);
}
EXPORT_SYMBOL_GPL(mt76_vif_cleanup);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH mt76 3/5] wifi: mt76: mt7996: take over connection monitoring
2026-08-18 12:58 [PATCH mt76 1/5] wifi: mt76: mt7996: program a link again if the driver holds it Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 2/5] wifi: mt76: check the owner of a remain-on-channel request Felix Fietkau
@ 2026-08-18 12:58 ` Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 4/5] wifi: mt76: clear offchannel state if a scan has no channel to restore Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 5/5] wifi: mt76: account non-AQL frames per peer rather than per link Felix Fietkau
3 siblings, 0 replies; 5+ messages in thread
From: Felix Fietkau @ 2026-08-18 12:58 UTC (permalink / raw)
To: linux-wireless
mt76 runs its own beacon monitor. mt76_beacon_mon_check() counts a miss
for each affiliated link of an MLD, and it stops the count while the phy
is off channel. mt7996 supplies it through mt76_rx_beacon(), but it does
not set CONNECTION_MONITOR. Therefore mac80211 also starts its legacy
monitor.
The legacy monitor knows one interface, not a set of links. It also
counts while the radio serves a different link. After seven beacon
intervals it removes an association that still carries traffic on the
other links.
Set the CONNECTION_MONITOR flag to disable the legacy monitor.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
index fb635a092584..2db63c3be9d1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c
@@ -540,6 +540,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed)
ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR);
ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID);
ieee80211_hw_set(hw, CHANCTX_STA_CSA);
+ ieee80211_hw_set(hw, CONNECTION_MONITOR);
hw->max_tx_fragments = 4;
wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH mt76 4/5] wifi: mt76: clear offchannel state if a scan has no channel to restore
2026-08-18 12:58 [PATCH mt76 1/5] wifi: mt76: mt7996: program a link again if the driver holds it Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 2/5] wifi: mt76: check the owner of a remain-on-channel request Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 3/5] wifi: mt76: mt7996: take over connection monitoring Felix Fietkau
@ 2026-08-18 12:58 ` Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 5/5] wifi: mt76: account non-AQL frames per peer rather than per link Felix Fietkau
3 siblings, 0 replies; 5+ messages in thread
From: Felix Fietkau @ 2026-08-18 12:58 UTC (permalink / raw)
To: linux-wireless
mt76_scan_complete() restores the operating channel only if the phy has one.
__mt76_set_channel() is the only function that clears phy->offchannel.
A phy can run a scan while it has no operating channel. A 6 GHz radio with
no configured interface is one example. After such a scan, the flag stays
set.
mt76_txq_schedule_list() skips each TXQ whose wcid points to a phy in this
state. All traffic for the stations on that band stops. The frames stay in
the mac80211 queues. They do not reach the hardware, and no counter shows
them. Only an unrelated channel set clears the flag.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/channel.c | 2 ++
drivers/net/wireless/mediatek/mt76/scan.c | 7 +++++++
2 files changed, 9 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c
index 1d2635672ac3..903f3a420e4c 100644
--- a/drivers/net/wireless/mediatek/mt76/channel.c
+++ b/drivers/net/wireless/mediatek/mt76/channel.c
@@ -330,6 +330,8 @@ void mt76_roc_complete(struct mt76_phy *phy)
!test_bit(MT76_MCU_RESET, &dev->phy.state)) {
__mt76_set_channel(phy, &phy->main_chandef, false);
mt76_offchannel_notify(phy, false);
+ } else {
+ phy->offchannel = false;
}
mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link);
phy->roc_vif = NULL;
diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c
index 3594b599662d..c8c4d096c809 100644
--- a/drivers/net/wireless/mediatek/mt76/scan.c
+++ b/drivers/net/wireless/mediatek/mt76/scan.c
@@ -29,6 +29,13 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort)
__mt76_set_channel(phy, &phy->main_chandef, false);
if (offchannel)
mt76_offchannel_notify(phy, false);
+ } else {
+ /* A phy that has no operating channel has nothing to restore,
+ * but the flag also gates the TX queues of every station whose
+ * wcid points at this band, so leaving it set stalls them until
+ * something else happens to set a channel.
+ */
+ phy->offchannel = false;
}
mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink);
memset(&dev->scan, 0, sizeof(dev->scan));
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH mt76 5/5] wifi: mt76: account non-AQL frames per peer rather than per link
2026-08-18 12:58 [PATCH mt76 1/5] wifi: mt76: mt7996: program a link again if the driver holds it Felix Fietkau
` (2 preceding siblings ...)
2026-08-18 12:58 ` [PATCH mt76 4/5] wifi: mt76: clear offchannel state if a scan has no channel to restore Felix Fietkau
@ 2026-08-18 12:58 ` Felix Fietkau
3 siblings, 0 replies; 5+ messages in thread
From: Felix Fietkau @ 2026-08-18 12:58 UTC (permalink / raw)
To: linux-wireless
The driver increments wcid->non_aql_packets when a frame goes to the
hardware queue. The completion path decrements the counter again.
For an MLO peer, the hardware reports the completion on the link that it
used for the transmission. This link can differ from the link that the
driver queued the frame on. In that case, the increment and the
decrement go to different link wcids, and the count drifts upwards until
it blocks the station.
Keep the count on the wcid of the primary link, like the other peer-wide
state.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++
drivers/net/wireless/mediatek/mt76/tx.c | 11 ++++++++---
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 62b41c8bb7c0..2ade848fe3bb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -1426,6 +1426,13 @@ mtxq_to_txq(struct mt76_txq *mtxq)
return container_of(ptr, struct ieee80211_txq, drv_priv);
}
+/* peer-wide state uses the wcid of the primary link */
+static inline struct mt76_wcid *
+mt76_wcid_primary(struct mt76_wcid *wcid)
+{
+ return wcid->def_wcid ? wcid->def_wcid : wcid;
+}
+
static inline struct ieee80211_sta *
wcid_to_sta(struct mt76_wcid *wcid)
{
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 3707ee19e4ae..b1e894b19cbd 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid,
if (!wcid || info->tx_time_est)
return;
+ wcid = mt76_wcid_primary(wcid);
+
pending = atomic_dec_return(&wcid->non_aql_packets);
if (pending < 0)
atomic_cmpxchg(&wcid->non_aql_packets, pending, 0);
@@ -344,7 +346,8 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb,
if (!non_aql)
return idx;
- pending = atomic_inc_return(&wcid->non_aql_packets);
+ /* the hardware can report the completion on a different link */
+ pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets);
if (stop && pending >= MT_MAX_NON_AQL_PKT)
*stop = true;
@@ -506,7 +509,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q,
return 0;
}
- if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT)
+ if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >=
+ MT_MAX_NON_AQL_PKT)
return 0;
skb = mt76_txq_dequeue(phy, mtxq);
@@ -603,7 +607,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid)
continue;
}
- if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT)
+ if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >=
+ MT_MAX_NON_AQL_PKT)
continue;
if (dev->queue_ops->tx_cleanup &&
q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) {
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-18 13:26 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 12:58 [PATCH mt76 1/5] wifi: mt76: mt7996: program a link again if the driver holds it Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 2/5] wifi: mt76: check the owner of a remain-on-channel request Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 3/5] wifi: mt76: mt7996: take over connection monitoring Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 4/5] wifi: mt76: clear offchannel state if a scan has no channel to restore Felix Fietkau
2026-08-18 12:58 ` [PATCH mt76 5/5] wifi: mt76: account non-AQL frames per peer rather than per link Felix Fietkau
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox