]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: rtw88: usb: Preallocate and reuse the RX skbs
authorBitterblue Smith <rtl8821cerfe2@gmail.com>
Wed, 18 Dec 2024 22:35:49 +0000 (00:35 +0200)
committerPing-Ke Shih <pkshih@realtek.com>
Mon, 23 Dec 2024 08:06:02 +0000 (16:06 +0800)
The USB driver uses four USB Request Blocks for RX. Before submitting
one, it allocates a 32768 byte skb for the RX data. This allocation can
fail, maybe due to temporary memory fragmentation. When the allocation
fails, the corresponding URB is never submitted again. After four such
allocation failures, all RX stops because the driver is not requesting
data from the device anymore.

Don't allocate a 32768 byte skb when submitting a USB Request Block
(which happens very often). Instead preallocate 8 such skbs, and reuse
them over and over. If all 8 are busy, allocate a new one. This is
pretty rare. If the allocation fails, use a work to try again later.
When there are enough free skbs again, free the excess skbs.

Also, use WQ_BH for the RX workqueue. With a normal or high priority
workqueue the skbs are processed too slowly when the system is even a
little busy, like when opening a new page in a browser, and the driver
runs out of free skbs and allocates a lot of new ones.

This is more or less what the out-of-tree Realtek drivers do, except
they use a tasklet instead of a BH workqueue.

Tested with RTL8723DU, RTL8821AU, RTL8812AU, RTL8812BU, RTL8822CU,
RTL8811CU.

Closes: https://lore.kernel.org/linux-wireless/6e7ecb47-7ea0-433a-a19f-05f88a2edf6b@gmail.com/
Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/9cee7a34-c38d-4128-824d-0ec139ca5a4e@gmail.com
drivers/net/wireless/realtek/rtw88/usb.c
drivers/net/wireless/realtek/rtw88/usb.h

index 28679d9cdffceae5a7be08ccf9ddbae82b921eeb..1572b61cf87738be96d62f7d3f1a969ecff1de27 100644 (file)
@@ -586,7 +586,7 @@ static void rtw_usb_rx_handler(struct work_struct *work)
                                goto skip_packet;
                        }
 
-                       skb = alloc_skb(skb_len, GFP_KERNEL);
+                       skb = alloc_skb(skb_len, GFP_ATOMIC);
                        if (!skb) {
                                rtw_dbg(rtwdev, RTW_DBG_USB,
                                        "failed to allocate RX skb of size %u\n",
@@ -613,32 +613,70 @@ skip_packet:
                        rx_desc += next_pkt;
                } while (rx_desc + pkt_desc_sz < rx_skb->data + rx_skb->len);
 
-               dev_kfree_skb_any(rx_skb);
+               if (skb_queue_len(&rtwusb->rx_free_queue) >= RTW_USB_RX_SKB_NUM)
+                       dev_kfree_skb_any(rx_skb);
+               else
+                       skb_queue_tail(&rtwusb->rx_free_queue, rx_skb);
        }
 }
 
 static void rtw_usb_read_port_complete(struct urb *urb);
 
-static void rtw_usb_rx_resubmit(struct rtw_usb *rtwusb, struct rx_usb_ctrl_block *rxcb)
+static void rtw_usb_rx_resubmit(struct rtw_usb *rtwusb,
+                               struct rx_usb_ctrl_block *rxcb,
+                               gfp_t gfp)
 {
        struct rtw_dev *rtwdev = rtwusb->rtwdev;
+       struct sk_buff *rx_skb;
        int error;
 
-       rxcb->rx_skb = alloc_skb(RTW_USB_MAX_RECVBUF_SZ, GFP_ATOMIC);
-       if (!rxcb->rx_skb)
-               return;
+       rx_skb = skb_dequeue(&rtwusb->rx_free_queue);
+       if (!rx_skb)
+               rx_skb = alloc_skb(RTW_USB_MAX_RECVBUF_SZ, gfp);
+
+       if (!rx_skb)
+               goto try_later;
+
+       skb_reset_tail_pointer(rx_skb);
+       rx_skb->len = 0;
+
+       rxcb->rx_skb = rx_skb;
 
        usb_fill_bulk_urb(rxcb->rx_urb, rtwusb->udev,
                          usb_rcvbulkpipe(rtwusb->udev, rtwusb->pipe_in),
                          rxcb->rx_skb->data, RTW_USB_MAX_RECVBUF_SZ,
                          rtw_usb_read_port_complete, rxcb);
 
-       error = usb_submit_urb(rxcb->rx_urb, GFP_ATOMIC);
+       error = usb_submit_urb(rxcb->rx_urb, gfp);
        if (error) {
-               kfree_skb(rxcb->rx_skb);
+               skb_queue_tail(&rtwusb->rx_free_queue, rxcb->rx_skb);
+
                if (error != -ENODEV)
                        rtw_err(rtwdev, "Err sending rx data urb %d\n",
                                error);
+
+               if (error == -ENOMEM)
+                       goto try_later;
+       }
+
+       return;
+
+try_later:
+       rxcb->rx_skb = NULL;
+       queue_work(rtwusb->rxwq, &rtwusb->rx_urb_work);
+}
+
+static void rtw_usb_rx_resubmit_work(struct work_struct *work)
+{
+       struct rtw_usb *rtwusb = container_of(work, struct rtw_usb, rx_urb_work);
+       struct rx_usb_ctrl_block *rxcb;
+       int i;
+
+       for (i = 0; i < RTW_USB_RXCB_NUM; i++) {
+               rxcb = &rtwusb->rx_cb[i];
+
+               if (!rxcb->rx_skb)
+                       rtw_usb_rx_resubmit(rtwusb, rxcb, GFP_ATOMIC);
        }
 }
 
