qcacld-3.0: Set hw mode during channel switch in STA/P2P-CLI case

Set the hw mode, if needed, during channel switch in STA and
P2P-CLI scenario. The STA/P2P-CLI on receiving the channel change
event may need to do a hw mode change to get the best out of the
hw capabilities.

e.g.1, In a STA+SAP concurrency, the STA and SAP are doing SCC on
channel 6. When the STA interface receives a channel event for
channel 36, it is better the driver moves to a DBS scenario.

e.g.2, In a STA+SAP concurrency, the STA is on channel 6 and the
SAP is on channel 36. When the STA interface receives a channel
event for channel 36, it is better the driver moves from a DBS
scenario to a SCC scenario.

For MCC upgrade, the following steps are taken
1. Opportunistic timer is started
2. vdev restart is initiated on the new channel
3. PM will check if MCC upgrade can be done on timer expiry

For DBS downgrade, the following steps are taken
1. PM will initiate HW mode change to DBS right away
2. vdev restart is initiated on the new channel

Change-Id: I202842bf28c3117e8cc91954cdfd3b39a0062f4e
CRs-Fixed: 972184
diff --git a/core/cds/inc/cds_concurrency.h b/core/cds/inc/cds_concurrency.h
index 09b88ba..8bfa566 100644
--- a/core/cds/inc/cds_concurrency.h
+++ b/core/cds/inc/cds_concurrency.h
@@ -656,4 +656,5 @@
 QDF_STATUS qdf_reset_connection_update(void);
 QDF_STATUS qdf_set_connection_update(void);
 QDF_STATUS qdf_init_connection_update(void);
+QDF_STATUS cds_stop_start_opportunistic_timer(void);
 #endif /* __CDS_CONCURRENCY_H */
diff --git a/core/cds/src/cds_concurrency.c b/core/cds/src/cds_concurrency.c
index dea6bf2..adf5108 100644
--- a/core/cds/src/cds_concurrency.c
+++ b/core/cds/src/cds_concurrency.c
@@ -3614,8 +3614,8 @@
 #ifdef QCA_WIFI_3_0_EMU
 	/* For M2M emulation only: if we have a connection on 2.4, stay in DBS */
 	if (hdd_ctx->config->enable_m2m_limitation &&
-		CDS_IS_CHANNEL_24GHZ(conc_connection_list[0].chan)) {
-		cds_debug("For emulation only: connection on 2.4, stay in DBS");
+	    CDS_IS_CHANNEL_24GHZ(conc_connection_list[0].chan)) {
+		cds_debug("emulation only: conn on 2.4, stay in DBS");
 		goto done;
 	}
 #endif
@@ -7983,3 +7983,35 @@
 		return CDS_NOP;
 	}
 }
+
+/**
+ * cds_stop_start_opportunistic_timer() - Start and stop the opportunistic timer
+ *
+ * Stops and starts the opportunistic timer for DBS_OPPORTUNISTIC_TIME seconds
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS cds_stop_start_opportunistic_timer(void)
+{
+	QDF_STATUS status = QDF_STATUS_E_FAILURE;
+	p_cds_contextType cds_ctx;
+
+	cds_ctx = cds_get_global_context();
+	if (!cds_ctx) {
+		cds_err("Invalid CDS context");
+		return status;
+	}
+
+	qdf_mc_timer_stop(&cds_ctx->dbs_opportunistic_timer);
+
+	status = qdf_mc_timer_start(
+			&cds_ctx->dbs_opportunistic_timer,
+			DBS_OPPORTUNISTIC_TIME * 1000);
+
+	if (!QDF_IS_STATUS_SUCCESS(status)) {
+		cds_err("failed to start opportunistic timer");
+		return status;
+	}
+
+	return status;
+}
diff --git a/core/mac/inc/sir_api.h b/core/mac/inc/sir_api.h
index c44d049..5e8e93e 100644
--- a/core/mac/inc/sir_api.h
+++ b/core/mac/inc/sir_api.h
@@ -105,6 +105,7 @@
  * @CDS_UPDATE_REASON_OPPORTUNISTIC: Opportunistic HW mode update
  * @CDS_UPDATE_REASON_NSS_UPDATE: NSS update
  * @CDS_UPDATE_REASON_CHANNEL_SWITCH: Channel switch
+ * @CDS_UPDATE_REASON_CHANNEL_SWITCH_STA: Channel switch for STA
  */
 enum cds_conn_update_reason {
 	CDS_UPDATE_REASON_SET_OPER_CHAN,
@@ -116,6 +117,7 @@
 	CDS_UPDATE_REASON_OPPORTUNISTIC,
 	CDS_UPDATE_REASON_NSS_UPDATE,
 	CDS_UPDATE_REASON_CHANNEL_SWITCH,
+	CDS_UPDATE_REASON_CHANNEL_SWITCH_STA,
 };
 
 typedef enum {
@@ -5518,4 +5520,39 @@
 	uint32_t   wait_time;
 	uint32_t   flags;
 };
