The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS
@ 2026-07-25 15:04 luka.gejak
  2026-07-25 15:04 ` [PATCH v2 01/11] wifi: rtw88: add the RTL8723B chip type and SDIO helper luka.gejak
                   ` (10 more replies)
  0 siblings, 11 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

This is the first of two series adding support for the Realtek RTL8723B
802.11n chipset and its RTL8723BS SDIO variant to rtw88. It contains
only the changes to the shared rtw88 core that the chip driver depends
on. The chip itself, the build glue and the MAINTAINERS entry are a
second series.

v1 had 19 patches; this has 11.

There is no rtw88-style firmware for this chip and no documentation for
the vendor blob it does use, so while bringing it up I deliberately kept
the driver as close to the vendor driver's behaviour as I could. With
that many unknowns at once, matching the vendor exactly was the only way
to tell which difference actually mattered when something did not work,
rather than guessing. A number of the v1 patches came from that: they
reproduced vendor behaviour that was useful to hold fixed during bring
up, not behaviour the chip turns out to require.

Now that the chip works end to end, and prompted by Bitterblue Smith's
review, I went back and removed each of those on hardware to find which
were actually load bearing. Eight were not. The changelog below gives
the measurement for each.

Why the remaining changes are needed
====================================

The vendor firmware's association behaviour and SDIO transfer contract
differ from the firmware the existing rtw88 8723-family devices use, and
those differences sit in code that lives in the core, so the chip driver
cannot handle them on its own.

What is left falls into four groups:

  - firmware (patches 3, 4): the GNT_BT command rtw88 does not
    implement, and a fix to the reserved page upload handshake;
  - coexistence (5, 6): the PTA antenna path has to be programmed
    directly during scan and reasserted when associating;
  - SDIO (7, 8, 9): software free-page and OQT accounting, RX
    aggregation and interrupt setup, and TX back-pressure with a retry
    on page starvation;
  - association (10, 11): the vendor join sequence, which programs the
    BSS parameters directly and waits for a beacon from the target
    before authenticating.

Patches 1 and 2 are the chip type and helper plus a one-line receive
fix.

Scope
=====

Everything is gated on rtw_is_8723bs(), which is false for every chip
currently supported, so behaviour for existing devices is unchanged.
The one exception is patch 2, which extends an existing RTL8703B
zero-length-packet check; it is gated on the SDIO interface rather than
the chip id, since that is the only place the behaviour has been
observed.

Patch 8 changes how the SDIO interrupt status is acknowledged. In v1
that applied to all SDIO parts; it is now scoped to this chip only, and
the others keep writing the status word back unchanged.

Fixed in the second series
==========================

Two of the review points are addressed in the chip driver rather than
here, because that is the right place for both:

  - the hardware capability is filled in from a chip-specific
    read_efuse, instead of special casing rtw_dump_hw_feature() in the
    core;
  - the receive FCS handling is a chip configuration fix. v1 stopped the
    core advertising RX_INCLUDES_FCS for this chip. The real cause was
    that the chip cleared BIT_APP_FCS by assigning WLAN_RCR_CFG over
    hal.rcr wholesale, where the 8723x siblings only write the register
    and leave hal.rcr alone. Setting that bit in the chip's WLAN_RCR_CFG
    makes the descriptor carry the FCS like every other rtw88 chip, so
    the core needs no special case and both hunks are gone from this
    series.

Neither change is visible here. I planned to send that series once this
one is applied to rtw-next, but I can send it now alongside if you would
rather review them together.

Two things deliberately kept
============================

Two further patches could be removed without breaking anything on my
test setup, and I have still kept them, because passing on a single AP
is not evidence that dropping them is safe:

  - the basic-rate handling in patch 11 programs the response rates
    from the BSS rate elements. Removing it associates and passes
    traffic here, but this AP advertises a conventional basic-rate set;
    an AP with an unusual one may not behave the same. rtw88xxa writes
    a fixed per-band set instead, so there may be a simplification here,
    but it needs more than one AP to justify.
  - the ERP preamble and slot-time handlers in patch 11 respond to
    BSS_CHANGED_ERP_*. Dropping them passes a static test, but that test
    cannot see a mid-session ERP change, such as a legacy station
    joining the BSS.

If you would rather have either of them out, say so and they can go.

Testing
=======

Tested on RTL8723BS hardware together with the second series: repeated
scan, authentication, association, WPA2 handshake, DHCP, bidirectional
traffic, reconnects and module reloads. On one AP at strong signal
(-38 dBm), 2.4 GHz HT40, throughput is roughly 34 Mbit/s down and
17 Mbit/s up, with no "failed to get tx report" warnings and a clean
log.

Every patch builds with W=1 with no warnings on its own, and sparse and
smatch are clean at the tip. checkpatch --strict is clean across this
series. The other rtw88 bus and chip modules continue to build.

Changes in v2:
  - Dropped "tx: extend the TX report purge timeout". Instrumenting the
    report path shows payload[6] & 0xfc equals the enqueued sequence
    number, 32 out of 32 times, with rtw88's existing decode, and no
    report is missed. The longer timeout was covering for the next
    patch, not for the hardware.
  - Dropped "fw: handle the RTL8723BS management TX reports". 0x12 and
    0x32 are the first payload byte of C2H_CCX_TX_RPT, not C2H IDs;
    every C2H event this chip sends carries a known id, so the existing
    handler already covers them. Thanks to Bitterblue for catching this.
  - Dropped "fw: send rate adaptation and RSSI info in the vendor
    layout". The vendor byte layout is equivalent to what the existing
    macros produce; uplink and the negotiated rate are unchanged without
    it (16.4/16.5/17.0 against 15.4/16.7/16.6 Mbit/s, MCS7 both ways).
  - Dropped "fw: send the media status report in the vendor layout". The
    role field the vendor sets makes no difference here, as with the
    other chips.
  - Dropped "sdio: handle the RTL8723BS management TX path". Sequence
    numbers do work for management frames on this chip, so reporting
    completion at DMA completion is unnecessary.
  - Dropped "calibrate and tune the PHY" entirely. All three parts were
    vendor-matching scaffolding and none survived measurement: the
    scan-time initial gain override is worse than
    rtw_phy_dig_set_max_coverage() (five scans found 41 BSSes without it
    against 34 with it); the IQ calibration works from the normal
    phy_calibration path; and the hardcoded per-rate TX AGC table
    overrode the efuse calibration by up to 22 index units and bypassed
    the regulatory limit the by-rate path applies, which is not
    something a driver should do.
  - Dropped "match the RTL8723BS firmware connect and power save
    behaviour". Deferring the connect report changes nothing, and the
    LPS gating cannot be justified from measurement: with RTW_DBG_PS
    enabled, LPS never engages on this setup at all, so the gating never
    takes effect here.
  - Dropped "advertise the correct receive capabilities". Both halves
    move into the chip driver, as described above.
  - "fw: add the vendor firmware commands" now adds only GNT_BT. rtw88
    already implements MACID_CFG and the WL channel info report, and the
    coexistence antenna select reserve became unused once the PHY patch
    went.
  - The zero-length packet check is gated on SDIO rather than the chip
    id.
  - The SDIO interrupt acknowledgment change is scoped to this chip.
  - The association sequence no longer re-applies the BSS capability at
    association time; BSS_CHANGED_ERP_PREAMBLE and BSS_CHANGED_ERP_SLOT
    are handled later in the same callback.
  - Register accesses that had raw addresses now use the existing
    REG_GNT_BT and REG_BT_COEX_ENH_INTR_CTRL, plus a named define for
    the BB antenna select register.

The implementation is based on the initial RTL8723B work by Michael
Straube <straube.linux@gmail.com>:

  https://github.com/mistraube/rtw88/tree/rtl8723bs

Based on the rtw-next branch from pkshih/rtw.

Luka Gejak (11):
  wifi: rtw88: add the RTL8723B chip type and SDIO helper
  wifi: rtw88: rx: mark zero length packets on RTL8723BS
  wifi: rtw88: fw: add the GNT_BT firmware command
  wifi: rtw88: fw: fix the reserved page upload on RTL8723BS
  wifi: rtw88: coex: add the RTL8723BS scan antenna workaround
  wifi: rtw88: coex: reassert the antenna path when associating
  wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS
  wifi: rtw88: sdio: set up RX aggregation and interrupts for RTL8723BS
  wifi: rtw88: sdio: add TX back-pressure and retry on page starvation
  wifi: rtw88: record beacons from the target BSSID before
    authenticating
  wifi: rtw88: run the RTL8723BS association register sequence

 drivers/net/wireless/realtek/rtw88/coex.c     | 225 +++++++-
 drivers/net/wireless/realtek/rtw88/coex.h     |   3 +
 drivers/net/wireless/realtek/rtw88/fw.c       |  38 +-
 drivers/net/wireless/realtek/rtw88/fw.h       |   4 +
 drivers/net/wireless/realtek/rtw88/mac80211.c | 527 +++++++++++++++++-
 drivers/net/wireless/realtek/rtw88/main.c     |   2 +
 drivers/net/wireless/realtek/rtw88/main.h     |  29 +
 drivers/net/wireless/realtek/rtw88/reg.h      |   3 +
 drivers/net/wireless/realtek/rtw88/rx.c       |   8 +-
 drivers/net/wireless/realtek/rtw88/sdio.c     | 328 ++++++++++-
 drivers/net/wireless/realtek/rtw88/sdio.h     |  18 +-
 drivers/net/wireless/realtek/rtw88/sec.h      |   1 +
 12 files changed, 1154 insertions(+), 32 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 01/11] wifi: rtw88: add the RTL8723B chip type and SDIO helper
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 02/11] wifi: rtw88: rx: mark zero length packets on RTL8723BS luka.gejak
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

The RTL8723B is a 802.11n combo chip whose SDIO variant, RTL8723BS, runs
a Realtek vendor firmware that differs from the firmware used by the
other rtw88 8723 family devices. Supporting it needs a number of small
adjustments spread across the shared core, all of which have to be
restricted to this one chip and bus combination.

