The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Ping-Ke Shih <pkshih@realtek.com>
To: "luka.gejak@linux.dev" <luka.gejak@linux.dev>,
	"linux-wireless@vger.kernel.org" <linux-wireless@vger.kernel.org>
Cc: "linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	"Michael Straube" <straube.linux@gmail.com>,
	Bitterblue Smith <rtl8821cerfe2@gmail.com>,
	Peter Robinson <pbrobinson@gmail.com>,
	"Hans de Goede" <johannes.goede@oss.qualcomm.com>
Subject: RE: [PATCH v4 7/7] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation
Date: Fri, 14 Aug 2026 07:14:52 +0000	[thread overview]
Message-ID: <498fd000eb68454fb9842c3f9879ea4e@realtek.com> (raw)
In-Reply-To: <20260811091203.26841-8-luka.gejak@linux.dev>

luka.gejak@linux.dev <luka.gejak@linux.dev> wrote:
> From: Luka Gejak <luka.gejak@linux.dev>
> 
> Two problems show up on RTL8723BS uplink. The per-AC software FIFO is
> unbounded, so mac80211 keeps handing frames down until latency collapses
> under load. And when the chip runs out of free TX pages the queue is
> simply abandoned for that pass, which stalls the AC until something else
> kicks the worker.
> 
> Stop the mac80211 queue once a data AC fills past a high watermark and
> wake it from the drain path when it falls back to a low one. Convert the
> TX work item to a delayed work so a temporary page shortage can be
> retried shortly afterwards instead of stalling, and cancel it on
> teardown.
> 
> Measured on RTL8723BS hardware, uplink goes from 11.9 Mbit/s with 204
> TCP retransmits to 20.1 Mbit/s with 2.
> 
> Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
> ---
>  drivers/net/wireless/realtek/rtw88/sdio.c | 99 ++++++++++++++++++++---
>  drivers/net/wireless/realtek/rtw88/sdio.h |  3 +-
>  2 files changed, 91 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
> index e9f0d23113f3..ed1bed8ea144 100644
> --- a/drivers/net/wireless/realtek/rtw88/sdio.c
> +++ b/drivers/net/wireless/realtek/rtw88/sdio.c
> @@ -22,6 +22,16 @@
>  #define RTW_SDIO_INDIRECT_RW_RETRIES                   50
>  #define RTW_SDIO_OQT_TIMEOUT_MS                                1000
> 
> +/*
> + * 8723BS SDIO TX FIFO back-pressure watermarks: stop the mac80211 queue once
> + * the per-AC software FIFO fills past the high watermark, and wake it from the
> + * TX drain path once it falls back to the low one. Bounds the queueing latency
> + * that otherwise causes uplink bufferbloat / congestion collapse.
> + */
> +#define RTW_SDIO_TX_FIFO_HIWATER                       16
> +#define RTW_SDIO_TX_FIFO_LOWATER                       8
> +#define RTW_SDIO_TX_RETRY_DELAY                        msecs_to_jiffies(1)

If HZ is smaller than 1000, the value will be 0 I think. Is it expected?
(may be not a problem)