+
+/**
+ * struct csa_offload_params - CSA offload request parameters
+ * @channel: channel
+ * @switch_mode: switch mode
+ * @sec_chan_offset: second channel offset
+ * @new_ch_width: new channel width
+ * @new_ch_freq_seg1: channel center freq 1
+ * @new_ch_freq_seg2: channel center freq 2
+ * @ies_present_flag: IE present flag
+ */
+struct csa_offload_params {
+	uint8_t channel;
+	uint8_t switch_mode;
+	uint8_t sec_chan_offset;
+	uint8_t new_ch_width;
+	uint8_t new_op_class;
+	uint8_t new_ch_freq_seg1;
+	uint8_t new_ch_freq_seg2;
+	uint32_t ies_present_flag;
+	tSirMacAddr bssId;
+};
+
+/**
+ * struct sir_saved_csa_params - saved csa offload params
+ * @message_type: Message type
+ * @length: Message length
+ * @session_id: Session id
+ */
+struct sir_saved_csa_params {
+	uint16_t message_type;
+	uint16_t length;
+	uint32_t session_id;
+};
+
 #endif /* __SIR_API_H */
diff --git a/core/mac/src/include/sir_params.h b/core/mac/src/include/sir_params.h
index affd556..30a0ad1 100644
--- a/core/mac/src/include/sir_params.h
+++ b/core/mac/src/include/sir_params.h
@@ -642,10 +642,7 @@
 #define SIR_LIM_SCH_CLEAN_MSG              (SIR_LIM_ITC_MSG_TYPES_BEGIN + 0xB)
 /* Message from ISR upon Radar Detection */
 #define SIR_LIM_RADAR_DETECT_IND           (SIR_LIM_ITC_MSG_TYPES_BEGIN + 0xC)
-
-/* /////////////////////////////////// */
-/* message id Available */
-/* ////////////////////////////////// */
+#define SIR_LIM_CSA_POST_HW_MODE_CHANGE    (SIR_LIM_ITC_MSG_TYPES_BEGIN + 0xD)
 
 /* Message from Hal to send out a DEL-TS indication */
 #define SIR_LIM_DEL_TS_IND                  (SIR_LIM_ITC_MSG_TYPES_BEGIN + 0xE)
diff --git a/core/mac/src/pe/include/lim_session.h b/core/mac/src/pe/include/lim_session.h
index 24069b3..a952383 100644
--- a/core/mac/src/pe/include/lim_session.h
+++ b/core/mac/src/pe/include/lim_session.h
@@ -471,6 +471,7 @@
 	uint8_t country_info_present;
 	uint8_t nss;
 	bool add_bss_failed;