Add the chip type and a helper that tests for it, so the changes that
follow can be gated without repeating the chip and bus comparison.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/main.h | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index c6e981ba7986..8f86f7c12de5 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;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 02/11] wifi: rtw88: rx: mark zero length packets on RTL8723BS
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
  2026-07-25 15:04 ` [PATCH v2 01/11] wifi: rtw88: add the RTL8723B chip type and SDIO helper luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 03/11] wifi: rtw88: fw: add the GNT_BT firmware command luka.gejak
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

Like the RTL8703B, the RTL8723BS reports receive descriptors with a zero
packet length, which the vendor driver drops outright. rtw88 already
flags these as having no PSDU for the RTL8703B, so extend the same
handling rather than passing an empty frame up.

This is gated on the SDIO interface rather than the chip id, because it
has only been observed there; the USB variant is not known to do it.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/rx.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c
index 01fd299abb7f..5303975d00a2 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");
 	}
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 03/11] wifi: rtw88: fw: add the GNT_BT firmware command
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
  2026-07-25 15:04 ` [PATCH v2 01/11] wifi: rtw88: add the RTL8723B chip type and SDIO helper luka.gejak
  2026-07-25 15:04 ` [PATCH v2 02/11] wifi: rtw88: rx: mark zero length packets on RTL8723BS luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 04/11] wifi: rtw88: fw: fix the reserved page upload on RTL8723BS luka.gejak
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

The RTL8723BS coexistence sequence needs an H2C command that rtw88 does
not implement: the firmware GNT_BT state, which hands the shared antenna
to WiFi. Add it here so the coexistence change that follows has it
available.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/fw.c | 11 +++++++++++
 drivers/net/wireless/realtek/rtw88/fw.h |  4 ++++
 2 files changed, 15 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c
index 945fedcd375b..dd6595b8175e 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.c
+++ b/drivers/net/wireless/realtek/rtw88/fw.c
@@ -786,6 +786,17 @@ void rtw_fw_media_status_report(struct rtw_dev *rtwdev, u8 mac_id, bool connect)
 	rtw_fw_send_h2c_command(rtwdev, h2c_pkt);
 }
 
+/* 8723BS SDIO: set the firmware GNT_BT state (0 = WiFi owns the antenna). */
+void rtw_fw_set_gnt_bt(struct rtw_dev *rtwdev, u8 state)
+{
+	u8 h2c_pkt[H2C_PKT_SIZE] = {0};
+
+	SET_H2C_CMD_ID_CLASS(h2c_pkt, H2C_CMD_GNT_BT);
+	SET_GNT_BT_STATE(h2c_pkt, state);
+
+	rtw_fw_send_h2c_command(rtwdev, h2c_pkt);
+}
+
 void rtw_fw_update_wl_phy_info(struct rtw_dev *rtwdev)
 {
 	struct rtw_traffic_stats *stats = &rtwdev->stats;
diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h
index 48ad9ceab6ea..f5ed65c46350 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.h
+++ b/drivers/net/wireless/realtek/rtw88/fw.h
@@ -572,6 +572,7 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id)
 #define H2C_CMD_QUERY_BT_MP_INFO	0x67
 #define H2C_CMD_BT_WIFI_CONTROL		0x69
 #define H2C_CMD_WIFI_CALIBRATION	0x6d
+#define H2C_CMD_GNT_BT			0x6e
 #define H2C_CMD_QUERY_BT_HID_INFO	0x73
 
 #define H2C_CMD_KEEP_ALIVE		0x03
@@ -684,6 +685,8 @@ static inline void rtw_h2c_pkt_set_header(u8 *h2c_pkt, u8 sub_id)
 	le32p_replace_bits((__le32 *)(h2c_pkt) + 0x01, value, GENMASK(31, 24))
 #define SET_QUERY_BT_INFO(h2c_pkt, value)                                      \
 	le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8))
+#define SET_GNT_BT_STATE(h2c_pkt, value)                                       \
+	le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, BIT(8))
 #define SET_WL_CH_INFO_LINK(h2c_pkt, value)                                    \
 	le32p_replace_bits((__le32 *)(h2c_pkt) + 0x00, value, GENMASK(15, 8))
 #define SET_WL_CH_INFO_CHNL(h2c_pkt, value)                                    \
@@ -849,6 +852,7 @@ void rtw_fw_send_rssi_info(struct rtw_dev *rtwdev, struct rtw_sta_info *si);
 void rtw_fw_send_ra_info(struct rtw_dev *rtwdev, struct rtw_sta_info *si,
 			 bool reset_ra_mask);
 void rtw_fw_media_status_report(struct rtw_dev *rtwdev, u8 mac_id, bool conn);
+void rtw_fw_set_gnt_bt(struct rtw_dev *rtwdev, u8 state);
 void rtw_fw_update_wl_phy_info(struct rtw_dev *rtwdev);
 void rtw_fw_beacon_filter_config(struct rtw_dev *rtwdev, bool connect,
 				 struct ieee80211_vif *vif);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 04/11] wifi: rtw88: fw: fix the reserved page upload on RTL8723BS
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (2 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 03/11] wifi: rtw88: fw: add the GNT_BT firmware command luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 05/11] wifi: rtw88: coex: add the RTL8723BS scan antenna workaround luka.gejak
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

Two things stop the reserved page handshake from completing on this
chip. BIT_EN_BCNQ_DL has to be cleared so the upload is not treated as
a real beacon, which rtw88 only does for PCIe while the vendor driver
does it unconditionally. The software beacon download path also has to
be pointed at port 0. Without either, BIT_BCN_VALID is never asserted
and the upload reports a timeout.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/fw.c | 27 ++++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c
index dd6595b8175e..7c17086cac32 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.c
+++ b/drivers/net/wireless/realtek/rtw88/fw.c
@@ -1477,10 +1477,14 @@ void rtw_add_rsvd_page_sta(struct rtw_dev *rtwdev,
 	rtw_add_rsvd_page(rtwdev, rtwvif, RSVD_LPS_PG_INFO, true);
 }
 
+/* REG_DWBCN1_CTRL bit 20 (SW_BCN_SEL for port 0), relative to byte +2 */
+#define BIT_OFFSET_DWBCN1_SW_BCN_SEL_PORT0	(20 - 16)
+
 int rtw_fw_write_data_rsvd_page(struct rtw_dev *rtwdev, u16 pg_addr,
 				u8 *buf, u32 size)
 {
-	u8 bckp[3];
+	const bool is_8723bs_sdio = rtw_is_8723bs(rtwdev);
+	u8 bckp[4];
 	u8 val;
 	u16 rsvd_pg_head;
 	u32 bcn_valid_addr;
@@ -1510,13 +1514,28 @@ int rtw_fw_write_data_rsvd_page(struct rtw_dev *rtwdev, u16 pg_addr,
 	rtw_write8(rtwdev, REG_BCN_CTRL,
 		   (bckp[2] & ~BIT_EN_BCN_FUNCTION) | BIT_DIS_TSF_UDT);
 
-	if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE) {
+	/* Clear BIT_EN_BCNQ_DL so the chip does not treat the reserved-page
+	 * upload as a real beacon; otherwise BIT_BCN_VALID never asserts. The
+	 * vendor rtl8723bs driver does this unconditionally; rtw88 only did it
+	 * for PCIe, which left 8723BS SDIO's BCN_VALID handshake failing.
+	 */
+	if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE || is_8723bs_sdio) {
 		val = rtw_read8(rtwdev, REG_FWHW_TXQ_CTRL + 2);
 		bckp[1] = val;
 		val &= ~(BIT_EN_BCNQ_DL >> 16);
 		rtw_write8(rtwdev, REG_FWHW_TXQ_CTRL + 2, val);
 	}
 
+	/* 8723BS SDIO: point the SW beacon download path at port 0, else
+	 * BIT_BCN_VALID is never asserted after the SDIO upload completes.
+	 */
+	if (is_8723bs_sdio) {
+		val = rtw_read8(rtwdev, REG_DWBCN1_CTRL + 2);
+		bckp[3] = val;
+		val &= ~BIT(BIT_OFFSET_DWBCN1_SW_BCN_SEL_PORT0);
+		rtw_write8(rtwdev, REG_DWBCN1_CTRL + 2, val);
+	}
+
 	ret = rtw_hci_write_data_rsvd_page(rtwdev, buf, size);
 	if (ret) {
 		rtw_err(rtwdev, "failed to write data to rsvd page\n");
@@ -1537,11 +1556,13 @@ int rtw_fw_write_data_rsvd_page(struct rtw_dev *rtwdev, u16 pg_addr,
 	}
 
 restore:
+	if (is_8723bs_sdio)
+		rtw_write8(rtwdev, REG_DWBCN1_CTRL + 2, bckp[3]);
 	rsvd_pg_head = rtwdev->fifo.rsvd_boundary;
 	rtw_write16(rtwdev, REG_FIFOPAGE_CTRL_2,
 		    rsvd_pg_head | BIT_BCN_VALID_V1);
 	rtw_write8(rtwdev, REG_BCN_CTRL, bckp[2]);
-	if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE)
+	if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE || is_8723bs_sdio)
 		rtw_write8(rtwdev, REG_FWHW_TXQ_CTRL + 2, bckp[1]);
 	rtw_write8(rtwdev, REG_CR + 1, bckp[0]);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 05/11] wifi: rtw88: coex: add the RTL8723BS scan antenna workaround
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (3 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 04/11] wifi: rtw88: fw: fix the reserved page upload on RTL8723BS luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 06/11] wifi: rtw88: coex: reassert the antenna path when associating luka.gejak
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

On the RTL8723BS the PTA antenna path has to be programmed directly
during scan. The vendor driver keeps its own scan-time antenna state and
does not run the generic coexistence algorithm on boards where BT is
disabled, and following it is necessary here: without the antenna and
CCK priority setup applied at scan start, the site survey does not hear
the AP reliably.

Detect the BT-disabled case, replay the vendor BT_MP and BT_INFO queries
once the firmware has been up long enough for RX DMA to be stable, then
establish the scan path antenna configuration and skip the generic run.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/coex.c | 179 +++++++++++++++++++++-
 drivers/net/wireless/realtek/rtw88/coex.h |   2 +
 drivers/net/wireless/realtek/rtw88/reg.h  |   1 +
 3 files changed, 181 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw88/coex.c b/drivers/net/wireless/realtek/rtw88/coex.c
index 37c336def419..daba6082f850 100644
--- a/drivers/net/wireless/realtek/rtw88/coex.c
+++ b/drivers/net/wireless/realtek/rtw88/coex.c
@@ -1443,6 +1443,156 @@ static void rtw_coex_set_ant_path(struct rtw_dev *rtwdev, bool force, u8 phase)
 #define case_ALGO(src) \
 	case COEX_ALGO_##src: return #src
 
+/* 8723BS SDIO WiFi/BT coexistence antenna handling. On BT-disabled boards the
+ * scan/auth window still routes through the PTA mux; these helpers force the
+ * vendor-shaped WiFi-owned antenna path so directed management TX reaches air.
+ */
+#define REG_8723BS_BT_COEX_CTRL		0x0039
+#define REG_8723BS_BB_ANT_CFG		0x0930
+#define REG_8723BS_BB_ANT_CFG1		0x0944
+#define REG_8723BS_BB_ANT_BUF		0x0974
+#define RTW8723BS_COEX_H_WLAN_ACTIVE	0x1800101b
+
+static bool rtw_coex_8723bs_ant_is_aux(struct rtw_dev *rtwdev)
+{
+	return !!(rtwdev->efuse.bt_setting & BIT(6));
+}
+
+static bool rtw_coex_8723bs_bt_disabled(struct rtw_dev *rtwdev)
+{
+	return rtw_is_8723bs(rtwdev) && rtwdev->coex.stat.bt_disabled;
+}
+
+static u32 rtw_coex_8723bs_pta_ant_path(struct rtw_dev *rtwdev)
+{
+	return rtw_coex_8723bs_ant_is_aux(rtwdev) ? 0x80 : 0x200;
+}
+
+/* Write BB_SEL_BTG, retrying once with SYS_FUNC BB reset if the first
+ * write does not stick (RF/BB clock may have been gated).
+ */
+static u32 rtw_coex_8723bs_write_bb_sel_btg(struct rtw_dev *rtwdev, u32 value)
+{
+	u8 sys_func_before;
+	u32 readback;
+
+	sys_func_before = rtw_read8(rtwdev, REG_SYS_FUNC_EN);
+	if ((sys_func_before & (BIT(0) | BIT(1))) != (BIT(0) | BIT(1))) {
+		rtw_write8_set(rtwdev, REG_SYS_FUNC_EN, BIT(0) | BIT(1));
+		usleep_range(10, 11);
+	}
+
+	rtw_write32(rtwdev, REG_BB_SEL_BTG_8723B, value);
+	readback = rtw_read32(rtwdev, REG_BB_SEL_BTG_8723B);
+	if (readback == value)
+		return readback;
+
+	usleep_range(10, 11);
+	rtw_write8_set(rtwdev, REG_SYS_FUNC_EN, BIT(0) | BIT(1));
+	rtw_write32(rtwdev, REG_BB_SEL_BTG_8723B, value);
+
+	return rtw_read32(rtwdev, REG_BB_SEL_BTG_8723B);
+}
+
+static u32 rtw_coex_8723bs_reassert_pta_ant(struct rtw_dev *rtwdev)
+{
+	return rtw_coex_8723bs_write_bb_sel_btg(rtwdev,
+						rtw_coex_8723bs_pta_ant_path(rtwdev));
+}
+
+static void rtw_coex_8723bs_set_cck_pri(struct rtw_dev *rtwdev, bool high)
+{
+	if (!rtw_coex_8723bs_bt_disabled(rtwdev))
+		return;
+
+	if (high) {
+		rtw_write32(rtwdev, REG_BT_COEX_TABLE_H,
+			    RTW8723BS_COEX_H_WLAN_ACTIVE);
+	} else {
+		rtw_coex_set_wl_pri_mask(rtwdev, COEX_WLPRI_TX_CCK, false);
+		rtw_coex_set_wl_pri_mask(rtwdev, COEX_WLPRI_RX_CCK, false);
+	}
+}
+
+static void rtw_coex_8723bs_restore_pad_ctrl(struct rtw_dev *rtwdev,
+					     bool keep_pta_owner)
+{
+	u32 before, after;
+
+	before = rtw_read32(rtwdev, REG_PAD_CTRL1);
+	after = before & ~(BIT_LNAON_WLBT_SEL | BIT_SW_DPDT_SEL_DATA);
+	if (keep_pta_owner)
+		after |= BIT_PAPE_WLBT_SEL;
+	else
+		after &= ~BIT_PAPE_WLBT_SEL;
+	if (after != before)
+		rtw_write32(rtwdev, REG_PAD_CTRL1, after);
+}
+
+static void rtw_coex_8723bs_fw_gnt_bt_low(struct rtw_dev *rtwdev)
+{
+	if (!rtw_coex_8723bs_bt_disabled(rtwdev))
+		return;
+
+	if (rtw_read8(rtwdev, REG_GNT_BT) == 0x00 &&
+	    rtw_read8(rtwdev, REG_BT_COEX_ENH_INTR_CTRL) == 0x0c)
+		return;
+
+	rtw_fw_set_gnt_bt(rtwdev, 0);
+}
+
+static void rtw_coex_8723bs_reassert_ant_buffer(struct rtw_dev *rtwdev)
+{
+	u8 sys_func_before;
+
+	sys_func_before = rtw_read8(rtwdev, REG_SYS_FUNC_EN);
+	if ((sys_func_before & (BIT(0) | BIT(1))) != (BIT(0) | BIT(1))) {
+		rtw_write8_set(rtwdev, REG_SYS_FUNC_EN, BIT(0) | BIT(1));
+		usleep_range(10, 11);
+	}
+
+	rtw_write8_mask(rtwdev, REG_8723BS_BT_COEX_CTRL, BIT(3), 0x1);
+	rtw_write8(rtwdev, REG_8723BS_BB_ANT_BUF, 0xff);
+	rtw_write8_mask(rtwdev, REG_8723BS_BB_ANT_CFG1, 0x3, 0x3);
+	rtw_write8(rtwdev, REG_8723BS_BB_ANT_CFG, 0x77);
+}
+
+static void rtw_coex_8723bs_apply_scan_table(struct rtw_dev *rtwdev)
+{
+	rtwdev->coex.dm.cur_table = 2;
+	rtw_coex_set_table(rtwdev, true, 0x5a5a5a5a, 0x5a5a5a5a);
+}
+
+/* Non-connected scan/auth workaround: PS-TDMA type 8 off, PTA antenna path,
+ * coex table type 2 (matches the vendor non-connected arbitration).
+ */
+void rtw_coex_8723bs_scan_workaround(struct rtw_dev *rtwdev)
+{
+	struct rtw_coex_dm *coex_dm = &rtwdev->coex.dm;
+	struct rtw_coex_stat *coex_stat = &rtwdev->coex.stat;
+
+	if (!rtw_is_8723bs(rtwdev))
+		return;
+
+	coex_dm->cur_ps_tdma_on = false;
+	coex_dm->cur_ps_tdma = 8;
+	coex_dm->ps_tdma_para[0] = 0x08;
+	coex_dm->ps_tdma_para[1] = 0x00;
+	coex_dm->ps_tdma_para[2] = 0x00;
+	coex_dm->ps_tdma_para[3] = 0x00;
+	coex_dm->ps_tdma_para[4] = 0x00;
+
+	rtw_fw_coex_tdma_type(rtwdev, 0x08, 0x00, 0x00, 0x00, 0x00);
+	rtw_coex_8723bs_fw_gnt_bt_low(rtwdev);
+	rtw_coex_set_ant_path(rtwdev, true, COEX_SET_ANT_2G);
+	rtw_coex_8723bs_reassert_ant_buffer(rtwdev);
+	rtw_coex_8723bs_apply_scan_table(rtwdev);
+	if (coex_stat->bt_disabled)
+		rtw_coex_8723bs_set_cck_pri(rtwdev, true);
+	rtw_coex_8723bs_reassert_pta_ant(rtwdev);
+	rtw_coex_8723bs_restore_pad_ctrl(rtwdev, true);
+}
+
 static const char *rtw_coex_get_algo_string(u8 algo)
 {
 	switch (algo) {
@@ -2770,7 +2920,7 @@ void rtw_coex_power_on_setting(struct rtw_dev *rtwdev)
 	coex->stop_dm = true;
 	coex->wl_rf_off = false;
 
-	/* enable BB, we can write 0x948 */
+	/* enable BB, so BB_SEL_BTG is writable */
 	rtw_write8_set(rtwdev, REG_SYS_FUNC_EN,
 		       BIT_FEN_BB_GLB_RST | BIT_FEN_BB_RSTB);
 
@@ -2877,6 +3027,33 @@ void rtw_coex_scan_notify(struct rtw_dev *rtwdev, u8 type)
 	coex->freeze = false;
 	rtw_coex_write_scbd(rtwdev, COEX_SCBD_ACTIVE | COEX_SCBD_ONOFF, true);
 
+	/* 8723BS SDIO BT-disabled: keep the scan/auth PTA antenna state the
+	 * vendor uses and skip the generic coex run. At scan start (firmware
+	 * has been up long enough for stable RX DMA) replay the vendor BT_MP /
+	 * BT_INFO queries, then re-establish the scan-path PTA setup.
+	 */
+	if (rtw_coex_8723bs_bt_disabled(rtwdev)) {
+		if (type == COEX_SCAN_START_2G || type == COEX_SCAN_START) {
+			struct rtw_coex_info_req req = {};
+
+			coex_stat->cnt_wl[COEX_CNT_WL_SCANAP] = 0;
+			coex_stat->wl_hi_pri_task2 = true;
+
+			req.seq = 0x0e;
+			req.op_code = BT_MP_INFO_OP_SUPP_VER;
+			rtw_fw_query_bt_mp_info(rtwdev, &req);
+			req.seq = 0x0f;
+			req.op_code = BT_MP_INFO_OP_PATCH_VER;
+			rtw_fw_query_bt_mp_info(rtwdev, &req);
+			rtw_fw_query_bt_info(rtwdev);
+
+			rtw_coex_8723bs_scan_workaround(rtwdev);
+		} else {
+			coex_stat->wl_hi_pri_task2 = false;
+		}
+		return;
+	}
+
 	if (type == COEX_SCAN_START_5G) {
 		rtw_dbg(rtwdev, RTW_DBG_COEX,
 			"[BTCoex], SCAN START notify (5G)\n");
diff --git a/drivers/net/wireless/realtek/rtw88/coex.h b/drivers/net/wireless/realtek/rtw88/coex.h
index c398be8391f7..72b1353c9313 100644
--- a/drivers/net/wireless/realtek/rtw88/coex.h
+++ b/drivers/net/wireless/realtek/rtw88/coex.h
@@ -430,4 +430,6 @@ static inline void rtw_coex_active_query_bt_info(struct rtw_dev *rtwdev)
 		rtw_coex_query_bt_info(rtwdev);
 }
 
+void rtw_coex_8723bs_scan_workaround(struct rtw_dev *rtwdev);
+
 #endif
diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h
index 08e9494977e0..b82a8de66e84 100644
--- a/drivers/net/wireless/realtek/rtw88/reg.h
+++ b/drivers/net/wireless/realtek/rtw88/reg.h
@@ -628,6 +628,7 @@
 #define REG_PSD			0x0910
 #define BIT_PSD_INI		GENMASK(23, 22)
 #define REG_SINGLE_TONE_CONT_TX	0x0914
+#define REG_BB_SEL_BTG_8723B	0x0948
 #define REG_AGC_TABLE		0x0958
 #define REG_RFE_CTRL_E		0x0974
 #define REG_2ND_CCA_CTRL	0x0976
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 06/11] wifi: rtw88: coex: reassert the antenna path when associating
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (4 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 05/11] wifi: rtw88: coex: add the RTL8723BS scan antenna workaround luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 07/11] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS luka.gejak
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

The vendor ConnectNotify() returns early without sending H2C commands
when BT is disabled, because the scan path has already established the
antenna configuration, coexistence table and PS-TDMA. What it does keep
is a register level reassertion of the PTA antenna at associate start,
without which the antenna can be left pointing at BT across the
authentication exchange.

Add that reassertion, and the pre-authentication H2C sequence that the
join path added later needs.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/coex.c | 46 +++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/coex.h |  1 +
 2 files changed, 47 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/coex.c b/drivers/net/wireless/realtek/rtw88/coex.c
index daba6082f850..314b2a368a8f 100644
--- a/drivers/net/wireless/realtek/rtw88/coex.c
+++ b/drivers/net/wireless/realtek/rtw88/coex.c
@@ -1541,6 +1541,23 @@ static void rtw_coex_8723bs_fw_gnt_bt_low(struct rtw_dev *rtwdev)
 	rtw_fw_set_gnt_bt(rtwdev, 0);
 }
 
+static void rtw_coex_8723bs_force_assoc_pta_ant(struct rtw_dev *rtwdev)
+{
+	u32 ant_target;
+
+	if (!rtw_coex_8723bs_bt_disabled(rtwdev))
+		return;
+
+	ant_target = rtw_coex_8723bs_pta_ant_path(rtwdev);
+
+	rtw_coex_8723bs_fw_gnt_bt_low(rtwdev);
+	rtw_coex_set_ant_switch(rtwdev, COEX_SWITCH_CTRL_BY_PTA,
+				COEX_SWITCH_TO_NOCARE);
+	rtw_coex_8723bs_set_cck_pri(rtwdev, true);
+	rtw_coex_8723bs_write_bb_sel_btg(rtwdev, ant_target);
+	rtw_coex_8723bs_restore_pad_ctrl(rtwdev, true);
+}
+
 static void rtw_coex_8723bs_reassert_ant_buffer(struct rtw_dev *rtwdev)
 {
 	u8 sys_func_before;
@@ -1593,6 +1610,24 @@ void rtw_coex_8723bs_scan_workaround(struct rtw_dev *rtwdev)
 	rtw_coex_8723bs_restore_pad_ctrl(rtwdev, true);
 }
 
+/* Replayed immediately before start_clnt_join()/auth: BT_INFO + PS-TDMA type 8
+ * then the forced WiFi PTA antenna path, so the auth window is clean.
+ */
+void rtw_coex_8723bs_pre_auth_h2c(struct rtw_dev *rtwdev)
+{
+	lockdep_assert_held(&rtwdev->mutex);
+
+	if (!rtw_coex_8723bs_bt_disabled(rtwdev))
+		return;
+
+	rtw_fw_query_bt_info(rtwdev);
+	rtw_fw_coex_tdma_type(rtwdev, 0x08, 0x00, 0x00, 0x00, 0x00);
+	rtw_fw_coex_tdma_type(rtwdev, 0x08, 0x00, 0x00, 0x00, 0x00);
+	rtw_fw_coex_tdma_type(rtwdev, 0x08, 0x00, 0x00, 0x00, 0x00);
+	rtw_coex_8723bs_fw_gnt_bt_low(rtwdev);
+	rtw_coex_8723bs_force_assoc_pta_ant(rtwdev);
+}
+
 static const char *rtw_coex_get_algo_string(u8 algo)
 {
 	switch (algo) {
@@ -3115,6 +3150,17 @@ void rtw_coex_connect_notify(struct rtw_dev *rtwdev, u8 type)
 	if (coex->manual_control || coex->stop_dm)
 		return;
 
+	/* 8723BS SDIO BT-disabled: the vendor ConnectNotify() early-returns
+	 * without sending H2Cs; scan_workaround already established the PTA
+	 * path / coex table / PS-TDMA. Keep only the register-level PTA
+	 * reassertion at associate-start.
+	 */
+	if (rtw_coex_8723bs_bt_disabled(rtwdev)) {
+		if (type == COEX_ASSOCIATE_START)
+			rtw_coex_8723bs_force_assoc_pta_ant(rtwdev);
+		return;
+	}
+
 	rtw_coex_write_scbd(rtwdev, COEX_SCBD_ACTIVE | COEX_SCBD_ONOFF, true);
 
 	if (type == COEX_ASSOCIATE_5G_START) {
diff --git a/drivers/net/wireless/realtek/rtw88/coex.h b/drivers/net/wireless/realtek/rtw88/coex.h
index 72b1353c9313..c58ae0a50b30 100644
--- a/drivers/net/wireless/realtek/rtw88/coex.h
+++ b/drivers/net/wireless/realtek/rtw88/coex.h
@@ -431,5 +431,6 @@ static inline void rtw_coex_active_query_bt_info(struct rtw_dev *rtwdev)
 }
 
 void rtw_coex_8723bs_scan_workaround(struct rtw_dev *rtwdev);
+void rtw_coex_8723bs_pre_auth_h2c(struct rtw_dev *rtwdev);
 
 #endif
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 07/11] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (5 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 06/11] wifi: rtw88: coex: reassert the antenna path when associating luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 08/11] wifi: rtw88: sdio: set up RX aggregation and interrupts " luka.gejak
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

The RTL8723BS reports free TX page counts that the generic 8051 path
reads back from the chip on every transfer, which is both slow over SDIO
and unreliable on this part: the register frequently reads back zero
while pages are in fact available. It also gates transmission on an
output queue token count that rtw88 does not track at all.

Mirror the vendor driver and keep the per-queue and public page counts
in software, seeded at start and resynchronised from the chip only when
the cached counts say there is not enough room. Wait for an OQT credit
before writing, and account for the pages consumed after a successful
transfer.

Transfers also have to be padded up to the SDIO block size for this
chip rather than using the generic alignment, so size the write
separately from the frame and trim the skb back afterwards.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 220 ++++++++++++++++++++--
 drivers/net/wireless/realtek/rtw88/sdio.h |   6 +
 2 files changed, 215 insertions(+), 11 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 5b40d74b16ee..adf0b509e2cc 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -20,6 +20,7 @@
 #include "tx.h"
 
 #define RTW_SDIO_INDIRECT_RW_RETRIES			50
+#define RTW_SDIO_OQT_TIMEOUT_MS				1000
 
 static bool rtw_sdio_is_bus_addr(u32 addr)
 {
@@ -548,12 +549,91 @@ static int rtw_sdio_read_port(struct rtw_dev *rtwdev, u8 *buf, size_t count)
 	return ret;
 }
 
+static void rtw_sdio_init_free_txpg(struct rtw_dev *rtwdev)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	const struct rtw_page_table *pg_tbl;
+	u32 free_txpg;
+	u16 pubq_num;
+
+	if (!rtw_is_8723bs(rtwdev))
+		return;
+
+	pg_tbl = &rtwdev->chip->page_table[0];
+	pubq_num = rtwdev->fifo.acq_pg_num - pg_tbl->hq_num - pg_tbl->lq_num -
+		   pg_tbl->nq_num - pg_tbl->exq_num - pg_tbl->gapq_num;
+	free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+	if (free_txpg) {
+		atomic_set(&rtwsdio->free_pg_high, free_txpg & 0xff);
+		atomic_set(&rtwsdio->free_pg_normal, (free_txpg >> 8) & 0xff);
+		atomic_set(&rtwsdio->free_pg_low, (free_txpg >> 16) & 0xff);
+		atomic_set(&rtwsdio->free_pg_pub, (free_txpg >> 24) & 0xff);
+	} else {
+		atomic_set(&rtwsdio->free_pg_high, pg_tbl->hq_num);
+		atomic_set(&rtwsdio->free_pg_normal, pg_tbl->nq_num);
+		atomic_set(&rtwsdio->free_pg_low, pg_tbl->lq_num);
+		atomic_set(&rtwsdio->free_pg_pub, pubq_num);
+	}
+
+	atomic_set(&rtwsdio->tx_oqt_free,
+		   rtw_read8(rtwdev, REG_SDIO_OQT_FREE_PG));
+}
+
+static void rtw_sdio_sync_free_txpg(struct rtw_dev *rtwdev)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	u32 free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+
+	if (!free_txpg)
+		return;
+
+	atomic_set(&rtwsdio->free_pg_high, free_txpg & 0xff);
+	atomic_set(&rtwsdio->free_pg_normal, (free_txpg >> 8) & 0xff);
+	atomic_set(&rtwsdio->free_pg_low, (free_txpg >> 16) & 0xff);
+	atomic_set(&rtwsdio->free_pg_pub, (free_txpg >> 24) & 0xff);
+}
+
+static int rtw_sdio_8723bs_free_txpg(struct rtw_dev *rtwdev, u8 queue)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+	switch (queue) {
+	case RTW_TX_QUEUE_BCN:
+	case RTW_TX_QUEUE_H2C:
+	case RTW_TX_QUEUE_HI0:
+	case RTW_TX_QUEUE_MGMT:
+	case RTW_TX_QUEUE_VO:
+		return atomic_read(&rtwsdio->free_pg_high);
+	case RTW_TX_QUEUE_VI:
+		return atomic_read(&rtwsdio->free_pg_normal);
+	case RTW_TX_QUEUE_BE:
+	case RTW_TX_QUEUE_BK:
+		return atomic_read(&rtwsdio->free_pg_low);
+	default:
+		return -EINVAL;
+	}
+}
+
 static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,
 				    size_t count)
 {
 	unsigned int pages_free, pages_needed;
 
-	if (rtw_chip_wcpu_8051(rtwdev)) {
+	if (rtw_is_8723bs(rtwdev)) {
+		struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+		int dedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);
+
+		if (dedicated < 0)
+			return dedicated;
+		pages_free = dedicated + atomic_read(&rtwsdio->free_pg_pub);
+		pages_needed = DIV_ROUND_UP(count, rtwdev->chip->page_size);
+		if (pages_needed <= pages_free)
+			return 0;
+
+		rtw_sdio_sync_free_txpg(rtwdev);
+		dedicated = rtw_sdio_8723bs_free_txpg(rtwdev, queue);
+		pages_free = dedicated + atomic_read(&rtwsdio->free_pg_pub);
+	} else if (rtw_chip_wcpu_8051(rtwdev)) {
 		u32 free_txpg;
 
 		free_txpg = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG);
@@ -632,44 +712,131 @@ static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue,
 	return 0;
 }
 
+static int rtw_sdio_wait_tx_oqt(struct rtw_dev *rtwdev)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	int i;
+	u8 free;
+
+	if (!rtw_is_8723bs(rtwdev))
+		return 0;
+
+	if (atomic_add_unless(&rtwsdio->tx_oqt_free, -1, 0))
+		return 0;
+
+	for (i = 0; i < RTW_SDIO_OQT_TIMEOUT_MS; i++) {
+		free = rtw_read8(rtwdev, REG_SDIO_OQT_FREE_PG);
+		if (free) {
+			atomic_set(&rtwsdio->tx_oqt_free, free - 1);
+			return 0;
+		}
+		usleep_range(1000, 2000);
+	}
+
+	return -EBUSY;
+}
+
+static void rtw_sdio_8723bs_consume_txpg(struct rtw_dev *rtwdev, u8 queue,
+					 unsigned int pages)
+{
+	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	atomic_t *dedicated;
+	int free;
+
+	switch (queue) {
+	case RTW_TX_QUEUE_VI:
+		dedicated = &rtwsdio->free_pg_normal;
+		break;
+	case RTW_TX_QUEUE_BE:
+	case RTW_TX_QUEUE_BK:
+		dedicated = &rtwsdio->free_pg_low;
+		break;
+	default:
+		dedicated = &rtwsdio->free_pg_high;
+		break;
+	}
+
+	free = atomic_read(dedicated);
+	if (pages <= free) {
+		atomic_sub(pages, dedicated);
+	} else {
+		atomic_set(dedicated, 0);
+		atomic_sub(pages - free, &rtwsdio->free_pg_pub);
+	}
+}
+
 static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb,
 			       enum rtw_tx_queue_type queue)
 {
 	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+	unsigned int orig_len = skb->len;
+	bool rtl8723bs = rtw_is_8723bs(rtwdev);
+	unsigned int pages;
 	bool bus_claim;
 	size_t txsize;
+	size_t write_size;
 	u32 txaddr;
 	int ret;
 
-	txaddr = rtw_sdio_get_tx_addr(rtwdev, skb->len, queue);
-	if (!txaddr)
-		return -EINVAL;
+	if (rtl8723bs) {
+		txsize = round_up(orig_len, 4);
+		write_size = txsize > RTW_SDIO_BLOCK_SIZE ?
+			     round_up(txsize, RTW_SDIO_BLOCK_SIZE) : txsize;
+	} else {
+		txsize = sdio_align_size(rtwsdio->sdio_func, orig_len);
+		write_size = txsize;
+	}
+
+	if (write_size > orig_len) {
+		unsigned int padding = write_size - orig_len;
+
+		if (skb_tailroom(skb) < padding) {
+			ret = pskb_expand_head(skb, 0,
+					       padding - skb_tailroom(skb),
+					       GFP_KERNEL);
+			if (ret)
+				return ret;
+		}
+		skb_put_zero(skb, padding);
+	}
 
-	txsize = sdio_align_size(rtwsdio->sdio_func, skb->len);
+	txaddr = rtw_sdio_get_tx_addr(rtwdev, txsize, queue);
+	if (!txaddr) {
+		ret = -EINVAL;
+		goto out_trim;
+	}
 
 	ret = rtw_sdio_check_free_txpg(rtwdev, queue, txsize);
 	if (ret)
-		return ret;
+		goto out_trim;
+	ret = rtw_sdio_wait_tx_oqt(rtwdev);
+	if (ret)
+		goto out_trim;
 
 	if (!IS_ALIGNED((unsigned long)skb->data, RTW_SDIO_DATA_PTR_ALIGN))
 		rtw_warn(rtwdev, "Got unaligned SKB in %s() for queue %u\n",
 			 __func__, queue);
 
 	bus_claim = rtw_sdio_bus_claim_needed(rtwsdio);
-
 	if (bus_claim)
 		sdio_claim_host(rtwsdio->sdio_func);
-
-	ret = sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data, txsize);
-
+	ret = sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data,
+			       write_size);
 	if (bus_claim)
 		sdio_release_host(rtwsdio->sdio_func);
 
+	if (!ret && rtl8723bs) {
+		pages = DIV_ROUND_UP(txsize, rtwdev->chip->page_size);
+		rtw_sdio_8723bs_consume_txpg(rtwdev, queue, pages);
+	}
 	if (ret)
 		rtw_warn(rtwdev,
 			 "Failed to write %zu byte(s) to SDIO port 0x%08x",
-			 txsize, txaddr);
+			 write_size, txaddr);
 
+out_trim:
+	if (write_size > orig_len)
+		skb_trim(skb, orig_len);
 	return ret;
 }
 
@@ -749,8 +916,39 @@ static int rtw_sdio_setup(struct rtw_dev *rtwdev)
 	return 0;
 }
 
+static void rtw_sdio_8723bs_check_rqpn(struct rtw_dev *rtwdev)
+{
+	const struct rtw_chip_info *chip = rtwdev->chip;
+	struct rtw_fifo_conf *fifo = &rtwdev->fifo;
+	const struct rtw_page_table *pg_tbl;
+	u32 free_txpg;
+	u16 pubq_num;
+
+	if (!rtw_is_8723bs(rtwdev))
+		return;
+
+	free_txpg = rtw_read32(rtwdev, REG_SDIO_FREE_TXPG);
+	if (free_txpg || !fifo->acq_pg_num)
+		return;
+
+	pg_tbl = &chip->page_table[0];
+	if (fifo->acq_pg_num <= pg_tbl->hq_num + pg_tbl->lq_num +
+				    pg_tbl->nq_num + pg_tbl->exq_num +
+				    pg_tbl->gapq_num)
+		return;
+
+	pubq_num = fifo->acq_pg_num - pg_tbl->hq_num - pg_tbl->lq_num -
+		   pg_tbl->nq_num - pg_tbl->exq_num - pg_tbl->gapq_num;
+	rtw_write32(rtwdev, REG_RQPN_NPQ,
+		    BIT_RQPN_NE(pg_tbl->nq_num, pg_tbl->exq_num));
+	rtw_write32(rtwdev, REG_RQPN,
+		    BIT_RQPN_HLP(pg_tbl->hq_num, pg_tbl->lq_num, pubq_num));
+}
+
 static int rtw_sdio_start(struct rtw_dev *rtwdev)
 {
+	rtw_sdio_8723bs_check_rqpn(rtwdev);
+	rtw_sdio_init_free_txpg(rtwdev);
 	rtw_sdio_enable_rx_aggregation(rtwdev);
 	rtw_sdio_enable_interrupt(rtwdev);
 
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h
index 457e8b02380e..12086f1aa280 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.h
+++ b/drivers/net/wireless/realtek/rtw88/sdio.h
@@ -159,6 +159,12 @@ struct rtw_sdio {
 	struct workqueue_struct *txwq;
 	struct rtw_sdio_work_data *tx_handler_data;
 	struct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM];
+
+	atomic_t free_pg_high;
+	atomic_t free_pg_normal;
+	atomic_t free_pg_low;
+	atomic_t free_pg_pub;
+	atomic_t tx_oqt_free;
 };
 
 extern const struct dev_pm_ops rtw_sdio_pm_ops;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 08/11] wifi: rtw88: sdio: set up RX aggregation and interrupts for RTL8723BS
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (6 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 07/11] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 09/11] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation luka.gejak
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

Enable the existing RX aggregation setup for this chip and select the
larger DMA aggregation mode it needs. The RTL8723BS does not raise
CPWM1, so leave that source out of its interrupt mask, and set the SDIO
TX control bit the vendor driver uses to have transfers always
recognised.

The chip also keeps raising the interrupt after resume if undefined
status bits are written back when acknowledging, so acknowledge only the
defined and unmasked bits. That is scoped to this chip; the other SDIO
parts keep writing the status word back unchanged.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 28 ++++++++++++++++++++++-
 drivers/net/wireless/realtek/rtw88/sdio.h |  9 ++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index adf0b509e2cc..37d1d58fa55e 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -844,7 +844,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)
@@ -852,6 +856,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:
@@ -879,6 +884,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_set(rtwdev, REG_RXDMA_MODE, 3 << 2);
 }
 
 static void rtw_sdio_enable_interrupt(struct rtw_dev *rtwdev)
@@ -947,9 +954,19 @@ static void rtw_sdio_8723bs_check_rqpn(struct rtw_dev *rtwdev)
 
 static int rtw_sdio_start(struct rtw_dev *rtwdev)
 {
+	u32 clear;
+
 	rtw_sdio_8723bs_check_rqpn(rtwdev);
 	rtw_sdio_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;
@@ -1029,6 +1046,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);
 }
 
@@ -1293,6 +1312,13 @@ 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;
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h
index 12086f1aa280..541d4302ec6b 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)
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 09/11] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (7 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 08/11] wifi: rtw88: sdio: set up RX aggregation and interrupts " luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 10/11] wifi: rtw88: record beacons from the target BSSID before authenticating luka.gejak
  2026-07-25 15:04 ` [PATCH v2 11/11] wifi: rtw88: run the RTL8723BS association register sequence luka.gejak
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

