Postpone scan request until TDLS torn down

Introduce delayed_work and reschedule the scan request until
all TDLS peers are torn down.
Define tdls_scan_callback() and tdls_scan_done_callback() to
be called from cfg80211_scan() and cfg80211_scan_done().
Reject the scan request when TDLS connection is in progress.
Check the number of reschedule scan and allow scan if exceed max
attempt.

CRs-Fixed: 453508
Change-Id: Ieab5b72cd214b64ff041d952923533454fc98e85
diff --git a/CORE/HDD/src/wlan_hdd_tdls.c b/CORE/HDD/src/wlan_hdd_tdls.c
index beeecdb..ca99712 100644
--- a/CORE/HDD/src/wlan_hdd_tdls.c
+++ b/CORE/HDD/src/wlan_hdd_tdls.c
@@ -526,6 +526,9 @@
     pHddCtx->connected_peer_count = 0;
     sme_SetTdlsPowerSaveProhibited(WLAN_HDD_GET_HAL_CTX(pAdapter), 0);
 
+    pHddCtx->tdls_scan_ctxt.magic = 0;
+    pHddCtx->tdls_scan_ctxt.scan_request = NULL;
+
     for (staIdx = 0; staIdx < HDD_MAX_NUM_TDLS_STA; staIdx++)
     {
          pHddCtx->tdlsConnInfo[staIdx].staId = 0;
@@ -592,6 +595,7 @@
 void wlan_hdd_tdls_exit(hdd_adapter_t *pAdapter)
 {
     tdlsCtx_t *pHddTdlsCtx;
+    hdd_context_t *pHddCtx;
 
     if (mutex_lock_interruptible(&tdls_lock))
     {
@@ -600,6 +604,13 @@
        return;
     }
 
+    pHddCtx = WLAN_HDD_GET_CTX( pAdapter );
+    if (NULL == pHddCtx)
+    {
+        mutex_unlock(&tdls_lock);
+        return;
+    }
+
     pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
     if (NULL == pHddTdlsCtx)
     {
@@ -613,6 +624,9 @@
     wlan_hdd_tdls_timers_destroy(pHddTdlsCtx);
     wlan_hdd_tdls_free_list(pHddTdlsCtx);
 
+    pHddCtx->tdls_scan_ctxt.magic = 0;
+    wlan_hdd_tdls_free_scan_request(&pHddCtx->tdls_scan_ctxt);
+
     vos_mem_free(pHddTdlsCtx);
     pHddTdlsCtx = NULL;
     mutex_unlock(&tdls_lock);
@@ -1354,7 +1368,8 @@
 
     if ((NULL == pHddCtx) || (NULL == pHddTdlsCtx)) return;
 
-    if ((0 == pHddCtx->connected_peer_count) &&
+    if ((TDLS_CTX_MAGIC != pHddCtx->tdls_scan_ctxt.magic) &&
+        (0 == pHddCtx->connected_peer_count) &&
         (0 == pHddTdlsCtx->discovery_sent_cnt))
     {
         if (FALSE == sme_IsPmcBmps(WLAN_HDD_GET_HAL_CTX(pAdapter)))
@@ -1479,6 +1494,14 @@
 
     VOS_TRACE( VOS_MODULE_ID_HDD, TDLS_LOG_LEVEL,"%s mode %d", __func__, (int)tdls_mode);
 
+    if (NULL == pHddCtx) return;
+
+    if (pHddCtx->tdls_mode == tdls_mode)
+    {
+        hddLog(VOS_TRACE_LEVEL_ERROR, "%s already in mode %d", __func__, (int)tdls_mode);
+        return;
+    }
+
     status = hdd_get_front_adapter ( pHddCtx, &pAdapterNode );
 
     while ( NULL != pAdapterNode && VOS_STATUS_SUCCESS == status )
@@ -1576,3 +1599,216 @@
     sme_SetTdlsPowerSaveProhibited(WLAN_HDD_GET_HAL_CTX(pHddTdlsCtx->pAdapter), 1);
     return;
 }
+
+void wlan_hdd_tdls_free_scan_request (tdls_scan_context_t *tdls_scan_ctx)
+{
+    if (NULL == tdls_scan_ctx)
+        return;
+
+    tdls_scan_ctx->scan_request = NULL;
+        return;
+}
+
+int wlan_hdd_tdls_copy_scan_context(hdd_context_t *pHddCtx,
+                            struct wiphy *wiphy,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+                            struct net_device *dev,
+#endif
+                            struct cfg80211_scan_request *request)
+{
+    tdls_scan_context_t *scan_ctx;
+
+    if (NULL == pHddCtx )
+        return -1;
+
+    scan_ctx = &pHddCtx->tdls_scan_ctxt;
+
+    scan_ctx->wiphy = wiphy;
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+    scan_ctx->dev = dev;
+#endif
+
+    scan_ctx->scan_request = request;
+    return 0;
+}
+
+static void wlan_hdd_tdls_schedule_scan(struct work_struct *work)
+{
+    tdls_scan_context_t *scan_ctx =
+          container_of(work, tdls_scan_context_t, tdls_scan_work.work);
+
+    if (NULL == scan_ctx)
+        return;
+
+    if (unlikely(TDLS_CTX_MAGIC != scan_ctx->magic))
+        return;
+
+    scan_ctx->attempt++;
+
+    wlan_hdd_cfg80211_scan(scan_ctx->wiphy,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+                           scan_ctx->dev,
+#endif
+                           scan_ctx->scan_request);
+}
+
+static void wlan_hdd_tdls_scan_init_work(hdd_context_t *pHddCtx,
+                                struct wiphy *wiphy,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+                                struct net_device *dev,
+#endif
+                                struct cfg80211_scan_request *request,
+                                unsigned long delay)
+{
+    if (TDLS_CTX_MAGIC != pHddCtx->tdls_scan_ctxt.magic)
+    {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+        wlan_hdd_tdls_copy_scan_context(pHddCtx, wiphy, dev, request);
+#else
+        wlan_hdd_tdls_copy_scan_context(pHddCtx, wiphy, request);
+#endif
+        pHddCtx->tdls_scan_ctxt.attempt = 0;
+        pHddCtx->tdls_scan_ctxt.magic = TDLS_CTX_MAGIC;
+    }
+    INIT_DELAYED_WORK(&pHddCtx->tdls_scan_ctxt.tdls_scan_work, wlan_hdd_tdls_schedule_scan);
+
+    schedule_delayed_work(&pHddCtx->tdls_scan_ctxt.tdls_scan_work, delay);
+}
+
+/* return negative = caller should stop and return error code immediately
+   return 0 = caller should stop and return success immediately
+   return 1 = caller can continue to scan
+ */
+int wlan_hdd_tdls_scan_callback (hdd_adapter_t *pAdapter,
+                                struct wiphy *wiphy,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+                                struct net_device *dev,
+#endif
+                                struct cfg80211_scan_request *request)
+{
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0))
+    struct net_device *dev = request->wdev->netdev;
+#endif
+    hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
+    u16 connectedTdlsPeers;
+    unsigned long delay;
+
+    if (NULL == pHddCtx)
+        return 0;
+
+    /* if tdls is not enabled, then continue scan */
+    if (eTDLS_SUPPORT_NOT_ENABLED == pHddCtx->tdls_mode)
+        return 1;
+
+    if (wlan_hdd_tdls_is_progress(pAdapter, NULL, 0))
+    {
+        VOS_TRACE(VOS_MODULE_ID_HDD, TDLS_LOG_LEVEL,
+                "%s: tdls in progress. scan rejected",
+                __func__);
+        return -EBUSY;
+    }
+
+    /* tdls teardown is ongoing */
+    if (eTDLS_SUPPORT_DISABLED == pHddCtx->tdls_mode)
+    {
+        connectedTdlsPeers = wlan_hdd_tdlsConnectedPeers(pAdapter);
+        if (connectedTdlsPeers && (pHddCtx->tdls_scan_ctxt.attempt < TDLS_MAX_SCAN_SCHEDULE))
+        {
+            delay = (unsigned long)(TDLS_DELAY_SCAN_PER_CONNECTION*connectedTdlsPeers);
+            VOS_TRACE(VOS_MODULE_ID_HDD, TDLS_LOG_LEVEL,
+                    "%s: tdls disabled, but still connected_peers %d attempt %d. schedule scan %lu msec",
+                    __func__, connectedTdlsPeers, pHddCtx->tdls_scan_ctxt.attempt, delay);
+
+            wlan_hdd_tdls_scan_init_work (pHddCtx, wiphy,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+                                          dev,
+#endif
+                                          request,
+                                          msecs_to_jiffies(delay));
+            /* scan should not continue */
+            return 0;
+        }
+        /* no connected peer or max retry reached, scan continue */
+        VOS_TRACE(VOS_MODULE_ID_HDD, TDLS_LOG_LEVEL,
+                "%s: tdls disabled. connected_peers %d attempt %d. scan allowed",
+                __func__, connectedTdlsPeers, pHddCtx->tdls_scan_ctxt.attempt);
+        return 1;
+    }
+    /* while tdls is up, first time scan */
+    else if (eTDLS_SUPPORT_ENABLED == pHddCtx->tdls_mode ||
+        eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == pHddCtx->tdls_mode)
+    {
+        /* disable implicit trigger logic & tdls operatoin */
+        wlan_hdd_tdls_set_mode(pHddCtx, eTDLS_SUPPORT_DISABLED);
+        /* indicate the teardown all connected to peer */
+        connectedTdlsPeers = wlan_hdd_tdlsConnectedPeers(pAdapter);
+        if (connectedTdlsPeers)
+        {
+            tANI_U8 staIdx;
+
+            for (staIdx = 0; staIdx < HDD_MAX_NUM_TDLS_STA; staIdx++)
+            {
+                if (pHddCtx->tdlsConnInfo[staIdx].staId)
+                {
+                    VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
+                                   ("%s: indicate TDLS teadown (staId %d)"), __func__, pHddCtx->tdlsConnInfo[staIdx].staId) ;
+
+#ifdef CONFIG_TDLS_IMPLICIT
+                    cfg80211_tdls_oper_request(pAdapter->dev,
+                                               pHddCtx->tdlsConnInfo[staIdx].peerMac.bytes,
+                                               NL80211_TDLS_TEARDOWN,
+                                               eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON,
+                                               GFP_KERNEL);
+#endif
+                }
+            }
+            /* schedule scan */
+            delay = (unsigned long)(TDLS_DELAY_SCAN_PER_CONNECTION*connectedTdlsPeers);
+
+            VOS_TRACE(VOS_MODULE_ID_HDD, TDLS_LOG_LEVEL,
+                    "%s: tdls enabled (mode %d), connected_peers %d. schedule scan %lu msec",
+                    __func__, pHddCtx->tdls_mode, wlan_hdd_tdlsConnectedPeers(pAdapter),
+                    delay);
+
+            wlan_hdd_tdls_scan_init_work (pHddCtx, wiphy,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0))
+                                          dev,
+#endif
+                                          request,
+                                          msecs_to_jiffies(delay));
+            /* scan should not continue */
+            return 0;
+        }
+        /* no connected peer, scan continue */
+        VOS_TRACE(VOS_MODULE_ID_HDD, TDLS_LOG_LEVEL,
+                "%s: tdls_mode %d, and no tdls connection. scan allowed",
+                 __func__, pHddCtx->tdls_mode);
+    }
+    return 1;
+}
+
+void wlan_hdd_tdls_scan_done_callback(hdd_adapter_t *pAdapter)
+{
+    hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
+    tdls_scan_context_t *scan_ctx;
+
+    if (NULL == pHddCtx)
+        return;
+
+    /* free allocated memory at scan time */
+    scan_ctx = &pHddCtx->tdls_scan_ctxt;
+    wlan_hdd_tdls_free_scan_request (scan_ctx);
+    scan_ctx->attempt = 0;
+    scan_ctx->magic = 0;
+
+    /* if tdls was enabled before scan, re-enable tdls mode */
+    if(eTDLS_SUPPORT_ENABLED == pHddCtx->tdls_mode_last ||
+       eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == pHddCtx->tdls_mode_last)
+    {
+        VOS_TRACE(VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
+                       ("%s: revert tdls mode %d"), __func__, pHddCtx->tdls_mode_last);
+
+        wlan_hdd_tdls_set_mode(pHddCtx, pHddCtx->tdls_mode_last);
+    }
+    wlan_hdd_tdls_check_bmps(pAdapter);
+}