From 29187fa3938272f9ed1da2e6978c7dbb9446316f Mon Sep 17 00:00:00 2001
From: Owen Huang <Owen.Huang@infineon.com>
Date: Wed, 7 May 2025 04:52:21 -0500
Subject: [PATCH 2/4] non-upstream: workaround to recover credit if bus error
 happened

The SDIO CRC error happened on CMD 53 READ randomly, so maybe host
driver lost credit indication from FW.

So host driver need to know how many credits are queued in FW, then
recover lost credits.

Fixes SWWLAN-153663

Signed-off-by: Owen Huang <Owen.Huang@infineon.com>
---
 .../broadcom/brcm80211/brcmfmac/bcmsdh.c      |  6 +-
 .../broadcom/brcm80211/brcmfmac/fwsignal.c    | 88 ++++++++++++++++++-
 .../broadcom/brcm80211/brcmfmac/fwsignal.h    |  1 +
 3 files changed, 92 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c
index 8a24dc167cfd..f6ab73089456 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bcmsdh.c
@@ -37,6 +37,7 @@
 #include "core.h"
 #include "common.h"
 #include "cfg80211.h"
+#include "fwsignal.h"
 
 #define SDIOH_API_ACCESS_RETRY_LIMIT	2
 
@@ -340,6 +341,9 @@ static int brcmf_sdiod_skbuff_read(struct brcmf_sdio_dev *sdiodev,
 	if (err == -ENOMEDIUM)
 		brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM);
 
+	if (err)
+		brcmf_fws_set_credit_recover(sdiodev->bus_if->drvr);
+
 	return err;
 }
 
@@ -803,7 +807,7 @@ brcmf_sdiod_ramrw(struct brcmf_sdio_dev *sdiodev, bool write, u32 address,
 		}
 
 		if (err) {
-			brcmf_err("membytes transfer failed\n");
+			brcmf_err("membytes transfer failed write=%d err=%d\n", write, err);
 			break;
 		}
 		if (!write)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c
index 9ef60d2c0651..9ef4c8c8cdf1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c
@@ -72,6 +72,9 @@ enum brcmf_fws_tlv_type {
 };
 #undef BRCMF_FWS_TLV_DEF
 
+#define BRCMF_FWS_TYPE_FIFO_CREDITBACK_V2_LEN   12
+#define BRCMF_FIFO_CREDITBACK_TX_OFFSET         6
+
 /*
  * enum brcmf_fws_tlv_len - definition of tlv lengths.
  */
