diff --git a/qdf/inc/qdf_mc_timer.h b/qdf/inc/qdf_mc_timer.h index e8202d58d2..5498064b28 100644 --- a/qdf/inc/qdf_mc_timer.h +++ b/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 diff --git a/qdf/linux/src/qdf_mc_timer.c b/qdf/linux/src/qdf_mc_timer.c index e42fd5a2a6..9525c36d21 100644 --- a/qdf/linux/src/qdf_mc_timer.c +++ b/qdf/linux/src/qdf_mc_timer.c @@ -31,6 +31,7 @@ */ /* Include Files */ +#include #include #include #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; - 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; - 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) {