Two problems show up on RTL8723BS uplink. The per-AC software FIFO is
unbounded, so mac80211 keeps handing frames down until latency collapses
under load. And when the chip runs out of free TX pages the queue is
simply abandoned for that pass, which stalls the AC until something else
kicks the worker.

Stop the mac80211 queue once a data AC fills past a high watermark and
wake it from the drain path when it falls back to a low one. Convert the
TX work item to a delayed work so a temporary page shortage can be
retried shortly afterwards instead of stalling, and cancel it on
teardown.

Measured on RTL8723BS hardware, uplink goes from 11.9 Mbit/s with 204
TCP retransmits to 20.1 Mbit/s with 2.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 75 ++++++++++++++++++++---
 drivers/net/wireless/realtek/rtw88/sdio.h |  3 +-
 2 files changed, 68 insertions(+), 10 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 37d1d58fa55e..572b791249ed 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -22,6 +22,15 @@
 #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)
 {
 	return !!(addr & RTW_SDIO_BUS_MSK);
@@ -1030,7 +1039,7 @@ 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);
+	mod_delayed_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work, 0);
 }
 
 static void rtw_sdio_link_ps(struct rtw_dev *rtwdev, bool enter)
@@ -1154,6 +1163,18 @@ 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;
 }
 
@@ -1455,32 +1476,48 @@ 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)
+static int rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev,
+				     enum rtw_tx_queue_type queue,
+				     bool *processed)
 {
 	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
 	struct sk_buff *skb;
+	u16 q_map;
 	int ret;
 
+	*processed = false;
 	skb = skb_dequeue(&rtwsdio->tx_queue[queue]);
 	if (!skb)
-		return;
+		return 0;
 
+	*processed = true;
+	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 0;
 }
 
 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;
