Browse Source

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
Dustin Brown 7 years ago
parent
commit
f7fb76bd5e
2 changed files with 110 additions and 32 deletions
  1. 11 1
      qdf/inc/qdf_mc_timer.h
  2. 99 31
      qdf/linux/src/qdf_mc_timer.c

+ 11 - 1
qdf/inc/qdf_mc_timer.h

@@ -66,7 +66,7 @@ struct qdf_mc_timer_s;
 typedef struct qdf_mc_timer_node_s {
 	qdf_list_node_t node;
 	char *file_name;
-	unsigned int line_num;
+	uint32_t line_num;
 	struct qdf_mc_timer_s *qdf_timer;
 } qdf_mc_timer_node_t;
 #endif
@@ -90,6 +90,7 @@ void qdf_try_allowing_sleep(QDF_TIMER_TYPE type);
 #ifdef TIMER_MANAGER
 void qdf_mc_timer_manager_init(void);
 void qdf_mc_timer_manager_exit(void);
+void qdf_mc_timer_check_for_leaks(void);
 #else
 /**
  * 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)
 {
 }
+
+/**
+ * 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
 /**
  * qdf_mc_timer_get_current_state() - get the current state of the timer

+ 99 - 31
qdf/linux/src/qdf_mc_timer.c

@@ -31,6 +31,7 @@
  */
 
 /* Include Files */
+#include <qdf_debug_domain.h>
 #include <qdf_mc_timer.h>
 #include <qdf_lock.h>
 #include "qdf_lock.h"
@@ -146,10 +147,13 @@ EXPORT_SYMBOL(qdf_timer_module_init);
 
 #ifdef TIMER_MANAGER
 
-qdf_list_t qdf_timer_list;
-qdf_spinlock_t qdf_timer_list_lock;
+static qdf_list_t qdf_timer_domains[QDF_DEBUG_DOMAIN_COUNT];
+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
@@ -160,11 +164,68 @@ static void qdf_timer_clean(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);
 }
 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
  *
@@ -175,33 +236,33 @@ EXPORT_SYMBOL(qdf_mc_timer_manager_init);
  */
 static void qdf_timer_clean(void)
 {
-	uint32_t list_size;
-	qdf_list_node_t *node;
-	QDF_STATUS qdf_status;
+	bool leaks_detected = false;
+	int i;
+
+	/* detect and print leaks */
+	for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i) {
+		qdf_list_t *timers = &qdf_timer_domains[i];
 
-	qdf_mc_timer_node_t *timer_node;
+		if (qdf_list_empty(timers))
+			continue;
 
-	list_size = qdf_list_size(&qdf_timer_list);
+		leaks_detected = true;
 
-	if (!list_size)
+		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;
-	QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_INFO_HIGH,
-		  "%s: List is not Empty. list_size %d ",
-		  __func__, (int)list_size);
 
-	do {
-		qdf_spin_lock_irqsave(&qdf_timer_list_lock);
-		qdf_status = qdf_list_remove_front(&qdf_timer_list, &node);
-		qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
-		if (QDF_STATUS_SUCCESS == qdf_status) {
-			timer_node = (qdf_mc_timer_node_t *) node;
-			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);
+	/* panic, if enabled */
+	qdf_mc_timer_panic();
+
+	/* if we didn't crash, release the leaked timers */
+	for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i)
+		qdf_mc_timer_free_leaked_timers(&qdf_timer_domains[i]);
 }
 EXPORT_SYMBOL(qdf_timer_clean);
 
@@ -214,8 +275,13 @@ EXPORT_SYMBOL(qdf_timer_clean);
  */
 void qdf_mc_timer_manager_exit(void)
 {
+	int i;
+
 	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);
 }
 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,
 				   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;
 
 	/* 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;
 	}
 
-	qdf_mem_set(timer->timer_node, sizeof(qdf_mc_timer_node_t), 0);
-
 	timer->timer_node->file_name = file_name;
 	timer->timer_node->line_num = line_num;
 	timer->timer_node->qdf_timer = timer;
 
 	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);
 	qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
 	if (QDF_STATUS_SUCCESS != qdf_status) {
@@ -374,6 +440,8 @@ EXPORT_SYMBOL(qdf_mc_timer_init);
 #ifdef TIMER_MANAGER
 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;
 
 	/* 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);
-	v_status = qdf_list_remove_node(&qdf_timer_list,
+	v_status = qdf_list_remove_node(active_timers,
 					&timer->timer_node->node);
 	qdf_spin_unlock_irqrestore(&qdf_timer_list_lock);
 	if (v_status != QDF_STATUS_SUCCESS) {