+	struct csa_offload_params saved_csa_params;
 } tPESession, *tpPESession;
 
 /*-------------------------------------------------------------------------
diff --git a/core/mac/src/pe/lim/lim_process_message_queue.c b/core/mac/src/pe/lim/lim_process_message_queue.c
index 2591138..664c523 100644
--- a/core/mac/src/pe/lim/lim_process_message_queue.c
+++ b/core/mac/src/pe/lim/lim_process_message_queue.c
@@ -62,6 +62,7 @@
 #include "qdf_types.h"
 #include "cds_packet.h"
 #include "qdf_mem.h"
+#include "cds_concurrency.h"
 
 void lim_log_session_states(tpAniSirGlobal pMac);
 static void lim_process_normal_hdd_msg(tpAniSirGlobal mac_ctx,
@@ -1147,6 +1148,210 @@
 #endif
 
 /**
+ * lim_handle_hw_mode_change_on_csa() - Do HW mode change on CSA for STA mode
+ * @mac_ctx: Global MAC context
+ * @msg: Received message
+ *
+ * Checks if hw mode change is required for the new channel.
+ * If MCC upgrade is required, this function will start the opportunistic
+ * timer and the caller will invoke the other APIs to perform vdev restart on
+ * the new channel.
+ *
+ * If DBS downgrade is required, this function will initiate the hw mode
+ * change and vdev restart will happen on the new channel after getting hw
+ * mode response
+ *
+ * Return: QDF_STATUS_SUCCESS if processing of csa params (and hence vdev
+ * restart) needs to happen or if no hw mode change is required,
+ * QDF_STATUS_E_FAILURE otherwise.
+ */
+static QDF_STATUS lim_handle_hw_mode_change_on_csa(tpAniSirGlobal mac_ctx,
+							tpSirMsgQ msg)
+{
+	tpPESession session_entry;
+	struct csa_offload_params *csa_params =
+				(struct csa_offload_params *) (msg->bodyptr);
+	tpDphHashNode sta_ds = NULL;
+	uint8_t session_id;
+	uint16_t aid = 0;
+	enum cds_conc_next_action action;
+	QDF_STATUS status = QDF_STATUS_E_FAILURE;
+
+	lim_log(mac_ctx, LOG1, FL("handle hw mode change for csa"));
+
+	if (!csa_params) {
+		lim_log(mac_ctx, LOGE, FL("limMsgQ body ptr is NULL"));
+		/* qdf_mem_free() can handle NULL values */
+		goto err;
+	}
+
+	session_entry = pe_find_session_by_bssid(mac_ctx,
+					csa_params->bssId, &session_id);
+	if (!session_entry) {
+		lim_log(mac_ctx, LOGE, FL("Session does not exist"));
+		goto err;
+	}
+
+	sta_ds = dph_lookup_hash_entry(mac_ctx, session_entry->bssId, &aid,
+			&session_entry->dph.dphHashTable);
+
+	if (!sta_ds) {
+		lim_log(mac_ctx, LOGE, FL("sta_ds does not exist"));
+		goto err;
+	}
+
+	/* Since all the write to the policy manager table happens in the
+	 * MC thread context and this channel change event is also processed
+	 * in the MC thread context, explicit lock/unlock of qdf_conc_list_lock
+	 * is not done here
+	 */
+	action = cds_get_pref_hw_mode_for_chan(session_entry->smeSessionId,
+				csa_params->channel);
+
+	if (action == CDS_NOP) {
+		lim_log(mac_ctx, LOG1, FL("no need for hw mode change"));
+		/* Proceed with processing csa params. So, not freeing it */
+		return QDF_STATUS_SUCCESS;
+	}
+
+	lim_log(mac_ctx, LOG1, FL("session:%d action:%d"),
+		session_entry->smeSessionId, action);
+
+	/*     1. Start opportunistic timer
+	 *     2. Do vdev restart on the new channel (by the caller)
+	 *     3. PM will check if MCC upgrade can be done after timer expiry
+	 */
+	if (action == CDS_MCC_UPGRADE) {
+		status = cds_stop_start_opportunistic_timer();
+		if (QDF_IS_STATUS_SUCCESS(status))
+			lim_log(mac_ctx, LOG1,
+				FL("opportunistic timer for MCC upgrade"));
+
+		/* After opportunistic timer is triggered, we can go ahead
+		 * with processing the csa params. So, not freeing the memory
+		 * through 'err' label.
+		 */
+		return QDF_STATUS_SUCCESS;
+	}
+
+	/*     CDS_DBS_DOWNGRADE:
+	 *     1. PM will initiate HW mode change to DBS rightaway
+	 *     2. Do vdev restart on the new channel (on getting hw mode resp)
+	 */
+	status = cds_next_actions(session_entry->smeSessionId, action,
+				CDS_UPDATE_REASON_CHANNEL_SWITCH_STA);
+	if (!QDF_IS_STATUS_SUCCESS(status)) {
+		lim_log(mac_ctx, LOGE, FL("no set hw mode command was issued"));
+		/* Proceed with processing csa params. So, not freeing it */
+		return QDF_STATUS_SUCCESS;
+	} else {
+		/* Save the csa params to be used after DBS downgrade */
+		qdf_mem_copy(&session_entry->saved_csa_params, csa_params,
+				sizeof(session_entry->saved_csa_params));
+
+		lim_log(mac_ctx, LOG1,
+			FL("saved csa params for dbs downgrade for %pM"),
+			session_entry->saved_csa_params.bssId);
+
+		/* Returning error so that csa params are not processed here */
+		status = QDF_STATUS_E_FAILURE;
+	}
+
+err:
+	qdf_mem_free(csa_params);
+	return status;
+}
+
+/**
+ * lim_handle_hw_mode_change_on_csa_event() - Handle hw mode change on csa
+ * @mac_ctx: Pointer to the Global Mac Context
+ * @msg: Received message
+ *
+ * Checks if a hw mode change is required for the received csa event. Processes
+ * csa params and do vdev restart immediately if the there is no need for a hw
+ * mode change or if MCC upgrade is required
+ *
+ * Return: None
+ */
+static void lim_handle_hw_mode_change_on_csa_event(tpAniSirGlobal mac_ctx,
+							tpSirMsgQ msg)
+{
+	QDF_STATUS qdf_status;
+
+	lim_log(mac_ctx, LOG1, FL("lim received csa offload event"));
+	if (mac_ctx->policy_manager_enabled &&
+			wma_is_hw_dbs_capable() == true) {
+		/* Check if a hw mode change is required */
+		qdf_status = lim_handle_hw_mode_change_on_csa(mac_ctx,
+				msg);
+		/* Process csa params and do vdev restart immediately if
+		 * there is no need for a hw mode change or if MCC upgrade is
+		 * required.
+		 */
+		if (QDF_IS_STATUS_SUCCESS(qdf_status))
+			lim_handle_csa_offload_msg(mac_ctx, msg);
+	} else {
+		lim_handle_csa_offload_msg(mac_ctx, msg);
+	}
+}
+
+/**
+ * lim_handle_csa_event_post_dbs_downgrade() - Process csa event post dbs
+ * downgrade
+ * @mac_ctx: Pointer to the Global Mac Context
+ * @msg: Received message
+ *
+ * Process the csa event to do vdev restart on the new channel after the dbs
+ * downgrade. If there was a DBS downgrade as part of the event
+ * WMA_CSA_OFFLOAD_EVENT, SIR_LIM_CSA_POST_HW_MODE_CHANGE will be received after
+ * receiving the set hw mode response, where this processing will happen.
+ *
+ * Return: None
+ */
+static void lim_handle_csa_event_post_dbs_downgrade(tpAniSirGlobal mac_ctx,
+							tpSirMsgQ msg)
+{
+	tSirMsgQ csa_msg;
+	tpPESession session_entry;
+
+	struct sir_saved_csa_params *buf =
+		(struct sir_saved_csa_params *)msg->bodyptr;
+
+	/* Null check for 'msg' already done before coming here in the caller */
+
+	session_entry = pe_find_session_by_sme_session_id(mac_ctx,
+				buf->session_id);
+	if (!session_entry) {
+		lim_log(mac_ctx, LOGE, FL("Invalid session id:%d"),
+			buf->session_id);
+		return;
+	}
+
+	lim_log(mac_ctx, LOG1,
+			FL("received csa offload event post hw change for %pM"),
+			session_entry->saved_csa_params.bssId);
+
+	csa_msg.bodyptr = qdf_mem_malloc(
+			sizeof(struct csa_offload_params));
+	if (!csa_msg.bodyptr) {
+		lim_log(mac_ctx, LOGE, FL("malloc failed for csa msg"));
+		goto clean_msg_body;
+	}
+
+	qdf_mem_copy((void *)csa_msg.bodyptr,
+			(void *)&session_entry->saved_csa_params,
+			sizeof(struct csa_offload_params));
+	/* If there was a DBS downgrade as part of the event
+	 * WMA_CSA_OFFLOAD_EVENT, SIR_LIM_CSA_POST_HW_MODE_CHANGE will
+	 * be received after receiving the set hw mode response.
+	 */
+	lim_handle_csa_offload_msg(mac_ctx, &csa_msg);
+clean_msg_body:
+	if (msg->bodyptr)
+		qdf_mem_free(msg->bodyptr);
+}
+
+/**
  * lim_process_messages() - Process messages from upper layers.
  *
  * @mac_ctx:          Pointer to the Global Mac Context.
@@ -1605,7 +1810,10 @@
 		lim_handle_delete_bss_rsp(mac_ctx, msg);
 		break;
 	case WMA_CSA_OFFLOAD_EVENT:
-		lim_handle_csa_offload_msg(mac_ctx, msg);
+		lim_handle_hw_mode_change_on_csa_event(mac_ctx, msg);
+		break;
+	case SIR_LIM_CSA_POST_HW_MODE_CHANGE:
+		lim_handle_csa_event_post_dbs_downgrade(mac_ctx, msg);
 		break;
 	case WMA_SET_BSSKEY_RSP:
 	case WMA_SET_STA_BCASTKEY_RSP:
diff --git a/core/mac/src/pe/lim/lim_send_sme_rsp_messages.c b/core/mac/src/pe/lim/lim_send_sme_rsp_messages.c
index dd1e592..2256f85 100644
--- a/core/mac/src/pe/lim/lim_send_sme_rsp_messages.c
+++ b/core/mac/src/pe/lim/lim_send_sme_rsp_messages.c
@@ -2016,7 +2016,8 @@
 {
 	tpPESession session_entry;
 	tSirMsgQ mmh_msg;
-	tpCSAOffloadParams csa_params = (tpCSAOffloadParams) (msg->bodyptr);
+	struct csa_offload_params *csa_params =
+				(struct csa_offload_params *) (msg->bodyptr);
 	tpSmeCsaOffloadInd csa_offload_ind;
 	tpDphHashNode sta_ds = NULL;
 	uint8_t session_id;
@@ -2027,6 +2028,8 @@
 	tLimWiderBWChannelSwitchInfo *chnl_switch_info = NULL;
 	tLimChannelSwitchInfo *lim_ch_switch = NULL;
 
+	lim_log(mac_ctx, LOG1, FL("handle csa offload msg"));
+
 	if (!csa_params) {
 		lim_log(mac_ctx, LOGE, FL("limMsgQ body ptr is NULL"));
 		return;
@@ -2037,7 +2040,8 @@
 			csa_params->bssId, &session_id);
 	if (!session_entry) {
 		lim_log(mac_ctx, LOGE,
-			FL("Session does not exist"));
+			FL("Session does not exists for %pM"),
+				csa_params->bssId);
 		goto err;
 	}
 
@@ -2059,7 +2063,7 @@
 
 		lim_ch_switch = &session_entry->gLimChannelSwitch;
 		session_entry->gLimChannelSwitch.switchMode =
-			csa_params->switchmode;
+			csa_params->switch_mode;
 		/* timer already started by firmware, switch immediately */
 		session_entry->gLimChannelSwitch.switchCount = 0;
 		session_entry->gLimChannelSwitch.primaryChannel =
