Linux wireless drivers development
 help / color / mirror / Atom feed
From: Ping-Ke Shih <pkshih@realtek.com>
To: "luka.gejak@linux.dev" <luka.gejak@linux.dev>
Cc: "linux-wireless@vger.kernel.org" <linux-wireless@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	"Michael Straube" <straube.linux@gmail.com>,
	Peter Robinson <pbrobinson@gmail.com>,
	Bitterblue Smith <rtl8821cerfe2@gmail.com>
Subject: RE: [PATCH v2 07/11] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS
Date: Mon, 27 Jul 2026 08:52:30 +0000	[thread overview]
Message-ID: <cc9251b01235422e8b5a22fc0997b0f7@realtek.com> (raw)
In-Reply-To: <20260725150427.93887-8-luka.gejak@linux.dev>

luka.gejak@linux.dev <luka.gejak@linux.dev> wrote:
> From: Luka Gejak <luka.gejak@linux.dev>
> 
> The RTL8723BS reports free TX page counts that the generic 8051 path
> reads back from the chip on every transfer, which is both slow over SDIO
> and unreliable on this part: the register frequently reads back zero
> while pages are in fact available. It also gates transmission on an
> output queue token count that rtw88 does not track at all.
> 
> Mirror the vendor driver and keep the per-queue and public page counts
> in software, seeded at start and resynchronised from the chip only when
> the cached counts say there is not enough room. Wait for an OQT credit

Does OQT mean Output Queue Token? Could you explain a little bit?

> before writing, and account for the pages consumed after a successful
> transfer.
> 
> Transfers also have to be padded up to the SDIO block size for this
> chip rather than using the generic alignment, so size the write
> separately from the frame and trim the skb back afterwards.

I think this can largely improve SDIO. Please share the performance 
numbers before/after this patch.

> 
> Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
> ---
>  drivers/net/wireless/realtek/rtw88/sdio.c | 220 ++++++++++++++++++++--
>  drivers/net/wireless/realtek/rtw88/sdio.h |   6 +
>  2 files changed, 215 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
> index 5b40d74b16ee..adf0b509e2cc 100644
> --- a/drivers/net/wireless/realtek/rtw88/sdio.c
> +++ b/drivers/net/wireless/realtek/rtw88/sdio.c
> @@ -20,6 +20,7 @@
>  #include "tx.h"
> 
>  #define RTW_SDIO_INDIRECT_RW_RETRIES                   50
> +#define RTW_SDIO_OQT_TIMEOUT_MS                                1000
> 
>  static bool rtw_sdio_is_bus_addr(u32 addr)
>  {
> @@ -548,12 +549,91 @@ static int rtw_sdio_read_port(struct rtw_dev *rtwdev, u8 *buf, size_t count)
>         return ret;
>  }
> 
> +static void rtw_sdio_init_free_txpg(struct rtw_dev *rtwdev)

_8723bs_ specific so add to the function name

> +{
> +       struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +       const struct rtw_page_table *pg_tbl;
> +       u32 free_txpg;
> +       u16 pubq_num;
> +
> +       if (!rtw_is_8723bs(rtwdev))
> +               return;
> +
> +       pg_tbl = &rtwdev->chip->page_table[0];
> +       pubq_num = rtwdev->fifo.acq_pg_num - pg_tbl->hq_num - pg_tbl->lq_num -
> +                  pg_tbl->nq_num - pg_tbl->exq_num - pg_tbl->gapq_num;
> +       free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
> +       if (free_txpg) {
> +               atomic_set(&rtwsdio->free_pg_high, free_txpg & 0xff);
> +               atomic_set(&rtwsdio->free_pg_normal, (free_txpg >> 8) & 0xff);
> +               atomic_set(&rtwsdio->free_pg_low, (free_txpg >> 16) & 0xff);
> +               atomic_set(&rtwsdio->free_pg_pub, (free_txpg >> 24) & 0xff);
> +       } else {
> +               atomic_set(&rtwsdio->free_pg_high, pg_tbl->hq_num);
> +               atomic_set(&rtwsdio->free_pg_normal, pg_tbl->nq_num);
> +               atomic_set(&rtwsdio->free_pg_low, pg_tbl->lq_num);
> +               atomic_set(&rtwsdio->free_pg_pub, pubq_num);
> +       }
> +
> +       atomic_set(&rtwsdio->tx_oqt_free,
> +                  rtw_read8(rtwdev, REG_SDIO_OQT_FREE_PG));
> +}
> +
> +static void rtw_sdio_sync_free_txpg(struct rtw_dev *rtwdev)