> +
>  static bool rtw_sdio_is_bus_addr(u32 addr)
>  {
>         return !!(addr & RTW_SDIO_BUS_MSK);
> @@ -1053,7 +1063,11 @@ static void rtw_sdio_tx_kick_off(struct rtw_dev *rtwdev)
>  {
>         struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> 
> -       queue_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work);
> +       /*
> +        * A retry may already be pending with a delay; re-arm it so a newly
> +        * queued frame is not held back by it.
> +        */
> +       mod_delayed_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work, 0);
>  }
> 
>  static void rtw_sdio_link_ps(struct rtw_dev *rtwdev, bool enter)
> @@ -1177,6 +1191,19 @@ static int rtw_sdio_tx_write(struct rtw_dev *rtwdev,
> 
>         skb_queue_tail(&rtwsdio->tx_queue[queue], skb);
> 
> +       /*
> +        * Back-pressure on the data ACs (BK/BE/VI/VO): once the FIFO fills past
> +        * the high watermark, stop the corresponding mac80211 queue so it stops
> +        * handing us frames, bounding the queueing latency. Resumed from the TX
> +        * drain path once the FIFO drains below the low watermark.
> +        */
> +       if (rtw_is_8723bs(rtwdev) && queue < RTW_TX_QUEUE_BCN &&
> +           !rtwsdio->queue_stopped[queue] &&
> +           skb_queue_len(&rtwsdio->tx_queue[queue]) >= RTW_SDIO_TX_FIFO_HIWATER) {
> +               rtwsdio->queue_stopped[queue] = true;
> +               ieee80211_stop_queue(rtwdev->hw, skb_get_queue_mapping(skb));
> +       }
> +

Can we move this chunk into a function? Then, you don't need to write
complicated conditions into a if-statement. 

As well as the chunk to call ieee80211_wake_queue().

>         return 0;
>  }
> 
> @@ -1479,43 +1506,92 @@ static void rtw_sdio_indicate_tx_status(struct rtw_dev *rtwdev,
>         ieee80211_tx_status_irqsafe(hw, skb);
>  }
> 
> -static void rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev,
> -                                     enum rtw_tx_queue_type queue)
> +/*
> + * Send one frame from @queue. Returns 1 when a frame was written, 0 when the
> + * queue was empty 

I'd prefer 0 is successful case. 1 is special case (queue was empty).

> and a negative errno when the write failed, in which case
> + * the frame is put back at the head of the queue.
> + */
> +static int rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev,
> +                                    enum rtw_tx_queue_type queue)
>  {
>         struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
>         struct sk_buff *skb;
> +       u16 q_map;
>         int ret;
> 
>         skb = skb_dequeue(&rtwsdio->tx_queue[queue]);
>         if (!skb)
> -               return;
> +               return 0;
> 
> +       q_map = skb_get_queue_mapping(skb);
>         ret = rtw_sdio_write_port(rtwdev, skb, queue);
>         if (ret) {
>                 skb_queue_head(&rtwsdio->tx_queue[queue], skb);
> -               return;
> +               return ret;
>         }
> 
>         rtw_sdio_indicate_tx_status(rtwdev, skb);
> +
> +       if (rtw_is_8723bs(rtwdev) && queue < RTW_TX_QUEUE_BCN &&
> +           rtwsdio->queue_stopped[queue] &&
> +           skb_queue_len(&rtwsdio->tx_queue[queue]) <=
> +           RTW_SDIO_TX_FIFO_LOWATER) {
> +               rtwsdio->queue_stopped[queue] = false;
> +               ieee80211_wake_queue(rtwdev->hw, q_map);
> +       }

Move to a function.

> +
> +       return 1;
>  }
> 
>  static void rtw_sdio_tx_handler(struct work_struct *work)
>  {
>         struct rtw_sdio_work_data *work_data =
> -               container_of(work, struct rtw_sdio_work_data, work);
> +               container_of(to_delayed_work(work), struct rtw_sdio_work_data,
> +                            work);
>         struct rtw_sdio *rtwsdio;
>         struct rtw_dev *rtwdev;
> -       int limit, queue;
> +       int limit, queue, ret;
> +       bool rtl8723bs;
> 
>         rtwdev = work_data->rtwdev;
>         rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +       rtl8723bs = rtw_is_8723bs(rtwdev);
> 
>         if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE))
>                 rtw_sdio_deep_ps_leave(rtwdev);
> 
>         for (queue = RTK_MAX_TX_QUEUE_NUM - 1; queue >= 0; queue--) {
>                 for (limit = 0; limit < 1000; limit++) {
> -                       rtw_sdio_process_tx_queue(rtwdev, queue);
> +                       ret = rtw_sdio_process_tx_queue(rtwdev, queue);
> +                       if (ret == 0)
> +                               break;
> +
> +                       if (ret < 0) {
> +                               /*
> +                                * A page shortage is transient: leave the
> +                                * frame queued and come back shortly instead
> +                                * of stalling this AC until something else
> +                                * kicks the worker.
> +                                */
> +                               if (rtl8723bs && ret == -EBUSY) {
> +                                       queue_delayed_work(rtwsdio->txwq,
> +                                                          &work_data->work,
> +                                                          RTW_SDIO_TX_RETRY_DELAY);
> +                                       return;
> +                               }
> +                               break;
> +                       }
> +
> +                       /*
> +                        * Restart from the highest priority queue after every
> +                        * management frame so the join sequence is not held up
> +                        * behind a data backlog.
> +                        */
> +                       if (rtl8723bs && queue == RTW_TX_QUEUE_MGMT) {
> +                               queue_delayed_work(rtwsdio->txwq,
> +                                                  &work_data->work, 0);
> +                               return;
> +                       }

Move this chunk into a function named reschedule_tx_work() or something
like that.

> 
>                         if (skb_queue_empty(&rtwsdio->tx_queue[queue]))
>                                 break;
> @@ -1542,14 +1618,16 @@ static int rtw_sdio_init_tx(struct rtw_dev *rtwdev)
>                 return -ENOMEM;
>         }
> 
> -       for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++)
> +       for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) {
>                 skb_queue_head_init(&rtwsdio->tx_queue[i]);
> +               rtwsdio->queue_stopped[i] = false;
> +       }
>         rtwsdio->tx_handler_data = kmalloc_obj(*rtwsdio->tx_handler_data);
>         if (!rtwsdio->tx_handler_data)
>                 goto err_destroy_wq;
> 
>         rtwsdio->tx_handler_data->rtwdev = rtwdev;
> -       INIT_WORK(&rtwsdio->tx_handler_data->work, rtw_sdio_tx_handler);
> +       INIT_DELAYED_WORK(&rtwsdio->tx_handler_data->work, rtw_sdio_tx_handler);
> 
>         return 0;
> 
> @@ -1563,6 +1641,7 @@ static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev)
>         struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
>         int i;
> 
> +       cancel_delayed_work_sync(&rtwsdio->tx_handler_data->work);
>         destroy_workqueue(rtwsdio->txwq);
>         kfree(rtwsdio->tx_handler_data);
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h
> index 7474a7511811..2f1b67f4bbc7 100644
> --- a/drivers/net/wireless/realtek/rtw88/sdio.h
> +++ b/drivers/net/wireless/realtek/rtw88/sdio.h
> @@ -156,7 +156,7 @@ struct rtw_sdio_tx_data {
>  };
> 
>  struct rtw_sdio_work_data {
> -       struct work_struct work;
> +       struct delayed_work work;
>         struct rtw_dev *rtwdev;
>  };
> 
> @@ -172,6 +172,7 @@ struct rtw_sdio {
>         struct workqueue_struct *txwq;
>         struct rtw_sdio_work_data *tx_handler_data;
>         struct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM];
> +       bool queue_stopped[RTK_MAX_TX_QUEUE_NUM];

