wlan: Blocking request for firmware memory dump

Currently,fw memory dump requests from cfg80211 followed
an event based protocol.This has been modified to a
blocking mechanism with a timeout.Now only one fw mem dump
request can be serviced at a time.Request through ioctl is
also changed to the same.Status is sent to userspace in case of
requests through cfg80211 layer.

Change-Id: I359c71f995192ccb6dc766176160f3e1c1244787
CRs-Fixed: 930754
diff --git a/CORE/HDD/inc/wlan_hdd_main.h b/CORE/HDD/inc/wlan_hdd_main.h
index 8f39865..66a6e71 100644
--- a/CORE/HDD/inc/wlan_hdd_main.h
+++ b/CORE/HDD/inc/wlan_hdd_main.h
@@ -1227,12 +1227,34 @@
 #define PROCFS_MEMDUMP_DIR  "debug"
 #define PROCFS_MEMDUMP_NAME "fwdump"
 #define FW_MEM_DUMP_REQ_ID 1
+#define FW_MEM_DUMP_TIMEOUT_MS 3000
+#define FW_MEM_DUMP_MAGIC 0x3C3A2D44
+
+/**
+ * struct hdd_fw_mem_dump_req_ctx - hdd fw mem dump req context
+ *
+ * @magic : magic for validating cfg80211 requests
+ * @status: status for cfg80211 requests
+ * @pHDDCtx: ptr to HDD context
+ * @req_completion: completion variable for fw mem dump
+ */
+struct hdd_fw_mem_dump_req_ctx {
+   uint32_t magic;
+   bool status;
+   struct completion req_completion;
+};
+
+/**
+ * callback type to check fw mem dump request.Called from SVC
+ * context and update status in HDD.
+ */
+typedef void (*hdd_fw_mem_dump_req_cb)(struct hdd_fw_mem_dump_req_ctx *);
 
 int memdump_init(void);
 int memdump_deinit(void);
 void wlan_hdd_fw_mem_dump_cb(void *,tAniFwrDumpRsp *);
 int wlan_hdd_fw_mem_dump_req(hdd_context_t * pHddCtx);
-
+void wlan_hdd_fw_mem_dump_req_cb(struct hdd_fw_mem_dump_req_ctx*);
 #ifdef WLAN_FEATURE_LINK_LAYER_STATS
 /**
  * struct hdd_ll_stats_context - hdd link layer stats context
diff --git a/CORE/HDD/src/wlan_hdd_cfg80211.c b/CORE/HDD/src/wlan_hdd_cfg80211.c
index 84c7f48..4d4dc5a 100644
--- a/CORE/HDD/src/wlan_hdd_cfg80211.c
+++ b/CORE/HDD/src/wlan_hdd_cfg80211.c
@@ -5852,6 +5852,16 @@
     /*call common API for FW mem dump req*/
     ret = wlan_hdd_fw_mem_dump_req(pHddCtx);
 
+    if (true == ret)
+    {
+        /*indicate to userspace the status of fw mem dump */
+        wlan_indicate_mem_dump_complete(true);
+    }
+    else
+    {
+        /*else send failure to userspace */
+        wlan_indicate_mem_dump_complete(false);
+    }
     EXIT();
     return ret;
 }
diff --git a/CORE/HDD/src/wlan_hdd_main.c b/CORE/HDD/src/wlan_hdd_main.c
index d9f1df3..d150651 100755
--- a/CORE/HDD/src/wlan_hdd_main.c
+++ b/CORE/HDD/src/wlan_hdd_main.c
@@ -12165,6 +12165,7 @@
     int status;
     hdd_context_t *hdd_ctx = (hdd_context_t *)PDE_DATA(file_inode(file));
     size_t ret_count;
+    loff_t bytes_left;
     ENTER();
 
     hddLog(LOG1, FL("Read req for size:%zu pos:%llu"), count, *pos);
@@ -12180,12 +12181,13 @@
 
     /* run fs_read_handler in an atomic context*/
     vos_ssr_protect(__func__);
