소스 검색

qcacmn: Add QDF memory stats

Create debug file system entries to show current QDF memory usage.
The 'list' option adds a little overhead, which is minimized by
locking node by node while traversing the entire allocated memory
list. Major operation is lock-less, using node reference count.

Following debugfs entries are added,

	1. /sys/kernel/debug/<module_name>_qdf/mem/list
	   This lists QDF allocation by file and line number. It takes
	   some time to traverse the entire allocated list.

	2. /sys/kernel/debug/<module_name>_qdf/mem/kmalloc
	   This file shows total kmalloc done by qdf_mem_alloc().

	3. /sys/kernel/debug/<module_name>_qdf/mem/dma
	   This file shows total allocation done by qdf_mem_alloc_consistent()

Change-Id: Ie40a7cb6ee03dd58aebd0fbda0118ab06b64725a
CRs-Fixed: 1084097
Mahesh Kumar Kalikot Veetil 8 년 전
부모
커밋
bdcb4db461
1개의 변경된 파일414개의 추가작업 그리고 6개의 파일을 삭제
  1. 414 6
      qdf/linux/src/qdf_mem.c

+ 414 - 6
qdf/linux/src/qdf_mem.c

@@ -30,12 +30,17 @@
  * This file provides OS dependent memory management APIs
  */
 
+#include "qdf_debugfs.h"
 #include "qdf_mem.h"
 #include "qdf_nbuf.h"
 #include "qdf_lock.h"
 #include "qdf_mc_timer.h"
 #include "qdf_module.h"
 #include <qdf_trace.h>
+#include "qdf_atomic.h"
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/string.h>
 
 #ifdef CONFIG_MCL
 #include <host_diag_core_event.h>
