wlan: Fix race between STA connect and SAP channel switch

If SAP channel change is in progress and STA connect req is received
driver ends up sending two change channel req to firmware.
As WDA and WDI is not sessionized to handle two back to back channel
change req the ADD BSS req is dropped and no response for ADD BSS is
sent to LIM. This result in roam command timeout.

The fix adds logic to
- Flush channel change workqueue when STA connection is requested.
- Wait for channel change to complete when STA connection is requested
  OR SAP is turned off.
- Check for DFS channel of SAP if STA conenction fails as during
  connect driver flush the channel change workqueue.

Change-Id: I8b67538178ccd866a1a9430ed55a6fa8d5dbfd40
CRs-Fixed: 2204574
diff --git a/CORE/HDD/inc/wlan_hdd_main.h b/CORE/HDD/inc/wlan_hdd_main.h
index b6db4a1..2a3058e 100644
--- a/CORE/HDD/inc/wlan_hdd_main.h
+++ b/CORE/HDD/inc/wlan_hdd_main.h
@@ -74,6 +74,10 @@
 /*--------------------------------------------------------------------------- 
   Preprocessor definitions and constants
   -------------------------------------------------------------------------*/
+
+/* SAP channel change wait time in ms */
+#define HDD_SAP_CHAN_CNG_WAIT_TIME 1500
+
 /** Number of attempts to detect/remove card */
 #define LIBRA_CARD_INSERT_DETECT_MAX_COUNT      5
 #define LIBRA_CARD_REMOVE_DETECT_MAX_COUNT      5
@@ -2318,6 +2322,14 @@
 int wlan_hdd_check_and_stop_mon(hdd_adapter_t *sta_adapter, bool wait);
 
 /**
+ * hdd_wait_for_ecsa_complete() - wait if ecsa is in progress
+ * @hdd_ctx: hdd context
+ *
+ * Return: int.
+ */
+int hdd_wait_for_ecsa_complete(hdd_context_t *hdd_ctx);
+
+/**
  * hdd_is_sta_sap_scc_allowed_on_dfs_chan() - check if sta+sap scc allowed on
  * dfs chan
  * @hdd_ctx: pointer to hdd context
diff --git a/CORE/HDD/src/wlan_hdd_assoc.c b/CORE/HDD/src/wlan_hdd_assoc.c
index 857527a..44000f6 100644
--- a/CORE/HDD/src/wlan_hdd_assoc.c
+++ b/CORE/HDD/src/wlan_hdd_assoc.c
@@ -162,6 +162,12 @@
 
 /* The time after add bss, in which SAP should start ECSA to move to SCC */
 #define ECSA_SCC_CHAN_CHANGE_DEFER_INTERVAL 1500
+/*
+ * Time in ms after disconnect, in which the SAP should move to non DFS channel.
+ * This will avoid multiple SAP channel switch if disconnet is followed by
+ * connect.
+ */
+#define ECSA_DFS_CHAN_CHANGE_DEFER_TIME 200
 
 #ifdef WLAN_FEATURE_11W
 void hdd_indicateUnprotMgmtFrame(hdd_adapter_t *pAdapter,
@@ -1601,18 +1607,24 @@
             return;
         }
 
+        if (sap_ctx->sapsMachine != eSAP_STARTED) {
+            hddLog(LOG1, FL("SAP is not in eSAP_STARTED state"));
+            return;
+        }
+
         chan_state = vos_nv_getChannelEnabledState(sap_ctx->channel);
 
         hddLog(LOG1, "SAP is operating on channel (%hu), chan_state %d",
                 sap_ctx->channel, chan_state);
-
         if (vos_nv_getChannelEnabledState(sap_ctx->channel) !=
                 NV_CHANNEL_DFS) {
             hddLog(LOG1, "SAP is on non DFS channel. nothing to do");
             return;
         }
+
         hddLog(LOG1, "Schedule workqueue to move the SAP to non DFS channel");
-        schedule_delayed_work(&hdd_ctx->ecsa_chan_change_work, 0);
+        schedule_delayed_work(&hdd_ctx->ecsa_chan_change_work,
+                            msecs_to_jiffies(ECSA_DFS_CHAN_CHANGE_DEFER_TIME));
     }
 }
 
@@ -2812,6 +2824,10 @@
              }
         }
     }
+    else if (roamStatus == eCSR_ROAM_ASSOCIATION_FAILURE)
+    {
+        hdd_check_and_move_if_sap_is_on_dfs_chan(pHddCtx, pAdapter);
+    }
     return eHAL_STATUS_SUCCESS;
 }
 