-    ret_count = wlan_fwr_mem_dump_fsread_handler( buf, count, pos);
-    if(ret_count == 0)
+    ret_count = wlan_fwr_mem_dump_fsread_handler( buf, count, pos, &bytes_left);
+    if(bytes_left == 0)
     {
         /*Free the fwr mem dump buffer */
         wlan_free_fwr_mem_dump_buffer();
         wlan_set_fwr_mem_dump_state(FW_MEM_DUMP_IDLE);
+        ret_count=0;
     }
     /*if SSR/unload code is waiting for memdump_read to finish,signal it*/
     vos_ssr_unprotect(__func__);
@@ -12215,25 +12217,29 @@
 void wlan_hdd_fw_mem_dump_cb(void *fwMemDumpReqContext,
                          tAniFwrDumpRsp *dump_rsp)
 {
-    hdd_context_t *pHddCtx = (hdd_context_t *)fwMemDumpReqContext;
-    int status;
-    ENTER();
-    status = wlan_hdd_validate_context(pHddCtx);
-    if (0 != status) {
-        return;
-    }
+    struct hdd_fw_mem_dump_req_ctx *pHddFwMemDumpCtx = (struct hdd_fw_mem_dump_req_ctx *)fwMemDumpReqContext;
 
+    ENTER();
+    spin_lock(&hdd_context_lock);
+    if(!pHddFwMemDumpCtx || (FW_MEM_DUMP_MAGIC != pHddFwMemDumpCtx->magic)) {
+       spin_unlock(&hdd_context_lock);
+       return;
+    }
+    /* report the status to requesting function and free mem.*/
     if (dump_rsp->dump_status != eHAL_STATUS_SUCCESS) {
-        hddLog(LOGE, FL("fw dump request declined by fwr"));
-       //report failure to user space
-       wlan_indicate_mem_dump_complete(false);
+       hddLog(LOGE, FL("fw dump request declined by fwr"));
+       //set the request completion variable
+       complete(&(pHddFwMemDumpCtx->req_completion));
        //Free the allocated fwr dump
        wlan_free_fwr_mem_dump_buffer();
        wlan_set_fwr_mem_dump_state(FW_MEM_DUMP_IDLE);
-       return;
     }
-    else
-        hddLog(LOG1, FL("fw dump request accepted by fwr"));
+    else {
+       hddLog(LOG1, FL("fw dump request accepted by fwr"));
+       /* register the HDD callback which will be called by SVC */
+       wlan_set_svc_fw_mem_dump_req_cb((void*)wlan_hdd_fw_mem_dump_req_cb,(void*)pHddFwMemDumpCtx);
+    }
+    spin_unlock(&hdd_context_lock);
     EXIT();
 
 }