@@ -63,11 +68,12 @@ static uint8_t WLAN_MEM_TAIL[] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85,
 
 /**
  * struct s_qdf_mem_struct - memory object to dubug
- * @node: node to the list
- * @filename: name of file
- * @line_num: line number
- * @size: size of the file
- * @header: array that contains header
+ * @node:	node to the list
+ * @filename:	name of file
+ * @line_num:	line number
+ * @size:	size of the file
+ * @header:	array that contains header
+ * @in_use:	memory usage count
  */
 struct s_qdf_mem_struct {
 	qdf_list_node_t node;
@@ -75,8 +81,9 @@ struct s_qdf_mem_struct {
 	unsigned int line_num;
 	unsigned int size;
 	uint8_t header[8];
+	qdf_atomic_t in_use;
 };
-#endif
+#endif /* MEMORY_DEBUG */
 
 /* Preprocessor Definitions and Constants */
 #define QDF_GET_MEMORY_TIME_THRESHOLD 300
@@ -89,6 +96,398 @@ u_int8_t prealloc_disabled = 1;
 qdf_declare_param(prealloc_disabled, byte);
 EXPORT_SYMBOL(prealloc_disabled);
 
+#ifdef WLAN_DEBUGFS
+
+/**
+ * struct __qdf_mem_stat - qdf memory statistics
+ * @kmalloc:	total kmalloc allocations
+ * @dma:	total dma allocations
+ */
+static struct __qdf_mem_stat {
+	qdf_atomic_t kmalloc;
+	qdf_atomic_t dma;
+} qdf_mem_stat;
+
+
+/**
+ * struct __qdf_mem_info - memory statistics
+ * @file_name:	the file which allocated memory
+ * @line_num:	the line at which allocation happened
+ * @size:	the size of allocation
+ * @count:	how many allocations of same type
+ *
+ */
+struct __qdf_mem_info {
+	char *file_name;
+	unsigned int line_num;
+	unsigned int size;
+	unsigned int count;
+};
+
+/* Debugfs root directory for qdf_mem */
+static struct dentry *qdf_mem_debugfs_root;
+
+/*
+ * A table to identify duplicates in close proximity. The table depth defines
+ * the proximity scope. A deeper table takes more time. Chose any optimum value.
+ *
+ */
+#define QDF_MEM_STAT_TABLE_SIZE 4
+static struct __qdf_mem_info qdf_mem_info_table[QDF_MEM_STAT_TABLE_SIZE];
+
+static inline void qdf_mem_kmalloc_inc(qdf_size_t size)
+{
+	qdf_atomic_add(size, &qdf_mem_stat.kmalloc);
+}
+
+static inline void qdf_mem_dma_inc(qdf_size_t size)
+{
+	qdf_atomic_add(size, &qdf_mem_stat.dma);
+}
+
+static inline void qdf_mem_kmalloc_dec(qdf_size_t size)
+{
+	qdf_atomic_sub(size, &qdf_mem_stat.kmalloc);
+}
+
+static inline void qdf_mem_dma_dec(qdf_size_t size)
+{
+	qdf_atomic_sub(size, &qdf_mem_stat.dma);
+}
+
+
+/**
+ * qdf_mem_info_table_init() - initialize the stat table
+ *
+ * Return: None
+ */
+static void qdf_mem_info_table_init(void)
+{
+	memset(&qdf_mem_info_table, 0, sizeof(qdf_mem_info_table));
+}
+
+/**
+ * qdf_mem_get_node() - increase the node usage count
+ * @n:	node
+ *
+ * An increased usage count will block the memory from getting released.
+ * Initially the usage count is incremented from qdf_mem_malloc_debug().
+ * Corresponding qdf_mem_free() will decrement the reference count and frees up
+ * the memory when the usage count reaches zero. Here decrement and test is an
+ * atomic operation in qdf_mem_free() to avoid any race condition.
+ *
+ * If a caller wants to take the ownership of an allocated memory, it can call
+ * this function with the associated node.
+ *
+ * Return: None
+ *
+ */
+static void qdf_mem_get_node(qdf_list_node_t *n)
+{
+	struct s_qdf_mem_struct *m = container_of(n, typeof(*m), node);
+
+	qdf_atomic_inc(&m->in_use);
+}
+
+/**
+ * qdf_mem_put_node_free() - decrease the node usage count and free memory
+ * @n:	node
+ *
+ * Additionally it releases the memory when the usage count reaches zero. Usage
+ * count is decremented and tested against zero in qdf_mem_free(). If the count
+ * is 0, the node and associated memory gets freed.
+ *
+ * Return: None
+ *
+ */
+static void qdf_mem_put_node_free(qdf_list_node_t *n)
+{
+	struct s_qdf_mem_struct *m = container_of(n, typeof(*m), node);
+
+	/* qdf_mem_free() is expecting the same address returned by
+	 * qdf_mem_malloc_debug(), which is 'm + sizeof(s_qdf_mem_struct)' */
+	qdf_mem_free(m + 1);
+}
+
+/**
+ * qdf_mem_get_first() - get the first node.
+ *
+ * Return: node
+ */
+static qdf_list_node_t *qdf_mem_get_first(void)
+{
+	QDF_STATUS status;
+	qdf_list_node_t *node = NULL;
+
+	qdf_spin_lock_bh(&qdf_mem_list_lock);
+	status = qdf_list_peek_front(&qdf_mem_list, &node);
+	if (QDF_STATUS_SUCCESS == status)
+		qdf_mem_get_node(node);
+	qdf_spin_unlock_bh(&qdf_mem_list_lock);
+
+	return node;
+}
+
+/**
+ * qdf_mem_get_next() - get the next node
+ * @n: node
+ *
+ * Return: next node
+ */
+static qdf_list_node_t *qdf_mem_get_next(qdf_list_node_t *n)
+{
+	QDF_STATUS status;
+	qdf_list_node_t *node = NULL;
+
+	qdf_spin_lock_bh(&qdf_mem_list_lock);
+	status = qdf_list_peek_next(&qdf_mem_list, n, &node);
+	if (QDF_STATUS_SUCCESS == status)
+		qdf_mem_get_node(node);
+
+	qdf_spin_unlock_bh(&qdf_mem_list_lock);
+
+	qdf_mem_put_node_free(n);
+
+	return node;
+
+}
+
+static void qdf_mem_seq_print_header(struct seq_file *seq)
+{
+	seq_puts(seq, "\n");
+	seq_puts(seq, "filename                             line         size x    no  [ total ]\n");
+	seq_puts(seq, "\n");
+}
+
+/**
+ * qdf_mem_info_table_insert() - insert node into an array
+ * @n:	node
+ *
+ * Return:
+ *	true  - success
+ *	false - failure
+ */
+static bool qdf_mem_info_table_insert(qdf_list_node_t *n)
+{
+	int i;
+	struct __qdf_mem_info *t = qdf_mem_info_table;
+	bool dup;
+	bool consumed;
+	struct s_qdf_mem_struct *m = (struct s_qdf_mem_struct *)n;
+
+	for (i = 0; i < QDF_MEM_STAT_TABLE_SIZE; i++) {
+		if (!t[i].count) {
+			t[i].file_name = m->file_name;
+			t[i].line_num = m->line_num;
+			t[i].size = m->size;
+			t[i].count++;
+			break;
+		}
+		dup = !strcmp(t[i].file_name, m->file_name) &&
+		      (t[i].line_num == m->line_num) &&
+		      (t[i].size == m->size);
+		if (dup) {
+			t[i].count++;
+			break;
+		}
+	}
+
+	consumed = (i < QDF_MEM_STAT_TABLE_SIZE);
+
+	return consumed;
+}
+
+/**
+ * qdf_mem_seq_print() - print the table using seq_printf
+ * @seq:	seq_file handle
+ *
+ * Node table will be cleared once printed.
+ *
+ * Return: None
+ */
+static void qdf_mem_seq_print(struct seq_file *seq)
+{
+	int i;
+	struct __qdf_mem_info *t = qdf_mem_info_table;
+
+	for (i = 0; i < QDF_MEM_STAT_TABLE_SIZE && t[i].count; i++) {
+		seq_printf(seq,
+			   "%-35s%6d\t%6d x %4d\t[%7d]\n",
+			   kbasename(t[i].file_name),
+			   t[i].line_num, t[i].size,
+			   t[i].count,
+			   t[i].size * t[i].count);
+	}
+
+	qdf_mem_info_table_init();
+}
+
+/**
+ * qdf_mem_seq_start() - sequential callback to start
+ * @seq: seq_file handle
+ * @pos: The start position of the sequence
+ *
+ * Return:
+ *	SEQ_START_TOKEN - Prints header
+ *	None zero value - Node
+ *	NULL		- End of the sequence
+ */
+static void *qdf_mem_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	if (*pos == 0) {
+		qdf_mem_info_table_init();
+		return SEQ_START_TOKEN;
+	} else if (seq->private) {
+		return qdf_mem_get_next(seq->private);
+	}
+
+	return NULL;
+}
+
+/**
+ * qdf_mem_seq_next() - next sequential callback
+ * @seq:	seq_file handle
+ * @v:		the current iterator
+ * @pos:	the current position [not used]
+ *
+ * Get the next node and release previous node.
+ *
+ * Return:
+ *	None zero value - Next node
+ *	NULL		- No more to process in the list
+ */
+static void *qdf_mem_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	qdf_list_node_t *node;
+
+	++*pos;
+
+	if (v == SEQ_START_TOKEN)
+		node = qdf_mem_get_first();
+	else
+		node = qdf_mem_get_next(v);
+
+	return node;
+}
+
+/**
+ * qdf_mem_seq_stop() - stop sequential callback
+ * @seq:	seq_file handle
+ * @v:		current iterator
+ *
+ * Return:	None
+ */
+static void qdf_mem_seq_stop(struct seq_file *seq, void *v)
+{
+	seq->private = v;
+}
+
+/**
+ * qdf_mem_seq_show() - print sequential callback
+ * @seq:	seq_file handle
+ * @v:		current iterator
+ *
+ * Return: 0 - success
+ */
+static int qdf_mem_seq_show(struct seq_file *seq, void *v)
+{
+
+	if (v == SEQ_START_TOKEN) {
+		qdf_mem_seq_print_header(seq);
+		return 0;
+	}
+
+	while (!qdf_mem_info_table_insert(v))
+		qdf_mem_seq_print(seq);
+
+	return 0;
+}
+
+/* sequential file operation table */
+static const struct seq_operations qdf_mem_seq_ops = {
+	.start = qdf_mem_seq_start,
+	.next  = qdf_mem_seq_next,
+	.stop  = qdf_mem_seq_stop,
+	.show  = qdf_mem_seq_show,
+};
+
+
+static int qdf_mem_debugfs_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &qdf_mem_seq_ops);
+}
+
+/* debugfs file operation table */
+static const struct file_operations fops_qdf_mem_debugfs = {
+	.owner = THIS_MODULE,
+	.open = qdf_mem_debugfs_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = seq_release,
+};
+
+/**
+ * qdf_mem_debugfs_init() - initialize routine
+ *
+ * Return: QDF_STATUS
+ */
+static QDF_STATUS qdf_mem_debugfs_init(void)
+{
+	struct dentry *qdf_debugfs_root = qdf_debugfs_get_root();
+
+	if (!qdf_debugfs_root)
+		return QDF_STATUS_E_FAILURE;
+
+	qdf_mem_debugfs_root = debugfs_create_dir("mem", qdf_debugfs_root);
+
+	if (!qdf_mem_debugfs_root)
+		return QDF_STATUS_E_FAILURE;
+
+	debugfs_create_file("list",
+			    S_IRUSR | S_IWUSR,
+			    qdf_mem_debugfs_root,
+			    NULL,
+			    &fops_qdf_mem_debugfs);
+
+	debugfs_create_atomic_t("kmalloc",
+				S_IRUSR | S_IWUSR,
+				qdf_mem_debugfs_root,
+				&qdf_mem_stat.kmalloc);
+
+	debugfs_create_atomic_t("dma",
+				S_IRUSR | S_IWUSR,
+				qdf_mem_debugfs_root,
+				&qdf_mem_stat.dma);
+
+	return QDF_STATUS_SUCCESS;
+}
+
+
+/**
+ * qdf_mem_debugfs_exit() - cleanup routine
+ *
+ * Return: None
+ */
+static void qdf_mem_debugfs_exit(void)
+{
+	debugfs_remove_recursive(qdf_mem_debugfs_root);
+	qdf_mem_debugfs_root = NULL;
+}
+
+#else /* WLAN_DEBUGFS */
+
+static inline void qdf_mem_kmalloc_inc(qdf_size_t size) {}
+static inline void qdf_mem_dma_inc(qdf_size_t size) {}
+static inline void qdf_mem_kmalloc_dec(qdf_size_t size) {}
+static inline void qdf_mem_dma_dec(qdf_size_t size) {}
+static QDF_STATUS qdf_mem_debugfs_init(void)
+{
+	return QDF_STATUS_E_NOSUPPORT;
+}
+static void qdf_mem_debugfs_exit(void) {}
+
+#endif /* WLAN_DEBUGFS */
+
 /**
  * __qdf_mempool_init() - Create and initialize memory pool
  *
@@ -332,6 +731,7 @@ void qdf_mem_init(void)
 	qdf_list_create(&qdf_mem_list, 60000);
 	qdf_spinlock_create(&qdf_mem_list_lock);
 	qdf_net_buf_debug_init();
+	qdf_mem_debugfs_init();
 	return;
 }
 EXPORT_SYMBOL(qdf_mem_init);
@@ -414,6 +814,7 @@ EXPORT_SYMBOL(qdf_mem_clean);
  */
 void qdf_mem_exit(void)
 {
+	qdf_mem_debugfs_exit();
 	qdf_net_buf_debug_exit();
 	qdf_mem_clean();
 	qdf_list_destroy(&qdf_mem_list);
@@ -487,6 +888,8 @@ void *qdf_mem_malloc_debug(size_t size,
 		mem_struct->file_name = file_name;
 		mem_struct->line_num = line_num;
 		mem_struct->size = size;
+		qdf_atomic_inc(&mem_struct->in_use);
+		qdf_mem_kmalloc_inc(size);
 
 		qdf_mem_copy(&mem_struct->header[0],
 			     &WLAN_MEM_HEADER[0], sizeof(WLAN_MEM_HEADER));
@@ -579,6 +982,8 @@ void qdf_mem_free(void *ptr)
 	if (wcnss_prealloc_put(ptr))
 		return;
 #endif
+	if (!qdf_atomic_dec_and_test(&mem_struct->in_use))
+		return;
 
 	qdf_spin_lock_irqsave(&qdf_mem_list_lock);
 
@@ -615,6 +1020,7 @@ void qdf_mem_free(void *ptr)
 	list_del_init(&mem_struct->node);
 	qdf_mem_list.count--;
 	qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
+	qdf_mem_kmalloc_dec(mem_struct->size);
 	kfree(mem_struct);
 	return;
 
@@ -1057,6 +1463,7 @@ void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size,
 	if (alloc_mem == NULL)
 		qdf_print("%s Warning: unable to alloc consistent memory of size %zu!\n",
 			__func__, size);
+	qdf_mem_dma_inc(size);
 	return alloc_mem;
 }
 
@@ -1090,6 +1497,7 @@ inline void qdf_mem_free_consistent(qdf_device_t osdev, void *dev,
 				    qdf_dma_context_t memctx)
 {
 	dma_free_coherent(dev, size, vaddr, phy_addr);
+	qdf_mem_dma_dec(size);
 }
 
 #endif