* [PATCH rtw-next 0/2] wifi: rtw88: usb: keep bmc traffic from exhausting the TX page pool
@ 2026-09-02 10:41 Mehmet Fide
2026-09-02 10:41 ` [PATCH rtw-next 1/2] wifi: rtw88: usb: bound what the driver feeds the after-DTIM queue Mehmet Fide
2026-09-02 10:41 ` [PATCH rtw-next 2/2] wifi: rtw88: usb: only let the frames a dozing station needs use " Mehmet Fide
0 siblings, 2 replies; 3+ messages in thread
From: Mehmet Fide @ 2026-09-02 10:41 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, Mehmet Fide
From: Mehmet Fide <mehmet.fide@screeningeagle.com>
Hi Ping-Ke, Bitterblue,
this follows up on the discussion under the v3 routing patch [1]: with
that patch applied, a single dozing station is still enough to route all
broadcast and multicast traffic through the high queue, and since the
chip only drains that queue in the ATIM window after DTIM beacons while
the frames occupy the shared TX page pool, ordinary chatter empties the
pool and the AP stops accepting stations. Numbers and the ATIM window
experiment are in the thread [2]; the short version: at the default 2 TU
window the queue drains ~3 frames per DTIM, ~40 frames/s of mDNS takes
the pool from 1803 to 16 pages in about 100 s, and enlarging the window
to 4 TU or more makes the same storm harmless.
The series follows the order Ping-Ke suggested (bound first, then
filter):
Patch 1 caps how many frames the driver hands to the high queue with a
small token bucket set below the measured drain rate; the excess goes
out on the access category queue right away. This is the guarantee: the
pool stays healthy under any storm, including ARP/DHCP bursts.
Patch 2 admits only ARP, EAPOL and DHCP to the after-DTIM path, matching
the vendor driver's default high queue filter, so ordinary chatter never
reaches the beacon-paced queue and the budget is left for the frames a
sleeping station actually needs. At runtime the filter is evaluated
before a token is taken, so chatter does not consume the budget.
Not included: raising REG_ATIMWND. It would add drain capacity, but the
queue stays unbounded and a wider window costs every PS station awake
time after each DTIM; if the firmware is fine with a larger window on
the USB chips I can add it as a separate patch. The mac80211-side
buffering (IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING) needs a beacon-time
event; the firmware seems to have one (C2H_BCN_EARLY_RPT, used by the
vendor driver for TDLS), that is a follow-up once we know it works in AP
mode.
Tested on a Verdin AM62 AP with RTL8822BU (USB2, 20 MHz, WPA2, one
Windows client in power save), free page count read at 0x240, on top of
rtw-next with c710c7c2e038:
- baseline (rtw-next as is), one client in power save, ~80 bmc frames/s
generated on the AP: free pages 1803 -> 16 within 30 s and pinned for
the whole storm, 18 "error beacon valid" / rsvd page failures, and
after the storm three reconnect attempts failed (no association
until the client left and the pool recovered ~90 s later)
- patch 1 alone: 1803 pages throughout the same 180 s storm and a
120 s DHCP-port storm, no beacon errors, ping over the existing link
6/6, reconnects 3/3
- patches 1+2: 1803 pages throughout both storms as well, no
beacon errors, ping 6/6, reconnects 3/3; the filter keeps the
budget for ARP/EAPOL/DHCP, so the DHCP-port storm is the case where
the token bucket actually engages
- join/ping cycling without power save (10 cycles): unchanged
- with an iPhone (screen off) as the only associated station instead
of the Windows client: baseline 1803 -> 17 in 30 s and three join
attempts from a second device fail; with both patches 1803
throughout and the second device joins 3/3
- the same series on an RTL8821CU AP (i.MX8MP, PREEMPT_RT kernel,
411 free pages when idle): baseline drops to 16 pages within 30 s
and stays there, ping and three join attempts all fail; with the
series the count stays at 411 through the multicast storm, dips to
357 and recovers during the DHCP-port storm (the budget doing its
job), ping 6/6 and joins 3/3. No atomic-context or lockdep
complaints on the RT kernel.
[1] https://lore.kernel.org/linux-wireless/20260814053426.2473247-1-mehmet.fide@gmail.com/
[2] https://lore.kernel.org/linux-wireless/20260901091830.2506562-1-mehmet.fide@gmail.com/
Mehmet Fide (2):
wifi: rtw88: usb: bound what the driver feeds the after-DTIM queue
wifi: rtw88: usb: only let the frames a dozing station needs use the
after-DTIM queue
drivers/net/wireless/realtek/rtw88/usb.c | 82 +++++++++++++++++++++++-
drivers/net/wireless/realtek/rtw88/usb.h | 5 ++
2 files changed, 84 insertions(+), 3 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH rtw-next 1/2] wifi: rtw88: usb: bound what the driver feeds the after-DTIM queue
2026-09-02 10:41 [PATCH rtw-next 0/2] wifi: rtw88: usb: keep bmc traffic from exhausting the TX page pool Mehmet Fide
@ 2026-09-02 10:41 ` Mehmet Fide
2026-09-02 10:41 ` [PATCH rtw-next 2/2] wifi: rtw88: usb: only let the frames a dozing station needs use " Mehmet Fide
1 sibling, 0 replies; 3+ messages in thread
From: Mehmet Fide @ 2026-09-02 10:41 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, Mehmet Fide
From: Mehmet Fide <mehmet.fide@screeningeagle.com>
Frames routed to the high queue are transmitted right after DTIM beacons
only, inside the ATIM window, while they wait in the shared TX page pool.
The driver puts no limit on how many it hands over, so as long as one
station dozes, any sustained broadcast or multicast traffic outruns the
drain and empties the pool: measured on an RTL8822BU AP with a single
client in power save and ~40 frames/s of mDNS chatter, the free page
count at 0x240 goes from 1803 to 16 in about 100 seconds and stays there
for as long as the traffic lasts. From that point every other transmit
queues behind the backlog, authentication responses arrive too late for
anyone to join, and the reserved page download fails ("error beacon
valid"). The AP keeps beaconing and only a reboot recovers.
Feed the high queue through a small token bucket set below the measured
drain rate (about 3 frames per DTIM, ~15/s at dtim_period 2 on the
default 2 TU ATIM window); whatever exceeds the budget leaves on its
access category queue right away. The high queue backlog is now bounded
by the burst size under any load, so the page pool cannot run dry, at
the price that a dozing station may miss part of a broadcast storm.
Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com>
---
drivers/net/wireless/realtek/rtw88/usb.c | 42 ++++++++++++++++++++++--
drivers/net/wireless/realtek/rtw88/usb.h | 5 +++
2 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c
index c90802919473..80965e5ea778 100644
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -562,7 +562,38 @@ static int rtw_usb_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size)
return rtw_usb_write_data(rtwdev, &pkt_info, buf);
}
-static u8 rtw_usb_tx_queue_mapping_to_qsel(struct sk_buff *skb)
+#define RTW_USB_HIQ_RATE 10
+#define RTW_USB_HIQ_BURST 16
+
+static bool rtw_usb_hiq_take(struct rtw_usb *rtwusb)
+{
+ unsigned long flags, delta;
+ bool ok;
+ u32 add;
+
+ spin_lock_irqsave(&rtwusb->hiq_lock, flags);
+ delta = jiffies - rtwusb->hiq_refill;
+ if (delta >= HZ / RTW_USB_HIQ_RATE) {
+ add = delta / (HZ / RTW_USB_HIQ_RATE);
+ if (add >= RTW_USB_HIQ_BURST) {
+ rtwusb->hiq_tokens = RTW_USB_HIQ_BURST;
+ rtwusb->hiq_refill = jiffies;
+ } else {
+ rtwusb->hiq_tokens = min_t(u32, rtwusb->hiq_tokens + add,
+ RTW_USB_HIQ_BURST);
+ rtwusb->hiq_refill += add * (HZ / RTW_USB_HIQ_RATE);
+ }
+ }
+ ok = rtwusb->hiq_tokens > 0;
+ if (ok)
+ rtwusb->hiq_tokens--;
+ spin_unlock_irqrestore(&rtwusb->hiq_lock, flags);
+
+ return ok;
+}
+
+static u8 rtw_usb_tx_queue_mapping_to_qsel(struct rtw_usb *rtwusb,
+ struct sk_buff *skb)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
@@ -573,7 +604,8 @@ static u8 rtw_usb_tx_queue_mapping_to_qsel(struct sk_buff *skb)
qsel = TX_DESC_QSEL_MGMT;
else if (is_broadcast_ether_addr(hdr->addr1) ||
is_multicast_ether_addr(hdr->addr1))
- qsel = (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) ?
+ qsel = (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) &&
+ rtw_usb_hiq_take(rtwusb) ?
TX_DESC_QSEL_HIGH : skb->priority;
else if (skb_get_queue_mapping(skb) <= IEEE80211_AC_BK)
qsel = skb->priority;
@@ -593,7 +625,7 @@ static int rtw_usb_tx_write(struct rtw_dev *rtwdev,
u8 *pkt_desc;
int ep;
- pkt_info->qsel = rtw_usb_tx_queue_mapping_to_qsel(skb);
+ pkt_info->qsel = rtw_usb_tx_queue_mapping_to_qsel(rtwusb, skb);
pkt_desc = skb_push(skb, chip->tx_pkt_desc_sz);
memset(pkt_desc, 0, chip->tx_pkt_desc_sz);
ep = qsel_to_ep(rtwusb, pkt_info->qsel);
@@ -1034,6 +1066,10 @@ static int rtw_usb_init_tx(struct rtw_dev *rtwdev)
struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev);
int i;
+ spin_lock_init(&rtwusb->hiq_lock);
+ rtwusb->hiq_tokens = RTW_USB_HIQ_BURST;
+ rtwusb->hiq_refill = jiffies;
+
rtwusb->txwq = create_singlethread_workqueue("rtw88_usb: tx wq");
if (!rtwusb->txwq) {
rtw_err(rtwdev, "failed to create TX work queue\n");
diff --git a/drivers/net/wireless/realtek/rtw88/usb.h b/drivers/net/wireless/realtek/rtw88/usb.h
index 9b695b688b24..67463a4d2f90 100644
--- a/drivers/net/wireless/realtek/rtw88/usb.h
+++ b/drivers/net/wireless/realtek/rtw88/usb.h
@@ -75,6 +75,11 @@ struct rtw_usb {
u8 out_ep[RTW_USB_EP_MAX];
int qsel_to_ep[TX_DESC_QSEL_MAX];
+ /* protects hiq_tokens and hiq_refill */
+ spinlock_t hiq_lock;
+ u32 hiq_tokens;
+ unsigned long hiq_refill;
+
struct workqueue_struct *txwq, *rxwq;
struct sk_buff_head tx_queue[RTW_USB_EP_MAX];
--
2.54.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH rtw-next 2/2] wifi: rtw88: usb: only let the frames a dozing station needs use the after-DTIM queue
2026-09-02 10:41 [PATCH rtw-next 0/2] wifi: rtw88: usb: keep bmc traffic from exhausting the TX page pool Mehmet Fide
2026-09-02 10:41 ` [PATCH rtw-next 1/2] wifi: rtw88: usb: bound what the driver feeds the after-DTIM queue Mehmet Fide
@ 2026-09-02 10:41 ` Mehmet Fide
1 sibling, 0 replies; 3+ messages in thread
From: Mehmet Fide @ 2026-09-02 10:41 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, Mehmet Fide
From: Mehmet Fide <mehmet.fide@screeningeagle.com>
With the budget in place the high queue can no longer take the chip
down, but ordinary chatter still competes with the frames a sleeping
station actually needs. mac80211 marks every broadcast and multicast
frame with IEEE80211_TX_CTL_SEND_AFTER_DTIM while a station dozes,
mDNS and SSDP included, so under normal traffic the budget is spent on
frames nobody waits for.
Do what the vendor driver does with its default "allow special" high
queue filter: admit only ARP, EAPOL and DHCP to the after-DTIM path,
the frames a station coming out of power save has to see; everything
else goes out on its access category queue at line rate. Encrypted
group frames carry the IV between the header and the SNAP header, so
the parser accounts for hw_key->iv_len. With the filter in place the
page pool stays at 1803 through the same 180 second storm, a DHCP
flood still takes the after-DTIM path (and is then held by the budget),
and join/ping cycling without power save is unchanged (10/10).
Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com>
---
drivers/net/wireless/realtek/rtw88/usb.c | 42 +++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c
index 80965e5ea778..dd65b304b2c7 100644
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -5,6 +5,9 @@
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/mutex.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/unaligned.h>
#include "main.h"
#include "debug.h"
#include "mac.h"
@@ -562,6 +565,43 @@ static int rtw_usb_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size)
return rtw_usb_write_data(rtwdev, &pkt_info, buf);
}
+static bool rtw_usb_bmc_needs_dtim(struct sk_buff *skb)
+{
+ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ unsigned int hdrlen = ieee80211_hdrlen(hdr->frame_control);
+ unsigned int paylen = sizeof(rfc1042_header) + sizeof(__be16);
+ const struct udphdr *udp;
+ const struct iphdr *ip;
+ const u8 *snap;
+ __be16 proto;
+
+ if (info->control.hw_key)
+ hdrlen += info->control.hw_key->iv_len;
+
+ if (skb->len < hdrlen + paylen)
+ return false;
+
+ snap = skb->data + hdrlen;
+ proto = get_unaligned((__be16 *)(snap + sizeof(rfc1042_header)));
+
+ if (proto == htons(ETH_P_ARP) || proto == htons(ETH_P_PAE))
+ return true;
+
+ if (proto != htons(ETH_P_IP) || skb->len < hdrlen + paylen + sizeof(*ip))
+ return false;
+
+ ip = (const struct iphdr *)(snap + paylen);
+ if (ip->protocol != IPPROTO_UDP)
+ return false;
+
+ udp = (const struct udphdr *)((const u8 *)ip + ip->ihl * 4);
+ if (skb->len < (unsigned int)((const u8 *)udp - skb->data) + sizeof(*udp))
+ return false;
+
+ return udp->dest == htons(67) || udp->dest == htons(68);
+}
+
#define RTW_USB_HIQ_RATE 10
#define RTW_USB_HIQ_BURST 16
@@ -605,7 +645,7 @@ static u8 rtw_usb_tx_queue_mapping_to_qsel(struct rtw_usb *rtwusb,
else if (is_broadcast_ether_addr(hdr->addr1) ||
is_multicast_ether_addr(hdr->addr1))
qsel = (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) &&
- rtw_usb_hiq_take(rtwusb) ?
+ rtw_usb_bmc_needs_dtim(skb) && rtw_usb_hiq_take(rtwusb) ?
TX_DESC_QSEL_HIGH : skb->priority;
else if (skb_get_queue_mapping(skb) <= IEEE80211_AC_BK)
qsel = skb->priority;
--
2.54.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-02 10:41 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-02 10:41 [PATCH rtw-next 0/2] wifi: rtw88: usb: keep bmc traffic from exhausting the TX page pool Mehmet Fide
2026-09-02 10:41 ` [PATCH rtw-next 1/2] wifi: rtw88: usb: bound what the driver feeds the after-DTIM queue Mehmet Fide
2026-09-02 10:41 ` [PATCH rtw-next 2/2] wifi: rtw88: usb: only let the frames a dozing station needs use " Mehmet Fide
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®