_8723bs_ specific so add to the function name

> +{
> +       struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +       u32 free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
> +
> +       if (!free_txpg)
> +               return;
> +
> +       atomic_set(&rtwsdio->free_pg_high, free_txpg & 0xff);
> +       atomic_set(&rtwsdio->free_pg_normal, (free_txpg >> 8) & 0xff);
> +       atomic_set(&rtwsdio->free_pg_low, (free_txpg >> 16) & 0xff);
> +       atomic_set(&rtwsdio->free_pg_pub, (free_txpg >> 24) & 0xff);

It is clear that bit masks of REG_SDIO_FREE_TXPG are PG_{HIGH, NORMAL, LOW, PUB}. 
Let's define them in reg.h, and use u32_get_bits() to read out the values.

> +}
> +
> +static int rtw_sdio_8723bs_free_txpg(struct rtw_dev *rtwdev, u8 queue)
> +{
> +       struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +
> +       switch (queue) {
> +       case RTW_TX_QUEUE_BCN:
> +       case RTW_TX_QUEUE_H2C:
> +       case RTW_TX_QUEUE_HI0:
> +       case RTW_TX_QUEUE_MGMT:
> +       case RTW_TX_QUEUE_VO:
> +               return atomic_read(&rtwsdio->free_pg_high);
> +       case RTW_TX_QUEUE_VI:
> +               return atomic_read(&rtwsdio->free_pg_normal);
> +       case RTW_TX_QUEUE_BE:
> +       case RTW_TX_QUEUE_BK:
> +               return atomic_read(&rtwsdio->free_pg_low);
> +       default:
> +               return -EINVAL;
> +       }
> +}
> +
>  static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,
>                                     size_t count)
>  {
>         unsigned int pages_free, pages_needed;
> 
> -       if (rtw_chip_wcpu_8051(rtwdev)) {
> +       if (rtw_is_8723bs(rtwdev)) {
> +               struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +               int dedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);
> +
> +               if (dedicated < 0)
> +                       return dedicated;
> +               pages_free = dedicated + atomic_read(&rtwsdio->free_pg_pub);
> +               pages_needed = DIV_ROUND_UP(count, rtwdev->chip->page_size);
> +               if (pages_needed <= pages_free)
> +                       return 0;
> +
> +               rtw_sdio_sync_free_txpg(rtwdev);
> +               dedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);

Why do you check ` dedicated < 0` for this case?

> +               pages_free = dedicated + atomic_read(&rtwsdio->free_pg_pub);
> +       } else if (rtw_chip_wcpu_8051(rtwdev)) {
>                 u32 free_txpg;
> 
>                 free_txpg = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG);
> @@ -632,44 +712,131 @@ static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,
>         return 0;
>  }
> 
> +static int rtw_sdio_wait_tx_oqt(struct rtw_dev *rtwdev)
> +{
> +       struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +       int i;
> +       u8 free;
> +
> +       if (!rtw_is_8723bs(rtwdev))
> +               return 0;
> +
> +       if (atomic_add_unless(&rtwsdio->tx_oqt_free, -1, 0))
> +               return 0;
> +
> +       for (i = 0; i < RTW_SDIO_OQT_TIMEOUT_MS; i++) {
> +               free = rtw_read8(rtwdev, REG_SDIO_OQT_FREE_PG);
> +               if (free) {
> +                       atomic_set(&rtwsdio->tx_oqt_free, free - 1);
> +                       return 0;
> +               }
> +               usleep_range(1000, 2000);
> +       }
> +
> +       return -EBUSY;
> +}
> +
> +static void rtw_sdio_8723bs_consume_txpg(struct rtw_dev *rtwdev, u8 queue,
> +                                        unsigned int pages)
> +{
> +       struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +       atomic_t *dedicated;
> +       int free;
> +
> +       switch (queue) {
> +       case RTW_TX_QUEUE_VI:
> +               dedicated = &rtwsdio->free_pg_normal;
> +               break;
> +       case RTW_TX_QUEUE_BE:
> +       case RTW_TX_QUEUE_BK:
> +               dedicated = &rtwsdio->free_pg_low;
> +               break;
> +       default:
> +               dedicated = &rtwsdio->free_pg_high;
> +               break;
> +       }
> +
> +       free = atomic_read(dedicated);
> +       if (pages <= free) {
> +               atomic_sub(pages, dedicated);
> +       } else {
> +               atomic_set(dedicated, 0);
> +               atomic_sub(pages - free, &rtwsdio->free_pg_pub);
> +       }
> +}
> +
>  static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb,
>                                enum rtw_tx_queue_type queue)
>  {
>         struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
> +       unsigned int orig_len = skb->len;
> +       bool rtl8723bs = rtw_is_8723bs(rtwdev);

reverse X'mas tree 

> +       unsigned int pages;
>         bool bus_claim;
>         size_t txsize;
> +       size_t write_size;
>         u32 txaddr;
>         int ret;
> 
> -       txaddr = rtw_sdio_get_tx_addr(rtwdev, skb->len, queue);
> -       if (!txaddr)
> -               return -EINVAL;
> +       if (rtl8723bs) {
> +               txsize = round_up(orig_len, 4);
> +               write_size = txsize > RTW_SDIO_BLOCK_SIZE ?
> +                            round_up(txsize, RTW_SDIO_BLOCK_SIZE) : txsize;
> +       } else {
> +               txsize = sdio_align_size(rtwsdio->sdio_func, orig_len);
> +               write_size = txsize;
> +       }
> +
> +       if (write_size > orig_len) {
> +               unsigned int padding = write_size - orig_len;
> +
> +               if (skb_tailroom(skb) < padding) {
> +                       ret = pskb_expand_head(skb, 0,
> +                                              padding - skb_tailroom(skb),
> +                                              GFP_KERNEL);
> +                       if (ret)
> +                               return ret;
> +               }
> +               skb_put_zero(skb, padding);
> +       }
> 
> -       txsize = sdio_align_size(rtwsdio->sdio_func, skb->len);
> +       txaddr = rtw_sdio_get_tx_addr(rtwdev, txsize, queue);
> +       if (!txaddr) {
> +               ret = -EINVAL;
> +               goto out_trim;
> +       }
> 
>         ret = rtw_sdio_check_free_txpg(rtwdev, queue, txsize);
>         if (ret)
> -               return ret;
> +               goto out_trim;
> +       ret = rtw_sdio_wait_tx_oqt(rtwdev);
> +       if (ret)
> +               goto out_trim;
> 
>         if (!IS_ALIGNED((unsigned long)skb->data, RTW_SDIO_DATA_PTR_ALIGN))
>                 rtw_warn(rtwdev, "Got unaligned SKB in %s() for queue %u\n",
>                          __func__, queue);
> 
>         bus_claim = rtw_sdio_bus_claim_needed(rtwsdio);
> -
>         if (bus_claim)
>                 sdio_claim_host(rtwsdio->sdio_func);
> -
> -       ret = sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data, txsize);
> -
> +       ret = sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data,
> +                              write_size);
>         if (bus_claim)
>                 sdio_release_host(rtwsdio->sdio_func);
> 
> +       if (!ret && rtl8723bs) {
> +               pages = DIV_ROUND_UP(txsize, rtwdev->chip->page_size);
> +               rtw_sdio_8723bs_consume_txpg(rtwdev, queue, pages);
> +       }

