qcacld-3.0: Add debugfs framework support for CSR

Add debugfs framework support to get connect, scan,
roam scan statistics and offload info from debugfs.

Change-Id: I86bdd7e449488d2bcda1b2eaaeb07aac7465770b
CRs-Fixed: 2203626
diff --git a/Kbuild b/Kbuild
index f55013a..7434f3e 100755
--- a/Kbuild
+++ b/Kbuild
@@ -70,6 +70,7 @@
 HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs.o
 ifeq ($(CONFIG_WLAN_FEATURE_LINK_LAYER_STATS), y)
 HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs_llstat.o
+HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs_csr.o
 endif
 endif
 
diff --git a/core/hdd/inc/wlan_hdd_assoc.h b/core/hdd/inc/wlan_hdd_assoc.h
index 8a27418..6d2aef5 100644
--- a/core/hdd/inc/wlan_hdd_assoc.h
+++ b/core/hdd/inc/wlan_hdd_assoc.h
@@ -31,6 +31,8 @@
 #include <net/cfg80211.h>
 #include <linux/ieee80211.h>
 
+#define HDD_TIME_STRING_LEN 24
+
 /* Preprocessor Definitions and Constants */
 #ifdef FEATURE_WLAN_TDLS
 #define HDD_MAX_NUM_TDLS_STA          8
diff --git a/core/hdd/inc/wlan_hdd_debugfs.h b/core/hdd/inc/wlan_hdd_debugfs.h
index 01cf805..daa4795 100644
--- a/core/hdd/inc/wlan_hdd_debugfs.h
+++ b/core/hdd/inc/wlan_hdd_debugfs.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2018 The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for
  * any purpose with or without fee is hereby granted, provided that the
@@ -20,8 +20,77 @@
 #define _WLAN_HDD_DEBUGFS_H
 
 #ifdef WLAN_DEBUGFS
+
+#define HDD_DEBUGFS_FILE_NAME_MAX 24
+
+/**
+ * enum hdd_debugfs_file_id - Debugfs file Identifier
+ * @HDD_DEBUFS_FILE_ID_CONNECT_INFO: connect_info file id
+ * @HDD_DEBUFS_FILE_ID_ROAM_SCAN_STATS_INFO: roam_scan_stats file id
+ * @HDD_DEBUFS_FILE_ID_OFFLOAD_INFO: offload_info file id
+ * @HDD_DEBUGFS_FILE_ID_MAX: maximum id of csr debugfs file
+ */
+enum hdd_debugfs_file_id {
+	HDD_DEBUFS_FILE_ID_CONNECT_INFO = 0,
+	HDD_DEBUFS_FILE_ID_ROAM_SCAN_STATS_INFO = 1,
+	HDD_DEBUFS_FILE_ID_OFFLOAD_INFO = 2,
+
+	HDD_DEBUGFS_FILE_ID_MAX,
+};
+
+/**
+ * struct hdd_debugfs_file_info - Debugfs file info
+ * @name: name of debugfs file
+ * @id: id from enum hdd_debugfs_file_id used to identify file
+ * @buf_max_size: max size of buffer from which debugfs file is updated
+ * @entry: dentry pointer to debugfs file
+ */
+struct hdd_debugfs_file_info {
+	uint8_t name[HDD_DEBUGFS_FILE_NAME_MAX];
+	enum hdd_debugfs_file_id id;
+	ssize_t buf_max_size;
+	struct dentry *entry;
+};
+
 QDF_STATUS hdd_debugfs_init(struct hdd_adapter *adapter);
 void hdd_debugfs_exit(struct hdd_adapter *adapter);