+	bool processed;
 	int limit, queue;
 
 	rtwdev = work_data->rtwdev;
@@ -1491,7 +1528,24 @@ 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);
+			int ret;
+
+			ret = rtw_sdio_process_tx_queue(rtwdev, queue, &processed);
+			if (ret) {
+				if (rtw_is_8723bs(rtwdev) && ret == -EBUSY) {
+					mod_delayed_work(rtwsdio->txwq,
+							 &work_data->work,
+							 RTW_SDIO_TX_RETRY_DELAY);
+					return;
+				}
+				break;
+			}
+
+			if (rtw_is_8723bs(rtwdev) &&
+			    queue == RTW_TX_QUEUE_MGMT && processed) {
+				mod_delayed_work(rtwsdio->txwq, &work_data->work, 0);
+				return;
+			}
 
 			if (skb_queue_empty(&rtwsdio->tx_queue[queue]))
 				break;
@@ -1518,14 +1572,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;
 
@@ -1539,6 +1595,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 541d4302ec6b..f1f59f8301b8 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.h
+++ b/drivers/net/wireless/realtek/rtw88/sdio.h
@@ -152,7 +152,7 @@ struct rtw_sdio_tx_data {
 };
 
 struct rtw_sdio_work_data {
-	struct work_struct work;
+	struct delayed_work work;
 	struct rtw_dev *rtwdev;
 };
 
