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
This commit is contained in:

committed by
qcabuildsw

parent
805668a63d
commit
ea5d86fe25
@@ -41,6 +41,7 @@
|
|||||||
#define QDF_LOCK_STATS 0
|
#define QDF_LOCK_STATS 0
|
||||||
#define QDF_LOCK_STATS_DESTROY_PRINT 0
|
#define QDF_LOCK_STATS_DESTROY_PRINT 0
|
||||||
#define QDF_LOCK_STATS_BUG_ON 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_IRQ 1000
|
||||||
#define QDF_MAX_HOLD_TIME_ALOWED_SPINLOCK_BH 5000
|
#define QDF_MAX_HOLD_TIME_ALOWED_SPINLOCK_BH 5000
|
||||||
@@ -55,7 +56,12 @@ struct lock_stats {};
|
|||||||
#define BEFORE_UNLOCK(x...) do {} while (0)
|
#define BEFORE_UNLOCK(x...) do {} while (0)
|
||||||
#define qdf_lock_stats_create(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_destroy(x...) do {} while (0)
|
||||||
|
#define qdf_lock_stats_init(x...) do {} while (0)
|
||||||
|
#define qdf_lock_stats_deinit(x...) do {} while (0)
|
||||||
#else
|
#else
|
||||||
|
void qdf_lock_stats_init(void);
|
||||||
|
void qdf_lock_stats_deinit(void);
|
||||||
|
struct qdf_lock_cookie;
|
||||||
struct lock_stats {
|
struct lock_stats {
|
||||||
const char *initialization_fn;
|
const char *initialization_fn;
|
||||||
int line;
|
int line;
|
||||||
@@ -69,6 +75,7 @@ struct lock_stats {
|
|||||||
uint64_t max_held_time;
|
uint64_t max_held_time;
|
||||||
int num_large_contentions;
|
int num_large_contentions;
|
||||||
int num_large_holds;
|
int num_large_holds;
|
||||||
|
struct qdf_lock_cookie *cookie;
|
||||||
};
|
};
|
||||||
#define LARGE_CONTENTION QDF_LOG_TIMESTAMP_CYCLES_PER_10_US
|
#define LARGE_CONTENTION QDF_LOG_TIMESTAMP_CYCLES_PER_10_US
|
||||||
|
|
||||||
@@ -140,6 +147,10 @@ do {\
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} 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)
|
static inline void qdf_lock_stats_destroy(struct lock_stats *stats)
|
||||||
{
|
{
|
||||||
if (QDF_LOCK_STATS_DESTROY_PRINT) {
|
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->held_time),
|
||||||
qdf_log_timestamp_to_usecs(stats->max_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,
|
static inline void qdf_lock_stats_create(struct lock_stats *stats,
|
||||||
const char *func, int line)
|
const char *func, int line)
|
||||||
{
|
{
|
||||||
qdf_mem_zero(stats, sizeof(*stats));
|
qdf_mem_zero(stats, sizeof(*stats));
|
||||||
stats->initialization_fn = func;
|
stats->initialization_fn = func;
|
||||||
stats->line = line;
|
stats->line = line;
|
||||||
|
|
||||||
|
if (QDF_LOCK_STATS_LIST)
|
||||||
|
qdf_lock_stats_cookie_create(stats, func, line);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -221,6 +245,8 @@ static inline void qdf_spinlock_create(qdf_spinlock_t *lock, const char *func,
|
|||||||
int line)
|
int line)
|
||||||
{
|
{
|
||||||
__qdf_spinlock_create(&lock->lock);
|
__qdf_spinlock_create(&lock->lock);
|
||||||
|
|
||||||
|
/* spinlock stats create relies on the spinlock working allread */
|
||||||
qdf_lock_stats_create(&lock->stats, func, line);
|
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_acquire(qdf_spinlock_t *lock);
|
||||||
|
|
||||||
QDF_STATUS qdf_spinlock_release(qdf_spinlock_t *lock);
|
QDF_STATUS qdf_spinlock_release(qdf_spinlock_t *lock);
|
||||||
|
|
||||||
#endif /* _QDF_LOCK_H */
|
#endif /* _QDF_LOCK_H */
|
||||||
|
@@ -673,3 +673,164 @@ void qdf_spin_unlock_bh_outline(qdf_spinlock_t *lock)
|
|||||||
qdf_spin_unlock_bh(lock);
|
qdf_spin_unlock_bh(lock);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(qdf_spin_unlock_bh_outline);
|
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
|
||||||
|
Reference in New Issue
Block a user