diff --git a/CORE/HDD/src/wlan_hdd_cfg80211.c b/CORE/HDD/src/wlan_hdd_cfg80211.c
index bb8347c..ea39239 100644
--- a/CORE/HDD/src/wlan_hdd_cfg80211.c
+++ b/CORE/HDD/src/wlan_hdd_cfg80211.c
@@ -11692,6 +11692,8 @@
         mutex_lock(&pHddCtx->sap_lock);
         if(test_bit(SOFTAP_BSS_STARTED, &pAdapter->event_flags))
         {
+            vos_flush_delayed_work(&pHddCtx->ecsa_chan_change_work);
+            hdd_wait_for_ecsa_complete(pHddCtx);
             if ( VOS_STATUS_SUCCESS == (status = WLANSAP_StopBss(pHddCtx->pvosContext) ) )
             {
                 hdd_hostapd_state_t *pHostapdState = WLAN_HDD_GET_HOSTAP_STATE_PTR(pAdapter);
@@ -15800,7 +15802,10 @@
                    FL("Set HDD connState to eConnectionState_Connecting"));
             hdd_connSetConnectionState(WLAN_HDD_GET_STATION_CTX_PTR(pAdapter),
                                                  eConnectionState_Connecting);
+            vos_flush_delayed_work(&pHddCtx->ecsa_chan_change_work);
+            hdd_wait_for_ecsa_complete(pHddCtx);
         }
+
         status = sme_RoamConnect( WLAN_HDD_GET_HAL_CTX(pAdapter),
                             pAdapter->sessionId, pRoamProfile, &roamId);
 
@@ -21897,6 +21902,7 @@
    hdd_context_t *hdd_ctx;
    uint8_t channel;
    int ret;
+   ptSapContext sap_ctx;
    v_CONTEXT_t vos_ctx;
 
    hddLog(LOGE, FL("Set Freq %d"), csa_params->chandef.chan->center_freq);
@@ -21912,13 +21918,29 @@
        return -EINVAL;
    }
 
-   if ((WLAN_HDD_SOFTAP != adapter->device_mode) &&
-       (WLAN_HDD_P2P_GO != adapter->device_mode))
+   if (WLAN_HDD_SOFTAP != adapter->device_mode)
         return -ENOTSUPP;
 
+   sap_ctx = VOS_GET_SAP_CB(vos_ctx);
+   if (!sap_ctx) {
+       hddLog(LOGE, FL("sap_ctx is NULL"));
+       return -EINVAL;
+   }
+
+   ret = wlansap_chk_n_set_chan_change_in_progress(sap_ctx);
+   if (ret)
+       return ret;
+
+   INIT_COMPLETION(sap_ctx->ecsa_info.chan_switch_comp);
+
    channel = vos_freq_to_chan(csa_params->chandef.chan->center_freq);
    ret = wlansap_set_channel_change(vos_ctx, channel, false);
 
+   if (ret) {
+       wlansap_reset_chan_change_in_progress(sap_ctx);
+       complete(&sap_ctx->ecsa_info.chan_switch_comp);
+   }
+
    return ret;
 }
 
diff --git a/CORE/HDD/src/wlan_hdd_hostapd.c b/CORE/HDD/src/wlan_hdd_hostapd.c
index 87d3d6d..63e76bc 100644
--- a/CORE/HDD/src/wlan_hdd_hostapd.c
+++ b/CORE/HDD/src/wlan_hdd_hostapd.c
@@ -2471,11 +2471,25 @@
                 break;
             }
         case QCSAP_PARAM_SET_CHANNEL_CHANGE:
