AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/11 09:51 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch exclusively adds support and fixes for the Realtek RTL8723BS SDIO WiFi chip. SDIO devices require physical hardware or specific QEMU machine emulation which is not available in standard virtualized fuzzing environments. There is no software SDIO emulation framework (like dummy_hcd for USB) to reach these code paths during fuzzing.",
  "WorthFuzzing": false
}

1/1 2026/08/11 09:51 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit a41825617844ea5391f05ce448f869395b0e0ca8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Aug 11 09:51:49 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c\nindex 945fedcd375b6..c85c0e3e60a97 100644\n--- a/drivers/net/wireless/realtek/rtw88/fw.c\n+++ b/drivers/net/wireless/realtek/rtw88/fw.c\n@@ -317,6 +317,15 @@ void rtw_fw_c2h_cmd_handle(struct rtw_dev *rtwdev, struct sk_buff *skb)\n \tcase C2H_CCX_TX_RPT:\n \t\trtw_tx_report_handle(rtwdev, skb, C2H_CCX_TX_RPT);\n \t\tbreak;\n+\tcase C2H_VENDOR_TX_RPT:\n+\tcase C2H_WLAN_RFON:\n+\t\t/*\n+\t\t * The RTL8723BS firmware reports management TX through these\n+\t\t * two events instead, using the same payload layout.\n+\t\t */\n+\t\tif (rtw_is_8723bs(rtwdev))\n+\t\t\trtw_tx_report_handle(rtwdev, skb, C2H_CCX_TX_RPT);\n+\t\tbreak;\n \tcase C2H_BT_INFO:\n \t\trtw_coex_bt_info_notify(rtwdev, c2h-\u003epayload, len);\n \t\tbreak;\n@@ -365,6 +374,17 @@ void rtw_fw_c2h_cmd_rx_irqsafe(struct rtw_dev *rtwdev, u32 pkt_offset,\n \t\trtw_coex_info_response(rtwdev, skb);\n \t\tbreak;\n \tcase C2H_WLAN_RFON:\n+\t\t/*\n+\t\t * On 8723BS SDIO with v41 firmware, C2H 0x32 carries a scan TX\n+\t\t * report, not a WLAN_RFON event: defer it to\n+\t\t * rtw_fw_c2h_cmd_handle().\n+\t\t */\n+\t\tif (rtw_is_8723bs(rtwdev)) {\n+\t\t\t*((u32 *)skb-\u003ecb) = pkt_offset;\n+\t\t\tskb_queue_tail(\u0026rtwdev-\u003ec2h_queue, skb);\n+\t\t\tieee80211_queue_work(rtwdev-\u003ehw, \u0026rtwdev-\u003ec2h_work);\n+\t\t\tbreak;\n+\t\t}\n \t\tcomplete(\u0026rtwdev-\u003elps_leave_check);\n \t\tdev_kfree_skb_any(skb);\n \t\tbreak;\ndiff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h\nindex 48ad9ceab6ea1..bdda78dc6462a 100644\n--- a/drivers/net/wireless/realtek/rtw88/fw.h\n+++ b/drivers/net/wireless/realtek/rtw88/fw.h\n@@ -54,6 +54,11 @@ enum rtw_c2h_cmd_id {\n \tC2H_BT_MP_INFO = 0x0b,\n \tC2H_BT_HID_INFO = 0x45,\n \tC2H_RA_RPT = 0x0c,\n+\t/*\n+\t * 8723BS SDIO vendor v41 firmware management TX report (0x32 is\n+\t * reported as C2H_WLAN_RFON, handled per-chip in the C2H dispatch).\n+\t */\n+\tC2H_VENDOR_TX_RPT = 0x12,\n \tC2H_HW_FEATURE_REPORT = 0x19,\n \tC2H_WLAN_INFO = 0x27,\n \tC2H_WLAN_RFON = 0x32,\ndiff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h\nindex c6e981ba79868..8f86f7c12de5a 100644\n--- a/drivers/net/wireless/realtek/rtw88/main.h\n+++ b/drivers/net/wireless/realtek/rtw88/main.h\n@@ -194,6 +194,7 @@ enum rtw_chip_type {\n \tRTW_CHIP_TYPE_8723D,\n \tRTW_CHIP_TYPE_8821C,\n \tRTW_CHIP_TYPE_8703B,\n+\tRTW_CHIP_TYPE_8723B,\n \tRTW_CHIP_TYPE_8821A,\n \tRTW_CHIP_TYPE_8812A,\n \tRTW_CHIP_TYPE_8814A,\n@@ -2194,6 +2195,12 @@ static inline bool rtw_chip_has_tx_stbc(struct rtw_dev *rtwdev)\n \treturn rtwdev-\u003echip-\u003etx_stbc;\n }\n \n+static inline bool rtw_is_8723bs(struct rtw_dev *rtwdev)\n+{\n+\treturn rtwdev-\u003echip-\u003eid == RTW_CHIP_TYPE_8723B \u0026\u0026\n+\t       rtwdev-\u003ehci.type == RTW_HCI_TYPE_SDIO;\n+}\n+\n static inline u8 rtw_acquire_macid(struct rtw_dev *rtwdev)\n {\n \tunsigned long mac_id;\ndiff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c\nindex 01fd299abb7fe..a9361ef4b93c0 100644\n--- a/drivers/net/wireless/realtek/rtw88/rx.c\n+++ b/drivers/net/wireless/realtek/rtw88/rx.c\n@@ -253,10 +253,12 @@ static void rtw_rx_fill_rx_status(struct rtw_dev *rtwdev,\n \n \trtw_rx_addr_match(rtwdev, pkt_stat, hdr);\n \n-\t/* Rtl8723cs driver checks for size \u003c 14 or size \u003e 8192 and\n-\t * simply drops the packet.\n+\t/*\n+\t * Rtl8723cs and rtl8723bs drivers check for size \u003c 14 or size \u003e 8192\n+\t * and simply drop the packet.\n \t */\n-\tif (rtwdev-\u003echip-\u003eid == RTW_CHIP_TYPE_8703B \u0026\u0026 pkt_stat-\u003epkt_len == 0) {\n+\tif ((rtwdev-\u003echip-\u003eid == RTW_CHIP_TYPE_8703B || rtw_is_8723bs(rtwdev)) \u0026\u0026\n+\t    pkt_stat-\u003epkt_len == 0) {\n \t\trx_status-\u003eflag |= RX_FLAG_NO_PSDU;\n \t\trtw_dbg(rtwdev, RTW_DBG_RX, \"zero length packet\");\n \t}\ndiff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c\nindex 5b40d74b16ee0..ed1bed8ea144a 100644\n--- a/drivers/net/wireless/realtek/rtw88/sdio.c\n+++ b/drivers/net/wireless/realtek/rtw88/sdio.c\n@@ -20,6 +20,17 @@\n #include \"tx.h\"\n \n #define RTW_SDIO_INDIRECT_RW_RETRIES\t\t\t50\n+#define RTW_SDIO_OQT_TIMEOUT_MS\t\t\t\t1000\n+\n+/*\n+ * 8723BS SDIO TX FIFO back-pressure watermarks: stop the mac80211 queue once\n+ * the per-AC software FIFO fills past the high watermark, and wake it from the\n+ * TX drain path once it falls back to the low one. Bounds the queueing latency\n+ * that otherwise causes uplink bufferbloat / congestion collapse.\n+ */\n+#define RTW_SDIO_TX_FIFO_HIWATER\t\t\t16\n+#define RTW_SDIO_TX_FIFO_LOWATER\t\t\t8\n+#define RTW_SDIO_TX_RETRY_DELAY\t\t\tmsecs_to_jiffies(1)\n \n static bool rtw_sdio_is_bus_addr(u32 addr)\n {\n@@ -548,12 +559,122 @@ static int rtw_sdio_read_port(struct rtw_dev *rtwdev, u8 *buf, size_t count)\n \treturn ret;\n }\n \n+/*\n+ * The cached free page counters are a fast path hint only. They are written\n+ * from the single threaded TX work and, for H2C and reserved page writes,\n+ * from process context, so they are atomic_t; whenever they claim there is\n+ * not enough room they are resynchronised from the chip before the caller\n+ * gives up, which also absorbs a lost update.\n+ */\n+static void rtw_sdio_8723bs_store_free_txpg(struct rtw_dev *rtwdev,\n+\t\t\t\t\t    u32 free_txpg)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\n+\tatomic_set(\u0026rtwsdio-\u003efree_pg_high,\n+\t\t   u32_get_bits(free_txpg, BIT_FREE_TXPG_HIGH));\n+\tatomic_set(\u0026rtwsdio-\u003efree_pg_normal,\n+\t\t   u32_get_bits(free_txpg, BIT_FREE_TXPG_NORMAL));\n+\tatomic_set(\u0026rtwsdio-\u003efree_pg_low,\n+\t\t   u32_get_bits(free_txpg, BIT_FREE_TXPG_LOW));\n+\tatomic_set(\u0026rtwsdio-\u003efree_pg_pub,\n+\t\t   u32_get_bits(free_txpg, BIT_FREE_TXPG_PUB));\n+}\n+\n+static void rtw_sdio_8723bs_init_free_txpg(struct rtw_dev *rtwdev)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\tconst struct rtw_page_table *pg_tbl;\n+\tu32 free_txpg;\n+\tu16 pubq_num;\n+\n+\tfree_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);\n+\tif (free_txpg) {\n+\t\trtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg);\n+\t} else {\n+\t\tpg_tbl = \u0026rtwdev-\u003echip-\u003epage_table[0];\n+\t\tpubq_num = rtwdev-\u003efifo.acq_pg_num - pg_tbl-\u003ehq_num -\n+\t\t\t   pg_tbl-\u003elq_num - pg_tbl-\u003enq_num - pg_tbl-\u003eexq_num -\n+\t\t\t   pg_tbl-\u003egapq_num;\n+\n+\t\tatomic_set(\u0026rtwsdio-\u003efree_pg_high, pg_tbl-\u003ehq_num);\n+\t\tatomic_set(\u0026rtwsdio-\u003efree_pg_normal, pg_tbl-\u003enq_num);\n+\t\tatomic_set(\u0026rtwsdio-\u003efree_pg_low, pg_tbl-\u003elq_num);\n+\t\tatomic_set(\u0026rtwsdio-\u003efree_pg_pub, pubq_num);\n+\t}\n+\n+\tatomic_set(\u0026rtwsdio-\u003etx_oqt_free,\n+\t\t   rtw_read8(rtwdev, REG_SDIO_OQT_FREE_PG));\n+}\n+\n+static void rtw_sdio_8723bs_sync_free_txpg(struct rtw_dev *rtwdev)\n+{\n+\tu32 free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);\n+\n+\tif (free_txpg)\n+\t\trtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg);\n+}\n+\n+/*\n+ * Sum of the queue's dedicated counter and the public pool, clamped at zero:\n+ * a lost update between the check below and rtw_sdio_8723bs_consume_txpg()\n+ * can briefly drive a counter negative, and letting that wrap would hide the\n+ * shortage instead of triggering a resync from the chip.\n+ */\n+static unsigned int rtw_sdio_8723bs_pages_free(struct rtw_dev *rtwdev,\n+\t\t\t\t\t       atomic_t *dedicated)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\tint free;\n+\n+\tfree = atomic_read(dedicated) + atomic_read(\u0026rtwsdio-\u003efree_pg_pub);\n+\n+\treturn free \u003e 0 ? free : 0;\n+}\n+\n+static atomic_t *rtw_sdio_8723bs_free_txpg(struct rtw_dev *rtwdev, u8 queue)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\n+\tswitch (queue) {\n+\tcase RTW_TX_QUEUE_VI:\n+\t\treturn \u0026rtwsdio-\u003efree_pg_normal;\n+\tcase RTW_TX_QUEUE_BE:\n+\tcase RTW_TX_QUEUE_BK:\n+\t\treturn \u0026rtwsdio-\u003efree_pg_low;\n+\tcase RTW_TX_QUEUE_BCN:\n+\tcase RTW_TX_QUEUE_H2C:\n+\tcase RTW_TX_QUEUE_HI0:\n+\tcase RTW_TX_QUEUE_MGMT:\n+\tcase RTW_TX_QUEUE_VO:\n+\t\treturn \u0026rtwsdio-\u003efree_pg_high;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,\n \t\t\t\t    size_t count)\n {\n \tunsigned int pages_free, pages_needed;\n \n-\tif (rtw_chip_wcpu_8051(rtwdev)) {\n+\tif (rtw_is_8723bs(rtwdev)) {\n+\t\tatomic_t *dedicated;\n+\n+\t\tdedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);\n+\t\tif (!dedicated) {\n+\t\t\trtw_warn(rtwdev, \"Unknown mapping for queue %u\\n\", queue);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tpages_free = rtw_sdio_8723bs_pages_free(rtwdev, dedicated);\n+\t\tpages_needed = DIV_ROUND_UP(count, rtwdev-\u003echip-\u003epage_size);\n+\t\tif (pages_needed \u003c= pages_free)\n+\t\t\treturn 0;\n+\n+\t\trtw_sdio_8723bs_sync_free_txpg(rtwdev);\n+\t\tpages_free = rtw_sdio_8723bs_pages_free(rtwdev, dedicated);\n+\t} else if (rtw_chip_wcpu_8051(rtwdev)) {\n \t\tu32 free_txpg;\n \n \t\tfree_txpg = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG);\n@@ -632,44 +753,123 @@ static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,\n \treturn 0;\n }\n \n+static int rtw_sdio_8723bs_wait_tx_oqt(struct rtw_dev *rtwdev)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\tu8 free;\n+\tint i;\n+\n+\tif (atomic_add_unless(\u0026rtwsdio-\u003etx_oqt_free, -1, 0))\n+\t\treturn 0;\n+\n+\tfor (i = 0; i \u003c RTW_SDIO_OQT_TIMEOUT_MS; i++) {\n+\t\tfree = rtw_read8(rtwdev, REG_SDIO_OQT_FREE_PG);\n+\t\tif (free) {\n+\t\t\tatomic_set(\u0026rtwsdio-\u003etx_oqt_free, free - 1);\n+\t\t\treturn 0;\n+\t\t}\n+\t\tusleep_range(1000, 2000);\n+\t}\n+\n+\treturn -EBUSY;\n+}\n+\n+static void rtw_sdio_8723bs_consume_txpg(struct rtw_dev *rtwdev, u8 queue,\n+\t\t\t\t\t unsigned int pages)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\tatomic_t *dedicated;\n+\tunsigned int taken;\n+\tint free;\n+\n+\tdedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);\n+\tif (!dedicated)\n+\t\treturn;\n+\n+\tfree = atomic_read(dedicated);\n+\ttaken = min_t(unsigned int, pages, free \u003e 0 ? free : 0);\n+\tatomic_sub(taken, dedicated);\n+\n+\tpages -= taken;\n+\tif (pages \u0026\u0026 atomic_sub_return(pages, \u0026rtwsdio-\u003efree_pg_pub) \u003c 0)\n+\t\tatomic_set(\u0026rtwsdio-\u003efree_pg_pub, 0);\n+}\n+\n static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb,\n \t\t\t       enum rtw_tx_queue_type queue)\n {\n \tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\tbool rtl8723bs = rtw_is_8723bs(rtwdev);\n+\tunsigned int orig_len = skb-\u003elen;\n+\tunsigned int pages;\n+\tsize_t write_size;\n \tbool bus_claim;\n \tsize_t txsize;\n \tu32 txaddr;\n \tint ret;\n \n-\ttxaddr = rtw_sdio_get_tx_addr(rtwdev, skb-\u003elen, queue);\n-\tif (!txaddr)\n-\t\treturn -EINVAL;\n+\tif (rtl8723bs) {\n+\t\ttxsize = round_up(orig_len, 4);\n+\t\twrite_size = txsize \u003e RTW_SDIO_BLOCK_SIZE ?\n+\t\t\t     round_up(txsize, RTW_SDIO_BLOCK_SIZE) : txsize;\n+\t} else {\n+\t\ttxsize = sdio_align_size(rtwsdio-\u003esdio_func, orig_len);\n+\t\twrite_size = txsize;\n+\t}\n+\n+\tif (write_size \u003e orig_len) {\n+\t\tunsigned int padding = write_size - orig_len;\n \n-\ttxsize = sdio_align_size(rtwsdio-\u003esdio_func, skb-\u003elen);\n+\t\tif (skb_tailroom(skb) \u003c padding) {\n+\t\t\tret = pskb_expand_head(skb, 0,\n+\t\t\t\t\t       padding - skb_tailroom(skb),\n+\t\t\t\t\t       GFP_KERNEL);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t\tskb_put_zero(skb, padding);\n+\t}\n+\n+\ttxaddr = rtw_sdio_get_tx_addr(rtwdev, txsize, queue);\n+\tif (!txaddr) {\n+\t\tret = -EINVAL;\n+\t\tgoto out_trim;\n+\t}\n \n \tret = rtw_sdio_check_free_txpg(rtwdev, queue, txsize);\n \tif (ret)\n-\t\treturn ret;\n+\t\tgoto out_trim;\n+\n+\tif (rtl8723bs) {\n+\t\tret = rtw_sdio_8723bs_wait_tx_oqt(rtwdev);\n+\t\tif (ret)\n+\t\t\tgoto out_trim;\n+\t}\n \n \tif (!IS_ALIGNED((unsigned long)skb-\u003edata, RTW_SDIO_DATA_PTR_ALIGN))\n \t\trtw_warn(rtwdev, \"Got unaligned SKB in %s() for queue %u\\n\",\n \t\t\t __func__, queue);\n \n \tbus_claim = rtw_sdio_bus_claim_needed(rtwsdio);\n-\n \tif (bus_claim)\n \t\tsdio_claim_host(rtwsdio-\u003esdio_func);\n-\n-\tret = sdio_memcpy_toio(rtwsdio-\u003esdio_func, txaddr, skb-\u003edata, txsize);\n-\n+\tret = sdio_memcpy_toio(rtwsdio-\u003esdio_func, txaddr, skb-\u003edata,\n+\t\t\t       write_size);\n \tif (bus_claim)\n \t\tsdio_release_host(rtwsdio-\u003esdio_func);\n \n-\tif (ret)\n+\tif (ret) {\n \t\trtw_warn(rtwdev,\n \t\t\t \"Failed to write %zu byte(s) to SDIO port 0x%08x\",\n-\t\t\t txsize, txaddr);\n+\t\t\t write_size, txaddr);\n+\t} else if (rtl8723bs) {\n+\t\tpages = DIV_ROUND_UP(txsize, rtwdev-\u003echip-\u003epage_size);\n+\t\trtw_sdio_8723bs_consume_txpg(rtwdev, queue, pages);\n+\t}\n \n+out_trim:\n+\tif (write_size \u003e orig_len)\n+\t\tskb_trim(skb, orig_len);\n \treturn ret;\n }\n \n@@ -677,7 +877,11 @@ static void rtw_sdio_init(struct rtw_dev *rtwdev)\n {\n \tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n \n-\trtwsdio-\u003eirq_mask = REG_SDIO_HIMR_RX_REQUEST | REG_SDIO_HIMR_CPWM1;\n+\tif (rtw_is_8723bs(rtwdev))\n+\t\trtwsdio-\u003eirq_mask = REG_SDIO_HIMR_RX_REQUEST;\n+\telse\n+\t\trtwsdio-\u003eirq_mask = REG_SDIO_HIMR_RX_REQUEST |\n+\t\t\t\t    REG_SDIO_HIMR_CPWM1;\n }\n \n static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)\n@@ -685,6 +889,7 @@ static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)\n \tu8 size, timeout;\n \n \tswitch (rtwdev-\u003echip-\u003eid) {\n+\tcase RTW_CHIP_TYPE_8723B:\n \tcase RTW_CHIP_TYPE_8703B:\n \tcase RTW_CHIP_TYPE_8821A:\n \tcase RTW_CHIP_TYPE_8812A:\n@@ -712,6 +917,8 @@ static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)\n \t\t    FIELD_PREP(BIT_DMA_AGG_TO_V1, timeout));\n \n \trtw_write8_set(rtwdev, REG_RXDMA_MODE, BIT_DMA_MODE);\n+\tif (rtw_is_8723bs(rtwdev))\n+\t\trtw_write8_mask(rtwdev, REG_RXDMA_MODE, BIT_DMA_BURST_CNT, 0x3);\n }\n \n static void rtw_sdio_enable_interrupt(struct rtw_dev *rtwdev)\n@@ -749,9 +956,50 @@ static int rtw_sdio_setup(struct rtw_dev *rtwdev)\n \treturn 0;\n }\n \n+static void rtw_sdio_8723bs_check_rqpn(struct rtw_dev *rtwdev)\n+{\n+\tconst struct rtw_chip_info *chip = rtwdev-\u003echip;\n+\tstruct rtw_fifo_conf *fifo = \u0026rtwdev-\u003efifo;\n+\tconst struct rtw_page_table *pg_tbl;\n+\tu16 reserved_num;\n+\tu32 free_txpg;\n+\tu16 pubq_num;\n+\n+\tfree_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);\n+\tif (free_txpg || !fifo-\u003eacq_pg_num)\n+\t\treturn;\n+\n+\tpg_tbl = \u0026chip-\u003epage_table[0];\n+\treserved_num = pg_tbl-\u003ehq_num + pg_tbl-\u003elq_num + pg_tbl-\u003enq_num +\n+\t\t       pg_tbl-\u003eexq_num + pg_tbl-\u003egapq_num;\n+\tif (fifo-\u003eacq_pg_num \u003c= reserved_num)\n+\t\treturn;\n+\n+\tpubq_num = fifo-\u003eacq_pg_num - reserved_num;\n+\trtw_write32(rtwdev, REG_RQPN_NPQ,\n+\t\t    BIT_RQPN_NE(pg_tbl-\u003enq_num, pg_tbl-\u003eexq_num));\n+\trtw_write32(rtwdev, REG_RQPN,\n+\t\t    BIT_RQPN_HLP(pg_tbl-\u003ehq_num, pg_tbl-\u003elq_num, pubq_num));\n+}\n+\n static int rtw_sdio_start(struct rtw_dev *rtwdev)\n {\n+\tu32 clear;\n+\n+\tif (rtw_is_8723bs(rtwdev)) {\n+\t\trtw_sdio_8723bs_check_rqpn(rtwdev);\n+\t\trtw_sdio_8723bs_init_free_txpg(rtwdev);\n+\t}\n+\n \trtw_sdio_enable_rx_aggregation(rtwdev);\n+\n+\tif (rtw_is_8723bs(rtwdev)) {\n+\t\tclear = rtw_read32(rtwdev, REG_SDIO_HISR) \u0026\n+\t\t\tRTW_SDIO_HISR_CLEAR_MASK;\n+\t\tif (clear)\n+\t\t\trtw_write32(rtwdev, REG_SDIO_HISR, clear);\n+\t}\n+\n \trtw_sdio_enable_interrupt(rtwdev);\n \n \treturn 0;\n@@ -815,7 +1063,11 @@ static void rtw_sdio_tx_kick_off(struct rtw_dev *rtwdev)\n {\n \tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n \n-\tqueue_work(rtwsdio-\u003etxwq, \u0026rtwsdio-\u003etx_handler_data-\u003ework);\n+\t/*\n+\t * A retry may already be pending with a delay; re-arm it so a newly\n+\t * queued frame is not held back by it.\n+\t */\n+\tmod_delayed_work(rtwsdio-\u003etxwq, \u0026rtwsdio-\u003etx_handler_data-\u003ework, 0);\n }\n \n static void rtw_sdio_link_ps(struct rtw_dev *rtwdev, bool enter)\n@@ -831,6 +1083,8 @@ static void rtw_sdio_interface_cfg(struct rtw_dev *rtwdev)\n \n \tval = rtw_read32(rtwdev, REG_SDIO_TX_CTRL);\n \tval \u0026= 0xfff8;\n+\tif (rtw_is_8723bs(rtwdev))\n+\t\tval |= BIT_SDIO_TX_CTRL_ALWAYS_RECOGNIZE;\n \trtw_write32(rtwdev, REG_SDIO_TX_CTRL, val);\n }\n \n@@ -937,6 +1191,19 @@ static int rtw_sdio_tx_write(struct rtw_dev *rtwdev,\n \n \tskb_queue_tail(\u0026rtwsdio-\u003etx_queue[queue], skb);\n \n+\t/*\n+\t * Back-pressure on the data ACs (BK/BE/VI/VO): once the FIFO fills past\n+\t * the high watermark, stop the corresponding mac80211 queue so it stops\n+\t * handing us frames, bounding the queueing latency. Resumed from the TX\n+\t * drain path once the FIFO drains below the low watermark.\n+\t */\n+\tif (rtw_is_8723bs(rtwdev) \u0026\u0026 queue \u003c RTW_TX_QUEUE_BCN \u0026\u0026\n+\t    !rtwsdio-\u003equeue_stopped[queue] \u0026\u0026\n+\t    skb_queue_len(\u0026rtwsdio-\u003etx_queue[queue]) \u003e= RTW_SDIO_TX_FIFO_HIWATER) {\n+\t\trtwsdio-\u003equeue_stopped[queue] = true;\n+\t\tieee80211_stop_queue(rtwdev-\u003ehw, skb_get_queue_mapping(skb));\n+\t}\n+\n \treturn 0;\n }\n \n@@ -1095,6 +1362,14 @@ static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)\n \t\trtw_sdio_rx_isr(rtwdev);\n \t}\n \n+\t/*\n+\t * RTL8723BS keeps raising the interrupt after resume if undefined\n+\t * status bits are written back, so acknowledge only the bits that are\n+\t * both defined and unmasked. Other chips keep the existing behaviour.\n+\t */\n+\tif (rtw_is_8723bs(rtwdev))\n+\t\thisr \u0026= rtwsdio-\u003eirq_mask \u0026 RTW_SDIO_HISR_CLEAR_MASK;\n+\n \trtw_write32(rtwdev, REG_SDIO_HISR, hisr);\n \n \trtwsdio-\u003eirq_thread = NULL;\n@@ -1231,43 +1506,92 @@ static void rtw_sdio_indicate_tx_status(struct rtw_dev *rtwdev,\n \tieee80211_tx_status_irqsafe(hw, skb);\n }\n \n-static void rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev,\n-\t\t\t\t      enum rtw_tx_queue_type queue)\n+/*\n+ * Send one frame from @queue. Returns 1 when a frame was written, 0 when the\n+ * queue was empty and a negative errno when the write failed, in which case\n+ * the frame is put back at the head of the queue.\n+ */\n+static int rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev,\n+\t\t\t\t     enum rtw_tx_queue_type queue)\n {\n \tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n \tstruct sk_buff *skb;\n+\tu16 q_map;\n \tint ret;\n \n \tskb = skb_dequeue(\u0026rtwsdio-\u003etx_queue[queue]);\n \tif (!skb)\n-\t\treturn;\n+\t\treturn 0;\n \n+\tq_map = skb_get_queue_mapping(skb);\n \tret = rtw_sdio_write_port(rtwdev, skb, queue);\n \tif (ret) {\n \t\tskb_queue_head(\u0026rtwsdio-\u003etx_queue[queue], skb);\n-\t\treturn;\n+\t\treturn ret;\n \t}\n \n \trtw_sdio_indicate_tx_status(rtwdev, skb);\n+\n+\tif (rtw_is_8723bs(rtwdev) \u0026\u0026 queue \u003c RTW_TX_QUEUE_BCN \u0026\u0026\n+\t    rtwsdio-\u003equeue_stopped[queue] \u0026\u0026\n+\t    skb_queue_len(\u0026rtwsdio-\u003etx_queue[queue]) \u003c=\n+\t    RTW_SDIO_TX_FIFO_LOWATER) {\n+\t\trtwsdio-\u003equeue_stopped[queue] = false;\n+\t\tieee80211_wake_queue(rtwdev-\u003ehw, q_map);\n+\t}\n+\n+\treturn 1;\n }\n \n static void rtw_sdio_tx_handler(struct work_struct *work)\n {\n \tstruct rtw_sdio_work_data *work_data =\n-\t\tcontainer_of(work, struct rtw_sdio_work_data, work);\n+\t\tcontainer_of(to_delayed_work(work), struct rtw_sdio_work_data,\n+\t\t\t     work);\n \tstruct rtw_sdio *rtwsdio;\n \tstruct rtw_dev *rtwdev;\n-\tint limit, queue;\n+\tint limit, queue, ret;\n+\tbool rtl8723bs;\n \n \trtwdev = work_data-\u003ertwdev;\n \trtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\trtl8723bs = rtw_is_8723bs(rtwdev);\n \n \tif (!rtw_fw_feature_check(\u0026rtwdev-\u003efw, FW_FEATURE_TX_WAKE))\n \t\trtw_sdio_deep_ps_leave(rtwdev);\n \n \tfor (queue = RTK_MAX_TX_QUEUE_NUM - 1; queue \u003e= 0; queue--) {\n \t\tfor (limit = 0; limit \u003c 1000; limit++) {\n-\t\t\trtw_sdio_process_tx_queue(rtwdev, queue);\n+\t\t\tret = rtw_sdio_process_tx_queue(rtwdev, queue);\n+\t\t\tif (ret == 0)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (ret \u003c 0) {\n+\t\t\t\t/*\n+\t\t\t\t * A page shortage is transient: leave the\n+\t\t\t\t * frame queued and come back shortly instead\n+\t\t\t\t * of stalling this AC until something else\n+\t\t\t\t * kicks the worker.\n+\t\t\t\t */\n+\t\t\t\tif (rtl8723bs \u0026\u0026 ret == -EBUSY) {\n+\t\t\t\t\tqueue_delayed_work(rtwsdio-\u003etxwq,\n+\t\t\t\t\t\t\t   \u0026work_data-\u003ework,\n+\t\t\t\t\t\t\t   RTW_SDIO_TX_RETRY_DELAY);\n+\t\t\t\t\treturn;\n+\t\t\t\t}\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * Restart from the highest priority queue after every\n+\t\t\t * management frame so the join sequence is not held up\n+\t\t\t * behind a data backlog.\n+\t\t\t */\n+\t\t\tif (rtl8723bs \u0026\u0026 queue == RTW_TX_QUEUE_MGMT) {\n+\t\t\t\tqueue_delayed_work(rtwsdio-\u003etxwq,\n+\t\t\t\t\t\t   \u0026work_data-\u003ework, 0);\n+\t\t\t\treturn;\n+\t\t\t}\n \n \t\t\tif (skb_queue_empty(\u0026rtwsdio-\u003etx_queue[queue]))\n \t\t\t\tbreak;\n@@ -1294,14 +1618,16 @@ static int rtw_sdio_init_tx(struct rtw_dev *rtwdev)\n \t\treturn -ENOMEM;\n \t}\n \n-\tfor (i = 0; i \u003c RTK_MAX_TX_QUEUE_NUM; i++)\n+\tfor (i = 0; i \u003c RTK_MAX_TX_QUEUE_NUM; i++) {\n \t\tskb_queue_head_init(\u0026rtwsdio-\u003etx_queue[i]);\n+\t\trtwsdio-\u003equeue_stopped[i] = false;\n+\t}\n \trtwsdio-\u003etx_handler_data = kmalloc_obj(*rtwsdio-\u003etx_handler_data);\n \tif (!rtwsdio-\u003etx_handler_data)\n \t\tgoto err_destroy_wq;\n \n \trtwsdio-\u003etx_handler_data-\u003ertwdev = rtwdev;\n-\tINIT_WORK(\u0026rtwsdio-\u003etx_handler_data-\u003ework, rtw_sdio_tx_handler);\n+\tINIT_DELAYED_WORK(\u0026rtwsdio-\u003etx_handler_data-\u003ework, rtw_sdio_tx_handler);\n \n \treturn 0;\n \n@@ -1315,6 +1641,7 @@ static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev)\n \tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n \tint i;\n \n+\tcancel_delayed_work_sync(\u0026rtwsdio-\u003etx_handler_data-\u003ework);\n \tdestroy_workqueue(rtwsdio-\u003etxwq);\n \tkfree(rtwsdio-\u003etx_handler_data);\n \ndiff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h\nindex 457e8b02380e5..2f1b67f4bbc7c 100644\n--- a/drivers/net/wireless/realtek/rtw88/sdio.h\n+++ b/drivers/net/wireless/realtek/rtw88/sdio.h\n@@ -22,6 +22,7 @@\n \n /* SDIO Tx Control */\n #define REG_SDIO_TX_CTRL\t\t\t(SDIO_LOCAL_OFFSET + 0x0000)\n+#define BIT_SDIO_TX_CTRL_ALWAYS_RECOGNIZE\tBIT(4)\n \n /*SDIO status timeout*/\n #define REG_SDIO_TIMEOUT\t\t\t(SDIO_LOCAL_OFFSET + 0x0002)\n@@ -77,6 +78,14 @@\n /* the following two are RTL8188 SDIO Specific */\n #define REG_SDIO_HISR_MCU_ERR\t\t\tBIT(28)\n #define REG_SDIO_HISR_TSF_BIT32_TOGGLE\t\tBIT(29)\n+#define RTW_SDIO_HISR_CLEAR_MASK\t\t\\\n+\t(REG_SDIO_HISR_TXERR | REG_SDIO_HISR_RXERR | \\\n+\t REG_SDIO_HISR_TXFOVW | REG_SDIO_HISR_RXFOVW | \\\n+\t REG_SDIO_HISR_TXBCNOK | REG_SDIO_HISR_TXBCNERR | \\\n+\t REG_SDIO_HISR_C2HCMD | REG_SDIO_HISR_CPWM1 | \\\n+\t REG_SDIO_HISR_CPWM2 | REG_SDIO_HISR_HSISR_IND | \\\n+\t REG_SDIO_HISR_GTINT3_IND | REG_SDIO_HISR_GTINT4_IND | \\\n+\t REG_SDIO_HISR_PSTIMEOUT | REG_SDIO_HISR_OCPINT)\n \n /* HCI Current Power Mode */\n #define REG_SDIO_HCPWM\t\t\t\t(SDIO_LOCAL_OFFSET + 0x0019)\n@@ -86,6 +95,10 @@\n #define REG_SDIO_OQT_FREE_PG\t\t\t(SDIO_LOCAL_OFFSET + 0x001E)\n /* Free Tx Buffer Page */\n #define REG_SDIO_FREE_TXPG\t\t\t(SDIO_LOCAL_OFFSET + 0x0020)\n+#define BIT_FREE_TXPG_HIGH\t\t\tGENMASK(7, 0)\n+#define BIT_FREE_TXPG_NORMAL\t\t\tGENMASK(15, 8)\n+#define BIT_FREE_TXPG_LOW\t\t\tGENMASK(23, 16)\n+#define BIT_FREE_TXPG_PUB\t\t\tGENMASK(31, 24)\n /* HCI Current Power Mode 1 */\n #define REG_SDIO_HCPWM1\t\t\t\t(SDIO_LOCAL_OFFSET + 0x0024)\n /* HCI Current Power Mode 2 */\n@@ -143,7 +156,7 @@ struct rtw_sdio_tx_data {\n };\n \n struct rtw_sdio_work_data {\n-\tstruct work_struct work;\n+\tstruct delayed_work work;\n \tstruct rtw_dev *rtwdev;\n };\n \n@@ -159,6 +172,13 @@ struct rtw_sdio {\n \tstruct workqueue_struct *txwq;\n \tstruct rtw_sdio_work_data *tx_handler_data;\n \tstruct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM];\n+\tbool queue_stopped[RTK_MAX_TX_QUEUE_NUM];\n+\n+\tatomic_t free_pg_high;\n+\tatomic_t free_pg_normal;\n+\tatomic_t free_pg_low;\n+\tatomic_t free_pg_pub;\n+\tatomic_t tx_oqt_free;\n };\n \n extern const struct dev_pm_ops rtw_sdio_pm_ops;\ndiff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c\nindex 8786bbb421c2b..4e110a457e71d 100644\n--- a/drivers/net/wireless/realtek/rtw88/tx.c\n+++ b/drivers/net/wireless/realtek/rtw88/tx.c\n@@ -208,8 +208,9 @@ void rtw_tx_report_enqueue(struct rtw_dev *rtwdev, struct sk_buff *skb, u8 sn)\n \t__skb_queue_tail(\u0026tx_report-\u003equeue, skb);\n \tspin_unlock_irqrestore(\u0026tx_report-\u003eq_lock, flags);\n \n-\tif (rtwdev-\u003echip-\u003eid == RTW_CHIP_TYPE_8723D \u0026\u0026\n-\t    rtwdev-\u003ehci.type == RTW_HCI_TYPE_USB)\n+\tif ((rtwdev-\u003echip-\u003eid == RTW_CHIP_TYPE_8723D \u0026\u0026\n+\t     rtwdev-\u003ehci.type == RTW_HCI_TYPE_USB) ||\n+\t    rtw_is_8723bs(rtwdev))\n \t\ttimeout = msecs_to_jiffies(2500);\n \n \tmod_timer(\u0026tx_report-\u003epurge_timer, jiffies + timeout);\n"
}

2/1 2026/08/11 09:51 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch exclusively adds support and fixes for the Realtek RTL8723BS SDIO WiFi chip. SDIO devices require physical hardware or specific QEMU machine emulation which is not available in standard virtualized fuzzing environments. There is no software SDIO emulation framework (like dummy_hcd for USB) to reach these code paths during fuzzing.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit a41825617844ea5391f05ce448f869395b0e0ca8
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 09:51:49 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c
index 945fedcd375b6..c85c0e3e60a97 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.c
+++ b/drivers/net/wireless/realtek/rtw88/fw.c
@@ -317,6 +317,15 @@ void rtw_fw_c2h_cmd_handle(struct rtw_dev *rtwdev, struct sk_buff *skb)
 	case C2H_CCX_TX_RPT:
 		rtw_tx_report_handle(rtwdev, skb, C2H_CCX_TX_RPT);
 		break;
+	case C2H_VENDOR_TX_RPT:
+	case C2H_WLAN_RFON:
+		/*
+		 * The RTL8723BS firmware reports management TX through these
+		 * two events instead, using the same payload layout.
+		 */
+		if (rtw_is_8723bs(rtwdev))
+			rtw_tx_report_handle(rtwdev, skb, C2H_CCX_TX_RPT);
+		break;
 	case C2H_BT_INFO:
 		rtw_coex_bt_info_notify(rtwdev, c2h->payload, len);
 		break;
@@ -365,6 +374,17 @@ void rtw_fw_c2h_cmd_rx_irqsafe(struct rtw_dev *rtwdev, u32 pkt_offset,
 		rtw_coex_info_response(rtwdev, skb);
 		break;
 	case C2H_WLAN_RFON:
+		/*
+		 * On 8723BS SDIO with v41 firmware, C2H 0x32 carries a scan TX
+		 * report, not a WLAN_RFON event: defer it to
+		 * rtw_fw_c2h_cmd_handle().
+		 */
+		if (rtw_is_8723bs(rtwdev)) {
+			*((u32 *)skb->cb) = pkt_offset;
+			skb_queue_tail(&rtwdev->c2h_queue, skb);
+			ieee80211_queue_work(rtwdev->hw, &rtwdev->c2h_work);
+			break;
+		}
 		complete(&rtwdev->lps_leave_check);
 		dev_kfree_skb_any(skb);
 		break;
diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h
index 48ad9ceab6ea1..bdda78dc6462a 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.h
+++ b/drivers/net/wireless/realtek/rtw88/fw.h
@@ -54,6 +54,11 @@ enum rtw_c2h_cmd_id {
 	C2H_BT_MP_INFO = 0x0b,
 	C2H_BT_HID_INFO = 0x45,
 	C2H_RA_RPT = 0x0c,
+	/*
+	 * 8723BS SDIO vendor v41 firmware management TX report (0x32 is
+	 * reported as C2H_WLAN_RFON, handled per-chip in the C2H dispatch).
+	 */
+	C2H_VENDOR_TX_RPT = 0x12,
 	C2H_HW_FEATURE_REPORT = 0x19,
 	C2H_WLAN_INFO = 0x27,
 	C2H_WLAN_RFON = 0x32,
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index c6e981ba79868..8f86f7c12de5a 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -194,6 +194,7 @@ enum rtw_chip_type {
 	RTW_CHIP_TYPE_8723D,
 	RTW_CHIP_TYPE_8821C,
 	RTW_CHIP_TYPE_8703B,
+	RTW_CHIP_TYPE_8723B,
 	RTW_CHIP_TYPE_8821A,
 	RTW_CHIP_TYPE_8812A,
 	RTW_CHIP_TYPE_8814A,
@@ -2194,6 +2195,12 @@ static inline bool rtw_chip_has_tx_stbc(struct rtw_dev *rtwdev)
 	return rtwdev->chip->tx_stbc;
 }
 
+static inline bool rtw_is_8723bs(struct rtw_dev *rtwdev)
+{
+	return rtwdev->chip->id == RTW_CHIP_TYPE_8723B &&
+	       rtwdev->hci.type == RTW_HCI_TYPE_SDIO;
+}
+
 static inline u8 rtw_acquire_macid(struct rtw_dev *rtwdev)
 {
 	unsigned long mac_id;
diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c
index 01fd299abb7fe..a9361ef4b93c0 100644
--- a/drivers/net/wireless/realtek/rtw88/rx.c
+++ b/drivers/net/wireless/realtek/rtw88/rx.c
@@ -253,10 +253,12 @@ static void rtw_rx_fill_rx_status(struct rtw_dev *rtwdev,
 
 	rtw_rx_addr_match(rtwdev, pkt_stat, hdr);
 
-	/* Rtl8723cs driver checks for size < 14 or size > 8192 and
-	 * simply drops the packet.
+	/*
+	 * Rtl8723cs and rtl8723bs drivers check for size < 14 or size > 8192
+	 * and simply drop the packet.
 	 */
-	if (rtwdev->chip->id == RTW_CHIP_TYPE_8703B && pkt_stat->pkt_len == 0) {
+	if ((rtwdev->chip->id == RTW_CHIP_TYPE_8703B || rtw_is_8723bs(rtwdev)) &&
+	    pkt_stat->pkt_len == 0) {
 		rx_status->flag |= RX_FLAG_NO_PSDU;
 		rtw_dbg(rtwdev, RTW_DBG_RX, "zero length packet");
 	}
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 5b40d74b16ee0..ed1bed8ea144a 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -20,6 +20,17 @@
 #include "tx.h"
 
 #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)
 
 static bool rtw_sdio_is_bus_addr(u32 addr)
 {
@@ -548,12 +559,122 @@ static int rtw_sdio_read_port(struct rtw_dev *rtwdev, u8 *buf, size_t count)
 	return ret;
 }
 
+/*
+ * The cached free page counters are a fast path hint only. They are written
+ * from the single threaded TX work and, for H2C and reserved page writes,
+ * from process context, so they are atomic_t; whenever they claim there is
+ * not enough room they are resynchronised from the chip before the caller
+ * gives up, which also absorbs a lost update.
+ */
+static void rtw_sdio_8723bs_store_free_txpg(struct rtw_dev *rtwdev,
+					    u32 free_txpg)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+	atomic_set(&rtwsdio->free_pg_high,
+		   u32_get_bits(free_txpg, BIT_FREE_TXPG_HIGH));
+	atomic_set(&rtwsdio->free_pg_normal,
+		   u32_get_bits(free_txpg, BIT_FREE_TXPG_NORMAL));
+	atomic_set(&rtwsdio->free_pg_low,
+		   u32_get_bits(free_txpg, BIT_FREE_TXPG_LOW));
+	atomic_set(&rtwsdio->free_pg_pub,
+		   u32_get_bits(free_txpg, BIT_FREE_TXPG_PUB));
+}
+
+static void rtw_sdio_8723bs_init_free_txpg(struct rtw_dev *rtwdev)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	const struct rtw_page_table *pg_tbl;
+	u32 free_txpg;
+	u16 pubq_num;
+
+	free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+	if (free_txpg) {
+		rtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg);
+	} else {
+		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;
+
+		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_8723bs_sync_free_txpg(struct rtw_dev *rtwdev)
+{
+	u32 free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+
+	if (free_txpg)
+		rtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg);
+}
+
+/*
+ * Sum of the queue's dedicated counter and the public pool, clamped at zero:
+ * a lost update between the check below and rtw_sdio_8723bs_consume_txpg()
+ * can briefly drive a counter negative, and letting that wrap would hide the
+ * shortage instead of triggering a resync from the chip.
+ */
+static unsigned int rtw_sdio_8723bs_pages_free(struct rtw_dev *rtwdev,
+					       atomic_t *dedicated)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	int free;
+
+	free = atomic_read(dedicated) + atomic_read(&rtwsdio->free_pg_pub);
+
+	return free > 0 ? free : 0;
+}
+
+static atomic_t *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_VI:
+		return &rtwsdio->free_pg_normal;
+	case RTW_TX_QUEUE_BE:
+	case RTW_TX_QUEUE_BK:
+		return &rtwsdio->free_pg_low;
+	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 &rtwsdio->free_pg_high;
+	default:
+		return NULL;
+	}
+}
+
 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)) {
+		atomic_t *dedicated;
+
+		dedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);
+		if (!dedicated) {
+			rtw_warn(rtwdev, "Unknown mapping for queue %u\n", queue);
+			return -EINVAL;
+		}
+
+		pages_free = rtw_sdio_8723bs_pages_free(rtwdev, dedicated);
+		pages_needed = DIV_ROUND_UP(count, rtwdev->chip->page_size);
+		if (pages_needed <= pages_free)
+			return 0;
+
+		rtw_sdio_8723bs_sync_free_txpg(rtwdev);
+		pages_free = rtw_sdio_8723bs_pages_free(rtwdev, dedicated);
+	} else if (rtw_chip_wcpu_8051(rtwdev)) {
 		u32 free_txpg;
 
 		free_txpg = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG);
@@ -632,44 +753,123 @@ static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,
 	return 0;
 }
 
+static int rtw_sdio_8723bs_wait_tx_oqt(struct rtw_dev *rtwdev)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	u8 free;
+	int i;
+
+	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;
+	unsigned int taken;
+	int free;
+
+	dedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);
+	if (!dedicated)
+		return;
+
+	free = atomic_read(dedicated);
+	taken = min_t(unsigned int, pages, free > 0 ? free : 0);
+	atomic_sub(taken, dedicated);
+
+	pages -= taken;
+	if (pages && atomic_sub_return(pages, &rtwsdio->free_pg_pub) < 0)
+		atomic_set(&rtwsdio->free_pg_pub, 0);
+}
+
 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;