@@ -168,6 +168,7 @@ struct rtw_sdio {
 	struct workqueue_struct *txwq;
 	struct rtw_sdio_work_data *tx_handler_data;
 	struct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM];
+	bool queue_stopped[RTK_MAX_TX_QUEUE_NUM];
 
 	atomic_t free_pg_high;
 	atomic_t free_pg_normal;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 10/11] wifi: rtw88: record beacons from the target BSSID before authenticating
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (8 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 09/11] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  2026-07-25 15:04 ` [PATCH v2 11/11] wifi: rtw88: run the RTL8723BS association register sequence luka.gejak
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

The vendor start_clnt_join() does not begin authentication until it has
seen a beacon or probe response from the target BSSID, and the RTL8723BS
firmware depends on that ordering: authenticating earlier leaves the
firmware without the BSS parameters it needs and the exchange times out.

Record such frames from the receive path so the join sequence can wait
for one. The recording is gated on the chip and only active during the
pre-authentication window, so it costs other chips a single test per
frame.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/mac80211.c | 31 +++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/main.c     |  2 ++
 drivers/net/wireless/realtek/rtw88/main.h     | 20 ++++++++++++
 drivers/net/wireless/realtek/rtw88/sdio.c     |  5 +++
 4 files changed, 58 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c
index b01b98d24b0a..ff0f4f0bdc7b 100644
--- a/drivers/net/wireless/realtek/rtw88/mac80211.c
+++ b/drivers/net/wireless/realtek/rtw88/mac80211.c
@@ -15,6 +15,37 @@
 #include "wow.h"
 #include "sar.h"
 
+/* 8723BS SDIO: record a beacon/probe-resp seen from the target BSSID during
+ * the pre-auth window so the join sequence (mgd_prepare_tx) can wait for it,
+ * mirroring the vendor start_clnt_join(). Called from the SDIO RX path.
+ */
+void rtw8723bs_auth_sync_rx(struct rtw_dev *rtwdev,
+			    const struct ieee80211_hdr *hdr, u32 len,
+			    const struct rtw_rx_pkt_stat *pkt_stat,
+			    const struct ieee80211_rx_status *rx_status)
+{
+	struct rtw_auth_sync *sync = &rtwdev->auth_sync;
+	unsigned long flags;
+	__le16 fc = hdr->frame_control;
+
+	if (!rtw_is_8723bs(rtwdev) ||
+	    test_bit(RTW_FLAG_SCANNING, rtwdev->flags) ||
+	    pkt_stat->crc_err || pkt_stat->icv_err)
+		return;
+
+	if (!ieee80211_is_beacon(fc) && !ieee80211_is_probe_resp(fc))
+		return;
+
+	spin_lock_irqsave(&sync->lock, flags);
+	if (sync->active && ether_addr_equal(hdr->addr3, sync->bssid)) {
+		sync->seen = true;
+		sync->seen_count++;
+		wake_up(&sync->wait);
+	}
+	spin_unlock_irqrestore(&sync->lock, flags);
+}
+EXPORT_SYMBOL(rtw8723bs_auth_sync_rx);
+
 static void rtw_ops_tx(struct ieee80211_hw *hw,
 		       struct ieee80211_tx_control *control,
 		       struct sk_buff *skb)
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index cd9254370fcc..16228cae252e 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -2174,11 +2174,13 @@ int rtw_core_init(struct rtw_dev *rtwdev)
 
 	spin_lock_init(&rtwdev->txq_lock);
 	spin_lock_init(&rtwdev->tx_report.q_lock);
+	spin_lock_init(&rtwdev->auth_sync.lock);
 
 	mutex_init(&rtwdev->mutex);
 	mutex_init(&rtwdev->hal.tx_power_mutex);
 
 	init_waitqueue_head(&rtwdev->coex.wait);
+	init_waitqueue_head(&rtwdev->auth_sync.wait);
 	init_completion(&rtwdev->lps_leave_check);
 	init_completion(&rtwdev->fw_scan_density);
 
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index 8f86f7c12de5..4d3d1d99e305 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -2058,6 +2058,20 @@ struct rtw_hw_scan_info {
 	u8 op_bw;
 };
 
+/*
+ * Synchronises the pre-auth wait on a beacon or probe response from the
+ * target BSSID before the join sequence continues.
+ */
+struct rtw_auth_sync {
+	wait_queue_head_t wait;
+	/* Protects the fields below. */
+	spinlock_t lock;
+	u8 bssid[ETH_ALEN];
+	bool active;
+	bool seen;
+	u32 seen_count;
+};
+
 struct rtw_dev {
 	struct ieee80211_hw *hw;
 	struct device *dev;
@@ -2134,6 +2148,8 @@ struct rtw_dev {
 	struct completion fw_scan_density;
 	bool ap_active;
 
+	struct rtw_auth_sync auth_sync;
+
 	bool led_registered;
 	char led_name[32];
 	struct led_classdev led_cdev;
@@ -2291,4 +2307,8 @@ bool rtw_core_check_sta_active(struct rtw_dev *rtwdev);
 void rtw_core_enable_beacon(struct rtw_dev *rtwdev, bool enable);
 void rtw_set_ampdu_factor(struct rtw_dev *rtwdev, struct ieee80211_vif *vif,
 			  struct ieee80211_bss_conf *bss_conf);
+void rtw8723bs_auth_sync_rx(struct rtw_dev *rtwdev,
+			    const struct ieee80211_hdr *hdr, u32 len,
+			    const struct rtw_rx_pkt_stat *pkt_stat,
+			    const struct ieee80211_rx_status *rx_status);
 #endif
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 572b791249ed..b294bcc4d828 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -1203,6 +1203,11 @@ static void rtw_sdio_rx_skb(struct rtw_dev *rtwdev, struct sk_buff *skb,
 	rtw_update_rx_freq_for_invalid(rtwdev, skb, rx_status, pkt_stat);
 	rtw_rx_stats(rtwdev, pkt_stat->vif, skb);
 
+	if (skb->len >= sizeof(struct ieee80211_hdr_3addr))
+		rtw8723bs_auth_sync_rx(rtwdev,
+				       (struct ieee80211_hdr *)skb->data,
+				       skb->len, pkt_stat, rx_status);
+
 	ieee80211_rx_irqsafe(rtwdev->hw, skb);
 }
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 11/11] wifi: rtw88: run the RTL8723BS association register sequence
  2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
                   ` (9 preceding siblings ...)
  2026-07-25 15:04 ` [PATCH v2 10/11] wifi: rtw88: record beacons from the target BSSID before authenticating luka.gejak
@ 2026-07-25 15:04 ` luka.gejak
  10 siblings, 0 replies; 12+ messages in thread
