diff options
| author | Houston Hoffman <hhoffman@codeaurora.org> | 2017-01-16 11:00:32 -0800 |
|---|---|---|
| committer | qcabuildsw <qcabuildsw@localhost> | 2017-01-23 14:22:48 -0800 |
| commit | 4eb5341f7e8b7a53254129b6b785c32113d5477e (patch) | |
| tree | 044c8ae875173dcc2e506e923efdb6a733da6294 /qdf/linux/src/qdf_lock.c | |
| parent | e0d713db22713b1f122145e885e8c3c95f8a2087 (diff) | |
qcacmn: Add spinlockstats list
Keep all spinlocks on a list for offline debugging.
Also support detection of duplicate destroy calls.
Also support detection of not calling spinlock_destroy.
Change-Id: I75f520bb87c06111eabf0f610d4751e98a932c99
CRs-Fixed: 1111956
Diffstat (limited to 'qdf/linux/src/qdf_lock.c')
| -rw-r--r-- | qdf/linux/src/qdf_lock.c | 163 |
1 files changed, 162 insertions, 1 deletions
diff --git a/qdf/linux/src/qdf_lock.c b/qdf/linux/src/qdf_lock.c index 08dcc8df7695..c3d15a43e9fc 100644 --- a/qdf/linux/src/qdf_lock.c +++ b/qdf/linux/src/qdf_lock.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. * @@ -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 |