+	bool rtl8723bs = rtw_is_8723bs(rtwdev);
+	unsigned int orig_len = skb->len;
+	unsigned int pages;
+	size_t write_size;
 	bool bus_claim;
 	size_t txsize;
 	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;
 
-	txsize = sdio_align_size(rtwsdio->sdio_func, skb->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);
+	}
+
+	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;
+
+	if (rtl8723bs) {
+		ret = rtw_sdio_8723bs_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)
+	if (ret) {
 		rtw_warn(rtwdev,
 			 "Failed to write %zu byte(s) to SDIO port 0x%08x",
-			 txsize, txaddr);
+			 write_size, txaddr);
+	} else if (rtl8723bs) {
+		pages = DIV_ROUND_UP(txsize, rtwdev->chip->page_size);
+		rtw_sdio_8723bs_consume_txpg(rtwdev, queue, pages);
+	}
 
+out_trim:
+	if (write_size > orig_len)
+		skb_trim(skb, orig_len);
 	return ret;
 }
 
@@ -677,7 +877,11 @@ static void rtw_sdio_init(struct rtw_dev *rtwdev)
 {
 	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
 
-	rtwsdio->irq_mask = REG_SDIO_HIMR_RX_REQUEST | REG_SDIO_HIMR_CPWM1;
+	if (rtw_is_8723bs(rtwdev))
+		rtwsdio->irq_mask = REG_SDIO_HIMR_RX_REQUEST;
+	else
+		rtwsdio->irq_mask = REG_SDIO_HIMR_RX_REQUEST |
+				    REG_SDIO_HIMR_CPWM1;
 }
 
 static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)
@@ -685,6 +889,7 @@ static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)
 	u8 size, timeout;
 
 	switch (rtwdev->chip->id) {
+	case RTW_CHIP_TYPE_8723B:
 	case RTW_CHIP_TYPE_8703B:
 	case RTW_CHIP_TYPE_8821A:
 	case RTW_CHIP_TYPE_8812A:
@@ -712,6 +917,8 @@ static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)
 		    FIELD_PREP(BIT_DMA_AGG_TO_V1, timeout));
 
 	rtw_write8_set(rtwdev, REG_RXDMA_MODE, BIT_DMA_MODE);
+	if (rtw_is_8723bs(rtwdev))
+		rtw_write8_mask(rtwdev, REG_RXDMA_MODE, BIT_DMA_BURST_CNT, 0x3);
 }
 
 static void rtw_sdio_enable_interrupt(struct rtw_dev *rtwdev)
@@ -749,9 +956,50 @@ 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;
+	u16 reserved_num;
+	u32 free_txpg;
+	u16 pubq_num;
+
+	free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+	if (free_txpg || !fifo->acq_pg_num)
+		return;
+
+	pg_tbl = &chip->page_table[0];
+	reserved_num = pg_tbl->hq_num + pg_tbl->lq_num + pg_tbl->nq_num +
+		       pg_tbl->exq_num + pg_tbl->gapq_num;
+	if (fifo->acq_pg_num <= reserved_num)
+		return;
+
+	pubq_num = fifo->acq_pg_num - reserved_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)
 {
+	u32 clear;
+
+	if (rtw_is_8723bs(rtwdev)) {
+		rtw_sdio_8723bs_check_rqpn(rtwdev);
+		rtw_sdio_8723bs_init_free_txpg(rtwdev);
+	}
+
 	rtw_sdio_enable_rx_aggregation(rtwdev);
+
+	if (rtw_is_8723bs(rtwdev)) {
+		clear = rtw_read32(rtwdev, REG_SDIO_HISR) &
+			RTW_SDIO_HISR_CLEAR_MASK;
+		if (clear)
+			rtw_write32(rtwdev, REG_SDIO_HISR, clear);
+	}
+
 	rtw_sdio_enable_interrupt(rtwdev);
 
 	return 0;
@@ -815,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)
@@ -831,6 +1083,8 @@ static void rtw_sdio_interface_cfg(struct rtw_dev *rtwdev)
 
 	val = rtw_read32(rtwdev, REG_SDIO_TX_CTRL);
 	val &= 0xfff8;
+	if (rtw_is_8723bs(rtwdev))
+		val |= BIT_SDIO_TX_CTRL_ALWAYS_RECOGNIZE;
 	rtw_write32(rtwdev, REG_SDIO_TX_CTRL, val);
 }
 
@@ -937,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));
+	}
+
 	return 0;
 }
 
@@ -1095,6 +1362,14 @@ static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)
 		rtw_sdio_rx_isr(rtwdev);
 	}
 
+	/*
+	 * RTL8723BS keeps raising the interrupt after resume if undefined
+	 * status bits are written back, so acknowledge only the bits that are
+	 * both defined and unmasked. Other chips keep the existing behaviour.
+	 */
+	if (rtw_is_8723bs(rtwdev))
+		hisr &= rtwsdio->irq_mask & RTW_SDIO_HISR_CLEAR_MASK;
+
 	rtw_write32(rtwdev, REG_SDIO_HISR, hisr);
 
 	rtwsdio->irq_thread = NULL;
@@ -1231,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 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);
+	}
+
+	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;
+			}
 
 			if (skb_queue_empty(&rtwsdio->tx_queue[queue]))
 				break;
@@ -1294,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;
 
@@ -1315,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 457e8b02380e5..2f1b67f4bbc7c 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.h
+++ b/drivers/net/wireless/realtek/rtw88/sdio.h
@@ -22,6 +22,7 @@
 
 /* SDIO Tx Control */
 #define REG_SDIO_TX_CTRL			(SDIO_LOCAL_OFFSET + 0x0000)
+#define BIT_SDIO_TX_CTRL_ALWAYS_RECOGNIZE	BIT(4)
 
 /*SDIO status timeout*/
 #define REG_SDIO_TIMEOUT			(SDIO_LOCAL_OFFSET + 0x0002)
@@ -77,6 +78,14 @@
 /* the following two are RTL8188 SDIO Specific */
 #define REG_SDIO_HISR_MCU_ERR			BIT(28)
 #define REG_SDIO_HISR_TSF_BIT32_TOGGLE		BIT(29)
+#define RTW_SDIO_HISR_CLEAR_MASK		\
+	(REG_SDIO_HISR_TXERR | REG_SDIO_HISR_RXERR | \
+	 REG_SDIO_HISR_TXFOVW | REG_SDIO_HISR_RXFOVW | \
+	 REG_SDIO_HISR_TXBCNOK | REG_SDIO_HISR_TXBCNERR | \
+	 REG_SDIO_HISR_C2HCMD | REG_SDIO_HISR_CPWM1 | \
+	 REG_SDIO_HISR_CPWM2 | REG_SDIO_HISR_HSISR_IND | \
+	 REG_SDIO_HISR_GTINT3_IND | REG_SDIO_HISR_GTINT4_IND | \
+	 REG_SDIO_HISR_PSTIMEOUT | REG_SDIO_HISR_OCPINT)
 
 /* HCI Current Power Mode */
 #define REG_SDIO_HCPWM				(SDIO_LOCAL_OFFSET + 0x0019)