From: luka.gejak @ 2026-07-25 15:04 UTC (permalink / raw)
  To: Ping-Ke Shih
  Cc: linux-wireless, linux-kernel, Michael Straube, Peter Robinson,
	Bitterblue Smith, Luka Gejak

From: Luka Gejak <luka.gejak@linux.dev>

The RTL8723BS firmware does not derive the BSS parameters itself. The
vendor start_clnt_join() programs them directly around authentication,
and without that the station either fails to authenticate or is dropped
by the AP shortly after associating.

Reproduce that sequence: restrict the receive filter to the target BSSID
for the join window, program the basic and response rates from the BSS
rate elements, apply the preamble and slot time from the BSS capability,
enable TSF update, and wait for a beacon from the target before sending
the authentication frame. Restore the receive filter and response rates
on disconnect.

Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
---
 drivers/net/wireless/realtek/rtw88/mac80211.c | 496 +++++++++++++++++-
 drivers/net/wireless/realtek/rtw88/main.h     |   2 +
 drivers/net/wireless/realtek/rtw88/reg.h      |   2 +
 drivers/net/wireless/realtek/rtw88/sec.h      |   1 +
 4 files changed, 498 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c
index ff0f4f0bdc7b..2bb969e88381 100644
--- a/drivers/net/wireless/realtek/rtw88/mac80211.c
+++ b/drivers/net/wireless/realtek/rtw88/mac80211.c
@@ -46,6 +46,432 @@ void rtw8723bs_auth_sync_rx(struct rtw_dev *rtwdev,
 }
 EXPORT_SYMBOL(rtw8723bs_auth_sync_rx);
 