@@ -503,6 +506,7 @@ struct brcmf_fws_info {
 	unsigned long borrow_defer_timestamp;
 	bool bus_flow_blocked;
 	bool creditmap_received;
+	bool credit_recover;
 	u8 mode;
 	bool avoid_queueing;
 #if (KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE)
@@ -1530,8 +1534,81 @@ brcmf_fws_txs_process(struct brcmf_fws_info *fws, u8 flags, u32 hslot,
 	return 0;
 }
 
+static void brcmf_fws_credit_auto_recover(struct brcmf_fws_info *fws, u8 *data)
+{
+	int fifo, i;
+	u8 *fw_tx = data + BRCMF_FIFO_CREDITBACK_TX_OFFSET;
+	u8 *fw_back = data;
+	int borrowed = 0;
+	int loan = 0;
+	int missing = 0;
+	int host_record_credit;
+	int in_fw_credit;
+
+	brcmf_dbg(SDIO, "Enter: tx %pM back %pM\n", fw_tx, fw_back);
+	brcmf_dbg(SDIO, "Enter: credit [BK]:%d [BE]:%d [VI]:%d [VO]:%d [BCMC]:%d\n",
+		  fws->fifo_credit[0], fws->fifo_credit[1], fws->fifo_credit[2],
+		  fws->fifo_credit[3], fws->fifo_credit[4]);
+
+	/* must check from highest priority FIFO */
+	for (fifo = BRCMF_FWS_FIFO_COUNT - 1; fifo >= BRCMF_FWS_FIFO_AC_BK; fifo--) {
+		/* if no credit lost, continue to check next FIFO */
+		if (fws->init_fifo_credit[fifo] == fws->fifo_credit[fifo])
+			continue;
+
+		brcmf_dbg(SDIO, "FIFO %d init: %d current: %d\n",
+			  fifo, fws->init_fifo_credit[fifo], fws->fifo_credit[fifo]);
+
+		if (fifo <= BRCMF_FWS_FIFO_AC_VO) {
+			/* how many credit are borrowed from other FIFO */
+			for (i = 0; i <= BRCMF_FWS_FIFO_AC_VO; i++)
+				borrowed += fws->credits_borrowed[fifo][i];
+
+			/* how many credit are lend to other FIFO */
+			for (i = 0; i <= BRCMF_FWS_FIFO_AC_VO; i++)
+				loan += fws->credits_borrowed[i][fifo];
+
+			brcmf_dbg(SDIO, "borrowed: %d loan: %d\n", borrowed, loan);
+		}
+
+		/* calculate missed credit */
+		host_record_credit = fws->init_fifo_credit[fifo] -
+			fws->fifo_credit[fifo] + borrowed - loan;
+		in_fw_credit = fw_tx[fifo] - fw_back[fifo];
+		missing = host_record_credit - in_fw_credit;
+		brcmf_dbg(SDIO, "host %d fw %d missing: %d\n",
+			  host_record_credit, in_fw_credit, missing);
+
+		if (missing > 0)
+			brcmf_fws_return_credits(fws, fifo, missing);
+	}
+
+	brcmf_dbg(SDIO, "Leave: credit [BK]:%d [BE]:%d [VI]:%d [VO]:%d [BCMC]:%d\n",
+		  fws->fifo_credit[0], fws->fifo_credit[1], fws->fifo_credit[2],
+		  fws->fifo_credit[3], fws->fifo_credit[4]);
+}
+
+void brcmf_fws_set_credit_recover(struct brcmf_pub *drvr)
+{
+	struct brcmf_fws_info *fws = NULL;
+
+	if (!drvr)
+		return;
+
+	fws = drvr_to_fws(drvr);
+
+	if (!fws)
+		return;
+
+	brcmf_err("Trigger credit recover\n");
+
+	brcmf_fws_lock(fws);
+	fws->credit_recover = true;
+	brcmf_fws_unlock(fws);
+}
+
 static int brcmf_fws_fifocreditback_indicate(struct brcmf_fws_info *fws,
-					     u8 *data)
+					     s16 len, u8 *data)
 {
 	int i;
 
@@ -1547,6 +1624,13 @@ static int brcmf_fws_fifocreditback_indicate(struct brcmf_fws_info *fws,
 
 	brcmf_dbg(DATA, "map: credit %x delay %x\n", fws->fifo_credit_map,
 		  fws->fifo_delay_map);
+
+	/* when bus error happened, try to recover lost credit */
+	if (len == BRCMF_FWS_TYPE_FIFO_CREDITBACK_V2_LEN && fws->credit_recover) {
+		brcmf_err("Trigger credit recover\n");
+		brcmf_fws_credit_auto_recover(fws, data);
+		fws->credit_recover = false;
+	}
 	brcmf_fws_unlock(fws);
 	return BRCMF_FWS_RET_OK_SCHEDULE;
 }
@@ -1938,7 +2022,7 @@ void brcmf_fws_hdrpull(struct brcmf_if *ifp, s16 siglen, struct sk_buff *skb)
 			brcmf_fws_txstatus_indicate(fws, type, data);
 			break;
 		case BRCMF_FWS_TYPE_FIFO_CREDITBACK:
-			err = brcmf_fws_fifocreditback_indicate(fws, data);
+			err = brcmf_fws_fifocreditback_indicate(fws, len, data);
 			break;
 		case BRCMF_FWS_TYPE_RSSI:
 			brcmf_fws_rssi_indicate(fws, *data);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.h
index c3b2d3691c43..aa8b59930275 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.h
@@ -44,5 +44,6 @@ void brcmf_fws_bustxcomplete(struct brcmf_fws_info *fws, struct sk_buff *skb,
 			     bool success);
 void brcmf_fws_bus_blocked(struct brcmf_pub *drvr, bool flow_blocked);
 void brcmf_fws_rxreorder(struct brcmf_if *ifp, struct sk_buff *skb, bool inirq);
+void brcmf_fws_set_credit_recover(struct brcmf_pub *drvr);
 
 #endif /* FWSIGNAL_H_ */
-- 
2.25.0

