From nobody Fri Sep 25 19:13:35 2026 Received: from mta1.migadu.com (out-63.mta1.migadu.com [95.215.58.63]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 71185357D0F for ; Wed, 9 Sep 2026 07:46:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.63 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939978; cv=none; b=Q3d0eCUFJMW1CVTa2lFiXShfikj7Qv7ffXcYKzZ7My2SiDMz61QzRh/F/ePpLSuQZokX6wGnJ9cAlmdY8ex5EKIgsJcDp/4C4ct8E/akns2yuVGgFvQoxh3Z7Rp4PqdxWmdgKYagXBvaVDLouJjXEQGaBopSMSAWNmQfB5BGosk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939978; c=relaxed/simple; bh=+lksqU2WhGpad9JilfMgl01dXqETbmY1NREALhnrYhk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=A8XRIxI7rb/VWJ8lUdXlEllwh1CPLl8PJ0WToDInqQptBLcO6NX7tGulaMqa+8Lv0az18mrIQmvhwTTMOU3BsDX+bB35YKTuDgqJ5i+bGbi35QqiNpNMPSPwT0R4ClDYdHCiUrX0COnix/KnC2zBSAW2LsVTdiB4OzVbC+WUZ9k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=u38IEegn; arc=none smtp.client-ip=95.215.58.63 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="u38IEegn" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=+lksqU2WhGpad9JilfMgl01dXqETbmY1NREALhnrYhk=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939972; v=1; x=1789544772; b=u38IEegnjlBgCClb26xOlizFZ9fBFAmvmBw8cntMBE3myTbmfzrviyE1VGoJgPFzc1CmAcbs sJ8EM1jMoAC3gO/wm40e5CqP/BXRxr1VoCNOBUmNjlB3KugH3+O+6VNuO7ZPyotWXheEw6zID8x bj2uS+P01fgyeSoAcE41iK4k= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id 66143cb18931b1c6; Wed, 09 Sep 2026 07:46:12 +0000 X-Mizu-Trace-ID: 66143cb18931b1c6 X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 1/7] wifi: rtw88: add the RTL8723B chip type and SDIO helper Date: Wed, 9 Sep 2026 09:45:50 +0200 Message-ID: <20260909074556.55709-2-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak 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 Acked-by: Ping-Ke Shih Tested-by: Peter Robinson # Pine64+ and Pine64-LTS --- Notes: Changes in v11: none. Changes in v10: none. Changes in v9: none. Changes in v8: none. Changes in v7: none. Changes in v6: none. Changes in v5: none. 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/wirele= ss/realtek/rtw88/main.h index 6883fbd9f768..d59f6e323adf 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, @@ -2195,6 +2196,12 @@ static inline bool rtw_chip_has_tx_stbc(struct rtw_d= ev *rtwdev) return rtwdev->chip->tx_stbc; } =20 +static inline bool rtw_is_8723bs(struct rtw_dev *rtwdev) +{ + return rtwdev->chip->id =3D=3D RTW_CHIP_TYPE_8723B && + rtwdev->hci.type =3D=3D RTW_HCI_TYPE_SDIO; +} + static inline u8 rtw_acquire_macid(struct rtw_dev *rtwdev) { unsigned long mac_id; --=20 2.55.0 From nobody Fri Sep 25 19:13:35 2026 Received: from mta1.migadu.com (out-72.mta1.migadu.com [95.215.58.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 278A225F99F for ; Wed, 9 Sep 2026 07:46:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939981; cv=none; b=O0HOA7/bVhtUgMbqkuZ0e7ZTkl245W04HLyTSYp2MewJMhveKe2e0dzMTaoGTixW3mkY9r0GBqHj/t2cagzy4GAGlQYiYt10SdgfzxH6EkbswtcGWlBA20u6A+iYFjFHkAAmb5emo4kKuspmlrtnJP2IhIbmwOdMNMB5M7PULI4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939981; c=relaxed/simple; bh=1HiIcPTQpatgTULQZq+hKSjJxRyFSlBPlKLxkQTGBJc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RYcBC6eLu57vxmO2QLbHNVq4P9b/fZ+wQu7z/pPQijiNT3YkZerjuz8jToKRH2pN44tX3yEYjGr/TTCVlHPL/JPIY0ys1e0vRRTOkcPt2Q1CYTJfRtumrQCOYEOKNMXkjs4LBRPNL9POXHhaLmUFbxZhLXUSA1HrwEVJKeXly0c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=coLEw6CS; arc=none smtp.client-ip=95.215.58.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="coLEw6CS" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=1HiIcPTQpatgTULQZq+hKSjJxRyFSlBPlKLxkQTGBJc=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939977; v=1; x=1789544777; b=coLEw6CS11UEyfIKmGB6/shFa1kz53s+d0aPO4zmlEZRrd8jUoBOnwyth7Ex50gnRJnQ3FZ0 HtZpbWvk8qzdCDwCtzThDe5+q2fXLFZlCVp+GSUGhXi1GzxCVr+n+vrU+Va6N1U0FyctRMTANLw vWMLHILGtEVfM2RGI066ARbY= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id fa9e0f830415ec3f; Wed, 09 Sep 2026 07:46:17 +0000 X-Mizu-Trace-ID: fa9e0f830415ec3f X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 2/7] wifi: rtw88: rx: mark zero length packets on RTL8723BS Date: Wed, 9 Sep 2026 09:45:51 +0200 Message-ID: <20260909074556.55709-3-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak 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. Co-developed-by: Michael Straube Signed-off-by: Michael Straube Signed-off-by: Luka Gejak Acked-by: Ping-Ke Shih --- Notes: Changes in v11: none. Changes in v10: none. Changes in v9: none. Changes in v8: none. Changes in v7: none. Changes in v6: none. Changes in v5: the zero length test now evaluates pkt_stat->pkt_len first, so the unlikely case short circuits before the chip test. 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..fde21c840ac0 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 *rtw= dev, =20 rtw_rx_addr_match(rtwdev, pkt_stat, hdr); =20 - /* 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 =3D=3D RTW_CHIP_TYPE_8703B && pkt_stat->pkt_len =3D= =3D 0) { + if (pkt_stat->pkt_len =3D=3D 0 && + (rtwdev->chip->id =3D=3D RTW_CHIP_TYPE_8703B || rtw_is_8723bs(rtwdev)= )) { rx_status->flag |=3D RX_FLAG_NO_PSDU; rtw_dbg(rtwdev, RTW_DBG_RX, "zero length packet"); } --=20 2.55.0 From nobody Fri Sep 25 19:13:35 2026 Received: from mta0.migadu.com (out-1.mta0.migadu.com [91.218.175.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED3853A7F49 for ; Wed, 9 Sep 2026 07:46:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.1 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939987; cv=none; b=sIxVvts5yLCwhMRudP2B8PtaC3/rBAt7N/zLfXghMjHcAIRmJnPx0v5QUPkhmIZwaIfZhtyYKRl4qIETw1WsqvIiXw12WJFLhPBktDNffc9l9Xk7QpYtC4F5onamyTIft1rWCVcWFgw1IoIXObgTHNyOZaCex2CXfqyPTVB+/CQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939987; c=relaxed/simple; bh=bzxSM15rzJHsp6riBpQ3uZbf0JigR6jvr11t70GGikw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YZ7DJIxPyJJqrlY1g/Z4QfuAvIagLtnuxtNnh+MLGKGUccefiY8CvzDY0P4PNbao2RbsaTxCjdmXR3sMvB3fSWyLoha4YyKyggOk82yaMkDvBeM205MxF3Hw5Q/YQlthejsB3U7Kc6xIbiFFmsf/At87zggGXgU+FbuD70StlGc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=YqvuFNWP; arc=none smtp.client-ip=91.218.175.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="YqvuFNWP" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=bzxSM15rzJHsp6riBpQ3uZbf0JigR6jvr11t70GGikw=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939982; v=1; x=1789544782; b=YqvuFNWPB35XxwNnv9svJPxW5il/ZSY5j445VXwB5rNmW0lUl3xHZBu5GhW9Uhl+sJaZX5pa 8JKVbuDs34c6v3GBi24FVA5cCJXUinnEwFUiL9mlIgsSJ90iK7sAJ/IcBhMIO/Qkp0N4PGbG2O9 JzhSyfRE7WCazcWhVL0HVf5c= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id f19ea53a44304837; Wed, 09 Sep 2026 07:46:22 +0000 X-Mizu-Trace-ID: f19ea53a44304837 X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 3/7] wifi: rtw88: tx: extend the TX report purge timeout to RTL8723BS Date: Wed, 9 Sep 2026 09:45:52 +0200 Message-ID: <20260909074556.55709-4-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak Commit c80788f7c5ae ("wifi: rtw88: increase TX report timeout to fix race condition") raised the purge timeout to 2500 ms for the RTL8723DU, because the firmware can stay off channel during background scans for longer than the 500 ms default, which delays the TX reports and lets the purge timer drop the tracking skbs. The host stack then reads the missing status as loss and collapses TCP throughput. The RTL8723BS runs the same vendor firmware over a slower SDIO host and hits the same race. Testers on ARM SDIO boards see "failed to get tx report from firmware" under load, with the same throughput collapse. Extend the 2500 ms timeout to the RTL8723BS. Reported-by: Peter Robinson Closes: https://lore.kernel.org/all/CALeDE9PgQmpMfDt1DgfLD4tBFGH0MZ7GncV6RU= EOHhHbKF+TdQ@mail.gmail.com/ Signed-off-by: Luka Gejak Acked-by: Ping-Ke Shih --- Notes: Changes in v11: none. Changes in v10: none. Changes in v9: none. Changes in v8: none. Changes in v7: none. Changes in v6: none. Changes in v5: none. Restored in v4 after being dropped in v2, because testers on slower ARM SDIO hosts hit the warning that commit c80788f7c5ae already fixes for the RTL8723DU. drivers/net/wireless/realtek/rtw88/tx.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless= /realtek/rtw88/tx.c index 8786bbb421c2..4e110a457e71 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, stru= ct sk_buff *skb, u8 sn) __skb_queue_tail(&tx_report->queue, skb); spin_unlock_irqrestore(&tx_report->q_lock, flags); =20 - if (rtwdev->chip->id =3D=3D RTW_CHIP_TYPE_8723D && - rtwdev->hci.type =3D=3D RTW_HCI_TYPE_USB) + if ((rtwdev->chip->id =3D=3D RTW_CHIP_TYPE_8723D && + rtwdev->hci.type =3D=3D RTW_HCI_TYPE_USB) || + rtw_is_8723bs(rtwdev)) timeout =3D msecs_to_jiffies(2500); =20 mod_timer(&tx_report->purge_timer, jiffies + timeout); --=20 2.55.0 From nobody Fri Sep 25 19:13:35 2026 Received: from mta0.migadu.com (out-1.mta0.migadu.com [91.218.175.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9231938E8D0 for ; Wed, 9 Sep 2026 07:46:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.1 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939987; cv=none; b=aAsP+rBObyaeR9W5R87tMEkaBC2TFy8uf3xmg+Zw2eemBJ1ZTDF0ih1dGLft5EJi2fx0b8mMSKaTHYWN296DcHXVD19e4Cgr2zeL7IF/9U+/Rw0bw+gHdn6xhgMTUtT03r4XahjM8IFp2VHKFLS5fyWQnLfxulDwCV9TYek2GoQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939987; c=relaxed/simple; bh=FHAVAgnPTkkVTucfD73+axRxGRKhYp+PvfqXkQoGNzY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VTDDfQ8KDdAJgB0udmY5/HUeNAmo8hIWM1sedKzwvX56Wpj6sUgskUmACxAuULYLNNnoHcpQVp7wPMTYMW8rc5FDtC1+EM+zSAR9mbK+5NnMp4yuQOrioRdBMoXT2mAqaMRFK20YyNe4ZY3KaJWoPrBp46bf+gg3BCjqcuuyH0I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=AWe5UMiK; arc=none smtp.client-ip=91.218.175.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="AWe5UMiK" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=FHAVAgnPTkkVTucfD73+axRxGRKhYp+PvfqXkQoGNzY=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939983; v=1; x=1789544783; b=AWe5UMiK4qBgpXXl8qisMH/2TIBTsgjLsp6WhNOEVt7ZUhhW0a5xWIH0Jx4G6JApqG+h1y1O 4Bqa0o+lLw+y4qB5VSWT4lwFgI7hg5bAy5l43Z0Jbong7F0Y/ZsB0cbiUnahbK5CLgxURJ8Ta5Z WoxJY95aqLUZUGGUwRHLhqO0= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id bb76d9970d045c24; Wed, 09 Sep 2026 07:46:23 +0000 X-Mizu-Trace-ID: bb76d9970d045c24 X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 4/7] wifi: rtw88: sdio: zero the padding added to a TX transfer Date: Wed, 9 Sep 2026 09:45:53 +0200 Message-ID: <20260909074556.55709-5-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak rtw_sdio_write_port() rounds the transfer up with sdio_align_size() and then hands that length to sdio_memcpy_toio() while the skb still only holds skb->len bytes. The difference, between one and 511 bytes, is read from beyond the end of the frame and transmitted. Whether it stays inside the skb's allocation depends on how much tailroom the skb happens to have, so this is at best sending uninitialised memory over the air. Pad the skb up to the transfer size first. __skb_pad() zeroes the added bytes, reallocates a cloned skb rather than writing into a buffer a clone still shares, and leaves skb->len alone, so nothing else in the transmit path has to change. It must not free the skb on failure: rtw_sdio_write_data() frees the skb itself and rtw_sdio_process_tx_queue() requeues it, so both callers still own it and would double free. Found while reworking this path for the RTL8723BS. Measured on RTL8723BS hardware, padding the transfer costs nothing observable: uplink is 19.5 to 19.8 Mbit/s padded against 20.9 to 21.1 Mbit/s unpadded in an interleaved A/B, with scans, reconnection and a UDP flood clean in both. The other SDIO parts sharing this path are untested; I have only the RTL8723BS. Fixes: 65371a3f14e7 ("wifi: rtw88: sdio: Add HCI implementation for SDIO ba= sed chipsets") Signed-off-by: Luka Gejak Acked-by: Ping-Ke Shih --- Notes: New in v11. =20 Ping-Ke asked for this to come before the RTL8723BS accounting patch rather than after the series, so that it backports on its own and so that it is clear it is an existing problem rather than something the RTL8723BS work introduced. =20 The pad_size local is the shape he asked for on v9: declared at the top, computed unconditionally, and tested with if (pad_size > 0). drivers/net/wireless/realtek/rtw88/sdio.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wirele= ss/realtek/rtw88/sdio.c index 5b40d74b16ee..8466abad972a 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.c +++ b/drivers/net/wireless/realtek/rtw88/sdio.c @@ -636,6 +636,7 @@ static int rtw_sdio_write_port(struct rtw_dev *rtwdev, = struct sk_buff *skb, enum rtw_tx_queue_type queue) { struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + size_t pad_size; bool bus_claim; size_t txsize; u32 txaddr; @@ -646,6 +647,17 @@ static int rtw_sdio_write_port(struct rtw_dev *rtwdev,= struct sk_buff *skb, return -EINVAL; =20 txsize =3D sdio_align_size(rtwsdio->sdio_func, skb->len); + pad_size =3D txsize - skb->len; + + if (pad_size > 0) { + /* + * __skb_pad() must not free the skb on failure: both callers + * still own it, one requeues it and the other frees it. + */ + ret =3D __skb_pad(skb, pad_size, false); + if (ret) + return ret; + } =20 ret =3D rtw_sdio_check_free_txpg(rtwdev, queue, txsize); if (ret) --=20 2.55.0 From nobody Fri Sep 25 19:13:35 2026 Received: from mta0.migadu.com (out-2.mta0.migadu.com [91.218.175.2]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 55DA1449B2F for ; Wed, 9 Sep 2026 07:46:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.2 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939995; cv=none; b=Krr1dFlRaGu7QeVMctm0aBCMIo2FCcWB9Iv9Znz4IXkK9Y3Qz9GqWK68lX98lpL1xsAn1hEwU+yub95GqqiBREe8YIr+kdlUmNqESZ9ciiKSOcgKylh1WShoX8NVmO9KGh6hcf5ahfmc2A0Btluol7Lh6cLGi73i7ki1BpP9dOo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939995; c=relaxed/simple; bh=3jaARdBxN5sp+N3i/5BjAdSO1CpfwR1yGtCKV4TFN80=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uLazJYCB7i9C5xvKGM7MEFLQYEnJnGzb/xMcBqixuVEmleyU1NiSFaVIoeUjTEFDTzltCkjAZ2uM23P4MmwNAFHqEtbhwObIoe4KSJvojGGttqEkCa+YkP9D4X6/DH2xL2A7FWYFU+KCYlTy2QGvlLEgKtLq2rs6bHpnDaLVDZ4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=JJFlWW7Q; arc=none smtp.client-ip=91.218.175.2 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="JJFlWW7Q" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=3jaARdBxN5sp+N3i/5BjAdSO1CpfwR1yGtCKV4TFN80=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939988; v=1; x=1789544788; b=JJFlWW7QUxX1KHJvzRkKF8E3C/4tCuW74hRIre0uzR3U7neoq3VbE51KrYKcUrKka/3ij7Hh QRCulpSJyScNNBi7SJ6bh2qDcIlx6KCGIfhKfkf+CgaZfDZ4dAlAitDYXdq0uCHnSpdqqUomeQM MOmd0iGt8/eBYntsEnfLF3+E= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id ae1c2c72a3b298c0; Wed, 09 Sep 2026 07:46:28 +0000 X-Mizu-Trace-ID: ae1c2c72a3b298c0 X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 5/7] wifi: rtw88: sdio: track free TX pages and OQT credits for RTL8723BS Date: Wed, 9 Sep 2026 09:45:54 +0200 Message-ID: <20260909074556.55709-6-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak 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 a free count in the SDIO output queue, REG_SDIO_OQT_FREE_PG, which rtw88 does not track at all. The vendor driver calls this the OQT free space and never expands the acronym; the register holds the number of further transfers the SDIO output queue can accept, and the chip discards writes that arrive when it has run out. 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 a free output queue entry before writing, and account for the pages consumed after a successful transfer. rtw_sdio_write_port() becomes a dispatcher. It works out the transfer address and the aligned transfer size, which both paths need, and hands them to rtw_sdio_write_port_8723bs() or rtw_sdio_write_port_generic(). The transfer itself moves into rtw_sdio_write_to_port(), which both call, so the generic path is step for step what it was. The padding added by the previous patch stays in rtw_sdio_write_port(), so it still runs once for both paths and stays outside the credit mutex. The RTL8723BS path keeps a separate length for the accounting. The chip charges pages by the frame length rather than by the padded transfer, as the vendor driver does, and the two differ just above a block boundary: a 1025 byte frame is nine pages by length and twelve by the padded size. The check, the output queue wait and the accounting are serialised by a mutex. The TX worker and the H2C path reach this function concurrently, and two writers that both pass the checks can otherwise claim the same pages and output queue entry, after which the chip silently discards whichever transfer arrives second. The vendor driver avoids the same race by funnelling all transmission through one thread. Measured on RTL8723BS hardware against an iperf3 server one hop behind the AP, with the wlan0 byte counters as ground truth. On the generic path the association completes but no data passes at all: TCP and UDP both measure 0 bit/s in either direction. With this patch TCP is 25.3 Mbit/s up and 37.3 Mbit/s down, and UDP is 25.0 Mbit/s up at 0% loss. Signed-off-by: Luka Gejak Acked-by: Ping-Ke Shih --- Notes: Changes in v11: - rtw_sdio_write_port() works out the transfer address and the aligned transfer size and passes both down, since the two paths began the same way, as you asked. - the padding added by the previous patch moves into rtw_sdio_write_to_port() with the transfer, so both paths keep it. - the RTL8723BS path keeps its own length for the accounting. The chip charges pages by frame length rather than by the padded transfer, matching the vendor driver, and the two differ just above a block boundary: a 1025 byte frame is nine pages by length and twelve by the padded size, so using the aligned size would over charge. Changes in v10: - the transfer size comes from sdio_align_size() now. On this card, which reports multi block support and a 512 byte block size, it is identical to the open coded rounding: checked at the block boundaries and then against every frame of a session, 80000 frames with no difference. - the block padding is gone from this patch. Measured on hardware it is not something the chip needs. Transferring unpadded, exactly as the generic path does, gives 20.9 to 21.1 Mbit/s uplink against 19.5 to 19.8 padded, with scans, reconnection and a UDP flood clean either way. What the padding really fixes is that the aligned length is read from past the end of the frame, which every SDIO part does today, so it is a separate fix and not part of this patch. - dropped the comment above rtw_sdio_write_port_8723bs() and trimmed the ones on the page counters. Changes in v9: - rtw_sdio_write_port() is now only a dispatcher, with the two paths = in rtw_sdio_write_port_8723bs() and rtw_sdio_write_port_generic(), as you asked. The chip specific one takes the mutex with guard() directly, so the extra rtw_sdio_8723bs_write_port() layer, which on= ly existed to give the guard a scope while the generic path shared the outer function, is gone. - the unaligned SKB warning is in rtw_sdio_write_to_port() now, once, next to the transfer whose pointer it checks and after any padding. __func__ therefore reads rtw_sdio_write_to_port on every SDIO part. Changes in v8: - RTW_TX_QUEUE_VO is accounted against the normal page pool rather th= an the high one. rtw_sdio_get_tx_addr() writes it to the normal transm= it FIFO, and that is the pool the chip charges: over a saturating VO flood the high pool never lost a single page while the public pool drained. The vendor maps VO to the high queue, but it also writes VO to the high FIFO, which rtw88 does not. - the output queue entry claimed before the transfer is handed back when the transfer fails, rather than staying claimed until the cache next resyncs from the chip. - mutex_destroy() for tx_credit_lock on teardown and on the init error path, matching rtw_core_deinit(). - the comment on the cached page counters no longer explains the atomic_t by the several contexts that write them. Since v7 the transmit paths all update them under tx_credit_lock; what is left outside the lock is rtw_sdio_start() seeding them, and that is the reason they stay atomic_t. - the free page check, the output queue wait, the transfer and the accounting moved into rtw_sdio_8723bs_write_port(), which takes the lock with guard(mutex) as you suggested. The generic path is a short branch in rtw_sdio_write_port() that never touches the lock, and the transfer itself is shared as rtw_sdio_write_to_port(). - lockdep_assert_held() added to rtw_sdio_8723bs_write_port(), rtw_sdio_8723bs_wait_tx_oqt() and rtw_sdio_8723bs_consume_txpg(), and checked on a kernel built with CONFIG_PROVE_LOCKING over bidirectional traffic and three module reloads: nothing fired. - the padding uses a pad_size local and the comment is down to the one point that matters, that __skb_pad() must not free the skb. - the comment above the lock is gone; the one at the declaration of tx_credit_lock covers it. Changes in v7: - the free page check, the output queue wait and the accounting after the transfer are serialised by a mutex. The TX worker and the H2C path run concurrently, and two writers that both passed the checks could claim the same pages and output queue entry; the OQT refill could also read the register while the other writer was between its claim and its transfer. The chip silently discards the overcommitted transfer, which for H2C means a lost firmware command. - the CMD53 address is computed from skb->len again, as upstream does. Passing the aligned size changed the encoded transfer length for every other SDIO chip whenever sdio_align_size() padded; for the RTL8723BS the two encodings are the same value. - the padding is applied with __skb_pad() and only on the RTL8723BS path. The open coded version wrote into a cloned skb's shared buffer when the tailroom happened to be large enough, and it also ran on the generic path, giving other chips a new allocation and failure mode. __skb_pad() does not move skb->len, so the trim on the way out is gone too. It must not free the skb on failure, since one caller requeues it and the other frees it. =20 Changes in v6: none. Changes in v5: - the commit message now says what OQT is, as far as the vendor driver reveals it. - rtw_sdio_8723bs_sync_free_txpg() returns whether the chip reported anything and is the only caller of _store_free_txpg(), and _init_free_txpg() returns an error rather than nothing. That found a real bug: the public pool size was acq_pg_num minus the reserved queues with no check, so a chip coming up with no transmit page allocation would underflow a u16 and leave about 65000 free pages. It is now rtw_sdio_8723bs_pubq_num(), shared with the queue page allocation repair path, and it fails cleanly. - the output queue wait is bounded by a jiffies deadline rather than a loop count, so RTW_SDIO_OQT_TIMEOUT_MS is really milliseconds. - rtw_sdio_8723bs_check_rqpn() returns an error instead of silently doing nothing when the pool cannot cover the reserved queues, and rtw_sdio_start() propagates it. Both early returns are explained. drivers/net/wireless/realtek/rtw88/sdio.c | 349 ++++++++++++++++++++-- drivers/net/wireless/realtek/rtw88/sdio.h | 12 + 2 files changed, 334 insertions(+), 27 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wirele= ss/realtek/rtw88/sdio.c index 8466abad972a..0dc1277619f6 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.c +++ b/drivers/net/wireless/realtek/rtw88/sdio.c @@ -20,6 +20,7 @@ #include "tx.h" =20 #define RTW_SDIO_INDIRECT_RW_RETRIES 50 +#define RTW_SDIO_OQT_TIMEOUT_MS 1000 =20 static bool rtw_sdio_is_bus_addr(u32 addr) { @@ -548,12 +549,149 @@ static int rtw_sdio_read_port(struct rtw_dev *rtwdev= , u8 *buf, size_t count) return ret; } =20 +/* + * The counters are atomic_t because rtw_sdio_start() seeds them outside + * tx_credit_lock, which the transmit paths hold while using them. + */ +static void rtw_sdio_8723bs_store_free_txpg(struct rtw_dev *rtwdev, + u32 free_txpg) +{ + struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + + atomic_set(&rtwsdio->free_pg_high, + u32_get_bits(free_txpg, BIT_FREE_TXPG_HIGH)); + atomic_set(&rtwsdio->free_pg_normal, + u32_get_bits(free_txpg, BIT_FREE_TXPG_NORMAL)); + atomic_set(&rtwsdio->free_pg_low, + u32_get_bits(free_txpg, BIT_FREE_TXPG_LOW)); + atomic_set(&rtwsdio->free_pg_pub, + u32_get_bits(free_txpg, BIT_FREE_TXPG_PUB)); +} + +static bool rtw_sdio_8723bs_sync_free_txpg(struct rtw_dev *rtwdev) +{ + u32 free_txpg =3D rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); + + if (!free_txpg) + return false; + + rtw_sdio_8723bs_store_free_txpg(rtwdev, free_txpg); + + return true; +} + +static int rtw_sdio_8723bs_pubq_num(struct rtw_dev *rtwdev, u16 *pubq_num) +{ + const struct rtw_page_table *pg_tbl =3D &rtwdev->chip->page_table[0]; + u16 acq_pg_num =3D rtwdev->fifo.acq_pg_num; + u16 reserved_num; + + reserved_num =3D pg_tbl->hq_num + pg_tbl->lq_num + pg_tbl->nq_num + + pg_tbl->exq_num + pg_tbl->gapq_num; + if (acq_pg_num <=3D 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 =3D acq_pg_num - reserved_num; + + return 0; +} + +static int rtw_sdio_8723bs_init_free_txpg(struct rtw_dev *rtwdev) +{ + struct rtw_sdio *rtwsdio =3D (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 =3D rtw_sdio_8723bs_pubq_num(rtwdev, &pubq_num); + if (ret) + return ret; + + pg_tbl =3D &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; +} + +/* + * Clamped at zero: a lost update against rtw_sdio_8723bs_consume_txpg() c= an + * briefly drive a counter negative, and wrapping 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 =3D (struct rtw_sdio *)rtwdev->priv; + int free; + + free =3D atomic_read(dedicated) + atomic_read(&rtwsdio->free_pg_pub); + + return free > 0 ? free : 0; +} + +/* + * The pool a queue draws from follows the transmit FIFO that + * rtw_sdio_get_tx_addr() writes into, since that is the one the chip char= ges. + * RTW_TX_QUEUE_MGMT is the exception: it goes to the extra FIFO, which + * REG_SDIO_FREE_TXPG has no counter for and this chip allocates no pages = to, + * so it is accounted against the high pool and served from the public one. + */ +static atomic_t *rtw_sdio_8723bs_free_txpg(struct rtw_dev *rtwdev, u8 queu= e) +{ + struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + + switch (queue) { + case RTW_TX_QUEUE_VI: + case RTW_TX_QUEUE_VO: + 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: + 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; =20 - if (rtw_chip_wcpu_8051(rtwdev)) { + if (rtw_is_8723bs(rtwdev)) { + atomic_t *dedicated; + + dedicated =3D rtw_sdio_8723bs_free_txpg(rtwdev, queue); + if (!dedicated) { + rtw_warn(rtwdev, "Unknown mapping for queue %u\n", queue); + return -EINVAL; + } + + pages_free =3D rtw_sdio_8723bs_pages_free(rtwdev, dedicated); + pages_needed =3D DIV_ROUND_UP(count, rtwdev->chip->page_size); + if (pages_needed <=3D pages_free) + return 0; + + rtw_sdio_8723bs_sync_free_txpg(rtwdev); + pages_free =3D rtw_sdio_8723bs_pages_free(rtwdev, dedicated); + } else if (rtw_chip_wcpu_8051(rtwdev)) { u32 free_txpg; =20 free_txpg =3D rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG); @@ -632,36 +770,60 @@ static int rtw_sdio_check_free_txpg(struct rtw_dev *r= twdev, u8 queue, return 0; } =20 -static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb, - enum rtw_tx_queue_type queue) +static int rtw_sdio_8723bs_wait_tx_oqt(struct rtw_dev *rtwdev) { struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; - size_t pad_size; - bool bus_claim; - size_t txsize; - u32 txaddr; - int ret; + unsigned long deadline; + u8 free; =20 - txaddr =3D rtw_sdio_get_tx_addr(rtwdev, skb->len, queue); - if (!txaddr) - return -EINVAL; + lockdep_assert_held(&rtwsdio->tx_credit_lock); =20 - txsize =3D sdio_align_size(rtwsdio->sdio_func, skb->len); - pad_size =3D txsize - skb->len; + if (atomic_add_unless(&rtwsdio->tx_oqt_free, -1, 0)) + return 0; =20 - if (pad_size > 0) { - /* - * __skb_pad() must not free the skb on failure: both callers - * still own it, one requeues it and the other frees it. - */ - ret =3D __skb_pad(skb, pad_size, false); - if (ret) - return ret; - } + deadline =3D jiffies + msecs_to_jiffies(RTW_SDIO_OQT_TIMEOUT_MS); + do { + free =3D 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)); =20 - ret =3D rtw_sdio_check_free_txpg(rtwdev, queue, txsize); - if (ret) - return ret; + return -EBUSY; +} + +static void rtw_sdio_8723bs_consume_txpg(struct rtw_dev *rtwdev, u8 queue, + unsigned int pages) +{ + struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + atomic_t *dedicated; + unsigned int taken; + int free; + + lockdep_assert_held(&rtwsdio->tx_credit_lock); + + dedicated =3D rtw_sdio_8723bs_free_txpg(rtwdev, queue); + if (!dedicated) + return; + + free =3D atomic_read(dedicated); + taken =3D min_t(unsigned int, pages, free > 0 ? free : 0); + atomic_sub(taken, dedicated); + + pages -=3D taken; + if (pages && atomic_sub_return(pages, &rtwsdio->free_pg_pub) < 0) + atomic_set(&rtwsdio->free_pg_pub, 0); +} + +static int rtw_sdio_write_to_port(struct rtw_dev *rtwdev, struct sk_buff *= skb, + enum rtw_tx_queue_type queue, u32 txaddr, + size_t write_size) +{ + struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + bool bus_claim; + int ret; =20 if (!IS_ALIGNED((unsigned long)skb->data, RTW_SDIO_DATA_PTR_ALIGN)) rtw_warn(rtwdev, "Got unaligned SKB in %s() for queue %u\n", @@ -672,7 +834,8 @@ static int rtw_sdio_write_port(struct rtw_dev *rtwdev, = struct sk_buff *skb, if (bus_claim) sdio_claim_host(rtwsdio->sdio_func); =20 - ret =3D sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data, txsize); + ret =3D sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data, + write_size); =20 if (bus_claim) sdio_release_host(rtwsdio->sdio_func); @@ -680,11 +843,95 @@ static int rtw_sdio_write_port(struct rtw_dev *rtwdev= , struct sk_buff *skb, if (ret) rtw_warn(rtwdev, "Failed to write %zu byte(s) to SDIO port 0x%08x", - txsize, txaddr); + write_size, txaddr); =20 return ret; } =20 +static int rtw_sdio_write_port_8723bs(struct rtw_dev *rtwdev, + struct sk_buff *skb, + enum rtw_tx_queue_type queue, u32 txaddr, + size_t write_size) +{ + struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + unsigned int pages; + size_t txsize; + int ret; + + /* the chip charges pages by frame length, not by the padded transfer */ + txsize =3D round_up(skb->len, 4); + + guard(mutex)(&rtwsdio->tx_credit_lock); + + ret =3D rtw_sdio_check_free_txpg(rtwdev, queue, txsize); + if (ret) + return ret; + + ret =3D rtw_sdio_8723bs_wait_tx_oqt(rtwdev); + if (ret) + return ret; + + ret =3D rtw_sdio_write_to_port(rtwdev, skb, queue, txaddr, write_size); + if (ret) { + /* nothing was queued, so hand the output queue entry back */ + atomic_inc(&rtwsdio->tx_oqt_free); + return ret; + } + + pages =3D DIV_ROUND_UP(txsize, rtwdev->chip->page_size); + rtw_sdio_8723bs_consume_txpg(rtwdev, queue, pages); + + return 0; +} + +static int rtw_sdio_write_port_generic(struct rtw_dev *rtwdev, + struct sk_buff *skb, + enum rtw_tx_queue_type queue, u32 txaddr, + size_t write_size) +{ + int ret; + + ret =3D rtw_sdio_check_free_txpg(rtwdev, queue, write_size); + if (ret) + return ret; + + return rtw_sdio_write_to_port(rtwdev, skb, queue, txaddr, write_size); +} + +static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb, + enum rtw_tx_queue_type queue) +{ + struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; + size_t write_size; + size_t pad_size; + u32 txaddr; + int ret; + + txaddr =3D rtw_sdio_get_tx_addr(rtwdev, skb->len, queue); + if (!txaddr) + return -EINVAL; + + write_size =3D sdio_align_size(rtwsdio->sdio_func, skb->len); + pad_size =3D write_size - skb->len; + + if (pad_size > 0) { + /* + * __skb_pad() must not free the skb on failure: both callers + * still own it, one requeues it and the other frees it. + */ + ret =3D __skb_pad(skb, pad_size, false); + if (ret) + return ret; + } + + if (rtw_is_8723bs(rtwdev)) + return rtw_sdio_write_port_8723bs(rtwdev, skb, queue, txaddr, + write_size); + + return rtw_sdio_write_port_generic(rtwdev, skb, queue, txaddr, + write_size); +} + static void rtw_sdio_init(struct rtw_dev *rtwdev) { struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; @@ -761,8 +1008,51 @@ static int rtw_sdio_setup(struct rtw_dev *rtwdev) return 0; } =20 +/* + * 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 p= age + * count means the allocation latched during power on and must be left alo= ne, + * 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 =3D rtwdev->chip; + struct rtw_fifo_conf *fifo =3D &rtwdev->fifo; + const struct rtw_page_table *pg_tbl; + u32 free_txpg; + u16 pubq_num; + int ret; + + free_txpg =3D rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); + if (free_txpg || !fifo->acq_pg_num) + return 0; + + ret =3D rtw_sdio_8723bs_pubq_num(rtwdev, &pubq_num); + if (ret) + return ret; + + pg_tbl =3D &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) { + if (rtw_is_8723bs(rtwdev)) { + int ret =3D rtw_sdio_8723bs_check_rqpn(rtwdev); + + if (ret) + return ret; + + ret =3D rtw_sdio_8723bs_init_free_txpg(rtwdev); + if (ret) + return ret; + } + rtw_sdio_enable_rx_aggregation(rtwdev); rtw_sdio_enable_interrupt(rtwdev); =20 @@ -1306,6 +1596,8 @@ static int rtw_sdio_init_tx(struct rtw_dev *rtwdev) return -ENOMEM; } =20 + mutex_init(&rtwsdio->tx_credit_lock); + for (i =3D 0; i < RTK_MAX_TX_QUEUE_NUM; i++) skb_queue_head_init(&rtwsdio->tx_queue[i]); rtwsdio->tx_handler_data =3D kmalloc_obj(*rtwsdio->tx_handler_data); @@ -1318,6 +1610,7 @@ static int rtw_sdio_init_tx(struct rtw_dev *rtwdev) return 0; =20 err_destroy_wq: + mutex_destroy(&rtwsdio->tx_credit_lock); destroy_workqueue(rtwsdio->txwq); return -ENOMEM; } @@ -1332,6 +1625,8 @@ static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev) =20 for (i =3D 0; i < RTK_MAX_TX_QUEUE_NUM; i++) ieee80211_purge_tx_queue(rtwdev->hw, &rtwsdio->tx_queue[i]); + + mutex_destroy(&rtwsdio->tx_credit_lock); } =20 int rtw_sdio_probe(struct sdio_func *sdio_func, diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wirele= ss/realtek/rtw88/sdio.h index 457e8b02380e..f43f1c6309b7 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.h +++ b/drivers/net/wireless/realtek/rtw88/sdio.h @@ -86,6 +86,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 */ @@ -159,6 +163,14 @@ 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; + /* one writer at a time between the credit check and the accounting */ + struct mutex tx_credit_lock; }; =20 extern const struct dev_pm_ops rtw_sdio_pm_ops; --=20 2.55.0 From nobody Fri Sep 25 19:13:35 2026 Received: from mta1.migadu.com (out-94.mta1.migadu.com [95.215.58.94]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1C14738E8D0 for ; Wed, 9 Sep 2026 07:46:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.94 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939995; cv=none; b=sud+zSnHZhAvsijbEvMz5Rsm/F0WhGhxcav1r288Wn/3oZdkQgVhHkVzjUe0UT0H6BDfTEyDF89h96EjHWGhMzKlGriqHfBqB6+SHCLW1nHBkbCy6qfvV6zOipVr9toXSRHKNpomEN2s4AHZzT/Fj1afH3gTGX02TR7ZMUvRjfY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939995; c=relaxed/simple; bh=NRktDEvvt1oTgFWR9U0NuWU2MAog1XCHMm9cG7ZpJIM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=inFsdNUJNxbW+7qLD3dpAuorHbJvrsMH+RXyrQAUjUaUXpQET44ycXrT7oLrnElTW9JrxR6sHcSKt9XFJ7zDknhZo90tBh7GF+MgoDWN9MM9FgZ+pb0TJr3o62FCnFdq5YtJP6PgSMxGmmPL5KGJPXhc6VzSa6ziW+AhF1ChIPc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=IO1EmDgN; arc=none smtp.client-ip=95.215.58.94 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="IO1EmDgN" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=NRktDEvvt1oTgFWR9U0NuWU2MAog1XCHMm9cG7ZpJIM=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939989; v=1; x=1789544789; b=IO1EmDgNk/LNxWKfI/4MGnbh3eCVwOyT1sY2UjBDrkLbkMi9iWuYWvDrDQJRpj1PokQUhGh6 pm5yHfoa3oW1dovUkj3JrVgcQjha9QChMbQwN51R+P+RgO+ClK0u+o3lAyHnQRa48yb6TaOPd5n 6m6RRCyTJejEMhj6aJO8kAUc= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id 58eb08d609964b64; Wed, 09 Sep 2026 07:46:29 +0000 X-Mizu-Trace-ID: 58eb08d609964b64 X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 6/7] wifi: rtw88: sdio: set up RX aggregation and interrupts for RTL8723BS Date: Wed, 9 Sep 2026 09:45:55 +0200 Message-ID: <20260909074556.55709-7-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak Enable the existing RX aggregation setup for this chip and select the larger DMA burst count 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 was also seen to keep raising the interrupt after resume when undefined status bits were written back on acknowledgment, so acknowledge only the bits this driver defines. That observation dates from bring up and I cannot re-measure it: the test machine only offers s2idle and does not reliably return from it, so the resume path is not exercised here. The change is scoped to this chip; the other SDIO parts keep writing the status word back unchanged. Signed-off-by: Luka Gejak Acked-by: Ping-Ke Shih --- Notes: Changes in v11: none. Changes in v10: none. Changes in v9: none. Changes in v8: none. Changes in v7: none. Changes in v6: none. Changes in v5: fixed the interrupt acknowledgment. It masked the status word with both irq_mask and RTW_SDIO_HISR_CLEAR_MASK, but for this chip irq_mask is REG_SDIO_HIMR_RX_REQUEST alone and that bit is not in the clear mask, so the two had no bits in common and the driver acknowledged nothing at all. It now masks with the defined bits only, which is what the comment described. The commit message also no longer presents the resume behaviour as re-measured, because it is not. 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/wirele= ss/realtek/rtw88/sdio.c index 0dc1277619f6..696cd203919e 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.c +++ b/drivers/net/wireless/realtek/rtw88/sdio.c @@ -936,7 +936,11 @@ static void rtw_sdio_init(struct rtw_dev *rtwdev) { struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; =20 - rtwsdio->irq_mask =3D REG_SDIO_HIMR_RX_REQUEST | REG_SDIO_HIMR_CPWM1; + if (rtw_is_8723bs(rtwdev)) + rtwsdio->irq_mask =3D REG_SDIO_HIMR_RX_REQUEST; + else + rtwsdio->irq_mask =3D REG_SDIO_HIMR_RX_REQUEST | + REG_SDIO_HIMR_CPWM1; } =20 static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev) @@ -944,6 +948,7 @@ static void rtw_sdio_enable_rx_aggregation(struct rtw_d= ev *rtwdev) u8 size, timeout; =20 switch (rtwdev->chip->id) { + case RTW_CHIP_TYPE_8723B: case RTW_CHIP_TYPE_8703B: case RTW_CHIP_TYPE_8821A: case RTW_CHIP_TYPE_8812A: @@ -971,6 +976,8 @@ static void rtw_sdio_enable_rx_aggregation(struct rtw_d= ev *rtwdev) FIELD_PREP(BIT_DMA_AGG_TO_V1, timeout)); =20 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); } =20 static void rtw_sdio_enable_interrupt(struct rtw_dev *rtwdev) @@ -1042,6 +1049,8 @@ static int rtw_sdio_8723bs_check_rqpn(struct rtw_dev = *rtwdev) =20 static int rtw_sdio_start(struct rtw_dev *rtwdev) { + u32 clear; + if (rtw_is_8723bs(rtwdev)) { int ret =3D rtw_sdio_8723bs_check_rqpn(rtwdev); =20 @@ -1054,6 +1063,13 @@ static int rtw_sdio_start(struct rtw_dev *rtwdev) } =20 rtw_sdio_enable_rx_aggregation(rtwdev); + + if (rtw_is_8723bs(rtwdev)) { + clear =3D 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); =20 return 0; @@ -1133,6 +1149,8 @@ static void rtw_sdio_interface_cfg(struct rtw_dev *rt= wdev) =20 val =3D rtw_read32(rtwdev, REG_SDIO_TX_CTRL); val &=3D 0xfff8; + if (rtw_is_8723bs(rtwdev)) + val |=3D BIT_SDIO_TX_CTRL_ALWAYS_RECOGNIZE; rtw_write32(rtwdev, REG_SDIO_TX_CTRL, val); } =20 @@ -1397,6 +1415,14 @@ static void rtw_sdio_handle_interrupt(struct sdio_fu= nc *sdio_func) rtw_sdio_rx_isr(rtwdev); } =20 + /* + * 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 &=3D RTW_SDIO_HISR_CLEAR_MASK; + rtw_write32(rtwdev, REG_SDIO_HISR, hisr); =20 rtwsdio->irq_thread =3D NULL; diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wirele= ss/realtek/rtw88/sdio.h index f43f1c6309b7..634c0b1339bb 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.h +++ b/drivers/net/wireless/realtek/rtw88/sdio.h @@ -22,6 +22,7 @@ =20 /* SDIO Tx Control */ #define REG_SDIO_TX_CTRL (SDIO_LOCAL_OFFSET + 0x0000) +#define BIT_SDIO_TX_CTRL_ALWAYS_RECOGNIZE BIT(4) =20 /*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) =20 /* HCI Current Power Mode */ #define REG_SDIO_HCPWM (SDIO_LOCAL_OFFSET + 0x0019) --=20 2.55.0 From nobody Fri Sep 25 19:13:35 2026 Received: from mta1.migadu.com (out-99.mta1.migadu.com [95.215.58.99]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6C44D453A48 for ; Wed, 9 Sep 2026 07:46:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.99 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939998; cv=none; b=GJv2LwryonDKlO9SB22Flvtca4kKItABROpgTlq0yzks+6JGz2ulXSao0YrWJd8Kdv+7NIN7UOnEDRQsEqUWm5JOOutUo7saZIKBmFyMZ8HDJOGIzraQ7Q+3eTmQFjSTmD0WGK5G5F4/6eCFLXq7QbGFIdSDrliTgGuX63h/iUo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788939998; c=relaxed/simple; bh=TZBxT1TkkUM+QxDZqvgli/mkWNbY2yvm6Kn9t4ub76Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=j9fGMC/VvCzuXZNEJP2tO4aLvL4kXig7FW9yBuSwB/3uKL6G9wS1yiatghNMzXVpTqvYna0muDjuuHbSfP2QhAD6IbQMbwnbG/2+6KI0471hOruWbe5jYHZ3RW9VcYlANsUphSQK3J7T5ZY2Y2Smg7M0FWGBJP3y+e33JA/4qQE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=B2oK8kq/; arc=none smtp.client-ip=95.215.58.99 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="B2oK8kq/" X-Envelope-To: linux-kernel@vger.kernel.org DKIM-Signature: a=rsa-sha256; bh=TZBxT1TkkUM+QxDZqvgli/mkWNbY2yvm6Kn9t4ub76Y=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1788939994; v=1; x=1789544794; b=B2oK8kq/8Sh///yuebfc9Ds3hA4Z0aUjKWX2xRYb3gT6AwEslElsVSIzeY68kkv5Qt6aaLC+ ANY5KcmqhKTm1rmuptA9l2oke16TbJUcjFIkB5utJIskmDv/K1CeCATlw9m74ohGOiV4gPamZ9E 5sAi12q3nt1BYpkrn947nvr8= X-Envelope-To: linux-kernel@vger.kernel.org Received: by smtp.migadu.com with ESMTPS id 5125ac9f8371530f; Wed, 09 Sep 2026 07:46:34 +0000 X-Mizu-Trace-ID: 5125ac9f8371530f X-Migadu-Flow: FLOW_OUT From: luka.gejak@linux.dev To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, Michael Straube , Peter Robinson , Bitterblue Smith , Luka Gejak Subject: [PATCH v11 7/7] wifi: rtw88: sdio: add TX back-pressure and retry on page starvation Date: Wed, 9 Sep 2026 09:45:56 +0200 Message-ID: <20260909074556.55709-8-luka.gejak@linux.dev> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260909074556.55709-1-luka.gejak@linux.dev> References: <20260909074556.55709-1-luka.gejak@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Luka Gejak 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 a transfer cannot be completed 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. Both sides take the TX queue lock across the length check and the flag update, the way rtw_pci_tx_write() and rtw_pci_tx_isr() use irq_lock. Without it the producer could stop a queue on a length the drain path had already emptied, having seen the flag still clear and so skipped the wake, and the AC would have stayed stopped with nothing left to wake it. Convert the TX work item to a delayed work and re-arm it when a transfer fails for a reason that can clear on its own, so it is retried rather than the AC abandoned, and cancel the work on teardown. Retrying matters once the queue can be stopped. A stopped queue is handed no further frames, so nothing else would kick the worker, and the AC would stay stopped for good with the link still up and receive unaffected. The two retried cases, a transmit page or output queue shortage and a failed skb expansion, are also the two that fail silently; the rest are logged where they happen, so they are visible rather than an unexplained hang, and they keep the existing behaviour rather than being retried indefinitely. 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 Acked-by: Ping-Ke Shih --- Notes: Changes in v11: - -ENOMEM is retried again. It became reachable once more when the padding moved into the series, since __skb_pad() can return it, and without the retry a stopped access category has nothing left to kick it. This is the consequence noted in the v10 changelog. - one comment shortened to fit in 80 columns. Changes in v10: - -ENOMEM is no longer retried. With the padding moved out of patch 4 nothing on this path can return it, so the case was dead. If the padding fix lands in rtw_sdio_write_port() it becomes reachable again, for every chip, and this has to come back with it. - trimmed the comments here too. Changes in v9: none, beyond recording your Acked-by. Changes in v8: - the stop and the wake take the TX queue lock with guard(spinlock_irqsave) across the length check and the flag update, following pci.c, which holds irq_lock on both sides and needs no barrier because of it. The smp_mb() pair, the READ_ONCE and WRITE_ONCE accessors and the re-check that undid a stop all went with it. - q_map is read later, just before the call that uses it. It cannot m= ove past rtw_sdio_indicate_tx_status(), which consumes the skb, so that= is as far down as it goes. Changes in v7: - the mac80211 queue index is read before skb_queue_tail() publishes the skb to the TX worker, which may process and free it immediately; reading it afterwards was a use after free. - the stop path re-checks the FIFO after setting the stopped flag and undoes the stop if the worker drained it meanwhile. In that window the drain side still saw the flag clear, so neither side would have woken the queue and the AC stayed stopped for good. The flag is accessed with READ_ONCE/WRITE_ONCE and the re-check is ordered against the drain path with a barrier pair. =20 Changes in v6: - dropped rtw_sdio_reschedule_tx_work(). It was a thin wrapper around queue_delayed_work() and hid the kernel API for no gain. - the reschedule conditions moved into rtw_sdio_8723bs_reschedule_tx(= ), so rtw_sdio_tx_handler() no longer explains any chip specific condition in the common flow. - dropped the unconditional break on a failed transfer. v4 and v5 had it, and it quietly changed the other SDIO parts: upstream requeues the frame and the loop retries, and breaking gave up after the first failure. The two errors this chip needs to retry are handled in the helper above, so the rest can keep the existing behaviour and the other parts are untouched again. Changes in v5: - the back-pressure and wake conditions moved into rtw_sdio_8723bs_stop_tx_queue() and _wake_tx_queue(). - rtw_sdio_process_tx_queue() returns 0 on success and 1 for the empty queue case, rather than the other way round. - queue_stopped[] renamed to tx_queue_stopped[]. - fixed a transmit stall: the work item was only re-armed on a page shortage, so on any other failure a stopped access category would stay stopped for good. It now also re-arms on a failed skb expansion, which with the page shortage covers both failures that produce no log message. - RTW_SDIO_TX_RETRY_DELAY is left as msecs_to_jiffies(1): it does not become 0 for HZ < 1000, since msecs_to_jiffies() rounds up. drivers/net/wireless/realtek/rtw88/sdio.c | 137 ++++++++++++++++++++-- drivers/net/wireless/realtek/rtw88/sdio.h | 3 +- 2 files changed, 129 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wirele= ss/realtek/rtw88/sdio.c index 696cd203919e..dc2fd0f8f9ff 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.c +++ b/drivers/net/wireless/realtek/rtw88/sdio.c @@ -22,6 +22,11 @@ #define RTW_SDIO_INDIRECT_RW_RETRIES 50 #define RTW_SDIO_OQT_TIMEOUT_MS 1000 =20 +/* Bounds the queueing latency of the unbounded per-AC software FIFO. */ +#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); @@ -1133,7 +1138,11 @@ static void rtw_sdio_tx_kick_off(struct rtw_dev *rtw= dev) { struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; =20 - 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); } =20 static void rtw_sdio_link_ps(struct rtw_dev *rtwdev, bool enter) @@ -1242,12 +1251,61 @@ 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); } =20 +/* + * Both this and the wake below hold the TX queue lock across the length c= heck + * and the flag update, so a queue is only ever stopped while it really is + * above the watermark, and the drain path always sees the flag the produc= er + * set. + */ +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 =3D (struct rtw_sdio *)rtwdev->priv; + + if (!rtw_is_8723bs(rtwdev) || queue >=3D RTW_TX_QUEUE_BCN) + return; + + guard(spinlock_irqsave)(&rtwsdio->tx_queue[queue].lock); + + 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] =3D 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 =3D (struct rtw_sdio *)rtwdev->priv; + + if (!rtw_is_8723bs(rtwdev) || queue >=3D RTW_TX_QUEUE_BCN) + return; + + guard(spinlock_irqsave)(&rtwsdio->tx_queue[queue].lock); + + 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] =3D 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) { struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; enum rtw_tx_queue_type queue =3D rtw_tx_queue_mapping(skb); + u16 q_map =3D skb_get_queue_mapping(skb); struct rtw_sdio_tx_data *tx_data; =20 rtw_sdio_tx_skb_prepare(rtwdev, pkt_info, skb, queue); @@ -1255,8 +1313,11 @@ static int rtw_sdio_tx_write(struct rtw_dev *rtwdev, tx_data =3D rtw_sdio_get_tx_data(skb); tx_data->sn =3D pkt_info->sn; =20 + /* The TX worker may already have freed the skb, so do not touch it. */ skb_queue_tail(&rtwsdio->tx_queue[queue], skb); =20 + rtw_sdio_8723bs_stop_tx_queue(rtwdev, queue, q_map); + return 0; } =20 @@ -1559,33 +1620,80 @@ static void rtw_sdio_indicate_tx_status(struct rtw_= dev *rtwdev, ieee80211_tx_status_irqsafe(hw, skb); } =20 -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 ca= se + * 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 =3D (struct rtw_sdio *)rtwdev->priv; struct sk_buff *skb; + u16 q_map; int ret; =20 skb =3D skb_dequeue(&rtwsdio->tx_queue[queue]); if (!skb) - return; + return 1; =20 ret =3D rtw_sdio_write_port(rtwdev, skb, queue); if (ret) { skb_queue_head(&rtwsdio->tx_queue[queue], skb); - return; + return ret; } =20 + /* rtw_sdio_indicate_tx_status() consumes the skb, so read this first */ + q_map =3D skb_get_queue_mapping(skb); + rtw_sdio_indicate_tx_status(rtwdev, skb); + + rtw_sdio_8723bs_wake_tx_queue(rtwdev, queue, q_map); + + return 0; +} + +/* + * A page or output queue shortage, and a failed skb expansion, are transi= ent + * and leave the frame queued, so come back for it shortly. That matters o= nce + * 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 catego= ry + * 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 =3D (struct rtw_sdio *)rtwdev->priv; + unsigned long delay; + + if (!rtw_is_8723bs(rtwdev)) + return false; + + if (ret =3D=3D -EBUSY || ret =3D=3D -ENOMEM) + delay =3D RTW_SDIO_TX_RETRY_DELAY; + else if (ret =3D=3D 0 && queue =3D=3D RTW_TX_QUEUE_MGMT) + delay =3D 0; + else + return false; + + queue_delayed_work(rtwsdio->txwq, &work_data->work, delay); + + return true; } =20 static void rtw_sdio_tx_handler(struct work_struct *work) { struct rtw_sdio_work_data *work_data =3D - 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; =20 rtwdev =3D work_data->rtwdev; rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; @@ -1595,7 +1703,13 @@ static void rtw_sdio_tx_handler(struct work_struct *= work) =20 for (queue =3D RTK_MAX_TX_QUEUE_NUM - 1; queue >=3D 0; queue--) { for (limit =3D 0; limit < 1000; limit++) { - rtw_sdio_process_tx_queue(rtwdev, queue); + ret =3D rtw_sdio_process_tx_queue(rtwdev, queue); + if (ret > 0) + break; + + if (rtw_sdio_8723bs_reschedule_tx(rtwdev, work_data, + queue, ret)) + return; =20 if (skb_queue_empty(&rtwsdio->tx_queue[queue])) break; @@ -1624,14 +1738,16 @@ static int rtw_sdio_init_tx(struct rtw_dev *rtwdev) =20 mutex_init(&rtwsdio->tx_credit_lock); =20 - for (i =3D 0; i < RTK_MAX_TX_QUEUE_NUM; i++) + for (i =3D 0; i < RTK_MAX_TX_QUEUE_NUM; i++) { skb_queue_head_init(&rtwsdio->tx_queue[i]); + rtwsdio->tx_queue_stopped[i] =3D false; + } rtwsdio->tx_handler_data =3D kmalloc_obj(*rtwsdio->tx_handler_data); if (!rtwsdio->tx_handler_data) goto err_destroy_wq; =20 rtwsdio->tx_handler_data->rtwdev =3D rtwdev; - INIT_WORK(&rtwsdio->tx_handler_data->work, rtw_sdio_tx_handler); + INIT_DELAYED_WORK(&rtwsdio->tx_handler_data->work, rtw_sdio_tx_handler); =20 return 0; =20 @@ -1646,6 +1762,7 @@ static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev) struct rtw_sdio *rtwsdio =3D (struct rtw_sdio *)rtwdev->priv; int i; =20 + cancel_delayed_work_sync(&rtwsdio->tx_handler_data->work); destroy_workqueue(rtwsdio->txwq); kfree(rtwsdio->tx_handler_data); =20 diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wirele= ss/realtek/rtw88/sdio.h index 634c0b1339bb..6e7e6009744b 100644 --- a/drivers/net/wireless/realtek/rtw88/sdio.h +++ b/drivers/net/wireless/realtek/rtw88/sdio.h @@ -156,7 +156,7 @@ struct rtw_sdio_tx_data { }; =20 struct rtw_sdio_work_data { - struct work_struct work; + struct delayed_work work; struct rtw_dev *rtwdev; }; =20 @@ -172,6 +172,7 @@ struct rtw_sdio { struct workqueue_struct *txwq; struct rtw_sdio_work_data *tx_handler_data; struct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM]; + bool tx_queue_stopped[RTK_MAX_TX_QUEUE_NUM]; =20 atomic_t free_pg_high; atomic_t free_pg_normal; --=20 2.55.0