@@ -12370,9 +12376,11 @@
 int wlan_hdd_fw_mem_dump_req(hdd_context_t * pHddCtx)
 {
    tAniFwrDumpReq fw_mem_dump_req={0};
+   struct hdd_fw_mem_dump_req_ctx fw_mem_dump_ctx;
    eHalStatus status = eHAL_STATUS_FAILURE;
    int ret=0;
    ENTER();
+
    /*Check whether a dump request is already going on
     *Caution this function will free previously held memory if new dump request is allowed*/
    if (!wlan_fwr_mem_dump_test_and_set_write_allowed_bit()) {
@@ -12390,18 +12398,61 @@
        hddLog(LOGE, FL("Fwr mem Allocation failed"));
        return -ENOMEM;
    }
+   init_completion(&fw_mem_dump_ctx.req_completion);
+   fw_mem_dump_ctx.magic = FW_MEM_DUMP_MAGIC;
+   fw_mem_dump_ctx.status = false;
+
    fw_mem_dump_req.fwMemDumpReqCallback = wlan_hdd_fw_mem_dump_cb;
-   fw_mem_dump_req.fwMemDumpReqContext = pHddCtx;
+   fw_mem_dump_req.fwMemDumpReqContext = &fw_mem_dump_ctx;
    status = sme_FwMemDumpReq(pHddCtx->hHal, &fw_mem_dump_req);
    if(eHAL_STATUS_SUCCESS != status)
    {
        hddLog(VOS_TRACE_LEVEL_ERROR,
           "%s: fw_mem_dump_req failed ", __func__);
        wlan_free_fwr_mem_dump_buffer();
+       ret = -EFAULT;
+       goto cleanup;
    }
-   EXIT();
+   /*wait for fw mem dump completion to send event to userspace*/
+   ret = wait_for_completion_timeout(&fw_mem_dump_ctx.req_completion,msecs_to_jiffies(FW_MEM_DUMP_TIMEOUT_MS));
+   if (0 >= ret )
+   {
+      hddLog(VOS_TRACE_LEVEL_ERROR,
+          "%s: fw_mem_dump_req timeout %d ", __func__,ret);
+   }
+cleanup:
+   spin_lock(&hdd_context_lock);
+   fw_mem_dump_ctx.magic = 0;
+   spin_unlock(&hdd_context_lock);
 
-   return status;
+   EXIT();
+   return fw_mem_dump_ctx.status;
+}
+
+/**
+ * HDD callback which will be called by SVC to indicate mem dump completion.
+ */
+void wlan_hdd_fw_mem_dump_req_cb(struct hdd_fw_mem_dump_req_ctx* pHddFwMemDumpCtx)
+{
+   if (!pHddFwMemDumpCtx) {
+       hddLog(VOS_TRACE_LEVEL_ERROR,
+          "%s: HDD context not valid ", __func__);
+       return;
+   }
+   spin_lock(&hdd_context_lock);
+   /* check the req magic and set status */
+   if (pHddFwMemDumpCtx->magic == FW_MEM_DUMP_MAGIC)
+   {
+       pHddFwMemDumpCtx->status = true;
+       //signal the completion
+       complete(&(pHddFwMemDumpCtx->req_completion));
+   }
+   else
+   {
+       hddLog(VOS_TRACE_LEVEL_ERROR,
+          "%s: fw mem dump request possible timeout ", __func__);
+   }
+   spin_unlock(&hdd_context_lock);
 }
 
 void hdd_initialize_adapter_common(hdd_adapter_t *pAdapter)
diff --git a/CORE/SVC/inc/wlan_logging_sock_svc.h b/CORE/SVC/inc/wlan_logging_sock_svc.h
index d5afa33..5663cb1 100644
--- a/CORE/SVC/inc/wlan_logging_sock_svc.h
+++ b/CORE/SVC/inc/wlan_logging_sock_svc.h
@@ -66,6 +66,8 @@
 
 void wlan_logging_set_log_level(void);
 
+#define FW_MEM_DUMP_MAGIC 0x3C3A2D44
+
 enum FW_MEM_DUMP_STATE{
        FW_MEM_DUMP_IDLE,
        FW_MEM_DUMP_READ_IN_PROGRESS,
@@ -76,7 +78,8 @@
 bool wlan_fwr_mem_dump_test_and_set_write_allowed_bit(void);
 bool wlan_fwr_mem_dump_test_and_set_read_allowed_bit(void);
 void wlan_set_fwr_mem_dump_state(enum FW_MEM_DUMP_STATE fw_mem_dump_state);
-size_t wlan_fwr_mem_dump_fsread_handler(char __user *buf, size_t count, loff_t *pos);
+void wlan_set_svc_fw_mem_dump_req_cb(void*,void*);
+size_t wlan_fwr_mem_dump_fsread_handler(char __user *buf, size_t count, loff_t *pos,loff_t* bytes_left);
 void wlan_indicate_mem_dump_complete(bool );
 void wlan_store_fwr_mem_dump_size(uint32 dump_size);
 void wlan_free_fwr_mem_dump_buffer(void);
diff --git a/CORE/SVC/src/logging/wlan_logging_sock_svc.c b/CORE/SVC/src/logging/wlan_logging_sock_svc.c
index c5c7b03..7192b6d 100644
--- a/CORE/SVC/src/logging/wlan_logging_sock_svc.c
+++ b/CORE/SVC/src/logging/wlan_logging_sock_svc.c
@@ -124,10 +124,12 @@
 	unsigned int fw_mem_dump_pkt_drop_cnt;
 	/* Lock to synchronize of queue/dequeue of pkts in fw log pkt queue */
 	spinlock_t fw_mem_dump_lock;
-	/* Fw memory dump state */
+	/* Fw memory dump status */
 	enum FW_MEM_DUMP_STATE fw_mem_dump_status;
-	/* Completion variable for handling SSR/unload during copy_to_user */
-	struct completion fw_mem_copy_to_user_completion;
+	/* storage for HDD callback which completes fw mem dump request */
+	void * svc_fw_mem_dump_req_cb;
+	/* storage for HDD callback which completes fw mem dump request arg */
+	void * svc_fw_mem_dump_req_cb_arg;
 };
 
 struct pkt_stats_msg {
@@ -870,7 +872,6 @@
 	VOS_STATUS status = VOS_STATUS_E_FAILURE;
 	unsigned long flags;
 	int  byte_left = 0;
-
 	do {
 		spin_lock_irqsave(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
 
@@ -918,20 +919,18 @@
 					(int)(gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc - gwlan_logging.fw_mem_dump_ctx.fw_dump_start_loc));
 			if(skb->len > byte_left)
 			{
-				vos_mem_copy(gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc, &skb->data, byte_left);
+				vos_mem_copy(gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc, skb->data, byte_left);
 				//Update the current location ptr
 				gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc +=  byte_left;
 			}
 			else
 			{
-				vos_mem_copy(gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc, &skb->data, skb->len);
+				vos_mem_copy(gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc, skb->data, skb->len);
 				//Update the current location ptr
 				gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc +=  skb->len;
 			}
 		}
 		spin_unlock_irqrestore(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
-		//if(skb)
-		//	pr_err("Mem dump buffer overflow byte_left %d skb->len %d", byte_left, skb->len);
 		/*return vos pkt since skb is already detached */
 		vos_pkt_return_packet(current_pkt);
 	} while (next_pkt);
@@ -1229,7 +1228,6 @@
 	int ret_wait_status = 0;
 	int ret = 0;
 	unsigned long flags;
-
 	set_user_nice(current, -2);
 
 #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0))
@@ -1331,9 +1329,31 @@
 			fill_fw_mem_dump_buffer();
 		}
 		if(test_and_clear_bit(LOGGER_FW_MEM_DUMP_PKT_POST_DONE_MASK,&gwlan_logging.event_flag)){
-				/*indicate to user space that mem dump is complete */
+				spin_lock_irqsave(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock,flags);
+				/*Chnage fw memory dump to indicate write done*/
+				gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_status = FW_MEM_DUMP_WRITE_DONE;
+				/*reset dropped packet count upon completion of this request*/
+				gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_pkt_drop_cnt = 0;
+				spin_unlock_irqrestore(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock,flags);
 				fill_fw_mem_dump_buffer();
-				wlan_indicate_mem_dump_complete(true);
+				/*
+				 * Call the registered HDD callback for indicating
+				 * memdump complete. If it's null,then something is
+				 * not right.
+				 */
+				if (gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb &&
+				    gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb_arg) {
+					((hdd_fw_mem_dump_req_cb)
+					gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb)(
+					(struct hdd_fw_mem_dump_req_ctx*)
+					gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb_arg);
+
+					/*invalidate the callback pointers*/
+					spin_lock_irqsave(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock,flags);
+					gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb = NULL;
+					gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb_arg = NULL;
+					spin_unlock_irqrestore(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock,flags);
+				}
 		}
 
 		if (test_and_clear_bit(HOST_PKT_STATS_POST_MASK,
@@ -2032,6 +2052,8 @@
 		pr_err("%s: fw_mem_dump_req alloc failed for size %d bytes", __func__,gwlan_logging.fw_mem_dump_ctx.fw_dump_max_size);
 		return -ENOMEM;
 	}
+	vos_mem_zero(gwlan_logging.fw_mem_dump_ctx.fw_dump_start_loc,gwlan_logging.fw_mem_dump_ctx.fw_dump_max_size);
+
 	return 0;
 }
 