diff --git a/core/sme/inc/sme_internal.h b/core/sme/inc/sme_internal.h
index 21a7b78..9c9a997 100644
--- a/core/sme/inc/sme_internal.h
+++ b/core/sme/inc/sme_internal.h
@@ -220,6 +220,7 @@
 	ocb_callback dcc_stats_event_callback;
 	sme_set_thermal_level_callback set_thermal_level_cb;
 	void *saved_scan_cmd;
+	struct csa_offload_params saved_csa_params;
 } tSmeStruct, *tpSmeStruct;
 
 #endif /* #if !defined( __SMEINTERNAL_H ) */
diff --git a/core/sme/src/common/sme_api.c b/core/sme/src/common/sme_api.c
index 0a44d44..423f56d 100644
--- a/core/sme/src/common/sme_api.c
+++ b/core/sme/src/common/sme_api.c
@@ -156,6 +156,7 @@
 	enum cds_conn_update_reason reason;
 	tSmeCmd *saved_cmd;
 	tCsrRoamInfo roam_info = {0};
+	QDF_STATUS status;
 
 	sms_log(mac, LOG1, FL("%s"), __func__);
 	param = (struct sir_set_hw_mode_resp *)msg;
@@ -234,6 +235,28 @@
 					&roam_info, 0,
 					eCSR_ROAM_STATUS_UPDATE_HW_MODE,
 					eCSR_ROAM_RESULT_UPDATE_HW_MODE);
