diff --git a/qdf/inc/qdf_lock.h b/qdf/inc/qdf_lock.h index 50d3d5b918..7050a1112a 100644 --- a/qdf/inc/qdf_lock.h +++ b/qdf/inc/qdf_lock.h @@ -41,6 +41,7 @@ #define QDF_LOCK_STATS 0 #define QDF_LOCK_STATS_DESTROY_PRINT 0 #define QDF_LOCK_STATS_BUG_ON 0 +#define QDF_LOCK_STATS_LIST 0 #define QDF_MAX_HOLD_TIME_ALOWED_SPINLOCK_IRQ 1000 #define QDF_MAX_HOLD_TIME_ALOWED_SPINLOCK_BH 5000 @@ -55,7 +56,12 @@ struct lock_stats {}; #define BEFORE_UNLOCK(x...) do {} while (0) #define qdf_lock_stats_create(x...) do {} while (0) #define qdf_lock_stats_destroy(x...) do {} while (0) +#define qdf_lock_stats_init(x...) do {} while (0) +#define qdf_lock_stats_deinit(x...) do {} while (0) #else +void qdf_lock_stats_init(void); +void qdf_lock_stats_deinit(void); +struct qdf_lock_cookie; struct lock_stats { const char *initialization_fn; int line; @@ -69,6 +75,7 @@ struct lock_stats { uint64_t max_held_time; int num_large_contentions; int num_large_holds; + struct qdf_lock_cookie *cookie; }; #define LARGE_CONTENTION QDF_LOG_TIMESTAMP_CYCLES_PER_10_US @@ -140,6 +147,10 @@ do {\ } \ } while (0) +void qdf_lock_stats_cookie_destroy(struct lock_stats *stats); +void qdf_lock_stats_cookie_create(struct lock_stats *stats, + const char *func, int line); + static inline void qdf_lock_stats_destroy(struct lock_stats *stats) { if (QDF_LOCK_STATS_DESTROY_PRINT) { @@ -156,14 +167,27 @@ static inline void qdf_lock_stats_destroy(struct lock_stats *stats) qdf_log_timestamp_to_usecs(stats->held_time), qdf_log_timestamp_to_usecs(stats->max_held_time)); } + + if (QDF_LOCK_STATS_LIST) + qdf_lock_stats_cookie_destroy(stats); } +#ifndef MEMORY_DEBUG +#define qdf_mem_malloc_debug(x, y, z) qdf_mem_malloc(x) +#endif + +/* qdf_lock_stats_create() - initialize the lock stats structure + * + */ static inline void qdf_lock_stats_create(struct lock_stats *stats, const char *func, int line) { qdf_mem_zero(stats, sizeof(*stats)); stats->initialization_fn = func; stats->line = line; + + if (QDF_LOCK_STATS_LIST) + qdf_lock_stats_cookie_create(stats, func, line); } #endif @@ -221,6 +245,8 @@ static inline void qdf_spinlock_create(qdf_spinlock_t *lock, const char *func, int line) { __qdf_spinlock_create(&lock->lock); + + /* spinlock stats create relies on the spinlock working allread */ qdf_lock_stats_create(&lock->stats, func, line); } @@ -455,5 +481,4 @@ void qdf_runtime_lock_deinit(qdf_runtime_lock_t lock); QDF_STATUS qdf_spinlock_acquire(qdf_spinlock_t *lock); QDF_STATUS qdf_spinlock_release(qdf_spinlock_t *lock); - #endif /* _QDF_LOCK_H */ diff --git a/qdf/linux/src/qdf_lock.c b/qdf/linux/src/qdf_lock.c index 2c0ef1a19d..c3d15a43e9 100644 --- a/qdf/linux/src/qdf_lock.c +++ b/qdf/linux/src/qdf_lock.c @@ -673,3 +673,164 @@ void qdf_spin_unlock_bh_outline(qdf_spinlock_t *lock) qdf_spin_unlock_bh(lock); } EXPORT_SYMBOL(qdf_spin_unlock_bh_outline); + +#if QDF_LOCK_STATS_LIST +struct qdf_lock_cookie { + union { + struct { + struct lock_stats *stats; + const char *func; + int line; + } cookie; + struct { + struct qdf_lock_cookie *next; + } empty_node; + } u; +}; + +#ifndef QDF_LOCK_STATS_LIST_SIZE +#define QDF_LOCK_STATS_LIST_SIZE 256 +#endif + +static qdf_spinlock_t qdf_lock_list_spinlock; +static struct qdf_lock_cookie lock_cookies[QDF_LOCK_STATS_LIST_SIZE]; +static struct qdf_lock_cookie *lock_cookie_freelist; +static qdf_atomic_t lock_cookie_get_failures; +static qdf_atomic_t lock_cookie_untracked_num; +/* dummy value */ +#define DUMMY_LOCK_COOKIE 0xc00c1e + +/** + * qdf_is_lock_cookie - check if memory is a valid lock cookie + * + * return true if the memory is within the range of the lock cookie + * memory. + */ +static bool qdf_is_lock_cookie(struct qdf_lock_cookie *lock_cookie) +{ + return lock_cookie >= &lock_cookies[0] && + lock_cookie <= &lock_cookies[QDF_LOCK_STATS_LIST_SIZE-1]; +} + +/** + * qdf_is_lock_cookie_free() - check if the lock cookie is on the freelist + * @lock_cookie: lock cookie to check + * + * Check that the next field of the lock cookie points to a lock cookie. + * currently this is only true if the cookie is on the freelist. + * + * Checking for the function and line being NULL and 0 should also have worked. + */ +static bool qdf_is_lock_cookie_free(struct qdf_lock_cookie *lock_cookie) +{ + struct qdf_lock_cookie *tmp = lock_cookie->u.empty_node.next; + + return qdf_is_lock_cookie(tmp) || (tmp == NULL); +} + +static struct qdf_lock_cookie *qdf_get_lock_cookie(void) +{ + struct qdf_lock_cookie *lock_cookie; + + qdf_spin_lock_bh(&qdf_lock_list_spinlock); + lock_cookie = lock_cookie_freelist; + if (lock_cookie_freelist) + lock_cookie_freelist = lock_cookie_freelist->u.empty_node.next; + qdf_spin_unlock_bh(&qdf_lock_list_spinlock); + return lock_cookie; +} + +static void __qdf_put_lock_cookie(struct qdf_lock_cookie *lock_cookie) +{ + if (!qdf_is_lock_cookie(lock_cookie)) + QDF_BUG(0); + + lock_cookie->u.empty_node.next = lock_cookie_freelist; + lock_cookie_freelist = lock_cookie; +} + +static void qdf_put_lock_cookie(struct qdf_lock_cookie *lock_cookie) +{ + qdf_spin_lock_bh(&qdf_lock_list_spinlock); + __qdf_put_lock_cookie(lock_cookie); + qdf_spin_unlock_bh(&qdf_lock_list_spinlock); +} + +void qdf_lock_stats_init(void) +{ + int i; + + for (i = 0; i < QDF_LOCK_STATS_LIST_SIZE; i++) + __qdf_put_lock_cookie(&lock_cookies[i]); + + /* stats must be allocated for the spinlock before the cookie, + otherwise this qdf_lock_list_spinlock wouldnt get intialized + propperly */ + qdf_spinlock_create(&qdf_lock_list_spinlock); + qdf_atomic_init(&lock_cookie_get_failures); + qdf_atomic_init(&lock_cookie_untracked_num); +} + +void qdf_lock_stats_deinit(void) +{ + int i; + + qdf_spinlock_destroy(&qdf_lock_list_spinlock); + for (i = 0; i < QDF_LOCK_STATS_LIST_SIZE; i++) { + if (!qdf_is_lock_cookie_free(&lock_cookies[i])) + QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, + "%s: lock_not_destroyed, fun: %s, line %d", + __func__, lock_cookies[i].u.cookie.func, + lock_cookies[i].u.cookie.line); + } +} + +/* allocated separate memory in case the lock memory is freed without + running the deinitialization code. The cookie list will not be + corrupted. */ +void qdf_lock_stats_cookie_create(struct lock_stats *stats, + const char *func, int line) +{ + struct qdf_lock_cookie *cookie = qdf_get_lock_cookie(); + + if (cookie == NULL) { + int count; + qdf_atomic_inc(&lock_cookie_get_failures); + count = qdf_atomic_inc_return(&lock_cookie_untracked_num); + stats->cookie = (void *) DUMMY_LOCK_COOKIE; + QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, + "%s: cookie allocation failure, using dummy (%s:%d) count %d", + __func__, func, line, count); + return; + } + + stats->cookie = cookie; + stats->cookie->u.cookie.stats = stats; + stats->cookie->u.cookie.func = func; + stats->cookie->u.cookie.line = line; +} + +void qdf_lock_stats_cookie_destroy(struct lock_stats *stats) +{ + struct qdf_lock_cookie *cookie = stats->cookie; + + if (cookie == NULL) { + QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, + "%s: Double cookie destroy", __func__); + QDF_ASSERT(0); + return; + } + + stats->cookie = NULL; + if (cookie == (void *)DUMMY_LOCK_COOKIE) { + qdf_atomic_dec(&lock_cookie_untracked_num); + return; + } + + cookie->u.cookie.stats = NULL; + cookie->u.cookie.func = NULL; + cookie->u.cookie.line = 0; + + qdf_put_lock_cookie(cookie); +} +#endif