@@ -654,15 +692,16 @@ static void rtw_usb_read_port_complete(struct urb *urb)
                    urb->actual_length < 24) {
                        rtw_err(rtwdev, "failed to get urb length:%d\n",
                                urb->actual_length);
-                       if (skb)
-                               dev_kfree_skb_any(skb);
+                       skb_queue_tail(&rtwusb->rx_free_queue, skb);
                } else {
                        skb_put(skb, urb->actual_length);
                        skb_queue_tail(&rtwusb->rx_queue, skb);
                        queue_work(rtwusb->rxwq, &rtwusb->rx_work);
                }
-               rtw_usb_rx_resubmit(rtwusb, rxcb);
+               rtw_usb_rx_resubmit(rtwusb, rxcb, GFP_ATOMIC);
        } else {
+               skb_queue_tail(&rtwusb->rx_free_queue, skb);
+
                switch (urb->status) {
                case -EINVAL:
                case -EPIPE:
@@ -680,8 +719,6 @@ static void rtw_usb_read_port_complete(struct urb *urb)
                        rtw_err(rtwdev, "status %d\n", urb->status);
                        break;
                }
-               if (skb)
-                       dev_kfree_skb_any(skb);
        }
 }
 
@@ -869,16 +906,26 @@ static struct rtw_hci_ops rtw_usb_ops = {
 static int rtw_usb_init_rx(struct rtw_dev *rtwdev)
 {
        struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev);
+       struct sk_buff *rx_skb;
+       int i;
 
-       rtwusb->rxwq = create_singlethread_workqueue("rtw88_usb: rx wq");
+       rtwusb->rxwq = alloc_workqueue("rtw88_usb: rx wq", WQ_BH, 0);
        if (!rtwusb->rxwq) {
                rtw_err(rtwdev, "failed to create RX work queue\n");
                return -ENOMEM;
        }
 
        skb_queue_head_init(&rtwusb->rx_queue);
+       skb_queue_head_init(&rtwusb->rx_free_queue);
 
        INIT_WORK(&rtwusb->rx_work, rtw_usb_rx_handler);
+       INIT_WORK(&rtwusb->rx_urb_work, rtw_usb_rx_resubmit_work);
+
+       for (i = 0; i < RTW_USB_RX_SKB_NUM; i++) {
+               rx_skb = alloc_skb(RTW_USB_MAX_RECVBUF_SZ, GFP_KERNEL);
+               if (rx_skb)
+                       skb_queue_tail(&rtwusb->rx_free_queue, rx_skb);
+       }
 
        return 0;
 }
@@ -891,7 +938,7 @@ static void rtw_usb_setup_rx(struct rtw_dev *rtwdev)
        for (i = 0; i < RTW_USB_RXCB_NUM; i++) {
                struct rx_usb_ctrl_block *rxcb = &rtwusb->rx_cb[i];
 
-               rtw_usb_rx_resubmit(rtwusb, rxcb);
+               rtw_usb_rx_resubmit(rtwusb, rxcb, GFP_KERNEL);
        }
 }
 
@@ -903,6 +950,8 @@ static void rtw_usb_deinit_rx(struct rtw_dev *rtwdev)
 
        flush_workqueue(rtwusb->rxwq);
        destroy_workqueue(rtwusb->rxwq);
+
+       skb_queue_purge(&rtwusb->rx_free_queue);
 }
 
 static int rtw_usb_init_tx(struct rtw_dev *rtwdev)
index 86697a5c0103a5aab8f17f2b0f881b310b4b602c..9b695b688b242286709f096d2537378c007f1d78 100644 (file)
@@ -38,6 +38,7 @@
 #define RTW_USB_RXAGG_TIMEOUT          10
 
 #define RTW_USB_RXCB_NUM               4
+#define RTW_USB_RX_SKB_NUM             8
 
 #define RTW_USB_EP_MAX                 4
 
@@ -81,7 +82,9 @@ struct rtw_usb {
 
        struct rx_usb_ctrl_block rx_cb[RTW_USB_RXCB_NUM];
        struct sk_buff_head rx_queue;
+       struct sk_buff_head rx_free_queue;
        struct work_struct rx_work;
+       struct work_struct rx_urb_work;
 };
 
 static inline struct rtw_usb_tx_data *rtw_usb_get_tx_data(struct sk_buff *skb)