+		} else if (reason == CDS_UPDATE_REASON_CHANNEL_SWITCH_STA) {
+			struct sir_saved_csa_params *msg;
+
+			sms_log(mac, LOG1, FL("process channel switch sta"));
+			msg = qdf_mem_malloc(sizeof(*msg));
+			if (!msg) {
+				sms_log(mac, LOGE,
+					FL("memory alloc fail for csa "));
+				goto end;
+			}
+
+			msg->message_type = SIR_LIM_CSA_POST_HW_MODE_CHANGE;
+			msg->length = sizeof(*msg);
+			msg->session_id = command->u.set_hw_mode_cmd.session_id;
+
+			status = cds_send_mb_message_to_mac(msg);
+			if (!QDF_IS_STATUS_SUCCESS(status))
+				sms_log(mac, LOGE,
+					FL("failed to process csa params"));
+			else
+				sms_log(mac, LOG1,
+					FL("csa after hw mode change"));
 		} else {
 			sms_log(mac, LOGE,
 			      FL("Calling HDD callback for HW mode response"));
diff --git a/core/wma/inc/wma_if.h b/core/wma/inc/wma_if.h
index 8b01d00..dcce413 100644
--- a/core/wma/inc/wma_if.h
+++ b/core/wma/inc/wma_if.h
@@ -921,28 +921,6 @@
 	uint8_t nss;
 } tSwitchChannelParams, *tpSwitchChannelParams;
 