+/* ---- 8723BS SDIO association sequence (vendor start_clnt_join) ---- */
+
+#define RTW8723BS_JOIN_RETRY_LIMIT		0x30
+#define RTW8723BS_AUTH_SYNC_WAIT_FALLBACK_MS	120
+#define RTW8723BS_AUTH_SYNC_WAIT_MIN_MS		80
+#define RTW8723BS_AUTH_SYNC_WAIT_MAX_MS		160
+#define RTW8723BS_ACK_PREAMBLE_SHORT		BIT(7)
+#define RTW8723BS_SHORT_SLOT_TIME		9
+#define RTW8723BS_LONG_SLOT_TIME		20
+#define RTW8723BS_RRSR_1M			BIT(0)
+#define RTW8723BS_RRSR_2M			BIT(1)
+#define RTW8723BS_RRSR_5_5M			BIT(2)
+#define RTW8723BS_RRSR_11M			BIT(3)
+#define RTW8723BS_RRSR_6M			BIT(4)
+#define RTW8723BS_RRSR_9M			BIT(5)
+#define RTW8723BS_RRSR_12M			BIT(6)
+#define RTW8723BS_RRSR_18M			BIT(7)
+#define RTW8723BS_RRSR_24M			BIT(8)
+#define RTW8723BS_RRSR_36M			BIT(9)
+#define RTW8723BS_RRSR_48M			BIT(10)
+#define RTW8723BS_RRSR_54M			BIT(11)
+#define RTW8723BS_RRSR_CCK_RATES \
+	(RTW8723BS_RRSR_1M | RTW8723BS_RRSR_2M | \
+	 RTW8723BS_RRSR_5_5M | RTW8723BS_RRSR_11M)
+#define RTW8723BS_RRSR_2G_FORCE			RTW8723BS_RRSR_CCK_RATES
+#define RTW8723BS_RRSR_2G_ALLOW \
+	(RTW8723BS_RRSR_CCK_RATES | RTW8723BS_RRSR_6M | \
+	 RTW8723BS_RRSR_12M | RTW8723BS_RRSR_24M)
+
+/* Keep the RCR at the vendor target-only state (AMF + CBSSID) across the whole
+ * connect window, matching the vendor STA path. accept_all is kept only for the
+ * caller's intent; both paths converge the filter to target-only.
+ */
+static void rtw8723bs_auth_rx_filter(struct rtw_dev *rtwdev, bool accept_all)
+{
+	rtwdev->hal.rcr |= BIT_AMF | BIT_CBSSID_DATA | BIT_CBSSID_BCN;
+	rtwdev->hal.rcr &= ~BIT_AAP;
+	rtw_write32(rtwdev, REG_RCR, rtwdev->hal.rcr);
+}
+
+static void rtw8723bs_config_sec_cfg(struct rtw_dev *rtwdev)
+{
+	u16 sec = rtw_read16(rtwdev, RTW_SEC_CONFIG);
+
+	sec |= RTW_SEC_CHK_KEYID | RTW_SEC_TX_DEC_EN | RTW_SEC_RX_DEC_EN;
+	rtw_write16(rtwdev, RTW_SEC_CONFIG, sec);
+}
+
+static void rtw8723bs_config_default_key_search(struct rtw_dev *rtwdev,
+						bool enable)
+{
+	u16 sec = rtw_read16(rtwdev, RTW_SEC_CONFIG);
+
+	if (enable)
+		sec |= RTW_SEC_TX_BC_USE_DK | RTW_SEC_TX_UNI_USE_DK |
+		       RTW_SEC_RX_UNI_USE_DK;
+	else
+		sec &= ~(RTW_SEC_TX_UNI_USE_DK | RTW_SEC_RX_UNI_USE_DK |
+			 RTW_SEC_TX_BC_USE_DK | RTW_SEC_RX_BC_USE_DK);
+	rtw_write16(rtwdev, RTW_SEC_CONFIG, sec);
+}
+
+static void rtw8723bs_enable_tsf_update(struct rtw_dev *rtwdev)
+{
+	rtw_write8_clr(rtwdev, REG_BCN_CTRL, BIT_DIS_TSF_UDT);
+}
+
+static void rtw8723bs_set_ack_preamble(struct rtw_dev *rtwdev,
+				       bool short_preamble)
+{
+	u8 val = rtw_read8(rtwdev, REG_RRSR + 2) & ~RTW8723BS_ACK_PREAMBLE_SHORT;
+
+	if (short_preamble)
+		val |= RTW8723BS_ACK_PREAMBLE_SHORT;
+	rtw_write8(rtwdev, REG_RRSR + 2, val);
+}
+
+static void rtw8723bs_set_slot_time(struct rtw_dev *rtwdev, bool short_slot)
+{
+	rtw_write8(rtwdev, REG_SLOT,
+		   short_slot ? RTW8723BS_SHORT_SLOT_TIME :
+				RTW8723BS_LONG_SLOT_TIME);
+}
+
+static u16 rtw8723bs_rrsr_from_ie_rate(u8 rate)
+{
+	switch (rate & 0x7f) {
+	case 2:   return RTW8723BS_RRSR_1M;
+	case 4:   return RTW8723BS_RRSR_2M;
+	case 11:  return RTW8723BS_RRSR_5_5M;
+	case 22:  return RTW8723BS_RRSR_11M;
+	case 12:  return RTW8723BS_RRSR_6M;
+	case 18:  return RTW8723BS_RRSR_9M;
+	case 24:  return RTW8723BS_RRSR_12M;
+	case 36:  return RTW8723BS_RRSR_18M;
+	case 48:  return RTW8723BS_RRSR_24M;
+	case 72:  return RTW8723BS_RRSR_36M;
+	case 96:  return RTW8723BS_RRSR_48M;
+	case 108: return RTW8723BS_RRSR_54M;
+	default:  return 0;
+	}
+}
+
+static void rtw8723bs_collect_basic_rates(const u8 *ie, u16 *basic_rates,
+					  bool *valid)
+{
+	int i;
+
+	if (!ie)
+		return;
+
+	for (i = 0; i < ie[1]; i++) {
+		u16 r;
+
+		if (!(ie[i + 2] & 0x80))
+			continue;
+		r = rtw8723bs_rrsr_from_ie_rate(ie[i + 2]);
+		if (!r)
+			continue;
+		*basic_rates |= r;
+		*valid = true;
+	}
+}
+
+static void rtw8723bs_reset_response_rates(struct rtw_dev *rtwdev)
+{
+	rtw_write32(rtwdev, REG_RRSR, 0xffff1);
+	rtwdev->dm_info.rrsr_val_init = 0xffff1;
+}
+
+static void rtw8723bs_apply_basic_rates(struct rtw_dev *rtwdev,
+					struct ieee80211_vif *vif,
+					const u8 *bssid)
+{
+	struct ieee80211_bss_conf *conf = &vif->bss_conf;
+	struct cfg80211_bss *lookup_bss = NULL;
+	struct cfg80211_bss *bss = NULL;
+	bool valid = false;
+	u16 basic_rates = 0;
+
+	if (!rtw_is_8723bs(rtwdev) || vif->type != NL80211_IFTYPE_STATION)
+		return;
+
+	if (conf->bss) {
+		bss = conf->bss;
+	} else if (bssid && is_valid_ether_addr(bssid)) {
+		lookup_bss = cfg80211_get_bss(rtwdev->hw->wiphy, NULL,
+					      bssid, NULL, 0,
+					      IEEE80211_BSS_TYPE_ESS,
+					      IEEE80211_PRIVACY_ANY);
+		bss = lookup_bss;
+	}
+
+	if (bss) {
+		const u8 *rates, *ext;
+
+		rcu_read_lock();
+		rates = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
+		ext = ieee80211_bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES);
+		rtw8723bs_collect_basic_rates(rates, &basic_rates, &valid);
+		rtw8723bs_collect_basic_rates(ext, &basic_rates, &valid);
+		rcu_read_unlock();
+	}
+
+	if (valid) {
+		basic_rates |= RTW8723BS_RRSR_2G_FORCE;
+		basic_rates &= RTW8723BS_RRSR_2G_ALLOW;
+		rtw_write16(rtwdev, REG_RRSR, basic_rates);
+		rtw_write8(rtwdev, REG_RRSR + 2,
+			   rtw_read8(rtwdev, REG_RRSR + 2) & 0xf0);
+		rtwdev->dm_info.rrsr_val_init = basic_rates;
+	}
+
+	if (lookup_bss)
+		cfg80211_put_bss(rtwdev->hw->wiphy, lookup_bss);
+}
+
+/* Program response slot time (and, when set_preamble, the ACK preamble) from
+ * the selected scan BSS capabilities; the AP capabilities are not yet in
+ * bss_conf at mgd_prepare_tx() time.
+ */
+static void rtw8723bs_apply_bss_cap(struct rtw_dev *rtwdev,
+				    struct ieee80211_vif *vif,
+				    const u8 *bssid, bool set_preamble)
+{
+	struct ieee80211_bss_conf *conf = &vif->bss_conf;
+	struct cfg80211_bss *lookup_bss = NULL;
+	struct cfg80211_bss *bss = NULL;
+	bool short_preamble, short_slot;
+	u16 cap = 0;
+
+	if (!rtw_is_8723bs(rtwdev) || vif->type != NL80211_IFTYPE_STATION)
+		return;
+
+	if (conf->bss) {
+		bss = conf->bss;
+	} else if (bssid && is_valid_ether_addr(bssid)) {
+		lookup_bss = cfg80211_get_bss(rtwdev->hw->wiphy, NULL,
+					      bssid, NULL, 0,
+					      IEEE80211_BSS_TYPE_ESS,
+					      IEEE80211_PRIVACY_ANY);
+		bss = lookup_bss;
+	}
+
+	if (bss) {
+		cap = bss->capability;
+	} else if (conf->assoc_capability) {
+		cap = conf->assoc_capability;
+	} else {
+		short_preamble = conf->use_short_preamble;
+		short_slot = conf->use_short_slot;
+		goto program;
+	}
+	short_preamble = !!(cap & WLAN_CAPABILITY_SHORT_PREAMBLE);
+	short_slot = !!(cap & WLAN_CAPABILITY_SHORT_SLOT_TIME);
+
+program:
+	if (set_preamble)
+		rtw8723bs_set_ack_preamble(rtwdev, short_preamble);
+	rtw8723bs_set_slot_time(rtwdev, short_slot);
+
+	if (lookup_bss)
+		cfg80211_put_bss(rtwdev->hw->wiphy, lookup_bss);
+}
+
+static unsigned int rtw8723bs_auth_sync_wait_ms(struct ieee80211_vif *vif)
+{
+	u16 beacon_int = vif->bss_conf.beacon_int;
+	unsigned int wait_ms;
+
+	if (!beacon_int)
+		return RTW8723BS_AUTH_SYNC_WAIT_FALLBACK_MS;
+
+	wait_ms = DIV_ROUND_UP(beacon_int * 1024, 1000) + 20;
+	return clamp_t(unsigned int, wait_ms, RTW8723BS_AUTH_SYNC_WAIT_MIN_MS,
+		       RTW8723BS_AUTH_SYNC_WAIT_MAX_MS);
+}
+
+static void rtw8723bs_auth_sync_start(struct rtw_dev *rtwdev, const u8 *bssid)
+{
+	struct rtw_auth_sync *sync = &rtwdev->auth_sync;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sync->lock, flags);
+	ether_addr_copy(sync->bssid, bssid);
+	sync->seen = false;
+	sync->seen_count = 0;
+	sync->active = true;
+	spin_unlock_irqrestore(&sync->lock, flags);
+}
+
+static void rtw8723bs_auth_sync_stop(struct rtw_dev *rtwdev)
+{
+	struct rtw_auth_sync *sync = &rtwdev->auth_sync;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sync->lock, flags);
+	sync->active = false;
+	spin_unlock_irqrestore(&sync->lock, flags);
+}
+
+static bool rtw8723bs_auth_sync_seen(struct rtw_dev *rtwdev)
+{
+	struct rtw_auth_sync *sync = &rtwdev->auth_sync;
+	unsigned long flags;
+	bool seen;
+
+	spin_lock_irqsave(&sync->lock, flags);
+	seen = sync->seen;
+	spin_unlock_irqrestore(&sync->lock, flags);
+
+	return seen;
+}
+
+static bool rtw8723bs_auth_sync_wait(struct rtw_dev *rtwdev,
+				     unsigned int wait_ms)
+{
+	struct rtw_auth_sync *sync = &rtwdev->auth_sync;
+
+	return wait_event_timeout(sync->wait, rtw8723bs_auth_sync_seen(rtwdev),
+				  msecs_to_jiffies(wait_ms)) > 0;
+}
+
+static bool rtw8723bs_mgd_prepare_is_auth(struct rtw_dev *rtwdev,
+					  struct ieee80211_prep_tx_info *info)
+{
+	return rtw_is_8723bs(rtwdev) && info &&
+	       info->subtype == IEEE80211_STYPE_AUTH;
+}
+
+/* Replicate the vendor start_clnt_join() register programming right before
+ * auth. Returns true for a fresh join (BSSID changed).
+ */
+static bool rtw8723bs_mgd_prepare_join(struct rtw_dev *rtwdev,
+				       struct ieee80211_vif *vif,
+				       const u8 *bssid)
+{
+	struct rtw_vif *rtwvif = (struct rtw_vif *)vif->drv_priv;
+	bool fresh_join;
+	u16 retry_limit;
+
+	if (!is_valid_ether_addr(bssid))
+		return false;
+
+	fresh_join = !ether_addr_equal(rtwvif->bssid, bssid);
+
+	ether_addr_copy(rtwvif->bssid, bssid);
+	rtwvif->aid = 0;
+	rtwvif->net_type = RTW_NET_MGD_LINKED;
+	rtw_vif_port_config(rtwdev, rtwvif,
+			    PORT_SET_BSSID | PORT_SET_AID | PORT_SET_NET_TYPE);
+
+	/* Do not narrow RRSR or switch to short-preamble responses before the
+	 * exchange: the whole auth/assoc runs on the init response set
+	 * (0xffff1, long preamble). Only slot time is programmed early.
+	 */
+	rtw8723bs_apply_bss_cap(rtwdev, vif, bssid, false);
+
+	rtw_fw_beacon_filter_config(rtwdev, false, vif);
+
+	/* Match the vendor start_clnt_join() TX state (set_msr directly, keep
+	 * BCN_CTRL / BCNQ_DL set, reassert TBTT/RESP_SIFS every join).
+	 */
+	rtw_write8(rtwdev, REG_BCN_CTRL,
+		   BIT_DIS_TSF_UDT | BIT_EN_BCN_FUNCTION);
+	rtw_write32_set(rtwdev, REG_FWHW_TXQ_CTRL, BIT_EN_BCNQ_DL);
+	rtw_write8(rtwdev, REG_TBTT_PROHIBIT + 1, 0x64 & 0xff);
+	rtw_write8(rtwdev, REG_TBTT_PROHIBIT + 2,
+		   (rtw_read8(rtwdev, REG_TBTT_PROHIBIT + 2) & 0xf0) | (0x64 >> 8));
+	rtw_write16(rtwdev, REG_RESP_SIFS_CCK, 0x0808);
+	rtw_write16(rtwdev, REG_RESP_SIFS_OFDM, 0x0a0a);
+
+	rtw_write16(rtwdev, REG_RXFLTMAP0, 0xffff);
+	rtw_write16(rtwdev, REG_RXFLTMAP2, 0xffff);
+	rtw8723bs_auth_rx_filter(rtwdev, true);
+
+	retry_limit = (RTW8723BS_JOIN_RETRY_LIMIT << 8) |
+		      RTW8723BS_JOIN_RETRY_LIMIT;
+	rtw_write16(rtwdev, REG_RETRY_LIMIT, retry_limit);
+
+	rtw8723bs_config_sec_cfg(rtwdev);
+
+	return fresh_join;
+}
+
+/* The vendor sends a deauth to the target before auth to clear stale AP-side
+ * state; synthesize and TX one, then let the AP settle.
+ */
+static void rtw8723bs_tx_pre_auth_deauth(struct rtw_dev *rtwdev,
+					 struct ieee80211_vif *vif,
+					 const u8 *bssid)
+{
+	struct ieee80211_tx_control control = {};
+	struct ieee80211_tx_info *info;
+	struct ieee80211_mgmt *mgmt;
+	struct sk_buff *skb;
+	unsigned int frame_len, headroom;
+
+	frame_len = sizeof(struct ieee80211_hdr_3addr) + sizeof(mgmt->u.deauth);
+	headroom = rtwdev->chip->tx_pkt_desc_sz + 8;
+
+	skb = dev_alloc_skb(headroom + frame_len);
+	if (!skb)
+		return;
+
+	skb_reserve(skb, headroom);
+	mgmt = skb_put_zero(skb, frame_len);
+	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+					  IEEE80211_STYPE_DEAUTH);
+	memcpy(mgmt->da, bssid, ETH_ALEN);
+	memcpy(mgmt->sa, vif->addr, ETH_ALEN);
+	memcpy(mgmt->bssid, bssid, ETH_ALEN);
+	mgmt->u.deauth.reason_code = cpu_to_le16(WLAN_REASON_DEAUTH_LEAVING);
+
+	info = IEEE80211_SKB_CB(skb);
+	memset(info, 0, sizeof(*info));
+	info->control.vif = vif;
+
+	rtw_tx(rtwdev, &control, skb);
+	msleep(100);
+}
+
+/* Orchestrate the pre-auth join: program the vendor join state, send the
+ * pre-auth deauth, wait for a beacon from the target, then replay the
+ * pre-auth coex H2Cs - once per fresh BSSID.
+ */
+static void rtw8723bs_mgd_prepare_auth_join(struct rtw_dev *rtwdev,
+					    struct ieee80211_vif *vif,
+					    struct ieee80211_prep_tx_info *info)
+{
+	struct rtw_vif *rtwvif;
+	const u8 *bssid = NULL;
+	bool fresh_join;
+
+	if (!rtw8723bs_mgd_prepare_is_auth(rtwdev, info) || !vif ||
+	    test_bit(RTW_FLAG_SCANNING, rtwdev->flags))
+		return;
+
+	rtwvif = (struct rtw_vif *)vif->drv_priv;
+
+	if (!is_zero_ether_addr(vif->cfg.ap_addr))
+		bssid = vif->cfg.ap_addr;
+	else if (vif->bss_conf.bssid && !is_zero_ether_addr(vif->bss_conf.bssid))
+		bssid = vif->bss_conf.bssid;
+
+	if (!bssid)
+		return;
+
+	fresh_join = rtw8723bs_mgd_prepare_join(rtwdev, vif, bssid);
+
+	if (fresh_join || !rtwvif->pre_auth_join_done) {
+		unsigned int wait_ms = rtw8723bs_auth_sync_wait_ms(vif);
+
+		rtw8723bs_auth_sync_start(rtwdev, bssid);
+		rtw8723bs_tx_pre_auth_deauth(rtwdev, vif, bssid);
+		rtw8723bs_auth_sync_wait(rtwdev, wait_ms);
+		rtw8723bs_auth_sync_stop(rtwdev);
+		rtwvif->pre_auth_join_done = true;
+	}
+
+	if (!rtwvif->pre_auth_h2c_sent) {
+		rtw_coex_8723bs_pre_auth_h2c(rtwdev);
+		rtwvif->pre_auth_h2c_sent = true;
+	}
+}
+
 static void rtw_ops_tx(struct ieee80211_hw *hw,
 		       struct ieee80211_tx_control *control,
 		       struct sk_buff *skb)