add 'tx_' prefix? tx_queue_stopped[] 

> 
>         atomic_t free_pg_high;
>         atomic_t free_pg_normal;
> --
> 2.53.0


      reply	other threads:[~2026-08-14  7:15 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-11  9:11 [PATCH v4 0/7] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
2026-08-11  9:11 ` [PATCH v4 1/7] wifi: rtw88: add the RTL8723B chip type and SDIO helper luka.gejak
2026-08-14  7:15   ` Ping-Ke Shih
2026-08-11  9:11 ` [PATCH v4 2/7] wifi: rtw88: rx: mark zero length packets on RTL8723BS luka.gejak
2026-08-14  5:44   ` Ping-Ke Shih
2026-08-11  9:11 ` [PATCH v4 3/7] wifi: rtw88: tx: extend the TX report purge timeout to RTL8723BS luka.gejak
2026-08-14  7:15   ` Ping-Ke Shih
2026-08-11  9:12 ` [PATCH v4 4/7] wifi: rtw88: fw: handle the RTL8723BS management TX reports luka.gejak
2026-08-11 11:42   ` Bitterblue Smith
2026-08-14  5:52     ` Ping-Ke Shih
2026-08-11  9:12 ` [PATCH v4 5/7] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS luka.gejak
2026-08-14  6:32   ` Ping-Ke Shih
2026-08-11  9:12 ` [PATCH v4 6/7] wifi: rtw88: sdio: set up RX aggregation and interrupts " luka.gejak
2026-08-14  6:41   ` Ping-Ke Shih
2026-08-11  9:12 ` [PATCH v4 7/7] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation luka.gejak
2026-08-14  7:14   ` Ping-Ke Shih [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=498fd000eb68454fb9842c3f9879ea4e@realtek.com \
    --to=pkshih@realtek.com \
    --cc=johannes.goede@oss.qualcomm.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=luka.gejak@linux.dev \
    --cc=pbrobinson@gmail.com \
    --cc=rtl8821cerfe2@gmail.com \
    --cc=straube.linux@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox