| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/19 07:58 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the Realtek rtw88 Wi-Fi driver, specifically adding support and fixing logic for the RTL8723BS SDIO module. This is a vendor-specific hardware driver that requires a physical SDIO Wi-Fi chip to be present. Since SDIO hardware and this specific Realtek chip are not emulated in standard QEMU or GCE environments, the modified code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/19 07:58 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit db6dab14026bee907f788aa5e5ad88c6d62b5fed\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Aug 19 07:58:40 2026 +0000\n\n syz-cluster: applied patch under review\n\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..fde21c840ac02 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 (pkt_stat-\u003epkt_len == 0 \u0026\u0026\n+\t (rtwdev-\u003echip-\u003eid == RTW_CHIP_TYPE_8703B || rtw_is_8723bs(rtwdev))) {\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..e5bf9cba071ea 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,157 @@ 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+/*\n+ * Refresh the cached counters from the chip. Returns false when the chip\n+ * reports no free pages at all, which means the counts cannot be trusted\n+ * and the caller has to decide what to do instead.\n+ */\n+static bool 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\treturn false;\n+\n+\trtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg);\n+\n+\treturn true;\n+}\n+\n+/*\n+ * Size of the public page pool: whatever the transmit page allocation has\n+ * left once the per queue pools are taken out. Fails if the allocation\n+ * cannot cover the reserved queues, since the remainder would underflow and\n+ * there would be no sensible pool to hand out.\n+ */\n+static int rtw_sdio_8723bs_pubq_num(struct rtw_dev *rtwdev, u16 *pubq_num)\n+{\n+\tconst struct rtw_page_table *pg_tbl = \u0026rtwdev-\u003echip-\u003epage_table[0];\n+\tu16 acq_pg_num = rtwdev-\u003efifo.acq_pg_num;\n+\tu16 reserved_num;\n+\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 (acq_pg_num \u003c= reserved_num) {\n+\t\trtw_err(rtwdev,\n+\t\t\t\"no transmit pages left for the public queue: %u of %u reserved\\n\",\n+\t\t\treserved_num, acq_pg_num);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t*pubq_num = acq_pg_num - reserved_num;\n+\n+\treturn 0;\n+}\n+\n+static int 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+\tu16 pubq_num;\n+\tint ret;\n+\n+\t/* Seed from the page table when the chip has nothing to report yet. */\n+\tif (!rtw_sdio_8723bs_sync_free_txpg(rtwdev)) {\n+\t\tret = rtw_sdio_8723bs_pubq_num(rtwdev, \u0026pubq_num);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tpg_tbl = \u0026rtwdev-\u003echip-\u003epage_table[0];\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+\treturn 0;\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 +788,124 @@ 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+\tunsigned long deadline;\n+\tu8 free;\n+\n+\tif (atomic_add_unless(\u0026rtwsdio-\u003etx_oqt_free, -1, 0))\n+\t\treturn 0;\n+\n+\tdeadline = jiffies + msecs_to_jiffies(RTW_SDIO_OQT_TIMEOUT_MS);\n+\tdo {\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} while (time_before(jiffies, deadline));\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 +913,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 +925,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 +953,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 +992,61 @@ static int rtw_sdio_setup(struct rtw_dev *rtwdev)\n \treturn 0;\n }\n \n+/*\n+ * Reprogram the queue page allocation if the chip came up with none. This is\n+ * a repair path, not part of the normal start sequence: a non-zero free page\n+ * count means the allocation latched during power on and must be left alone,\n+ * and without a transmit page pool there is nothing to divide up either.\n+ */\n+static int 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+\tu32 free_txpg;\n+\tu16 pubq_num;\n+\tint ret;\n+\n+\tfree_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);\n+\tif (free_txpg || !fifo-\u003eacq_pg_num)\n+\t\treturn 0;\n+\n+\tret = rtw_sdio_8723bs_pubq_num(rtwdev, \u0026pubq_num);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tpg_tbl = \u0026chip-\u003epage_table[0];\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+\treturn 0;\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\tint ret = rtw_sdio_8723bs_check_rqpn(rtwdev);\n+\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = rtw_sdio_8723bs_init_free_txpg(rtwdev);\n+\t\tif (ret)\n+\t\t\treturn ret;\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 RTW_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 +1110,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 +1130,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@@ -922,6 +1223,50 @@ static int rtw_sdio_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size)\n \treturn rtw_sdio_write_data(rtwdev, \u0026pkt_info, skb, RTW_TX_QUEUE_H2C);\n }\n \n+/*\n+ * Back-pressure on the data ACs (BK/BE/VI/VO): once the software FIFO fills\n+ * past the high watermark, stop the corresponding mac80211 queue so it stops\n+ * handing frames down, which bounds the queueing latency. The queue is woken\n+ * again from the TX drain path once the FIFO falls back to the low watermark.\n+ */\n+static void rtw_sdio_8723bs_stop_tx_queue(struct rtw_dev *rtwdev,\n+\t\t\t\t\t enum rtw_tx_queue_type queue,\n+\t\t\t\t\t u16 q_map)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\n+\tif (!rtw_is_8723bs(rtwdev) || queue \u003e= RTW_TX_QUEUE_BCN)\n+\t\treturn;\n+\n+\tif (rtwsdio-\u003etx_queue_stopped[queue])\n+\t\treturn;\n+\n+\tif (skb_queue_len(\u0026rtwsdio-\u003etx_queue[queue]) \u003c RTW_SDIO_TX_FIFO_HIWATER)\n+\t\treturn;\n+\n+\trtwsdio-\u003etx_queue_stopped[queue] = true;\n+\tieee80211_stop_queue(rtwdev-\u003ehw, q_map);\n+}\n+\n+static void rtw_sdio_8723bs_wake_tx_queue(struct rtw_dev *rtwdev,\n+\t\t\t\t\t enum rtw_tx_queue_type queue,\n+\t\t\t\t\t u16 q_map)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\n+\tif (!rtw_is_8723bs(rtwdev) || queue \u003e= RTW_TX_QUEUE_BCN)\n+\t\treturn;\n+\n+\tif (!rtwsdio-\u003etx_queue_stopped[queue])\n+\t\treturn;\n+\n+\tif (skb_queue_len(\u0026rtwsdio-\u003etx_queue[queue]) \u003e RTW_SDIO_TX_FIFO_LOWATER)\n+\t\treturn;\n+\n+\trtwsdio-\u003etx_queue_stopped[queue] = false;\n+\tieee80211_wake_queue(rtwdev-\u003ehw, q_map);\n+}\n+\n static int rtw_sdio_tx_write(struct rtw_dev *rtwdev,\n \t\t\t struct rtw_tx_pkt_info *pkt_info,\n \t\t\t struct sk_buff *skb)\n@@ -937,6 +1282,8 @@ static int rtw_sdio_tx_write(struct rtw_dev *rtwdev,\n \n \tskb_queue_tail(\u0026rtwsdio-\u003etx_queue[queue], skb);\n \n+\trtw_sdio_8723bs_stop_tx_queue(rtwdev, queue, skb_get_queue_mapping(skb));\n+\n \treturn 0;\n }\n \n@@ -1095,6 +1442,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 this\n+\t * driver defines. Other chips keep the existing behaviour.\n+\t */\n+\tif (rtw_is_8723bs(rtwdev))\n+\t\thisr \u0026= RTW_SDIO_HISR_CLEAR_MASK;\n+\n \trtw_write32(rtwdev, REG_SDIO_HISR, hisr);\n \n \trtwsdio-\u003eirq_thread = NULL;\n@@ -1231,33 +1586,81 @@ 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 0 when a frame was written, 1 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 1;\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+\trtw_sdio_8723bs_wake_tx_queue(rtwdev, queue, q_map);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Decide whether the RTL8723BS wants the TX work to run again, and if so\n+ * arrange it and tell the caller to stop draining. Two cases need it.\n+ *\n+ * A transmit page or output queue shortage and a failed skb expansion are\n+ * transient and leave the frame queued, so come back for it shortly. That\n+ * matters once the mac80211 queue can be stopped: a stopped queue is handed\n+ * no further frames, so nothing else would kick this work item and the access\n+ * category would stay stopped for good. The remaining errors are logged where\n+ * they happen and are not retried.\n+ *\n+ * After a management frame, restart from the highest priority queue so the\n+ * join sequence is not held up behind a data backlog.\n+ */\n+static bool rtw_sdio_8723bs_reschedule_tx(struct rtw_dev *rtwdev,\n+\t\t\t\t\t struct rtw_sdio_work_data *work_data,\n+\t\t\t\t\t enum rtw_tx_queue_type queue, int ret)\n+{\n+\tstruct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n+\tunsigned long delay;\n+\n+\tif (!rtw_is_8723bs(rtwdev))\n+\t\treturn false;\n+\n+\tif (ret == -EBUSY || ret == -ENOMEM)\n+\t\tdelay = RTW_SDIO_TX_RETRY_DELAY;\n+\telse if (ret == 0 \u0026\u0026 queue == RTW_TX_QUEUE_MGMT)\n+\t\tdelay = 0;\n+\telse\n+\t\treturn false;\n+\n+\tqueue_delayed_work(rtwsdio-\u003etxwq, \u0026work_data-\u003ework, delay);\n+\n+\treturn true;\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 \n \trtwdev = work_data-\u003ertwdev;\n \trtwsdio = (struct rtw_sdio *)rtwdev-\u003epriv;\n@@ -1267,7 +1670,13 @@ static void rtw_sdio_tx_handler(struct work_struct *work)\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 \u003e 0)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (rtw_sdio_8723bs_reschedule_tx(rtwdev, work_data,\n+\t\t\t\t\t\t\t queue, ret))\n+\t\t\t\treturn;\n \n \t\t\tif (skb_queue_empty(\u0026rtwsdio-\u003etx_queue[queue]))\n \t\t\t\tbreak;\n@@ -1294,14 +1703,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-\u003etx_queue_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 +1726,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..cec3dd1c4b2e6 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 tx_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/19 07:58 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies the Realtek rtw88 Wi-Fi driver, specifically adding support and fixing logic for the RTL8723BS SDIO module. This is a vendor-specific hardware driver that requires a physical SDIO Wi-Fi chip to be present. Since SDIO hardware and this specific Realtek chip are not emulated in standard QEMU or GCE environments, the modified code paths are impossible to reach and fuzz in virtualized environments.",
"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 db6dab14026bee907f788aa5e5ad88c6d62b5fed
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 19 07:58:40 2026 +0000
syz-cluster: applied patch under review
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..fde21c840ac02 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 (pkt_stat->pkt_len == 0 &&
+ (rtwdev->chip->id == RTW_CHIP_TYPE_8703B || rtw_is_8723bs(rtwdev))) {
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..e5bf9cba071ea 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,157 @@ 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));
+}
+
+/*
+ * Refresh the cached counters from the chip. Returns false when the chip
+ * reports no free pages at all, which means the counts cannot be trusted
+ * and the caller has to decide what to do instead.
+ */
+static bool rtw_sdio_8723bs_sync_free_txpg(struct rtw_dev *rtwdev)
+{
+ u32 free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+
+ if (!free_txpg)
+ return false;
+
+ rtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg);
+
+ return true;
+}
+
+/*
+ * Size of the public page pool: whatever the transmit page allocation has
+ * left once the per queue pools are taken out. Fails if the allocation
+ * cannot cover the reserved queues, since the remainder would underflow and
+ * there would be no sensible pool to hand out.
+ */
+static int rtw_sdio_8723bs_pubq_num(struct rtw_dev *rtwdev, u16 *pubq_num)
+{
+ const struct rtw_page_table *pg_tbl = &rtwdev->chip->page_table[0];
+ u16 acq_pg_num = rtwdev->fifo.acq_pg_num;
+ u16 reserved_num;
+
+ reserved_num = pg_tbl->hq_num + pg_tbl->lq_num + pg_tbl->nq_num +
+ pg_tbl->exq_num + pg_tbl->gapq_num;
+ if (acq_pg_num <= reserved_num) {
+ rtw_err(rtwdev,
+ "no transmit pages left for the public queue: %u of %u reserved\n",
+ reserved_num, acq_pg_num);
+ return -EINVAL;
+ }
+
+ *pubq_num = acq_pg_num - reserved_num;
+
+ return 0;
+}
+
+static int 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;
+ u16 pubq_num;
+ int ret;
+
+ /* Seed from the page table when the chip has nothing to report yet. */
+ if (!rtw_sdio_8723bs_sync_free_txpg(rtwdev)) {
+ ret = rtw_sdio_8723bs_pubq_num(rtwdev, &pubq_num);
+ if (ret)
+ return ret;
+
+ pg_tbl = &rtwdev->chip->page_table[0];
+ 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));
+
+ return 0;
+}
+
+/*
+ * 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 +788,124 @@ 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;
+ unsigned long deadline;
+ u8 free;
+
+ if (atomic_add_unless(&rtwsdio->tx_oqt_free, -1, 0))
+ return 0;
+
+ deadline = jiffies + msecs_to_jiffies(RTW_SDIO_OQT_TIMEOUT_MS);
+ do {
+ 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);
+ } while (time_before(jiffies, deadline));
+
+ 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 +913,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 +925,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 +953,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 +992,61 @@ static int rtw_sdio_setup(struct rtw_dev *rtwdev)
return 0;
}
+/*
+ * Reprogram the queue page allocation if the chip came up with none. This is
+ * a repair path, not part of the normal start sequence: a non-zero free page
+ * count means the allocation latched during power on and must be left alone,
+ * and without a transmit page pool there is nothing to divide up either.
+ */
+static int 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;
+ int ret;
+
+ free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+ if (free_txpg || !fifo->acq_pg_num)
+ return 0;
+
+ ret = rtw_sdio_8723bs_pubq_num(rtwdev, &pubq_num);
+ if (ret)
+ return ret;
+
+ pg_tbl = &chip->page_table[0];
+ 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));
+
+ return 0;
+}
+
static int rtw_sdio_start(struct rtw_dev *rtwdev)
{
+ u32 clear;
+
+ if (rtw_is_8723bs(rtwdev)) {
+ int ret = rtw_sdio_8723bs_check_rqpn(rtwdev);
+
+ if (ret)
+ return ret;
+
+ ret = rtw_sdio_8723bs_init_free_txpg(rtwdev);
+ if (ret)
+ return ret;
+ }
+
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 +1110,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 +1130,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);
}
@@ -922,6 +1223,50 @@ static int rtw_sdio_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size)
return rtw_sdio_write_data(rtwdev, &pkt_info, skb, RTW_TX_QUEUE_H2C);
}
+/*
+ * Back-pressure on the data ACs (BK/BE/VI/VO): once the software FIFO fills
+ * past the high watermark, stop the corresponding mac80211 queue so it stops
+ * handing frames down, which bounds the queueing latency. The queue is woken
+ * again from the TX drain path once the FIFO falls back to the low watermark.
+ */
+static void rtw_sdio_8723bs_stop_tx_queue(struct rtw_dev *rtwdev,
+ enum rtw_tx_queue_type queue,
+ u16 q_map)
+{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+ if (!rtw_is_8723bs(rtwdev) || queue >= RTW_TX_QUEUE_BCN)
+ return;
+
+ if (rtwsdio->tx_queue_stopped[queue])
+ return;
+
+ if (skb_queue_len(&rtwsdio->tx_queue[queue]) < RTW_SDIO_TX_FIFO_HIWATER)
+ return;
+
+ rtwsdio->tx_queue_stopped[queue] = true;
+ ieee80211_stop_queue(rtwdev->hw, q_map);
+}
+
+static void rtw_sdio_8723bs_wake_tx_queue(struct rtw_dev *rtwdev,
+ enum rtw_tx_queue_type queue,
+ u16 q_map)
+{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+ if (!rtw_is_8723bs(rtwdev) || queue >= RTW_TX_QUEUE_BCN)
+ return;
+
+ if (!rtwsdio->tx_queue_stopped[queue])
+ return;
+
+ if (skb_queue_len(&rtwsdio->tx_queue[queue]) > RTW_SDIO_TX_FIFO_LOWATER)
+ return;
+
+ rtwsdio->tx_queue_stopped[queue] = false;
+ ieee80211_wake_queue(rtwdev->hw, q_map);
+}
+
static int rtw_sdio_tx_write(struct rtw_dev *rtwdev,
struct rtw_tx_pkt_info *pkt_info,
struct sk_buff *skb)
@@ -937,6 +1282,8 @@ static int rtw_sdio_tx_write(struct rtw_dev *rtwdev,
skb_queue_tail(&rtwsdio->tx_queue[queue], skb);
+ rtw_sdio_8723bs_stop_tx_queue(rtwdev, queue, skb_get_queue_mapping(skb));
+
return 0;
}
@@ -1095,6 +1442,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 this
+ * driver defines. Other chips keep the existing behaviour.
+ */
+ if (rtw_is_8723bs(rtwdev))
+ hisr &= RTW_SDIO_HISR_CLEAR_MASK;
+
rtw_write32(rtwdev, REG_SDIO_HISR, hisr);
rtwsdio->irq_thread = NULL;
@@ -1231,33 +1586,81 @@ 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 0 when a frame was written, 1 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 1;
+ 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);
+
+ rtw_sdio_8723bs_wake_tx_queue(rtwdev, queue, q_map);
+
+ return 0;
+}
+
+/*
+ * Decide whether the RTL8723BS wants the TX work to run again, and if so
+ * arrange it and tell the caller to stop draining. Two cases need it.
+ *
+ * A transmit page or output queue shortage and a failed skb expansion are
+ * transient and leave the frame queued, so come back for it shortly. That
+ * matters once the mac80211 queue can be stopped: a stopped queue is handed
+ * no further frames, so nothing else would kick this work item and the access
+ * category would stay stopped for good. The remaining errors are logged where
+ * they happen and are not retried.
+ *
+ * After a management frame, restart from the highest priority queue so the
+ * join sequence is not held up behind a data backlog.
+ */
+static bool rtw_sdio_8723bs_reschedule_tx(struct rtw_dev *rtwdev,
+ struct rtw_sdio_work_data *work_data,
+ enum rtw_tx_queue_type queue, int ret)
+{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+ unsigned long delay;
+
+ if (!rtw_is_8723bs(rtwdev))
+ return false;
+
+ if (ret == -EBUSY || ret == -ENOMEM)
+ delay = RTW_SDIO_TX_RETRY_DELAY;
+ else if (ret == 0 && queue == RTW_TX_QUEUE_MGMT)
+ delay = 0;
+ else
+ return false;
+
+ queue_delayed_work(rtwsdio->txwq, &work_data->work, delay);
+
+ return true;
}
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;
rtwdev = work_data->rtwdev;
rtwsdio = (struct rtw_sdio *)rtwdev->priv;
@@ -1267,7 +1670,13 @@ static void rtw_sdio_tx_handler(struct work_struct *work)
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 (rtw_sdio_8723bs_reschedule_tx(rtwdev, work_data,
+ queue, ret))
+ return;
if (skb_queue_empty(&rtwsdio->tx_queue[queue]))
break;
@@ -1294,14 +1703,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->tx_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 +1726,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..cec3dd1c4b2e6 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 tx_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/19 07:58 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12640 output: 19 thoughts: 1041 Thoughts: **Analyzing the Reachability of This rtw88 Patch in a Virtualized Environment** Okay, so I'm looking at this patch for the `rtw88` driver, specifically targeting the SDIO interface and adding support/fixing logic for `RTW_CHIP_TYPE_8723B` and `RTW_CHIP_TYPE_8723BS`. It seems pretty extensive – there are new helper functions like `rtw_is_8723bs`, modifications to existing functions like `rtw_rx_fill_rx_status`, and a whole slew of new SDIO-specific functions related to TX page management and queue handling. Many existing functions are also being updated. The changes touch upon core SDIO operations – reading/writing ports, initializing, enabling RX aggregation, TX handling, and interrupt processing. The crux of the matter is whether this code is even *reachable* in a typical virtualized environment that Syzkaller can easily fuzz. My immediate concern is that `rtw88` is a Realtek Wi-Fi driver, and this patch specifically deals with the SDIO interface. SDIO devices are notoriously difficult to emulate well in standard QEMU or GCE setups, especially when you're talking about specific vendor-specific hardware. While QEMU *does* have SDHCI emulation, that doesn't automatically mean we have a virtual SDIO *card* that this Realtek driver can interact with. `mac80211_hwsim` is an interesting example of software emulation, but it's a virtual driver, not an emulator for realtek hardware. The instructions specifically say to return `WorthFuzzing=false` if the code paths are unreachable in virtualized environments like QEMU or GCE, even when using software-emulated hardware. It calls out vendor-specific drivers that need physical PCIe hardware, which pretty much describes the situation with SDIO Wi-Fi chips. I need to confirm that `rtw88` SDIO can't be probed without real hardware. Syzkaller has USB and PCI fuzzing, but SDIO? I don't recall seeing any SDIO fuzzing support directly. The instructions suggest the code is *unreachable*. I'm thinking about the function call chain: `rtw_sdio_write_port` is called from `rtw_sdio_tx_write`, which is a key part of the SDIO driver. So, realistically, if there's no SDIO fuzzer in Syzkaller and no virtual SDIO hardware that can be meaningfully used by the `rtw88` driver, then this code is dead on arrival from a fuzzing perspective. The patch is probably not worth fuzzing. I could try to search for "sdio" in the kernel or syzkaller, or for any SDIO fuzzing drivers, but I am almost certain I would find nothing. |
| 4/2 | 2026/08/19 07:59 | tool | grepper |
0mError:no matches Args: {
"Expression": "sdio.*fuzz"
}Results: {
"error": "no matches"
} |
| 5/2 | 2026/08/19 07:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14332 output: 18 thoughts: 31 Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience: **Investigating SDIO Host Software Implementation** Right, let's dive in. I need to get a handle on the software SDIO host implementation within the kernel. The logical starting point is likely the `drivers/mmc/core/` directory. That's the common ground for MMC and SD-related code, so it's the natural place to start looking for the core SDIO host drivers. I'll initiate a search for `sdio` within that directory, using my preferred search tools, to pinpoint the relevant source files. This should quickly reveal if and where the kernel handles software implementations of the SDIO host. I'm expecting to uncover relevant drivers, probably with names that give clues about the underlying hardware or bus interfaces. |
| 6/2 | 2026/08/19 07:59 | tool | grepper |
0mArgs:{
"Expression": "sdio.*dummy"
}Results: {
"Output": "drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c=82=static void brcmf_sdiod_ib_irqhandler(struct sdio_func *func)\n--\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c-92-/* dummy handler for SDIO function 2 interrupt */\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c:93:static void brcmf_sdiod_dummy_irqhandler(struct sdio_func *func)\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c-94-{\n--\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c=97=int brcmf_sdiod_intr_register(struct brcmf_sdio_dev *sdiodev)\n--\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c-159-\t\tsdio_claim_irq(sdiodev-\u003efunc1, brcmf_sdiod_ib_irqhandler);\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c:160:\t\tsdio_claim_irq(sdiodev-\u003efunc2, brcmf_sdiod_dummy_irqhandler);\ndrivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c-161-\t\tsdio_release_host(sdiodev-\u003efunc1);\n--\ndrivers/pinctrl/meson/pinctrl-meson-g12a.c=1001=static const char * const sdio_groups[] = {\ndrivers/pinctrl/meson/pinctrl-meson-g12a.c-1002-\t\"sdio_d0\", \"sdio_d1\", \"sdio_d2\", \"sdio_d3\",\ndrivers/pinctrl/meson/pinctrl-meson-g12a.c:1003:\t\"sdio_cmd\", \"sdio_clk\", \"sdio_dummy\",\ndrivers/pinctrl/meson/pinctrl-meson-g12a.c-1004-};\n"
} |
| 7/2 | 2026/08/19 07:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15164 output: 21 thoughts: 231 Thoughts: **My Assessment: This Patch is Unfuzzable in a Virtualized Environment** Okay, so looking at this code, it's pretty clear we have a problem. There doesn't appear to be a virtual SDIO host controller available for fuzzing, which is the first hurdle. But even if we could somehow conjure one up, we're dealing with the Realtek `rtw88` Wi-Fi driver here. It's designed to work with *specific* Realtek hardware on the SDIO bus. That means the driver's `probe` function will never be triggered without that hardware present. Therefore, none of this code will even execute in our virtualized test environment. And even if we could inject *some* SDIO devices, we're still stuck with a vendor-specific hardware driver. That prompts this particular question: "Return WorthFuzzing=false if the patch only contains code that is impossible to reach in virtualized environments," which, based on the description, explicitly includes vendor-specific drivers that rely on physical hardware that's not emulated. So, realistically, we're not going to be able to make use of this code to fuzz. Before I write this off completely I should double-check to be sure that `rtw88` has a software emulation mode. If it does, there's a chance. If not, this is definitely a dead end for fuzzing. |
| 8/2 | 2026/08/19 07:59 | tool | grepper |
0mError:no matches Args: {
"Expression": "rtw88.*emul"
}Results: {
"error": "no matches"
} |
| 9/2 | 2026/08/19 07:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15729 output: 21 thoughts: 27 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the rtw88 SDIO Probe Function** Okay, let's get down to brass tacks. I'm going to take a look at the `drivers/net/wireless/realtek/rtw88/sdio.c` file, specifically the probe function. I need to refresh my memory on the SDIO initialization sequence for the rtw88 series. My main areas of interest are, of course, the resource allocation, the platform driver setup, and the subsequent interactions with the SDIO bus. I want to see how they handle the card detection and initialization. Things like power management and any potential race conditions during the initial bring-up always catch my eye. I want to make sure they're handling interrupts correctly and, if applicable, the associated interrupt masking/unmasking. Let's see how they deal with any SDIO specific quirks – Realtek always seems to have a few of those. Time to analyze the code carefully and confirm it aligns with the design I remember. |
| 10/2 | 2026/08/19 07:59 | tool | grepper |
0mArgs:{
"Expression": "rtw_sdio_probe"
}Results: {
"Output": "drivers/net/wireless/realtek/rtw88/rtw8723cs.c=21=static struct sdio_driver rtw_8723cs_driver = {\n--\ndrivers/net/wireless/realtek/rtw88/rtw8723cs.c-23-\t.id_table = rtw_8723cs_id_table,\ndrivers/net/wireless/realtek/rtw88/rtw8723cs.c:24:\t.probe = rtw_sdio_probe,\ndrivers/net/wireless/realtek/rtw88/rtw8723cs.c-25-\t.remove = rtw_sdio_remove,\n--\ndrivers/net/wireless/realtek/rtw88/rtw8723ds.c=27=static struct sdio_driver rtw_8723ds_driver = {\ndrivers/net/wireless/realtek/rtw88/rtw8723ds.c-28-\t.name = KBUILD_MODNAME,\ndrivers/net/wireless/realtek/rtw88/rtw8723ds.c:29:\t.probe = rtw_sdio_probe,\ndrivers/net/wireless/realtek/rtw88/rtw8723ds.c-30-\t.remove = rtw_sdio_remove,\n--\ndrivers/net/wireless/realtek/rtw88/rtw8821cs.c=22=static struct sdio_driver rtw_8821cs_driver = {\ndrivers/net/wireless/realtek/rtw88/rtw8821cs.c-23-\t.name = KBUILD_MODNAME,\ndrivers/net/wireless/realtek/rtw88/rtw8821cs.c:24:\t.probe = rtw_sdio_probe,\ndrivers/net/wireless/realtek/rtw88/rtw8821cs.c-25-\t.remove = rtw_sdio_remove,\n--\ndrivers/net/wireless/realtek/rtw88/rtw8822bs.c=22=static struct sdio_driver rtw_8822bs_driver = {\ndrivers/net/wireless/realtek/rtw88/rtw8822bs.c-23-\t.name = KBUILD_MODNAME,\ndrivers/net/wireless/realtek/rtw88/rtw8822bs.c:24:\t.probe = rtw_sdio_probe,\ndrivers/net/wireless/realtek/rtw88/rtw8822bs.c-25-\t.remove = rtw_sdio_remove,\n--\ndrivers/net/wireless/realtek/rtw88/rtw8822cs.c=22=static struct sdio_driver rtw_8822cs_driver = {\ndrivers/net/wireless/realtek/rtw88/rtw8822cs.c-23-\t.name = KBUILD_MODNAME,\ndrivers/net/wireless/realtek/rtw88/rtw8822cs.c:24:\t.probe = rtw_sdio_probe,\ndrivers/net/wireless/realtek/rtw88/rtw8822cs.c-25-\t.remove = rtw_sdio_remove,\n--\ndrivers/net/wireless/realtek/rtw88/sdio.c=1724=static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev)\n--\ndrivers/net/wireless/realtek/rtw88/sdio.c-1736-\ndrivers/net/wireless/realtek/rtw88/sdio.c:1737:int rtw_sdio_probe(struct sdio_func *sdio_func,\ndrivers/net/wireless/realtek/rtw88/sdio.c-1738-\t\t const struct sdio_device_id *id)\n--\ndrivers/net/wireless/realtek/rtw88/sdio.c-1811-}\ndrivers/net/wireless/realtek/rtw88/sdio.c:1812:EXPORT_SYMBOL(rtw_sdio_probe);\ndrivers/net/wireless/realtek/rtw88/sdio.c-1813-\n--\ndrivers/net/wireless/realtek/rtw88/sdio.h=184=extern const struct dev_pm_ops rtw_sdio_pm_ops;\ndrivers/net/wireless/realtek/rtw88/sdio.h-185-\ndrivers/net/wireless/realtek/rtw88/sdio.h:186:int rtw_sdio_probe(struct sdio_func *sdio_func,\ndrivers/net/wireless/realtek/rtw88/sdio.h-187-\t\t const struct sdio_device_id *id);\n"
} |
| 11/2 | 2026/08/19 07:59 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17257 output: 125 thoughts: 79 Thoughts: **My Assessment of the SDIO Wi-Fi Driver Patch** Alright, looking at this patch for the SDIO Wi-Fi driver, I've come to a pretty straightforward conclusion. This is a vendor-specific driver, specifically for the Realtek 8723BS SDIO module. That immediately tells me we're dealing with hardware dependencies. And, critically, the standard QEMU environment, or even something like GCE, isn't going to emulate that specific hardware. So, realistically, any code paths this patch touches are *completely* unreachable in any virtualized setup. You can't execute this code without the physical hardware present. Therefore, considering the limitations of our fuzzing environment, I'm going to set `WorthFuzzing=false`. It's a waste of resources to dedicate time to this patch right now. We need to be able to actually *reach* the modified code to make fuzzing effective. |
| 12/2 | 2026/08/19 07:59 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch modifies the Realtek rtw88 Wi-Fi driver, specifically adding support and fixing logic for the RTL8723BS SDIO module. This is a vendor-specific hardware driver that requires a physical SDIO Wi-Fi chip to be present. Since SDIO hardware and this specific Realtek chip are not emulated in standard QEMU or GCE environments, the modified code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch modifies the Realtek rtw88 Wi-Fi driver, specifically adding support and fixing logic for the RTL8723BS SDIO module. This is a vendor-specific hardware driver that requires a physical SDIO Wi-Fi chip to be present. Since SDIO hardware and this specific Realtek chip are not emulated in standard QEMU or GCE environments, the modified code paths are impossible to reach and fuzz in virtualized environments.",
"WorthFuzzing": false
} |
| 13/1 | 2026/08/19 07:59 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|