+
+/**
+ * hdd_wait_for_debugfs_threads_completion() - Wait for debugfs threads
+ * completion before proceeding further to stop modules
+ *
+ * Return: true if there is no debugfs open
+ *         false if there is at least one debugfs open
+ */
+bool hdd_wait_for_debugfs_threads_completion(void);
+
+/**
+ * hdd_return_debugfs_threads_count() - Return active debugfs threads
+ *
+ * Return: total number of active debugfs threads in driver
+ */
+int hdd_return_debugfs_threads_count(void);
+
+/**
+ * hdd_debugfs_thread_increment() - Increment debugfs thread count
+ *
+ * This function is used to increment and keep track of debugfs thread count.
+ * This is invoked for every file open operation.
+ *
+ * Return: None
+ */
+void hdd_debugfs_thread_increment(void);
+
+/**
+ * hdd_debugfs_thread_decrement() - Decrement debugfs thread count
+ *
+ * This function is used to decrement and keep track of debugfs thread count.
+ * This is invoked for every file release operation.
+ *
+ * Return: None
+ */
+void hdd_debugfs_thread_decrement(void);
+
 #else
 static inline QDF_STATUS hdd_debugfs_init(struct hdd_adapter *adapter)
 {
@@ -31,5 +100,56 @@
 static inline void hdd_debugfs_exit(struct hdd_adapter *adapter)
 {
 }
+
+/**
+ * hdd_wait_for_debugfs_threads_completion() - Wait for debugfs threads
+ * completion before proceeding further to stop modules
+ *
+ * Return: true if there is no debugfs open
+ *         false if there is at least one debugfs open
+ */
+static inline
+bool hdd_wait_for_debugfs_threads_completion(void)
+{
+	return true;
+}
+
+/**
+ * hdd_return_debugfs_threads_count() - Return active debugfs threads
+ *
+ * Return: total number of active debugfs threads in driver
+ */
+static inline
+int hdd_return_debugfs_threads_count(void)
+{
+	return 0;
+}
+
+/**
+ * hdd_debugfs_thread_increment() - Increment debugfs thread count
+ *
+ * This function is used to increment and keep track of debugfs thread count.
+ * This is invoked for every file open operation.
+ *
+ * Return: None
+ */
+static inline
+void hdd_debugfs_thread_increment(void)
+{
+}
+
+/**
+ * hdd_debugfs_thread_decrement() - Decrement debugfs thread count
+ *
+ * This function is used to decrement and keep track of debugfs thread count.
+ * This is invoked for every file release operation.
+ *
+ * Return: None
+ */
+static inline
+void hdd_debugfs_thread_decrement(void)
+{
+}
+
 #endif /* #ifdef WLAN_DEBUGFS */
 #endif /* #ifndef _WLAN_HDD_DEBUGFS_H */
diff --git a/core/hdd/inc/wlan_hdd_debugfs_csr.h b/core/hdd/inc/wlan_hdd_debugfs_csr.h
new file mode 100644
index 0000000..60a7dbe
--- /dev/null
+++ b/core/hdd/inc/wlan_hdd_debugfs_csr.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018 The Linux Foundation. All rights reserved.
+ *
+ * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
+ *
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This file was originally distributed by Qualcomm Atheros, Inc.
+ * under proprietary terms before Copyright ownership was assigned
+ * to the Linux Foundation.
+ */
+
+/**
+ * DOC: wlan_hdd_debugfs_csr.h
+ *
+ * WLAN Host Device Driver implementation to update
+ * debugfs with connect, scan and roam information
+ */
+
+#ifndef _WLAN_HDD_DEBUGFS_CSR_H
+#define _WLAN_HDD_DEBUGFS_CSR_H
+
+#include <wlan_hdd_includes.h>
+
+#ifdef WLAN_DEBUGFS
+
+/**
+ * struct wlan_hdd_debugfs_buffer_info - Debugfs buffer info
+ * @length: current length of the debugfs buffer
+ * @max_buf_len: maximum buffer length of the debugfs buffer
+ * @id: id from enum hdd_debugfs_file_id used to identify file
+ * @data: start of debugfs buffer from which file read starts
+ * @adapter: pointer to adapter
+ *
+ * This structure is used to hold the debugfs buffer details and is stored in
+ * private data of file argument in file open operation.
+ */
+struct wlan_hdd_debugfs_buffer_info {
+	ssize_t length;
+	ssize_t max_buf_len;
+	enum hdd_debugfs_file_id id;
+	uint8_t *data;
+	struct hdd_adapter *adapter;
+};
+
+/**
+ * wlan_hdd_debugfs_csr_init() - Create wifi diagnostic debugfs files
+ * @adapter: pointer to adapter for which debugfs files are to be created
+ *
+ * Return: None
+ */
+void wlan_hdd_debugfs_csr_init(struct hdd_adapter *adapter);
+
+/**
+ * wlan_hdd_debugfs_csr_deinit() - Remove wifi diagnostic debugfs files
+ * @adapter: pointer to adapter for which debugfs files are to be removed
+ *
+ * Return: None
+ */
+void wlan_hdd_debugfs_csr_deinit(struct hdd_adapter *adapter);
+
+/**
+ * wlan_hdd_current_time_info_debugfs() - API to get time into user buffer
+ * @buf: output buffer to hold current time when queried
+ * @buf_avail_len: available buffer length
+ *
+ * Return: No.of bytes copied
+ */
+ssize_t
+wlan_hdd_current_time_info_debugfs(uint8_t *buf, ssize_t buf_avail_len);
+
+#else
+/**
+ * wlan_hdd_debugfs_csr_init() - Create wifi diagnostic debugfs files
+ * @adapter: pointer to adapter for which debugfs files are to be created
+ *
+ * Return: None
+ */
+static inline void wlan_hdd_debugfs_csr_init(struct hdd_adapter *adapter)
+{
+}
+
+/**
+ * wlan_hdd_debugfs_csr_deinit() - Remove wifi diagnostic debugfs files
+ * @adapter: pointer to adapter for which debugfs files are to be removed
+ *
+ * Return: None
+ */
+static inline void wlan_hdd_debugfs_csr_deinit(struct hdd_adapter *adapter)
+{
+}
+
+/**
+ * wlan_hdd_current_time_info_debugfs() - API to get time into user buffer
+ * @buf: output buffer to hold current time when queried
+ * @buf_avail_len: available buffer length
+ *
+ * Return: No.of bytes copied
+ */
+static inline ssize_t
+wlan_hdd_current_time_info_debugfs(uint8_t *buf, ssize_t buf_avail_len)
+{
+	return 0;
+}
+
+#endif
+
+#endif /* _WLAN_HDD_DEBUGFS_CSR_H */
diff --git a/core/hdd/inc/wlan_hdd_main.h b/core/hdd/inc/wlan_hdd_main.h
index 265ef67..c02df39 100644
--- a/core/hdd/inc/wlan_hdd_main.h
+++ b/core/hdd/inc/wlan_hdd_main.h
@@ -63,6 +63,7 @@
 #include "wlan_hdd_tdls.h"
 #include "wlan_hdd_tsf.h"
 #include "wlan_hdd_cfg80211.h"
+#include "wlan_hdd_debugfs.h"
 #include <qdf_defer.h>
 #include "sap_api.h"
 #include <wlan_hdd_lro.h>
@@ -254,6 +255,8 @@
 
 #define WLAN_WAIT_TIME_APF     1000
 
+#define WLAN_WAIT_TIME_FW_ROAM_STATS 1000
+
 /* Maximum time(ms) to wait for RSO CMD status event */
 #define WAIT_TIME_RSO_CMD_STATUS 2000
 
@@ -1470,6 +1473,10 @@
 #ifdef FEATURE_WLAN_APF
 	struct hdd_apf_context apf_context;
 #endif /* FEATURE_WLAN_APF */
+
+#ifdef WLAN_DEBUGFS
+	struct hdd_debugfs_file_info csr_file[HDD_DEBUGFS_FILE_ID_MAX];
+#endif /* WLAN_DEBUGFS */
 };
 
 #define WLAN_HDD_GET_STATION_CTX_PTR(adapter) (&(adapter)->session.station)
