qcacmn: Add qdf_mc_timer_check_for_leaks API

Add a new  QDF MC Timer API for asserting that there are no active MC
Timers allocated. This is useful for detecting MC Timer leaks at
runtime.

Change-Id: I272ce806111b01b5c7f6b0dfef2e992c27b83529
CRs-Fixed: 2125800
This commit is contained in:
Dustin Brown
2017-12-06 16:18:53 -08:00
committed by snandini
parent c5ac638d51
commit f7fb76bd5e
2 changed files with 110 additions and 32 deletions

View File

@@ -66,7 +66,7 @@ struct qdf_mc_timer_s;
typedef struct qdf_mc_timer_node_s { typedef struct qdf_mc_timer_node_s {
qdf_list_node_t node; qdf_list_node_t node;
char *file_name; char *file_name;
unsigned int line_num; uint32_t line_num;
struct qdf_mc_timer_s *qdf_timer; struct qdf_mc_timer_s *qdf_timer;
} qdf_mc_timer_node_t; } qdf_mc_timer_node_t;
#endif #endif
@@ -90,6 +90,7 @@ void qdf_try_allowing_sleep(QDF_TIMER_TYPE type);
#ifdef TIMER_MANAGER #ifdef TIMER_MANAGER
void qdf_mc_timer_manager_init(void); void qdf_mc_timer_manager_init(void);
void qdf_mc_timer_manager_exit(void); void qdf_mc_timer_manager_exit(void);
void qdf_mc_timer_check_for_leaks(void);
#else #else
/** /**
* qdf_mc_timer_manager_init() - initialize QDF debug timer manager * qdf_mc_timer_manager_init() - initialize QDF debug timer manager
@@ -110,6 +111,15 @@ static inline void qdf_mc_timer_manager_init(void)
static inline void qdf_mc_timer_manager_exit(void) static inline void qdf_mc_timer_manager_exit(void)
{ {
} }
/**
* qdf_mc_timer_check_for_leaks() - Assert there are no active mc timers
*
* If there are active timers, this API prints them and panics.
*
* Return: None
*/
static inline void qdf_mc_timer_check_for_leaks(void) { }
#endif #endif
/** /**
* qdf_mc_timer_get_current_state() - get the current state of the timer * qdf_mc_timer_get_current_state() - get the current state of the timer

View File

@@ -31,6 +31,7 @@
*/ */
/* Include Files */ /* Include Files */
#include <qdf_debug_domain.h>
#include <qdf_mc_timer.h> #include <qdf_mc_timer.h>
#include <qdf_lock.h> #include <qdf_lock.h>
#include "qdf_lock.h" #include "qdf_lock.h"
@@ -146,10 +147,13 @@ EXPORT_SYMBOL(qdf_timer_module_init);
#ifdef TIMER_MANAGER #ifdef TIMER_MANAGER
qdf_list_t qdf_timer_list; static qdf_list_t qdf_timer_domains[QDF_DEBUG_DOMAIN_COUNT];
qdf_spinlock_t qdf_timer_list_lock; static qdf_spinlock_t qdf_timer_list_lock;
static void qdf_timer_clean(void); static inline qdf_list_t *qdf_timer_list_get(enum qdf_debug_domain domain)
{
return &qdf_timer_domains[domain];
}
/** /**
* qdf_mc_timer_manager_init() - initialize QDF debug timer manager * qdf_mc_timer_manager_init() - initialize QDF debug timer manager
@@ -160,11 +164,68 @@ static void qdf_timer_clean(void);
*/ */
void qdf_mc_timer_manager_init(void) void qdf_mc_timer_manager_init(void)
{ {
qdf_list_create(&qdf_timer_list, 1000); int i;
for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i)
qdf_list_create(&qdf_timer_domains[i], 1000);
qdf_spinlock_create(&qdf_timer_list_lock); qdf_spinlock_create(&qdf_timer_list_lock);
} }
EXPORT_SYMBOL(qdf_mc_timer_manager_init); EXPORT_SYMBOL(qdf_mc_timer_manager_init);
static inline void qdf_mc_timer_panic(void)
{
QDF_BUG(0);
}
static void qdf_mc_timer_print_list(qdf_list_t *timers)
{
QDF_STATUS status;
qdf_list_node_t *node;
qdf_spin_lock_irqsave(&qdf_timer_list_lock);
status = qdf_list_peek_front(timers, &node);
while (QDF_IS_STATUS_SUCCESS(status)) {
qdf_mc_timer_node_t *timer_node = (qdf_mc_timer_node_t *)node;
const char *filename = kbasename(timer_node->file_name);
uint32_t line = timer_node->line_num;
qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
qdf_err("timer Leak@ File %s, @Line %u", filename, line);
qdf_spin_lock_irqsave(&qdf_timer_list_lock);
status = qdf_list_peek_next(timers, node, &node);
}
qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
}
void qdf_mc_timer_check_for_leaks(void)
{
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
qdf_list_t *timers = qdf_timer_list_get(current_domain);
if (qdf_list_empty(timers))
return;
qdf_err("Timer leaks detected in %s domain!",
qdf_debug_domain_name(current_domain));
qdf_mc_timer_print_list(timers);
qdf_mc_timer_panic();
}
static void qdf_mc_timer_free_leaked_timers(qdf_list_t *timers)
{
QDF_STATUS status;
qdf_list_node_t *node;
qdf_spin_lock_irqsave(&qdf_timer_list_lock);
status = qdf_list_remove_front(timers, &node);
while (QDF_IS_STATUS_SUCCESS(status)) {
qdf_mem_free(node);
status = qdf_list_remove_front(timers, &node);
}
qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
}
/** /**
* qdf_timer_clean() - clean up QDF timer debug functionality * qdf_timer_clean() - clean up QDF timer debug functionality
* *
@@ -175,33 +236,33 @@ EXPORT_SYMBOL(qdf_mc_timer_manager_init);
*/ */
static void qdf_timer_clean(void) static void qdf_timer_clean(void)
{ {
uint32_t list_size; bool leaks_detected = false;
qdf_list_node_t *node; int i;
QDF_STATUS qdf_status;
qdf_mc_timer_node_t *timer_node; /* detect and print leaks */
for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i) {
qdf_list_t *timers = &qdf_timer_domains[i];
list_size = qdf_list_size(&qdf_timer_list); if (qdf_list_empty(timers))
continue;
if (!list_size) leaks_detected = true;
qdf_err("\nTimer leaks detected in the %s (Id %d) domain!\n",
qdf_debug_domain_name(i), i);
qdf_mc_timer_print_list(timers);
}
/* we're done if there were no leaks */
if (!leaks_detected)
return; return;
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_INFO_HIGH,
"%s: List is not Empty. list_size %d ",
__func__, (int)list_size);
do { /* panic, if enabled */
qdf_spin_lock_irqsave(&qdf_timer_list_lock); qdf_mc_timer_panic();
qdf_status = qdf_list_remove_front(&qdf_timer_list, &node);
qdf_spin_unlock_irqrestore(&qdf_timer_list_lock); /* if we didn't crash, release the leaked timers */
if (QDF_STATUS_SUCCESS == qdf_status) { for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i)
timer_node = (qdf_mc_timer_node_t *) node; qdf_mc_timer_free_leaked_timers(&qdf_timer_domains[i]);
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
"timer Leak@ File %s, @Line %d",
timer_node->file_name,
(int)timer_node->line_num);
qdf_mem_free(timer_node);
}
} while (qdf_status == QDF_STATUS_SUCCESS);
} }
EXPORT_SYMBOL(qdf_timer_clean); EXPORT_SYMBOL(qdf_timer_clean);
@@ -214,8 +275,13 @@ EXPORT_SYMBOL(qdf_timer_clean);
*/ */
void qdf_mc_timer_manager_exit(void) void qdf_mc_timer_manager_exit(void)
{ {
int i;
qdf_timer_clean(); qdf_timer_clean();
qdf_list_destroy(&qdf_timer_list);
for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i)
qdf_list_destroy(&qdf_timer_domains[i]);
qdf_spinlock_destroy(&qdf_timer_list_lock); qdf_spinlock_destroy(&qdf_timer_list_lock);
} }
EXPORT_SYMBOL(qdf_mc_timer_manager_exit); EXPORT_SYMBOL(qdf_mc_timer_manager_exit);
@@ -259,6 +325,8 @@ QDF_STATUS qdf_mc_timer_init_debug(qdf_mc_timer_t *timer,
void *user_data, char *file_name, void *user_data, char *file_name,
uint32_t line_num) uint32_t line_num)
{ {
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
qdf_list_t *active_timers = qdf_timer_list_get(current_domain);
QDF_STATUS qdf_status; QDF_STATUS qdf_status;
/* check for invalid pointer */ /* check for invalid pointer */
@@ -279,14 +347,12 @@ QDF_STATUS qdf_mc_timer_init_debug(qdf_mc_timer_t *timer,
return QDF_STATUS_E_NOMEM; return QDF_STATUS_E_NOMEM;
} }
qdf_mem_set(timer->timer_node, sizeof(qdf_mc_timer_node_t), 0);
timer->timer_node->file_name = file_name; timer->timer_node->file_name = file_name;
timer->timer_node->line_num = line_num; timer->timer_node->line_num = line_num;
timer->timer_node->qdf_timer = timer; timer->timer_node->qdf_timer = timer;
qdf_spin_lock_irqsave(&qdf_timer_list_lock); qdf_spin_lock_irqsave(&qdf_timer_list_lock);
qdf_status = qdf_list_insert_front(&qdf_timer_list, qdf_status = qdf_list_insert_front(active_timers,
&timer->timer_node->node); &timer->timer_node->node);
qdf_spin_unlock_irqrestore(&qdf_timer_list_lock); qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
if (QDF_STATUS_SUCCESS != qdf_status) { if (QDF_STATUS_SUCCESS != qdf_status) {
@@ -374,6 +440,8 @@ EXPORT_SYMBOL(qdf_mc_timer_init);
#ifdef TIMER_MANAGER #ifdef TIMER_MANAGER
QDF_STATUS qdf_mc_timer_destroy(qdf_mc_timer_t *timer) QDF_STATUS qdf_mc_timer_destroy(qdf_mc_timer_t *timer)
{ {
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
qdf_list_t *active_timers = qdf_timer_list_get(current_domain);
QDF_STATUS v_status = QDF_STATUS_SUCCESS; QDF_STATUS v_status = QDF_STATUS_SUCCESS;
/* check for invalid pointer */ /* check for invalid pointer */
@@ -393,7 +461,7 @@ QDF_STATUS qdf_mc_timer_destroy(qdf_mc_timer_t *timer)
} }
qdf_spin_lock_irqsave(&qdf_timer_list_lock); qdf_spin_lock_irqsave(&qdf_timer_list_lock);
v_status = qdf_list_remove_node(&qdf_timer_list, v_status = qdf_list_remove_node(active_timers,
&timer->timer_node->node); &timer->timer_node->node);
qdf_spin_unlock_irqrestore(&qdf_timer_list_lock); qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
if (v_status != QDF_STATUS_SUCCESS) { if (v_status != QDF_STATUS_SUCCESS) {