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
diff --git a/CORE/SAP/src/sapApiLinkCntl.c b/CORE/SAP/src/sapApiLinkCntl.c
index a34e5cd..5e90549 100644
--- a/CORE/SAP/src/sapApiLinkCntl.c
+++ b/CORE/SAP/src/sapApiLinkCntl.c
@@ -1215,7 +1215,6 @@
              FL("sapdfs: from state eSAP_DISCONNECTING => eSAP_STARTING on sapctx[%pK]"),
              sap_ctx);
    sap_ctx->sapsMachine = eSAP_STARTING;
-   sap_ctx->ecsa_info.channel_switch_in_progress = false;
    sap_event.event = eSAP_MAC_START_BSS_SUCCESS;
    sap_event.params = csr_roam_info;
    sap_event.u1 = eCSR_ROAM_INFRA_IND;
@@ -1223,6 +1222,10 @@
 
    /* Handle the event */
    status = sapFsm(sap_ctx, &sap_event);
+
+   wlansap_reset_chan_change_in_progress(sap_ctx);
+   complete(&sap_ctx->ecsa_info.chan_switch_comp);
+
    return status;
 }
 
diff --git a/CORE/SAP/src/sapInternal.h b/CORE/SAP/src/sapInternal.h
index d679580..a7e3fb9 100644
--- a/CORE/SAP/src/sapInternal.h
+++ b/CORE/SAP/src/sapInternal.h
@@ -208,12 +208,16 @@
 
 /**
  * struct ecsa_info - structure to store ecsa info
+ * @ecsa_lock: ecsa lock
  * @new_channel: new channel to which switch is requested
  * @channel_switch_in_progress: check if channel switch is in progress
+ * @chan_switch_comp: channel switch comp var
  */
 struct ecsa_info {
+    spinlock_t ecsa_lock;
     uint8_t new_channel;
     bool channel_switch_in_progress;
+    struct completion chan_switch_comp;
 };
 
 typedef struct sSapContext {
@@ -945,6 +949,35 @@
                                           struct hdd_cache_sta_info *astainfo,
                                           u8 *mac_addr);
 
+/**
+ * wlansap_chk_n_set_chan_change_in_progress() -
+ * check if chan change is in progress and set it if not
+ *
+ * @sap_ctx: sap context
+ *
+ * Return: 0 if channel change is not in progress else error
+ */
+int wlansap_chk_n_set_chan_change_in_progress(ptSapContext sap_ctx);
+/**
+ * wlansap_reset_chan_change_in_progress() - reset channel change in progress
+ *
+ * @sap_ctx: sap context
+ *
+ * Return: 0 if channel change is reset else error
+ */
+int wlansap_reset_chan_change_in_progress(ptSapContext sap_ctx);
+
+/**
+ * wlansap_get_change_in_progress() - get channel change in progress
+ *
+ * @sap_ctx: sap context
+ *
+ * Return: true if channel change in progress else false
+ */
+bool wlansap_get_change_in_progress(ptSapContext sap_ctx);
+
+
+
 #ifdef __cplusplus
 }
 #endif 
diff --git a/CORE/SAP/src/sapModule.c b/CORE/SAP/src/sapModule.c
index 20febb5..6268e7d 100644
--- a/CORE/SAP/src/sapModule.c
+++ b/CORE/SAP/src/sapModule.c
@@ -173,6 +173,15 @@
         return VOS_STATUS_E_FAULT;
     }
 
+    if (!VOS_IS_STATUS_SUCCESS(
+         vos_spin_lock_init(&pSapCtx->ecsa_info.ecsa_lock)))
+    {
+        VOS_TRACE( VOS_MODULE_ID_SAP, VOS_TRACE_LEVEL_ERROR,
+                 "WLANSAP_Start failed init ecsa_lock");
+        vos_free_context(pvosGCtx, VOS_MODULE_ID_SAP, pSapCtx);
+        return VOS_STATUS_E_FAULT;
+    }
+
     // Setup the "link back" to the VOSS context
     pSapCtx->pvosGCtx = pvosGCtx;
 
@@ -639,6 +648,7 @@
         pSapCtx->scanBandPreference = pConfig->scanBandPreference;
         pSapCtx->acsBandSwitchThreshold = pConfig->acsBandSwitchThreshold;
         pSapCtx->pUsrContext = pUsrContext;
+        init_completion(&pSapCtx->ecsa_info.chan_switch_comp);
 
         //Set the BSSID to your "self MAC Addr" read the mac address from Configuation ITEM received from HDD
         pSapCtx->csrRoamProfile.BSSIDs.numOfBSSIDs = 1;
@@ -2494,6 +2504,41 @@
   return true;
 }
 
+int wlansap_chk_n_set_chan_change_in_progress(ptSapContext sap_ctx)
+{
+   vos_spin_lock_acquire(&sap_ctx->ecsa_info.ecsa_lock);
+   if (sap_ctx->ecsa_info.channel_switch_in_progress) {
+       vos_spin_lock_release(&sap_ctx->ecsa_info.ecsa_lock);
+       hddLog(LOGE, FL("channel switch already in progress"));
+       return -EALREADY;
+   }
+   sap_ctx->ecsa_info.channel_switch_in_progress = true;
+   vos_spin_lock_release(&sap_ctx->ecsa_info.ecsa_lock);
+
+   return 0;
+}
+
+int wlansap_reset_chan_change_in_progress(ptSapContext sap_ctx)
+{
+   vos_spin_lock_acquire(&sap_ctx->ecsa_info.ecsa_lock);
+   sap_ctx->ecsa_info.channel_switch_in_progress = false;
+   vos_spin_lock_release(&sap_ctx->ecsa_info.ecsa_lock);
+
+   return 0;
+}
+
+bool wlansap_get_change_in_progress(ptSapContext sap_ctx)
+{
+   bool value;
+
+   vos_spin_lock_acquire(&sap_ctx->ecsa_info.ecsa_lock);
+   value = sap_ctx->ecsa_info.channel_switch_in_progress;
+   vos_spin_lock_release(&sap_ctx->ecsa_info.ecsa_lock);
+
+   return value;
+}
+
+
 int wlansap_set_channel_change(v_PVOID_t vos_ctx,
     uint32_t new_channel, bool allow_dfs_chan)
 {
@@ -2526,10 +2571,12 @@
         hddLog(LOGE, FL("channel %d already set"), new_channel);
         return -EALREADY;
    }
-   if (sap_ctx->ecsa_info.channel_switch_in_progress) {
-        hddLog(LOGE, FL("channel switch already in progress ignore"));
-        return -EALREADY;
+
+   if(!wlansap_get_change_in_progress(sap_ctx)) {
+       hddLog(LOGE, FL("channel_switch_in_progress should be set before calling channel change"));
+       return -EINVAL;
    }
+
    chan_state = vos_nv_getChannelEnabledState(new_channel);
    if ((chan_state == NV_CHANNEL_DISABLE) ||
        (chan_state == NV_CHANNEL_INVALID)) {
@@ -2549,7 +2596,6 @@
    }
 
    sap_ctx->ecsa_info.new_channel = new_channel;
-   sap_ctx->ecsa_info.channel_switch_in_progress = true;
    /*
     * Post the eSAP_CHANNEL_SWITCH_ANNOUNCEMENT_START
     * to SAP state machine to process the channel