rtw89_usb_ops_check_and_reclaim_tx_resource() returns a hardcoded placeholder value (42) instead of actual TX resource availability. This violates mac80211's flow control contract, preventing backpressure and causing uncontrolled URB accumulation under sustained TX load. Fix by adding per-channel atomic counters (tx_inflight[]) that track in-flight URBs. Increment before usb_submit_urb() with rollback on failure, decrement in the completion callback, and return the remaining capacity to mac80211. The firmware command channel (CH12) always returns 1 since it has its own flow control. The pre-increment pattern prevents a race where USB core completes the URB on another CPU before the submitting code increments the counter. 128 URBs per channel provides headroom for RTL8832CU at 160 MHz bandwidth. Tested on RTL8852AU (USB3 80 MHz) where 64 and 128 showed equivalent throughput, and on RTL8832AU where 128 sustained full throughput under 8-stream parallel load. Tested on D-Link DWA-X1850 (RTL8832AU), kernel 6.19.8, Fedora 43: Unpatched -> Patched (128 URBs) USB3 5GHz UL: 844 -> 837 Mbps (no regression) USB3 5GHz retx: 3 -> 0 USB3 2.4GHz UL: 162 -> 164 Mbps (no regression) 4-stream UL: 858 -> 826 Mbps (within variance) 8-stream UL: 872 -> 826 Mbps (within variance) UDP flood: 0% loss (690K datagrams) 60-second soak: 855 Mbps, 0 retransmits Reported-by: morrownr Signed-off-by: Lucid Duck --- Changes since v4: - Regenerated from kernel tree (not standalone repo). v4 had bare usb.c/usb.h paths instead of drivers/net/wireless/realtek/rtw89/ which broke patchwork delegation. Sorry about that. - No code changes from v4. Changes since v3: - Increased MAX_TX_URBS_PER_CH from 64 to 128 per Ping-Ke's suggestion, providing headroom for RTL8832CU at 160 MHz. - Simplified CH12 handling per Ping-Ke's suggestion: CH12 is now tracked in the counter like all other channels. The only special case is in check_and_reclaim where CH12 returns 1 (firmware command channel has its own flow control). - Removed all inline comments flagged in review. Changes since v2: - Increased MAX_TX_URBS_PER_CH from 32 to 64 based on URB scaling tests showing 32 drops 35% under multi-stream load. - Removed duplicate "TX flow control" comments. Changes since v1: - Removed duplicate comments per Ping-Ke. - Added throughput data to commit message per Ping-Ke. - Addressed Bitterblue's question about using skb_queue_len vs atomic counters (atomic is needed because the counter must update before usb_submit_urb returns, not after URB completion). drivers/net/wireless/realtek/rtw89/usb.c | 20 ++++++++++++++++++-- drivers/net/wireless/realtek/rtw89/usb.h | 3 +++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c index da1b7ce..9a2d68a 100644 --- a/drivers/net/wireless/realtek/rtw89/usb.c +++ b/drivers/net/wireless/realtek/rtw89/usb.c @@ -161,16 +161,24 @@ static u32 rtw89_usb_ops_check_and_reclaim_tx_resource(struct rtw89_dev *rtwdev, u8 txch) { + struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev); + int inflight; + if (txch == RTW89_TXCH_CH12) return 1; - return 42; /* TODO some kind of calculation? */ + inflight = atomic_read(&rtwusb->tx_inflight[txch]); + if (inflight >= RTW89_USB_MAX_TX_URBS_PER_CH) + return 0; + + return RTW89_USB_MAX_TX_URBS_PER_CH - inflight; } static void rtw89_usb_write_port_complete(struct urb *urb) { struct rtw89_usb_tx_ctrl_block *txcb = urb->context; struct rtw89_dev *rtwdev = txcb->rtwdev; + struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev); struct ieee80211_tx_info *info; struct rtw89_txwd_body *txdesc; struct sk_buff *skb; @@ -229,6 +237,8 @@ static void rtw89_usb_write_port_complete(struct urb *urb) break; } + atomic_dec(&rtwusb->tx_inflight[txcb->txch]); + kfree(txcb); } @@ -306,9 +316,13 @@ static void rtw89_usb_ops_tx_kick_off(struct rtw89_dev *rtwdev, u8 txch) skb_queue_tail(&txcb->tx_ack_queue, skb); + atomic_inc(&rtwusb->tx_inflight[txch]); + ret = rtw89_usb_write_port(rtwdev, txch, skb->data, skb->len, txcb); if (ret) { + atomic_dec(&rtwusb->tx_inflight[txch]); + if (ret != -ENODEV) rtw89_err(rtwdev, "write port txch %d failed: %d\n", txch, ret); @@ -666,8 +680,10 @@ static void rtw89_usb_init_tx(struct rtw89_dev *rtwdev) struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev); int i; - for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++) + for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++) { skb_queue_head_init(&rtwusb->tx_queue[i]); + atomic_set(&rtwusb->tx_inflight[i], 0); + } } static void rtw89_usb_deinit_tx(struct rtw89_dev *rtwdev) diff --git a/drivers/net/wireless/realtek/rtw89/usb.h b/drivers/net/wireless/realtek/rtw89/usb.h index 203ec8e..e62bde6 100644 --- a/drivers/net/wireless/realtek/rtw89/usb.h +++ b/drivers/net/wireless/realtek/rtw89/usb.h @@ -20,6 +20,8 @@ #define RTW89_MAX_ENDPOINT_NUM 9 #define RTW89_MAX_BULKOUT_NUM 7 +#define RTW89_USB_MAX_TX_URBS_PER_CH 128 + struct rtw89_usb_info { u32 usb_host_request_2; u32 usb_wlan0_1; @@ -63,6 +65,7 @@ struct rtw89_usb { struct usb_anchor tx_submitted; struct sk_buff_head tx_queue[RTW89_TXCH_NUM]; + atomic_t tx_inflight[RTW89_TXCH_NUM]; }; static inline struct rtw89_usb *rtw89_usb_priv(struct rtw89_dev *rtwdev) -- 2.53.0