@@ -424,6 +850,13 @@ static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw,
 	if (changed & BSS_CHANGED_ASSOC) {
 		rtw_vif_assoc_changed(rtwvif, conf);
 		if (vif->cfg.assoc) {
+			if (rtw_is_8723bs(rtwdev) &&
+			    vif->type == NL80211_IFTYPE_STATION) {
+				rtw8723bs_auth_rx_filter(rtwdev, false);
+				rtw8723bs_apply_basic_rates(rtwdev, vif, NULL);
+				rtw8723bs_enable_tsf_update(rtwdev);
+			}
+
 			rtw_coex_connect_notify(rtwdev, COEX_ASSOCIATE_FINISH);
 
 			rtw_fw_download_rsvd_page(rtwdev);
@@ -445,6 +878,13 @@ static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw,
 			if (test_bit(RTW_FLAG_SCANNING, rtwdev->flags))
 				rtw_hw_scan_abort(rtwdev);
 
+			if (rtw_is_8723bs(rtwdev) &&
+			    vif->type == NL80211_IFTYPE_STATION) {
+				rtw8723bs_auth_rx_filter(rtwdev, false);
+				rtw8723bs_reset_response_rates(rtwdev);
+				rtwvif->pre_auth_h2c_sent = false;
+				rtwvif->pre_auth_join_done = false;
+			}
 		}
 
 		config |= PORT_SET_NET_TYPE;
@@ -452,8 +892,26 @@ static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw,
 	}
 
 	if (changed & BSS_CHANGED_BSSID) {
+		bool bssid_cleared = is_zero_ether_addr(conf->bssid);
+		bool bssid_changed = !ether_addr_equal(rtwvif->bssid,
+						       conf->bssid);
+
+		if (rtw_is_8723bs(rtwdev) &&
+		    vif->type == NL80211_IFTYPE_STATION && bssid_changed) {
+			rtwvif->pre_auth_h2c_sent = false;
+			rtwvif->pre_auth_join_done = false;
+		}
 		ether_addr_copy(rtwvif->bssid, conf->bssid);
 		config |= PORT_SET_BSSID;
+		if (rtw_is_8723bs(rtwdev) &&
+		    vif->type == NL80211_IFTYPE_STATION && bssid_cleared) {
+			rtwvif->aid = 0;
+			rtwvif->net_type = RTW_NET_NO_LINK;
+			config |= PORT_SET_NET_TYPE | PORT_SET_AID;
+			rtw_write8(rtwdev, REG_BCN_CTRL,
+				   BIT_DIS_TSF_UDT | BIT_EN_BCN_FUNCTION |
+				   BIT_DIS_ATIM);
+		}
 		if (!rtw_core_check_sta_active(rtwdev))
 			rtw_clear_op_chan(rtwdev);
 		else
@@ -485,8 +943,17 @@ static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw,
 	if (changed & BSS_CHANGED_MU_GROUPS)
 		rtw_chip_set_gid_table(rtwdev, vif, conf);
 
-	if (changed & BSS_CHANGED_ERP_SLOT)
+	if (changed & BSS_CHANGED_ERP_PREAMBLE &&
+	    rtw_is_8723bs(rtwdev) &&
+	    vif->type == NL80211_IFTYPE_STATION)
+		rtw8723bs_set_ack_preamble(rtwdev, conf->use_short_preamble);
+
+	if (changed & BSS_CHANGED_ERP_SLOT) {
+		if (rtw_is_8723bs(rtwdev) &&
+		    vif->type == NL80211_IFTYPE_STATION)
+			rtw8723bs_set_slot_time(rtwdev, conf->use_short_slot);
 		rtw_conf_tx(rtwdev, rtwvif);
+	}
 
 	if (changed & BSS_CHANGED_PS)
 		rtw_recalc_lps(rtwdev, NULL);
@@ -646,11 +1113,19 @@ static int rtw_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 		key->hw_key_idx = hw_key_idx;
 		rtw_sec_write_cam(rtwdev, sec, sta, key,
 				  hw_key_type, hw_key_idx);
+		if (rtw_is_8723bs(rtwdev) && vif &&
+		    vif->type == NL80211_IFTYPE_STATION &&
+		    !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
+			rtw8723bs_config_default_key_search(rtwdev, true);
 		break;
 	case DISABLE_KEY:
 		rtw_hci_flush_all_queues(rtwdev, false);
 		rtw_mac_flush_all_queues(rtwdev, false);
 		rtw_sec_clear_cam(rtwdev, sec, key->hw_key_idx);
+		if (rtw_is_8723bs(rtwdev) && vif &&
+		    vif->type == NL80211_IFTYPE_STATION &&
+		    !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
+			rtw8723bs_config_default_key_search(rtwdev, false);
 		break;
 	}
 
@@ -740,8 +1215,23 @@ static void rtw_ops_mgd_prepare_tx(struct ieee80211_hw *hw,
 
 	mutex_lock(&rtwdev->mutex);
 	rtw_leave_lps_deep(rtwdev);
-	rtw_coex_connect_notify(rtwdev, COEX_ASSOCIATE_START);
-	rtw_chip_prepare_tx(rtwdev);
+
+	if (rtw_is_8723bs(rtwdev)) {
+		/* Wake from soft IPS and run the vendor join sequence. The RFK
+		 * is handled by the once-only power-on IQK plus the ps.c
+		 * post-IPS RF-bus recovery, not a fresh calibration here.
+		 */
+		if (rtw_leave_ips(rtwdev)) {
+			rtw_err(rtwdev, "failed to leave idle state for mgd tx\n");
+			goto out;
+		}
+		rtw_coex_connect_notify(rtwdev, COEX_ASSOCIATE_START);
+		rtw8723bs_mgd_prepare_auth_join(rtwdev, vif, info);
+	} else {
+		rtw_coex_connect_notify(rtwdev, COEX_ASSOCIATE_START);
+		rtw_chip_prepare_tx(rtwdev);
+	}
+out:
 	mutex_unlock(&rtwdev->mutex);
 }
 
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index 4d3d1d99e305..6e0cb58259e1 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -829,6 +829,8 @@ struct rtw_vif {
 	u8 bssid[ETH_ALEN];
 	u8 port;
 	u8 bcn_ctrl;
+	bool pre_auth_h2c_sent;
+	bool pre_auth_join_done;
 	struct list_head rsvd_page_list;
 	struct ieee80211_tx_queue_params tx_params[IEEE80211_NUM_ACS];
 	const struct rtw_vif_port *conf;
diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h
index b82a8de66e84..b6d4e8ed98c7 100644
--- a/drivers/net/wireless/realtek/rtw88/reg.h
+++ b/drivers/net/wireless/realtek/rtw88/reg.h
@@ -479,6 +479,7 @@
 #define BIT_DIS_TSF_UDT		BIT(4)
 #define BIT_EN_BCN_FUNCTION	BIT(3)
 #define BIT_EN_TXBCN_RPT	BIT(2)
+#define BIT_DIS_ATIM		BIT(0)
 #define REG_BCN_CTRL_CLINT0	0x0551
 #define REG_DRVERLYINT		0x0558
 #define REG_BCNDMATIM		0x0559
@@ -518,6 +519,7 @@
 #define BIT_UC_MD_EN		BIT(16)
 #define BIT_RXSK_PERPKT		BIT(15)
 #define BIT_HTC_LOC_CTRL	BIT(14)
+#define BIT_AMF			BIT(13)
 #define BIT_RPFM_CAM_ENABLE	BIT(12)
 #define BIT_TA_BCN		BIT(11)
 #define BIT_RCR_ADF		BIT(11)
diff --git a/drivers/net/wireless/realtek/rtw88/sec.h b/drivers/net/wireless/realtek/rtw88/sec.h
index efcf45433999..73f2af66effe 100644
--- a/drivers/net/wireless/realtek/rtw88/sec.h
+++ b/drivers/net/wireless/realtek/rtw88/sec.h
@@ -22,6 +22,7 @@
 #define RTW_SEC_RX_DEC_EN		BIT(3)
 #define RTW_SEC_TX_BC_USE_DK		BIT(6)
 #define RTW_SEC_RX_BC_USE_DK		BIT(7)
+#define RTW_SEC_CHK_KEYID		BIT(8)
 
 #define RTW_SEC_ENGINE_EN		BIT(9)
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-07-25 15:05 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-25 15:04 [PATCH v2 00/11] wifi: rtw88: preparations for RTL8723B/RTL8723BS luka.gejak
2026-07-25 15:04 ` [PATCH v2 01/11] wifi: rtw88: add the RTL8723B chip type and SDIO helper luka.gejak
2026-07-25 15:04 ` [PATCH v2 02/11] wifi: rtw88: rx: mark zero length packets on RTL8723BS luka.gejak
2026-07-25 15:04 ` [PATCH v2 03/11] wifi: rtw88: fw: add the GNT_BT firmware command luka.gejak
2026-07-25 15:04 ` [PATCH v2 04/11] wifi: rtw88: fw: fix the reserved page upload on RTL8723BS luka.gejak
2026-07-25 15:04 ` [PATCH v2 05/11] wifi: rtw88: coex: add the RTL8723BS scan antenna workaround luka.gejak
2026-07-25 15:04 ` [PATCH v2 06/11] wifi: rtw88: coex: reassert the antenna path when associating luka.gejak
2026-07-25 15:04 ` [PATCH v2 07/11] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS luka.gejak
2026-07-25 15:04 ` [PATCH v2 08/11] wifi: rtw88: sdio: set up RX aggregation and interrupts " luka.gejak
2026-07-25 15:04 ` [PATCH v2 09/11] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation luka.gejak
2026-07-25 15:04 ` [PATCH v2 10/11] wifi: rtw88: record beacons from the target BSSID before authenticating luka.gejak
2026-07-25 15:04 ` [PATCH v2 11/11] wifi: rtw88: run the RTL8723BS association register sequence luka.gejak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox