When rtw_usb_write_port() fails to submit a USB Request Block (URB)
(e.g., due to device disconnect or ENOMEM), the completion callback is
never executed.
Currently, the driver ignores the return value of rtw_usb_write_port()
in rtw_usb_write_data() and rtw_usb_tx_agg_skb(). Because these
functions rely on the completion callback to free the socket buffers
(skbs) and the transaction control block (txcb), a submission failure
results in:
1. A memory leak of the allocated skb in rtw_usb_write_data().
2. A memory leak of the txcb structure and all aggregated skbs in
rtw_usb_tx_agg_skb().
Fix this by checking the return value of rtw_usb_write_port(). If it
fails, explicitly free the skb in rtw_usb_write_data(), and properly
purge the tx_ack_queue and free the txcb in rtw_usb_tx_agg_skb().
The issue was discovered in practice during device disconnect/reconnect
scenarios and memory pressure conditions. Tested by verifying normal TX
operation continues after the fix without regressions.
Fixes: a82dfd33d123 ("wifi: rtw88: Add common USB chip support")
Cc: stable@vger.kernel.org
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Tested-by: Luka Gejak <luka.gejak@linux.dev>
Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/20260518142311.10328-2-luka.gejak@linux.dev
int agg_num = 0;
unsigned int align_next = 0;
u8 qsel;
+ int ret;
if (skb_queue_empty(list))
return false;
tx_desc = (struct rtw_tx_desc *)skb_head->data;
qsel = le32_get_bits(tx_desc->w1, RTW_TX_DESC_W1_QSEL);
- rtw_usb_write_port(rtwdev, qsel, skb_head, rtw_usb_write_port_tx_complete, txcb);
+ ret = rtw_usb_write_port(rtwdev, qsel, skb_head,
+ rtw_usb_write_port_tx_complete, txcb);
+ if (ret) {
+ ieee80211_purge_tx_queue(rtwdev->hw, &txcb->tx_ack_queue);
+ kfree(txcb);
+ return false;
+ }
return true;
}
ret = rtw_usb_write_port(rtwdev, qsel, skb,
rtw_usb_write_port_complete, skb);
- if (unlikely(ret))
+ if (unlikely(ret)) {
rtw_err(rtwdev, "failed to do USB write, ret=%d\n", ret);
+ dev_kfree_skb_any(skb);
+ }
return ret;
}