move to else-branch below?

>         if (ret)
>                 rtw_warn(rtwdev,
>                          "Failed to write %zu byte(s) to SDIO port 0x%08x",
> -                        txsize, txaddr);
> +                        write_size, txaddr);

		else (rtl8723bs) {
		   ...
		}

> 
> +out_trim:
> +       if (write_size > orig_len)
> +               skb_trim(skb, orig_len);
>         return ret;
>  }
> 
> @@ -749,8 +916,39 @@ static int rtw_sdio_setup(struct rtw_dev *rtwdev)
>         return 0;
>  }
> 
> +static void rtw_sdio_8723bs_check_rqpn(struct rtw_dev *rtwdev)
> +{
> +       const struct rtw_chip_info *chip = rtwdev->chip;
> +       struct rtw_fifo_conf *fifo = &rtwdev->fifo;
> +       const struct rtw_page_table *pg_tbl;
> +       u32 free_txpg;
> +       u16 pubq_num;
> +
> +       if (!rtw_is_8723bs(rtwdev))
> +               return;
> +
> +       free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
> +       if (free_txpg || !fifo->acq_pg_num)
> +               return;
> +
> +       pg_tbl = &chip->page_table[0];
> +       if (fifo->acq_pg_num <= pg_tbl->hq_num + pg_tbl->lq_num +
> +                                   pg_tbl->nq_num + pg_tbl->exq_num +
> +                                   pg_tbl->gapq_num)

second and third lines don't align. 

> +               return;
> +
> +       pubq_num = fifo->acq_pg_num - pg_tbl->hq_num - pg_tbl->lq_num -
> +                  pg_tbl->nq_num - pg_tbl->exq_num - pg_tbl->gapq_num;
> +       rtw_write32(rtwdev, REG_RQPN_NPQ,
> +                   BIT_RQPN_NE(pg_tbl->nq_num, pg_tbl->exq_num));
> +       rtw_write32(rtwdev, REG_RQPN,
> +                   BIT_RQPN_HLP(pg_tbl->hq_num, pg_tbl->lq_num, pubq_num));
> +}
> +
>  static int rtw_sdio_start(struct rtw_dev *rtwdev)
>  {
> +       rtw_sdio_8723bs_check_rqpn(rtwdev);

Should we check chip is RTL8723BS before calling? 
I'd have consistent the calling rule -- callers check?

> +       rtw_sdio_init_free_txpg(rtwdev);

This is also RTL8723BS specific. Why not having a '_8723bs_' ?

>         rtw_sdio_enable_rx_aggregation(rtwdev);
>         rtw_sdio_enable_interrupt(rtwdev);
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h
> index 457e8b02380e..12086f1aa280 100644
> --- a/drivers/net/wireless/realtek/rtw88/sdio.h
> +++ b/drivers/net/wireless/realtek/rtw88/sdio.h
> @@ -159,6 +159,12 @@ 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];
> +
> +       atomic_t free_pg_high;
> +       atomic_t free_pg_normal;
> +       atomic_t free_pg_low;
> +       atomic_t free_pg_pub;
> +       atomic_t tx_oqt_free;