diff --git a/core/hdd/src/wlan_hdd_debugfs.c b/core/hdd/src/wlan_hdd_debugfs.c
index db1ea4d..c451da8 100644
--- a/core/hdd/src/wlan_hdd_debugfs.c
+++ b/core/hdd/src/wlan_hdd_debugfs.c
@@ -41,6 +41,51 @@
 #define POWER_DEBUGFS_BUFFER_MAX_LEN 4096
 #endif
 
+#define MAX_DEBUGFS_WAIT_ITERATIONS 20
+#define DEBUGFS_WAIT_SLEEP_TIME 100
+
+static qdf_atomic_t debugfs_thread_count;
+
+void hdd_debugfs_thread_increment(void)
+{
+	qdf_atomic_inc(&debugfs_thread_count);
+}
+
+void hdd_debugfs_thread_decrement(void)
+{
+	qdf_atomic_dec(&debugfs_thread_count);
+}
+
+int hdd_return_debugfs_threads_count(void)
+{
+	return qdf_atomic_read(&debugfs_thread_count);
+}
+
+bool hdd_wait_for_debugfs_threads_completion(void)
+{
+	int count = MAX_DEBUGFS_WAIT_ITERATIONS;
+	int r;
+
+	while (count) {
+		r = hdd_return_debugfs_threads_count();
+		if (!r)
+			break;
+
+		if (--count) {
+			hdd_debug("Waiting for %d debugfs threads to exit", r);
+			qdf_sleep(DEBUGFS_WAIT_SLEEP_TIME);
+		}
+	}
+
+	/* at least one debugfs thread is executing */
+	if (!count) {
+		hdd_err("Timed-out waiting for debugfs threads");
+		return false;
+	}
+
+	return true;
+}
+
 /**
  * __wcnss_wowpattern_write() - wow_pattern debugfs handler
  * @file: debugfs file handle
diff --git a/core/hdd/src/wlan_hdd_debugfs_csr.c b/core/hdd/src/wlan_hdd_debugfs_csr.c
new file mode 100644
index 0000000..eb6f976
--- /dev/null
+++ b/core/hdd/src/wlan_hdd_debugfs_csr.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2018 The Linux Foundation. All rights reserved.
+ *
+ * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
+ *
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * DOC: wlan_hdd_debugfs_csr.c
+ *
+ * WLAN Host Device Driver implementation to update
+ * debugfs with roaming related information
+ */
+
+#include <wlan_hdd_debugfs_csr.h>
+#include <wlan_hdd_main.h>
+#include <cds_sched.h>
+#include <wma_api.h>
+#include "qwlan_version.h"
+#include "wmi_unified_param.h"
+#include "wlan_hdd_debugfs.h"
+
+ssize_t
+wlan_hdd_current_time_info_debugfs(uint8_t *buf, ssize_t buf_avail_len)
+{
+	ssize_t length;
+	char time_buffer[HDD_TIME_STRING_LEN];
+	int ret_val;
+
+	qdf_get_time_of_the_day_in_hr_min_sec_usec(time_buffer,
+						   sizeof(time_buffer));
+	ret_val = scnprintf(buf, buf_avail_len,
+			    "\nTime at which this file generated = %s\n",
+			    time_buffer);
+	if (ret_val < 0)
+		return 0;
+	length = ret_val;
+
+	return length;
+}
+
+/**
+ * wlan_hdd_debugfs_update_csr() - Function to update internal debugfs buffer
+ * and write into user-space buffer
+ * @hdd_ctx: hdd context
+ * @adapter: adapter
+ * @id: used to identify file for which this info has to be read
+ * @buf: output buffer to write
+ * @buf_avail_len: length of the available buffer
+ *
+ * Return: Number of bytes read on success, zero otherwise
+ */
+static ssize_t
+wlan_hdd_debugfs_update_csr(struct hdd_context *hdd_ctx,
+			    struct hdd_adapter *adapter,
+			    enum hdd_debugfs_file_id id,
+			    uint8_t *buf,
+			    ssize_t buf_avail_len)
+{
+	ssize_t len = 0;
+
+	switch (id) {
+	case HDD_DEBUFS_FILE_ID_CONNECT_INFO:
+		/* populate connect info */
+		break;
+	case HDD_DEBUFS_FILE_ID_ROAM_SCAN_STATS_INFO:
+		/* populate roam scan stats info */
+		break;
+	case HDD_DEBUFS_FILE_ID_OFFLOAD_INFO:
+		/* populate offload info */
+		break;
+	default:
+		hdd_err("Failed to fetch stats, unknown stats type");
+	}
+
+	return len;
+}
+
+/**
+ * __wlan_hdd_read_debugfs_csr() - Function to read debug stats
+ * @file: file pointer
+ * @buf: buffer
+ * @count: count
+ * @pos: position pointer
+ *
+ * Return: Number of bytes read on success, zero otherwise
+ */
+static ssize_t
+__wlan_hdd_read_debugfs_csr(struct file *file, char __user *buf,
+			    size_t count, loff_t *pos)
+{
+	struct wlan_hdd_debugfs_buffer_info *info;
+	struct hdd_adapter *adapter;
+	struct hdd_context *hdd_ctx;
+	int ret;
+	ssize_t length;
+
+	hdd_enter();
+
+	info = file->private_data;
+	if (!info || !info->data) {
+		hdd_err("No valid private data");
+		return 0;
+	}
+
+	adapter = info->adapter;
+	if ((!adapter) || (adapter->magic != WLAN_HDD_ADAPTER_MAGIC)) {
+		hdd_err("Invalid adapter or adapter has invalid magic");
+		return 0;
+	}
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+	ret = wlan_hdd_validate_context(hdd_ctx);
+	if (ret)
+		return 0;
+
+	if (!test_bit(DEVICE_IFACE_OPENED, &adapter->event_flags)) {
+		hdd_err("Interface is not enabled");
+		return 0;
+	}
+
+	if (*pos == 0) {
+		info->length = wlan_hdd_debugfs_update_csr(hdd_ctx, adapter,
+							   info->id,
+							   info->data,
+							   info->max_buf_len);
+	}
+
+	length = simple_read_from_buffer(buf, count, pos,
+					 info->data, info->length);
+	hdd_debug("length written = %zu, count: %zu, pos: %lld",
+		  length, count, *pos);
+
+	hdd_exit();
+	return length;
+}
+
+/**
+ * wlan_hdd_read_debugfs_csr() - SSR wrapper function to read stats
+ * @file: file pointer
+ * @buf: buffer
+ * @count: count
+ * @pos: position pointer
+ *
+ * Return: Number of bytes read on success, zero otherwise
+ */
+static ssize_t
+wlan_hdd_read_debugfs_csr(struct file *file, char __user *buf,
+			  size_t count, loff_t *pos)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+	ret = __wlan_hdd_read_debugfs_csr(file, buf, count, pos);
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
+/**
+ * __wlan_hdd_open_debugfs_csr() - Allocates memory for private data
+ * @inode: Pointer to inode structure
+ * @file: file pointer
+ *
+ * Return: zero
+ */
+static int __wlan_hdd_open_debugfs_csr(struct inode *inode,
+				       struct file *file)
+{
+	struct wlan_hdd_debugfs_buffer_info *info;
+	struct hdd_debugfs_file_info *csr;
+	struct hdd_adapter *adapter = NULL;
+	struct hdd_context *hdd_ctx;
+	int ret;
+
+	hdd_enter();
+
+	csr = inode->i_private;
+	if (!csr) {
+		hdd_err("No private data");
+		return -EINVAL;
+	}
+
+	adapter = qdf_container_of(csr, struct hdd_adapter,
+				   csr_file[csr->id]);
+	if (!adapter || (adapter->magic != WLAN_HDD_ADAPTER_MAGIC)) {
+		hdd_err("Invalid adapter or adapter has invalid magic");
+		return -EINVAL;
+	}
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+	ret = wlan_hdd_validate_context(hdd_ctx);
+	if (ret)
+		return -EINVAL;
+
+	if (!test_bit(DEVICE_IFACE_OPENED, &adapter->event_flags)) {
+		hdd_err("Interface is not enabled");
+		return -EINVAL;
+	}
+
+	info = qdf_mem_malloc(sizeof(*info));
+	if (!info) {
+		hdd_err("Not enough memory for file private data");
+		return -ENOMEM;
+	}
+
+	info->data = qdf_mem_malloc(csr->buf_max_size);
+	if (!info->data) {
+		hdd_err("roam stats debugfs buffer allocation failed");
+		qdf_mem_free(info);
+		return -ENOMEM;
+	}
+	info->length = 0;
+	info->max_buf_len = csr->buf_max_size;
+	info->id = csr->id;
+	info->adapter = adapter;
+
+	file->private_data = info;
+	hdd_exit();
+
+	return 0;
+}
+
+/**
+ * wlan_hdd_open_debugfs_csr() - SSR wrapper function to allocate memory for
+ * private data on file open
+ * @inode: Pointer to inode structure
+ * @file: file pointer
+ *
+ * Return: zero
+ */
+static int wlan_hdd_open_debugfs_csr(struct inode *inode,
+				     struct file *file)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+
+	hdd_debugfs_thread_increment();
+	ret = __wlan_hdd_open_debugfs_csr(inode, file);
+	if (ret)
+		hdd_debugfs_thread_decrement();
+
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
+/**
+ * __wlan_hdd_release_debugfs_csr() - Function to free private memory on
+ * release
+ * @inode: Pointer to inode structure
+ * @file: file pointer
+ *
+ * Return: zero
+ */
+static int __wlan_hdd_release_debugfs_csr(struct inode *inode,
+					  struct file *file)
+{
+	struct wlan_hdd_debugfs_buffer_info *info = file->private_data;
+
+	hdd_enter();
+
+	if (!info)
+		return 0;
+
+	file->private_data = NULL;
+	qdf_mem_free(info->data);
+	qdf_mem_free(info);
+
+	hdd_exit();
+
+	return 0;
+}
+
+/**
+ * wlan_hdd_release_debugfs_csr() - SSR wrapper function to free
+ * private data on release
+ * @inode: Pointer to inode structure
+ * @file: file pointer
+ *
+ * Return: zero
+ */
+static int wlan_hdd_release_debugfs_csr(struct inode *inode, struct file *file)
+{
+	int ret;
+
+	cds_ssr_protect(__func__);
+	ret = __wlan_hdd_release_debugfs_csr(inode, file);
+	hdd_debugfs_thread_decrement();
+	cds_ssr_unprotect(__func__);
+
+	return ret;
+}
+
+static const struct file_operations fops_csr_debugfs = {
+	.read = wlan_hdd_read_debugfs_csr,
+	.open = wlan_hdd_open_debugfs_csr,
+	.release = wlan_hdd_release_debugfs_csr,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+void wlan_hdd_debugfs_csr_init(struct hdd_adapter *adapter)
+{
+	/*
+	 * Create debufs diagnostic files for connect, offload info
+	 * and roam info and store in csr_file member of adapter
+	 */
+}
+
+void wlan_hdd_debugfs_csr_deinit(struct hdd_adapter *adapter)
+{
+	uint32_t i;
+	struct dentry *entry;
+
+	for (i = 0; i < HDD_DEBUGFS_FILE_ID_MAX; i++) {
+		entry = adapter->csr_file[i].entry;
+		if (!entry)
+			continue;
+
+		adapter->csr_file[i].entry = NULL;
+		debugfs_remove(entry);
+		entry = NULL;
+	}
+}
diff --git a/core/hdd/src/wlan_hdd_driver_ops.c b/core/hdd/src/wlan_hdd_driver_ops.c
index 67bed53..94f4071 100644
--- a/core/hdd/src/wlan_hdd_driver_ops.c
+++ b/core/hdd/src/wlan_hdd_driver_ops.c
@@ -39,6 +39,7 @@
 #include "pld_common.h"
 #include "wlan_hdd_driver_ops.h"
 #include "wlan_ipa_ucfg_api.h"
+#include "wlan_hdd_debugfs.h"
 
 #ifdef MODULE
 #define WLAN_MODULE_NAME  module_name(THIS_MODULE)
@@ -494,6 +495,9 @@
 	if (!cds_wait_for_external_threads_completion(__func__))
 		hdd_warn("External threads are still active attempting driver unload anyway");
 
+	if (!hdd_wait_for_debugfs_threads_completion())
+		hdd_warn("Debugfs threads are still active attempting driver unload anyway");
+
 	mutex_lock(&hdd_init_deinit_lock);
 	hdd_start_driver_ops_timer(eHDD_DRV_OP_REMOVE);
 	if (QDF_IS_EPPING_ENABLED(cds_get_conparam())) {
@@ -596,6 +600,9 @@
 	if (!cds_wait_for_external_threads_completion(__func__))
 		hdd_err("Host is not ready for SSR, attempting anyway");
 
+	if (!hdd_wait_for_debugfs_threads_completion())
+		hdd_err("Debufs threads are still pending, attempting SSR anyway");
+
 	if (!QDF_IS_EPPING_ENABLED(cds_get_conparam())) {
 		hif_disable_isr(hif_ctx);
 		hdd_wlan_shutdown();
diff --git a/core/hdd/src/wlan_hdd_main.c b/core/hdd/src/wlan_hdd_main.c
index c0172aa..c63884c 100644
--- a/core/hdd/src/wlan_hdd_main.c
+++ b/core/hdd/src/wlan_hdd_main.c
@@ -91,6 +91,7 @@
 #include "wlan_hdd_ocb.h"
 #include "wlan_hdd_nan.h"
 #include "wlan_hdd_debugfs.h"
+#include "wlan_hdd_debugfs_csr.h"
 #include "wlan_hdd_driver_ops.h"
 #include "epping_main.h"
 #include "wlan_hdd_data_stall_detection.h"
@@ -4327,6 +4328,8 @@
 	qdf_mutex_destroy(&adapter->disconnection_status_lock);
 	hdd_apf_context_destroy(adapter);
 
+	wlan_hdd_debugfs_csr_deinit(adapter);
+
 	hdd_debugfs_exit(adapter);
 
 	/*
@@ -4994,6 +4997,9 @@
 	hdd_info("%s interface created. iftype: %d", netdev_name(adapter->dev),
 		 session_type);
 
+	if (adapter->device_mode == QDF_STA_MODE)
+		wlan_hdd_debugfs_csr_init(adapter);
+
 	return adapter;
 
 err_free_netdev:
@@ -5380,7 +5386,6 @@
 	}
 
 	hdd_exit();
-
 	return QDF_STATUS_SUCCESS;
 }
 
@@ -9245,6 +9250,7 @@
 		hdd_tx_flow_control_is_pause);
 
 	hdd_exit();
+
 	return 0;
 }
 
@@ -10926,6 +10932,7 @@
 	bool is_recovery_stop = cds_is_driver_recovering();
 	int ret = 0;
 	int active_threads;
+	int debugfs_threads;
 	struct target_psoc_info *tgt_hdl;
 
 	hdd_enter();
@@ -10943,11 +10950,16 @@
 	cds_set_module_stop_in_progress(true);
 
 	active_threads = cds_return_external_threads_count();
-	if (active_threads > 0 || hdd_ctx->is_wiphy_suspended) {
-		hdd_warn("External threads %d wiphy suspend %d",
-			 active_threads, hdd_ctx->is_wiphy_suspended);
+	debugfs_threads = hdd_return_debugfs_threads_count();
 
-		cds_print_external_threads();
+	if (active_threads > 0 || debugfs_threads > 0 ||
+	    hdd_ctx->is_wiphy_suspended) {
+		hdd_warn("External threads %d, Debugfs threads %d, wiphy suspend %d",
+			 active_threads, debugfs_threads,
+			 hdd_ctx->is_wiphy_suspended);
+
+		if (active_threads)
+			cds_print_external_threads();
 
 		if (IS_IDLE_STOP && !ftm_mode) {
 			mutex_unlock(&hdd_ctx->iface_change_lock);