qcacld-3.0: Dump driver information

qcacld-2.0 to qcacld-3.0 propagation

Dump state information of HDD, SME, PE and WMA layers
into a buffer. Contents of this buffer will be copied
into user space using proc entry /proc/debugdriver/
driverdump.

Change-Id: Ifbb102e440d7df20defa1a397964cb9b55082bf9
CRs-Fixed: 955357
diff --git a/core/hdd/src/wlan_hdd_memdump.c b/core/hdd/src/wlan_hdd_memdump.c
index 1afe3fd..195d066 100644
--- a/core/hdd/src/wlan_hdd_memdump.c
+++ b/core/hdd/src/wlan_hdd_memdump.c
@@ -630,3 +630,237 @@
 	if (!QDF_IS_STATUS_SUCCESS(qdf_status))
 		hdd_err("Failed to deallocate timer");
 }
+
+#ifdef MULTI_IF_NAME
+#define PROCFS_DRIVER_DUMP_DIR "debugdriver" MULTI_IF_NAME
+#else
+#define PROCFS_DRIVER_DUMP_DIR "debugdriver"
+#endif
+#define PROCFS_DRIVER_DUMP_NAME "driverdump"
+#define PROCFS_DRIVER_DUMP_PERM 0444
+
+static struct proc_dir_entry *proc_file_driver, *proc_dir_driver;
+
+/**
+ * hdd_driver_mem_cleanup() - Frees memory allocated for
+ * driver dump
+ *
+ * This function unallocates driver dump memory.
+ *
+ * Return: None
+ */
+static void hdd_driver_mem_cleanup(void)
+{
+	hdd_context_t *hdd_ctx;
+
+	hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
+	if (!hdd_ctx) {
+		hdd_err("Invalid HDD context");
+		return;
+	}
+
+	if (hdd_ctx->driver_dump_mem) {
+		qdf_mem_free(hdd_ctx->driver_dump_mem);
+		hdd_ctx->driver_dump_mem = NULL;
+	}
+}
+
+
+/**
+ * hdd_driver_memdump_read() - perform read operation in driver
+ * memory dump proc file
+ * @file  - handle for the proc file.
+ * @buf   - pointer to user space buffer.
+ * @count - number of bytes to be read.
+ * @pos   - offset in the from buffer.
+ *
+ * This function performs read operation for the driver memory dump proc file.
+ *
+ * Return: number of bytes read on success, error code otherwise.
+ */
+static ssize_t hdd_driver_memdump_read(struct file *file, char __user *buf,
+					size_t count, loff_t *pos)
+{
+	int status;
+	QDF_STATUS qdf_status;
+	hdd_context_t *hdd_ctx;
+	size_t no_of_bytes_read = 0;
+
+	hdd_ctx = memdump_get_file_data(file);
+
+	hdd_notice("Read req for size:%zu pos:%llu", count, *pos);
+	status = wlan_hdd_validate_context(hdd_ctx);
+	if (status != 0)
+		return -EINVAL;
+
+	if (*pos < 0) {
+		hdd_err("Invalid start offset for memdump read");
+		return -EINVAL;
+	} else if (!count || (hdd_ctx->driver_dump_size &&
+				(*pos >= hdd_ctx->driver_dump_size))) {
+		hdd_err("No more data to copy");
+		return 0;
+	} else if ((*pos == 0) || (hdd_ctx->driver_dump_mem == NULL)) {
+		/*
+		 * Allocate memory for Driver memory dump.
+		 */
+		if (!hdd_ctx->driver_dump_mem) {
+			hdd_ctx->driver_dump_mem =
+				qdf_mem_malloc(DRIVER_MEM_DUMP_SIZE);
+			if (!hdd_ctx->driver_dump_mem) {
+				hdd_err("qdf_mem_malloc failed");
+				return -ENOMEM;
+			}
+		}
+
+		qdf_status = qdf_state_info_dump_all(hdd_ctx->driver_dump_mem,
+						DRIVER_MEM_DUMP_SIZE,
+						&hdd_ctx->driver_dump_size);
+		/*
+		 * If qdf_status is QDF_STATUS_E_NOMEM, then memory allocated is
+		 * insufficient to dump driver information. This print can give
+		 * information to allocate more memory if more information from
+		 * each layer is added in future.
+		 */
+		if (qdf_status != QDF_STATUS_SUCCESS)
+			hdd_err("Error in dump driver information, status %d",
+				qdf_status);
+		hdd_notice("driver_dump_size: %d",
+					hdd_ctx->driver_dump_size);
+	}
+
+	if (count > hdd_ctx->driver_dump_size - *pos)
+		no_of_bytes_read = hdd_ctx->driver_dump_size - *pos;
+	else
+		no_of_bytes_read = count;
+
+	if (copy_to_user(buf, hdd_ctx->driver_dump_mem + *pos,
+					no_of_bytes_read)) {
+		hdd_err("copy to user space failed");
+		return -EFAULT;
+	}
+
+	/* offset(pos) should be updated here based on the copy done */
+	*pos += no_of_bytes_read;
+
+	/* Entire driver memory dump copy completed */
+	if (*pos >= hdd_ctx->driver_dump_size)
+		hdd_driver_mem_cleanup();
+
+	return no_of_bytes_read;
+}
+
+
+/**
+ * struct driver_dump_fops - file operations for driver dump feature
+ * @read - read function for driver dump operation.
+ *
+ * This structure initialize the file operation handle for memory
+ * dump feature
+ */
+static const struct file_operations driver_dump_fops = {
+read: hdd_driver_memdump_read
+};
+
+/**
+ * hdd_driver_memdump_procfs_init() - Initialize procfs for driver memory dump
+ *
+ * This function create file under proc file system to be used later for
+ * processing driver memory dump
+ *
+ * Return:   0 on success, error code otherwise.
+ */
+static int hdd_driver_memdump_procfs_init(void)
+{
+	hdd_context_t *hdd_ctx;
+
+	hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
+	if (!hdd_ctx) {
+		hdd_err("Invalid HDD context");
+		return -EINVAL;
+	}
+
+	proc_dir_driver = proc_mkdir(PROCFS_DRIVER_DUMP_DIR, NULL);
+	if (proc_dir_driver == NULL) {
+		pr_debug("Error: Could not initialize /proc/%s\n",
+			 PROCFS_DRIVER_DUMP_DIR);
+		return -ENOMEM;
+	}
+
+	proc_file_driver = proc_create_data(PROCFS_DRIVER_DUMP_NAME,
+				     PROCFS_DRIVER_DUMP_PERM, proc_dir_driver,
+				     &driver_dump_fops, hdd_ctx);
+	if (proc_file_driver == NULL) {
+		remove_proc_entry(PROCFS_DRIVER_DUMP_NAME, proc_dir_driver);
+		pr_debug("Error: Could not initialize /proc/%s\n",
+			  PROCFS_DRIVER_DUMP_NAME);
+		return -ENOMEM;
+	}
+
+	pr_debug("/proc/%s/%s created\n", PROCFS_DRIVER_DUMP_DIR,
+		 PROCFS_DRIVER_DUMP_NAME);
+	return 0;
+}
+
+/**
+ * hdd_driver_memdump_procfs_remove() - Remove file/dir under procfs
+ * for driver memory dump
+ *
+ * This function removes file/dir under proc file system that was
+ * processing driver memory dump
+ *
+ * Return:  None
+ */
+static void hdd_driver_memdump_procfs_remove(void)
+{
+	remove_proc_entry(PROCFS_DRIVER_DUMP_NAME, proc_dir_driver);
+	pr_debug("/proc/%s/%s removed\n", PROCFS_DRIVER_DUMP_DIR,
+					  PROCFS_DRIVER_DUMP_NAME);
+	remove_proc_entry(PROCFS_DRIVER_DUMP_DIR, NULL);
+	pr_debug("/proc/%s removed\n", PROCFS_DRIVER_DUMP_DIR);
+}
+
+/**
+ * hdd_driver_memdump_init() - Intialization function for driver
+ * memory dump feature
+ *
+ * This function creates proc file for driver memdump feature
+ *
+ * Return - 0 on success, error otherwise
+ */
+int hdd_driver_memdump_init(void)
+{
+	int status;
+
+	if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE) {
+		hdd_err("Not initializing memdump in FTM mode");
+		return -EINVAL;
+	}
+
+	status = hdd_driver_memdump_procfs_init();
+	if (status) {
+		hdd_err("Failed to create proc file");
+		return status;
+	}
+
+	return 0;
+}
+
+/**
+ * hdd_driver_memdump_deinit() - De initialize driver memdump feature
+ *
+ * This function removes proc file created for driver memdump feature.
+ *
+ * Return: None
+ */
+void hdd_driver_memdump_deinit(void)
+{
+	if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE) {
+		hdd_err("Not deinitializing memdump in FTM mode");
+		return;
+	}
+
+	hdd_driver_memdump_procfs_remove();
+
+	hdd_driver_mem_cleanup();
+}