@@ -86,6 +95,10 @@
 #define REG_SDIO_OQT_FREE_PG			(SDIO_LOCAL_OFFSET + 0x001E)
 /* Free Tx Buffer Page */
 #define REG_SDIO_FREE_TXPG			(SDIO_LOCAL_OFFSET + 0x0020)
+#define BIT_FREE_TXPG_HIGH			GENMASK(7, 0)
+#define BIT_FREE_TXPG_NORMAL			GENMASK(15, 8)
+#define BIT_FREE_TXPG_LOW			GENMASK(23, 16)
+#define BIT_FREE_TXPG_PUB			GENMASK(31, 24)
 /* HCI Current Power Mode 1 */
 #define REG_SDIO_HCPWM1				(SDIO_LOCAL_OFFSET + 0x0024)
 /* HCI Current Power Mode 2 */
@@ -143,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;
 };
 
@@ -159,6 +172,13 @@ 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];
+
+	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;
 };
 
 extern const struct dev_pm_ops rtw_sdio_pm_ops;
diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c
index 8786bbb421c2b..4e110a457e71d 100644
--- a/drivers/net/wireless/realtek/rtw88/tx.c
+++ b/drivers/net/wireless/realtek/rtw88/tx.c
@@ -208,8 +208,9 @@ void rtw_tx_report_enqueue(struct rtw_dev *rtwdev, struct sk_buff *skb, u8 sn)
 	__skb_queue_tail(&tx_report->queue, skb);
 	spin_unlock_irqrestore(&tx_report->q_lock, flags);
 