-/**
- * struct tpCSAOffloadParams - CSA offload request parameters
- * @channel: channel
- * @switchmode: switch mode
- * @sec_chan_offset: second channel offset
- * @new_ch_width: new channel width
- * @new_ch_freq_seg1: channel center freq 1
- * @new_ch_freq_seg2: channel center freq 2
- * @ies_present_flag: IE present flag
- */
-typedef struct CSAOffloadParams {
-	uint8_t channel;
-	uint8_t switchmode;
-	uint8_t sec_chan_offset;
-	uint8_t new_ch_width;
-	uint8_t new_op_class;
-	uint8_t new_ch_freq_seg1;
-	uint8_t new_ch_freq_seg2;
-	uint32_t ies_present_flag;
-	tSirMacAddr bssId;
-} *tpCSAOffloadParams, tCSAOffloadParams;
-
 typedef void (*tpSetLinkStateCallback)(tpAniSirGlobal pMac, void *msgParam,
 		bool status);
 
diff --git a/core/wma/src/wma_features.c b/core/wma/src/wma_features.c
index 53f06fe..2e5671d 100644
--- a/core/wma/src/wma_features.c
+++ b/core/wma/src/wma_features.c
@@ -1143,7 +1143,7 @@
 	uint8_t vdev_id = 0;
 	uint8_t cur_chan = 0;
 	struct ieee80211_channelswitch_ie *csa_ie;
-	tpCSAOffloadParams csa_offload_event;
+	struct csa_offload_params *csa_offload_event;
 	struct ieee80211_extendedchannelswitch_ie *xcsa_ie;
 	struct ieee80211_ie_wide_bw_switch *wb_ie;
 	struct wma_txrx_node *intr = wma->interfaces;
@@ -1176,12 +1176,12 @@
 		csa_ie = (struct ieee80211_channelswitch_ie *)
 						(&csa_event->csa_ie[0]);
 		csa_offload_event->channel = csa_ie->newchannel;
-		csa_offload_event->switchmode = csa_ie->switchmode;
+		csa_offload_event->switch_mode = csa_ie->switchmode;
 	} else if (csa_event->ies_present_flag & WMI_XCSA_IE_PRESENT) {
 		xcsa_ie = (struct ieee80211_extendedchannelswitch_ie *)
 						(&csa_event->xcsa_ie[0]);
 		csa_offload_event->channel = xcsa_ie->newchannel;
-		csa_offload_event->switchmode = xcsa_ie->switchmode;
+		csa_offload_event->switch_mode = xcsa_ie->switchmode;
 		csa_offload_event->new_op_class = xcsa_ie->newClass;
 	} else {
 		WMA_LOGE("CSA Event error: No CSA IE present");