From 9f2e1eedcd5f64c55832de8263a849b88230c74c Mon Sep 17 00:00:00 2001
From: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
Date: Thu, 7 Sep 2023 14:47:24 -0500
Subject: [PATCH] brcmfmac: TWT: Handle timeout of Setup and Teardown events
 expected from FW

In case if the TWT AP did not response to the TWT Setup solicit from this
device, need to cleanup the session entry from the list after a timeout.

Introduce an aperiodic stale TWT session entry cleanup job using kernel
timer. Schedule this cleanup job to fire after 3000ms from sending a TWT
Setup or Teardown cmd to Firmware.

Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
 .../broadcom/brcm80211/brcmfmac/core.c        |  6 ++
 .../broadcom/brcm80211/brcmfmac/core.h        |  1 +
 .../broadcom/brcm80211/brcmfmac/twt.c         | 65 +++++++++++++++++++
 .../broadcom/brcm80211/brcmfmac/twt.h         | 11 ++++
 4 files changed, 83 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 63d99e870..f688d627c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -1210,6 +1210,8 @@ struct brcmf_if *brcmf_add_if(struct brcmf_pub *drvr, s32 bsscfgidx, s32 ifidx,
 	spin_lock_init(&ifp->twt_sess_list_lock);
 	 /* Initialize TWT Session list */
 	INIT_LIST_HEAD(&ifp->twt_sess_list);
+	/* Setup the aperiodic TWT Session cleanup activity */
+	timer_setup(&ifp->twt_evt_timeout, brcmf_twt_event_timeout_handler, 0);
 
 	if (mac_addr != NULL)
 		memcpy(ifp->mac_addr, mac_addr, ETH_ALEN);
@@ -1235,6 +1237,10 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx,
 		  ifp->ifidx);
 	ifidx = ifp->ifidx;
 