-            if ((WLAN_HDD_SOFTAP == pHostapdAdapter->device_mode) ||
-                (WLAN_HDD_P2P_GO == pHostapdAdapter->device_mode)) {
+            if (WLAN_HDD_SOFTAP == pHostapdAdapter->device_mode) {
+                ptSapContext sap_ctx;
+
+                sap_ctx = VOS_GET_SAP_CB(pVosContext);
+                if (!sap_ctx) {
+                    hddLog(LOGE, FL("sap_ctx is NULL"));
+                    return -EINVAL;
+                }
+                ret = wlansap_chk_n_set_chan_change_in_progress(sap_ctx);
+                if (ret)
+                    return ret;
+                INIT_COMPLETION(sap_ctx->ecsa_info.chan_switch_comp);
                 hddLog(LOG1, FL("ET Channel Change to new channel= %d"),
                        set_value);
                 ret = wlansap_set_channel_change(pVosContext, set_value, false);
+                if (ret) {
+                       wlansap_reset_chan_change_in_progress(sap_ctx);
+                       complete(&sap_ctx->ecsa_info.chan_switch_comp);
+                }
             } else {
                 hddLog(LOGE, FL("Channel %d Change Failed, Device in not in SAP/GO mode"),
                        set_value);
diff --git a/CORE/HDD/src/wlan_hdd_main.c b/CORE/HDD/src/wlan_hdd_main.c
index fee2bc2..ea33d3a 100644
--- a/CORE/HDD/src/wlan_hdd_main.c
+++ b/CORE/HDD/src/wlan_hdd_main.c
@@ -10212,28 +10212,38 @@
         hddLog(LOGE, FL("sta_adapter is NULL"));
         return;
     }
-    sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(sta_adapter);
 
-    if (sta_ctx->conn_info.connState != eConnectionState_Associated) {
-        chan_state = vos_nv_getChannelEnabledState(sap_ctx->channel);
-        hddLog(LOG1, FL("sta not in connected state %d, sta_sap_scc_on_dfs_chan %d, chan_state %d"),
-                sta_ctx->conn_info.connState, sta_sap_scc_on_dfs_chan,
-                chan_state);
-        if (sta_sap_scc_on_dfs_chan &&
-                (chan_state == NV_CHANNEL_DFS)) {
-            hddLog(LOG1, FL("Switch SAP to user configured channel"));
-            target_channel = sap_config->user_config_channel;
-            goto switch_channel;
-
-        }
+    if (wlansap_chk_n_set_chan_change_in_progress(sap_ctx))
         return;
+    INIT_COMPLETION(sap_ctx->ecsa_info.chan_switch_comp);
+
+    sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(sta_adapter);
+    if (sta_ctx->conn_info.connState != eConnectionState_Associated) {
+        if (sta_ctx->conn_info.connState == eConnectionState_NotConnected) {
+            chan_state = vos_nv_getChannelEnabledState(sap_ctx->channel);
+            hddLog(LOG1, FL("sta not in connected state %d, sta_sap_scc_on_dfs_chan %d, chan_state %d"),
+                   sta_ctx->conn_info.connState, sta_sap_scc_on_dfs_chan,
+                   chan_state);
+            if (sta_sap_scc_on_dfs_chan &&
+                (chan_state == NV_CHANNEL_DFS)) {
+               hddLog(LOGE, FL("Switch SAP to user configured channel"));
+               target_channel = sap_config->user_config_channel;
+               goto switch_channel;
+            }
+        }
+        goto abort;
     }
 
     target_channel = sta_ctx->conn_info.operationChannel;
 switch_channel:
     hddLog(LOGE, FL("Switch SAP to %d channel"),
            target_channel);
-    wlansap_set_channel_change(vos_ctx, target_channel, true);
+    if (!wlansap_set_channel_change(vos_ctx, target_channel, true))
+        return;
+
+abort:
+   wlansap_reset_chan_change_in_progress(sap_ctx);
+   complete(&sap_ctx->ecsa_info.chan_switch_comp);
 }
 
 /**
@@ -10253,6 +10263,45 @@
     vos_ssr_unprotect(__func__);
 }
 
+int hdd_wait_for_ecsa_complete(hdd_context_t *hdd_ctx)
+{
+    int ret;
+    ptSapContext sap_ctx = NULL;
+    v_CONTEXT_t vos_ctx;
+
+    vos_ctx = hdd_ctx->pvosContext;
+    if (!vos_ctx) {
+        hddLog(LOGE, FL("vos_ctx is NULL"));
+        return 0;
+    }
+
+    sap_ctx = VOS_GET_SAP_CB(vos_ctx);
+    if (!sap_ctx) {
+        hddLog(LOG1, FL("sap_ctx is NULL"));
+        return 0;
+    }
+    if(!sap_ctx->isSapSessionOpen) {
+        hddLog(LOG1, FL("sap session not opened, SAP in state %d"),
+               sap_ctx->sapsMachine);
+        return 0;
+    }
+
+    if (!wlansap_get_change_in_progress(sap_ctx)) {
+       hddLog(LOG1, FL("channel switch not in progress"));
+       return 0;
+    }
+    ret = wait_for_completion_timeout(&sap_ctx->ecsa_info.chan_switch_comp,
+                                  msecs_to_jiffies(HDD_SAP_CHAN_CNG_WAIT_TIME));
+    if (!ret)
+    {
+        hddLog(LOGE, FL("Timeout waiting for SAP channel switch"));
+        return ret;
+    }
+
+    return 0;
+}
+
+
 /**
  * hdd_is_sta_sap_scc_allowed_on_dfs_chan() - check if sta+sap scc allowed on
  * dfs chan