diff options
| author | Mahesh Kumar Kalikot Veetil <mkalikot@codeaurora.org> | 2016-10-27 14:56:41 -0700 |
|---|---|---|
| committer | qcabuildsw <qcabuildsw@localhost> | 2017-01-06 09:54:55 -0800 |
| commit | 77da044787c68253e9fa9405571b3ee54d41d564 (patch) | |
| tree | 06c5e634ebf1b4bb765c04aa31c1beff2fbcfe44 /qdf/linux/src | |
| parent | 6da21f5940187659e321bbebfe930fdd13aed887 (diff) | |
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
Diffstat (limited to 'qdf/linux/src')
| -rw-r--r-- | qdf/linux/src/qdf_mem.c | 422 |
1 files changed, 415 insertions, 7 deletions
diff --git a/qdf/linux/src/qdf_mem.c b/qdf/linux/src/qdf_mem.c index 34b8f1399556..c9f9fd111989 100644 --- a/qdf/linux/src/qdf_mem.c +++ b/qdf/linux/src/qdf_mem.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016 The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2017 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -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; @@ -995,6 +1401,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; } @@ -1028,6 +1435,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 |