@@ -2077,12 +2099,12 @@
           gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_status = FW_MEM_DUMP_READ_IN_PROGRESS;
 	}
 	spin_unlock_irqrestore(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
-	pr_info("%s:fw mem dump state --> %d ", __func__,gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_status);
+	//pr_info("%s:fw mem dump state --> %d ", __func__,gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_status);
 
 	return ret;
 }
 size_t wlan_fwr_mem_dump_fsread_handler(char __user *buf,
-		size_t count, loff_t *pos)
+		size_t count, loff_t *pos,loff_t* bytes_left)
 {
 	if (buf == NULL || gwlan_logging.fw_mem_dump_ctx.fw_dump_start_loc == NULL)
 	{
@@ -2100,16 +2122,24 @@
 		count = gwlan_logging.fw_mem_dump_ctx.fw_dump_max_size - *pos;
 	}
 	if (copy_to_user(buf, gwlan_logging.fw_mem_dump_ctx.fw_dump_start_loc, count)) {
-		pr_err("copy to user space failed");
+		pr_err("%s copy to user space failed",__func__);
 		return 0;
 	}
-
 	/* offset(pos) should be updated here based on the copy done*/
 	*pos += count;
-
+	*bytes_left = gwlan_logging.fw_mem_dump_ctx.fw_dump_max_size - *pos;
 	return count;
 }
 
+void  wlan_set_svc_fw_mem_dump_req_cb (void * fw_mem_dump_req_cb, void * fw_mem_dump_req_cb_arg)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
+	gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb = fw_mem_dump_req_cb;
+	gwlan_logging.fw_mem_dump_ctx.svc_fw_mem_dump_req_cb_arg = fw_mem_dump_req_cb_arg;
+	spin_unlock_irqrestore(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
+}
+
 void wlan_free_fwr_mem_dump_buffer (void )
 {
 	unsigned long flags;
@@ -2147,8 +2177,6 @@
 	void *vos_ctx;
 	int ret;
 	struct sk_buff *skb = NULL;
-	unsigned long flags;
-
 	vos_ctx = vos_get_global_context(VOS_MODULE_ID_SYS, NULL);
 	if (!vos_ctx) {
 		pr_err("Invalid VOS context");
@@ -2167,20 +2195,9 @@
 		return;
 	}
 
-	spin_lock_irqsave(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
-	/*Chnage fw memory dump to indicate write done*/
-	gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_status = FW_MEM_DUMP_WRITE_DONE;
-        /*reset dropped packet count upon completion of this request*/
-        gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_pkt_drop_cnt = 0;
-	spin_unlock_irqrestore(&gwlan_logging.fw_mem_dump_ctx.fw_mem_dump_lock, flags);
 
-	skb = cfg80211_vendor_event_alloc(hdd_ctx->wiphy,
-#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0))
-		NULL,
-#endif
-		sizeof(uint32_t) + NLA_HDRLEN + NLMSG_HDRLEN,
-		QCA_NL80211_VENDOR_SUBCMD_WIFI_LOGGER_MEMORY_DUMP_INDEX,
-		GFP_KERNEL);
+	skb = cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy,
+		sizeof(uint32_t) + NLA_HDRLEN + NLMSG_HDRLEN);
 
 	if (!skb) {
 		pr_err("cfg80211_vendor_event_alloc failed");
@@ -2203,8 +2220,8 @@
 			goto nla_put_failure;
 		}
 	}
-
-	cfg80211_vendor_event(skb, GFP_KERNEL);
+	/*indicate mem dump complete*/
+	cfg80211_vendor_cmd_reply(skb);
 	pr_info("Memdump event sent successfully to user space : recvd size %d",(int)(gwlan_logging.fw_mem_dump_ctx.fw_dump_current_loc - gwlan_logging.fw_mem_dump_ctx.fw_dump_start_loc));
 	return;