-	if (rtwdev->chip->id == RTW_CHIP_TYPE_8723D &&
-	    rtwdev->hci.type == RTW_HCI_TYPE_USB)
+	if ((rtwdev->chip->id == RTW_CHIP_TYPE_8723D &&
+	     rtwdev->hci.type == RTW_HCI_TYPE_USB) ||
+	    rtw_is_8723bs(rtwdev))
 		timeout = msecs_to_jiffies(2500);
 
 	mod_timer(&tx_report->purge_timer, jiffies + timeout);

3/2 2026/08/11 09:51 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12617
							output: 18
							thoughts: 456
						

Thoughts:
**SDIO Driver Patch Evaluation**

Okay, so I've got this patch series for the `rtw88` driver, specifically adding support for RTL8723BS SDIO. This hits quite a few files: `fw.c`, `fw.h`, `main.h`, `rx.c`, `sdio.c`, `sdio.h`, and `tx.c`. That's a wireless driver for Realtek 802.11ac/802.11n devices, so it's a critical part of the wireless stack, and the changes are focused on the SDIO interface and firmware handling, particularly around `rtw_is_8723bs(rtwdev)`. My immediate concern is, can we fuzz this? Is this even reachable within a virtualized environment?

SDIO is a tricky one. Standard QEMU doesn't typically emulate SDIO devices. Are there any software emulations for SDIO or MMC that we could leverage? I need to think this through carefully. Wait, mac80211_hwsim emulates mac80211, but this is a specific hardware driver, so does it matter that mac80211 is emulated if this is the hardware driver itself (rtw88)? Can *rtw88* be fuzzed?