+	/* Stop the aperiodic TWT Session cleanup activity */
+	if (timer_pending(&ifp->twt_evt_timeout))
+		del_timer_sync(&ifp->twt_evt_timeout);
+
 	if (ifp->ndev) {
 		if (bsscfgidx == 0) {
 			if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
index 7066c8f7c..cb49d610b 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
@@ -211,6 +211,7 @@ struct brcmf_if {
 	spinlock_t sta_list_lock;
 	struct list_head twt_sess_list;         /* dll of TWT sessions */
 	spinlock_t twt_sess_list_lock;
+	struct timer_list twt_evt_timeout;	/* TWT event timeout */
 	bool fmac_pkt_fwd_en;
 };
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c
index a477f56d0..04e823eac 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.c
@@ -492,6 +492,66 @@ brcmf_twt_add_session(struct brcmf_if *ifp, const u8 *peer_addr,
 	return ret;
 }
 
+/**
+ * brcmf_twt_event_timeout_handler - Iterate the session list and handle stale
+ *	TWT session entries which are failed to move to next state in FSM.
+ *
+ * @t: timer instance.
+ */
+void brcmf_twt_event_timeout_handler(struct timer_list *t)
+{
+	struct brcmf_if *ifp = from_timer(ifp, t, twt_evt_timeout);
+	struct brcmf_twt_session *twt_sess = NULL, *next = NULL;
+	unsigned long curr_ts = jiffies;
+	s32 ret = 0;
+
+	list_for_each_entry_safe(twt_sess, next, &ifp->twt_sess_list, list) {
+		/* For this session entry, Skip if the time since the TWT cmd sent to the
+		 * Firmware does not exceed the Event timeout configured.
+		 */
+		if (time_after(twt_sess->oper_start_ts + BRCMF_TWT_EVENT_TIMEOUT, curr_ts))
+			continue;
+
+		switch (twt_sess->state) {
+		case BRCMF_TWT_SESS_STATE_SETUP_INPROGRESS:
+			ret = brcmf_twt_update_session_state(ifp, twt_sess,
+							     BRCMF_TWT_SESS_STATE_SETUP_INCOMPLETE);
+			if (ret) {
+				brcmf_err("TWT: Failed to update session(%u) with state(%s)",
+					  twt_sess->twt_params.flow_id,
+					  brcmf_twt_session_state_str[BRCMF_TWT_SESS_STATE_SETUP_INCOMPLETE]);
+				continue;
+			}
+
+			break;
+		case BRCMF_TWT_SESS_STATE_TEARDOWN_INPROGRESS:
+			ret = brcmf_twt_update_session_state(ifp, twt_sess,
+							     BRCMF_TWT_SESS_STATE_TEARDOWN_INCOMPLETE);
+			if (ret) {
+				brcmf_err("TWT: Failed to update session(%u) with state(%s)",
+					  twt_sess->twt_params.flow_id,
+					  brcmf_twt_session_state_str[BRCMF_TWT_SESS_STATE_TEARDOWN_INCOMPLETE]);
+				continue;
+			}
+
+			break;
+		default:
+			continue;
+		}
+
+		ret = brcmf_twt_del_session(ifp, twt_sess);
+		if (ret) {
+			brcmf_err("TWT: Failed to Delete session(%u) from list",
+				  twt_sess->twt_params.flow_id);
+			break;
+		}
+
+		brcmf_dbg(TWT, "TWT: Cleared stale session(%u) with peer %pM, state(%s)",
+			  twt_sess->twt_params.flow_id, twt_sess->peer_addr.octet,
+			  brcmf_twt_session_state_str[twt_sess->state]);
+	}
+}
+
 /**
  * brcmf_twt_setup_event_handler() - Handle the TWT Setup Event notification from Firmware.
  *
@@ -935,6 +995,8 @@ brcmf_twt_setup_oper_handler(struct brcmf_if *ifp, struct brcmf_twt_params twt_p
 		goto exit;
 	}
 
+	/* Schedule the Cleanup timer to handle Setup Completion timeout */
+	mod_timer(&ifp->twt_evt_timeout, jiffies + BRCMF_TWT_EVENT_TIMEOUT);
 
 	brcmf_dbg(TWT, "TWT: Setup REQ: Session Setup In Progress\n"
 		  "Dialog Token		: %u\n"
@@ -1067,6 +1129,9 @@ brcmf_twt_teardown_oper_handler(struct brcmf_if *ifp, struct brcmf_twt_params tw
 		}
 	}
 
+	/* Schedule the Cleanup timer to handle Teardown Completion timeout */
+	mod_timer(&ifp->twt_evt_timeout, jiffies + BRCMF_TWT_EVENT_TIMEOUT);
+
 	brcmf_dbg(TWT, "TWT: Teardown REQ: Session Teardown In Progress\n"
 		  "Flow ID		: %u\n"
 		  "Broadcast TWT ID	: %u\n"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h
index f04696d86..a8c94d9a1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/twt.h
@@ -32,6 +32,8 @@
 #ifndef BRCMF_TWT_H
 #define BRCMF_TWT_H
 
+#include <linux/sched.h>
+#include <linux/jiffies.h>
 #include "vendor_ifx.h"
 #include "core.h"
 
@@ -40,6 +42,7 @@
 /* Min TWT Unit in TUs */
 #define WAKE_DUR_UNIT_TU 1024
 
+#define BRCMF_TWT_EVENT_TIMEOUT	msecs_to_jiffies(3000)
 /**
  * enum brcmf_twt_cmd - TWT iovar subcmds handled by firmware TWT module
  *
@@ -166,6 +169,7 @@ struct brcmf_twt_params {
  * @peer: TWT peer address.
  * @state: TWT session state, refer enum brcmf_twt_session_state.
  * @twt_params: TWT session parameters.
+ * @oper_req_ts: TWT session operation (setup, teardown, etc..) start timestamp.
  * @list: linked list.
  */
 struct brcmf_twt_session {
@@ -174,6 +178,7 @@ struct brcmf_twt_session {
 	struct ether_addr peer_addr;
 	enum brcmf_twt_session_state state;
 	struct brcmf_twt_params twt_params;
+	unsigned long oper_start_ts;
 	struct list_head list;
 };
 
@@ -325,6 +330,12 @@ void brcmf_twt_debugfs_create(struct brcmf_pub *drvr);
  */
 s32 brcmf_twt_cleanup_sessions(struct brcmf_if *ifp);
 
+/**
+ * brcmf_twt_event_timeout_handler - Iterate the session list and handle stale
+ *	TWT session entries which are failed to move to next state in FSM.
+ */
+void brcmf_twt_event_timeout_handler(struct timer_list *t);
+
 /**
  * brcmf_notify_twt_event() - Handle the TWT Event notifications from Firmware.
  *
-- 
2.39.5 (Apple Git-154)