Could I know the reason why these should be atomic_t? Which context are they running?

>  };
> 
>  extern const struct dev_pm_ops rtw_sdio_pm_ops;
> --
> 2.55.0


  reply	other threads:[~2026-07-27  8:52 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
2026-07-25 15:04 ` [PATCH v2 01/11] wifi: rtw88: add the RTL8723B chip type and SDIO helper luka.gejak
2026-07-27  7:12   ` Ping-Ke Shih
2026-07-27 13:25     ` Luka Gejak
2026-07-25 15:04 ` [PATCH v2 02/11] wifi: rtw88: rx: mark zero length packets on RTL8723BS luka.gejak
2026-07-27  7:14   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 03/11] wifi: rtw88: fw: add the GNT_BT firmware command luka.gejak
2026-07-25 15:04 ` [PATCH v2 04/11] wifi: rtw88: fw: fix the reserved page upload on RTL8723BS luka.gejak
2026-07-27  7:37   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 05/11] wifi: rtw88: coex: add the RTL8723BS scan antenna workaround luka.gejak
2026-07-27  7:58   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 06/11] wifi: rtw88: coex: reassert the antenna path when associating luka.gejak
2026-07-27  8:21   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 07/11] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS luka.gejak
2026-07-27  8:52   ` Ping-Ke Shih [this message]
2026-07-25 15:04 ` [PATCH v2 08/11] wifi: rtw88: sdio: set up RX aggregation and interrupts " luka.gejak
2026-07-27  8:59   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 09/11] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation luka.gejak
2026-07-27  9:21   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 10/11] wifi: rtw88: record beacons from the target BSSID before authenticating luka.gejak
2026-07-27  9:27   ` Ping-Ke Shih
2026-07-25 15:04 ` [PATCH v2 11/11] wifi: rtw88: run the RTL8723BS association register sequence luka.gejak
2026-07-27  9:33   ` Ping-Ke Shih

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=cc9251b01235422e8b5a22fc0997b0f7@realtek.com \
    --to=pkshih@realtek.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