Generally, I know hardware-specific drivers, especially those interacting with SDIO, USB, or PCIe, usually require actual hardware unless there's an emulator. USB drivers, we can sometimes get away with using `dummy_hcd` or `raw-gadget`. PCIe drivers are a nightmare in this case, fuzzing them is tough unless there's a software emulation. But what about SDIO? Is there a dummy SDIO host controller out there somewhere? I'll need to check if there is any SDIO fuzzing support, or if this even is reachable.

I need to do a quick search for SDIO emulation or, specifically, whether `rtw88` is fuzzed at all.

Wait a second. The prompt says, "Return WorthFuzzing=false if the patch only contains: ... Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers ... that require physical PCIe hardware cards not emulated in standard QEMU." OK, so SDIO is a hardware bus. I need to make sure I know if this is fuzable! Let's check if there's a way to fuzz SDIO!



4/2 2026/08/11 09:51 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "dummy_sdio"
}

Results:
{
  "error": "no matches"
}

5/2 2026/08/11 09:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13638
							output: 19
							thoughts: 14
						

Thoughts:
Okay, here's my thought process summarized as requested:

**Assessing the Landscape for SDIO Host Emulation**

Alright, let's get down to brass tacks. I need to figure out if there are any readily available SDIO host emulators out there. Before I start building something from scratch, it makes sense to see what's already been created. This is a common starting point for any project, isn't it? Saves time, energy, and resources to see if someone else has already tackled a similar challenge. I'll need to know what's out there so I can determine if any existing solution meets the requirements, or, if not, what aspects might be applicable, or at least serve as a useful starting point for my own work. My experience in this field helps me appreciate that good design requires a solid foundation in prior art.



6/2 2026/08/11 09:52 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "sdio.*emul"
}

Results:
{
  "error": "no matches"
}

7/2 2026/08/11 09:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13864
							output: 19
							thoughts: 184
						

Thoughts:
**Investigating the RTW88 Driver for Fuzzing Potential**

Okay, so I'm looking at this patch and the first thing I need to confirm is whether `rtw88` is *also* a USB driver. The code I'm examining references `rtw_is_8723bs(rtwdev)`, and that macro seems to specifically target the SDIO interface of the RTL8723BS chipset. The definition clearly checks for both the chip ID (`RTW_CHIP_TYPE_8723B`) and the HCI type being SDIO (`RTW_HCI_TYPE_SDIO`).

Now, that's interesting because I know that `rtw88` actually supports USB, PCI, *and* SDIO. So the question becomes: Can we fuzz the SDIO interface specifically within this driver? My experience tells me that SDIO interfaces aren't usually ideal candidates for fuzzing in the kernel. The major hurdle is the lack of a virtual SDIO host controller, a counterpart to something like `dummy_hcd` for USB or `vhci` for HCI. Without this, constructing and injecting SDIO traffic in a controlled environment is challenging. So I'll need to check if there is some kind of MMC/SDIO emulation that could be leveraged.



8/2 2026/08/11 09:52 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "mmc.*emul"
}

Results:
{
  "error": "no matches"
}

9/2 2026/08/11 09:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14360
							output: 19
							thoughts: 318
						

Thoughts:
**My Assessment of SDIO Emulation Feasibility**

Okay, so QEMU has SD card emulation, specifically the sdhci interface. My initial question is whether that extends to emulating *arbitrary* SDIO devices. The short answer, I believe, is no. QEMU handles SD cards, but it doesn't generally provide generic SDIO device emulation unless a specific implementation is built in.

Since I haven't come across QEMU specifically emulating this particular Realtek SDIO WiFi card, I'm assuming the relevant code paths, specifically the ones in `rtw88/sdio.c` that are tied to `RTW_HCI_TYPE_SDIO`, are effectively unreachable when running virtualized environments like GCE or, of course, QEMU.

I need to confirm this. I need to make *absolutely* sure there's no way to trigger those functions. I see the patch modifies several hardware-specific driver functions: `rtw_fw_c2h_cmd_handle`, `rtw_fw_c2h_cmd_rx_irqsafe`, `rtw_rx_fill_rx_status`, `rtw_sdio_read_port`, and `rtw_sdio_write_port`, among others. These are *all* low-level hardware interaction points. This reinforces my initial assessment.

The bigger question is if syzkaller can reach these, as I have been reviewing syzkaller reports. I know syzkaller can fuzz USB devices using the raw-gadget or dummy_hcd interfaces. It can also fuzz mac80211 devices using mac80211_hwsim and virtio devices. But, again, my working assumption is that it *can't* fuzz SDIO devices. The kernel lacks a general SDIO fuzzing interface, unlike USB or the others.

I'm going to double check this and confirm whether or not there's any syzkaller support for SDIO. I have a hunch, and it's time to verify it.



10/2 2026/08/11 09:52 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "syz_sdio"
}

Results:
{
  "error": "no matches"
}

11/2 2026/08/11 09:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15153
							output: 19
							thoughts: 13
						

Thoughts:
Here's my thought process, summarized as you requested:

**Assessing the Landscape for MMC Fuzzing Opportunities**

Okay, first things first, let's take a quick mental inventory. I need to figure out if there's any active MMC (MultiMediaCard) fuzzing going on right now. It's a key area to explore, given the potential vulnerabilities and widespread use of these devices. I'll need to consider what tools and approaches are currently being employed, and if there are any obvious gaps or under-explored areas ripe for investigation. Time to start the deep dive.



12/2 2026/08/11 09:52 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "syz_mmc"
}

Results:
{
  "error": "no matches"
}

13/2 2026/08/11 09:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15323
							output: 104
							thoughts: 742
						

Thoughts:
**Unreachable Code in a Fuzzing Context**

Okay, so the situation is clear. Syzkaller can't fuzz SDIO devices, period. That means any changes touching `drivers/net/wireless/realtek/rtw88/sdio.c`, or any SDIO-specific logic within `fw.c`, `rx.c`, or even `tx.c` for that matter, are essentially inaccessible in a virtualized environment. The prompt specifically calls out code paths that are impossible to reach in environments like GCE or QEMU, even when using software-emulated hardware - no USB gadget or mac80211_hwsim here. This clearly falls under that category.

Let me just quickly review the changes to be absolutely certain. The key function is `rtw_is_8723bs`, which becomes true when the chip is an 8723B and the HCI type is SDIO.

*   In `fw.c`, a couple of cases (C2H\_VENDOR\_TX\_RPT and C2H\_WLAN\_RFON) now trigger a different handler, `rtw_tx_report_handle`, *only* if `rtw_is_8723bs(rtwdev)` is true. So it's very specific to 8723BS and SDIO.
*   `rx.c` has a condition that gets extended from RTW\_CHIP\_TYPE\_8703B (which was already there) to include `rtw_is_8723bs(rtwdev)`. The added condition is specifically SDIO, for this chip.
*   `sdio.c` has a lot of direct SDIO-specific changes, which is a big red flag. Obvious, but still worth mentioning.
*   In `tx.c`, an `OR` condition has been added, allowing for the 8723BS.

Basically, all functional changes boil down to `rtw_is_8723bs(rtwdev)`, which needs `RTW_HCI_TYPE_SDIO`. Since QEMU, and therefore syzkaller, doesn't emulate SDIO hardware, nor is there a software SDIO framework of that sort, this code is completely unreachable in a fuzzing context.

Therefore, the only reasonable conclusion is that I need to return `WorthFuzzing=false`. The rationale is straightforward: this patch *only* modifies code specific to an SDIO WiFi chip (the RTL8723BS), which requires physical hardware and is simply not accessible or emulatable in virtualized environments like QEMU and thus impossible to reach with syzkaller.



14/2 2026/08/11 09:52 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch exclusively adds support and fixes for the Realtek RTL8723BS SDIO WiFi chip. SDIO devices require physical hardware or specific QEMU machine emulation which is not available in standard virtualized fuzzing environments. There is no software SDIO emulation framework (like dummy_hcd for USB) to reach these code paths during fuzzing.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch exclusively adds support and fixes for the Realtek RTL8723BS SDIO WiFi chip. SDIO devices require physical hardware or specific QEMU machine emulation which is not available in standard virtualized fuzzing environments. There is no software SDIO emulation framework (like dummy_hcd for USB) to reach these code paths during fuzzing.",
  "WorthFuzzing": false
}

15/1 2026/08/11 09:52 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)