[PATCH] wifi: mt76: mt7615: skip MT_TXD5_TX_STATUS_HOST for MT7663 group frames

Facundo Karanik <[email protected]> Sun, 2 Aug 2026 16:20:55 -0700
Newsgroups org.infradead.lists.linux-mediatek,org.kernel.vger.linux-wireless
Message-ID <CAHNRTq83iAAFyYNB5D0eg3HJggJNq3xjO8Z1zi9=+yGe4uy_Lw@mail.gmail.com>
Under the right circumstances, a broadcast or multicast packet can remain
stuck in the firmware/hardware queue. Following group packets then stop
completing and accumulate. On my system, this appeared as excessive airtime
queue limit (AQL) debt, which eventually stopped batman-adv traffic on that
interface.

For a packet to potentially get stuck it must have the
`MT_TXD5_TX_STATUS_HOST` bit set and remain pending while group traffic is
held for the next DTIM because a station is in power save. The queue stalls
when such a packet is present on the queue and the final station (still
treated by the firmware as power-saving) is removed, whether explicitly or
through normal teardown after the client disappears.

In summary, the packet must:

1. Be broadcast or multicast.
2. Be pending under group power-save/DTIM handling.
3. Have the `MT_TXD5_TX_STATUS_HOST` bit set.
4. Remain pending when the final power-saving station is removed.

The fix is to leave `MT_TXD5_TX_STATUS_HOST` clear for MT7663 group
frames. These frames normally use PID 0, the no-ACK packet identifier, and
(as far as I was able to find) the driver discards TX-status reports
carrying PID 0. The report therefore has no consumer or useful
per-recipient result. If a future path assigns another PID to a group
frame, `WARN_ON_ONCE()` records a stack trace while the status-request bit
remains clear.

My broader interpretation of this failure is that the firmware/hardware
does not expect a host TX report for packets that are not aimed at a single
station. The last power-saving client leaving might have been enough of an
edge case to surface this expectation as a bug. This is speculation, of
course.

After applying this patch, every single packet matching conditions 1, 2
and 4 cleared the queue without any issues. I also did A/B testing with all
conditions present. The stall occurred in 100% of tests without the patch
and 0% with the patch (which, of course, removes condition 3).

I let it run for about a week without either the stall coming up again or
any regressions showing up. The hardware used for testing is a TP-Link
Archer C6U v1 (MT7663E PCIe), running OpenWrt 24.10.3 and Linux 6.6.104. I
do not have any reason to believe this will break on newer kernel versions
or different hardware, although I was personally not able to test either.

This patch very likely solves the issue below, but it still needs to be
tested on that setup:

https://github.com/openwrt/mt76/issues/598#issuecomment-939419710

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Facundo Karanik <[email protected]>
---
 drivers/net/wireless/mediatek/mt76/mt7615/mac.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
index aad232c5a6fa..5c846f136961 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
@@ -823,8 +823,21 @@ int mt7615_mac_write_txwi(struct mt7615_dev *dev,
__le32 *txwi,

 	if (!ieee80211_is_beacon(fc)) {
 		struct ieee80211_hw *hw = mt76_hw(dev);
+		bool mt7663_group = is_mt7663(&dev->mt76) && multicast;

-		val = MT_TXD5_TX_STATUS_HOST | FIELD_PREP(MT_TXD5_PID, pid);
+		val = FIELD_PREP(MT_TXD5_PID, pid);
+		if (mt7663_group) {
+			/* Host TX status has no useful per-recipient result
+			 * for group frames. On MT7663, requesting it can
+			 * stall group traffic when the final power-saving
+			 * station disconnects. Force the request off, and
+			 * warn if the no-ACK PID invariant is violated.
+			 */
+			WARN_ON_ONCE(pid != MT_PACKET_ID_NO_ACK);
+		} else {
+			val |= MT_TXD5_TX_STATUS_HOST;
+		}
+
 		if (!ieee80211_hw_check(hw, SUPPORTS_PS))
 			val |= MT_TXD5_SW_POWER_MGMT;
 		txwi[5] = cpu_to_le32(val);
-- 
2.51.0