Преглед на файлове

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
Houston Hoffman преди 8 години
родител
ревизия
ea5d86fe25
променени са 2 файла, в които са добавени 187 реда и са изтрити 1 реда
  1. 26 1
      qdf/inc/qdf_lock.h
  2. 161 0
      qdf/linux/src/qdf_lock.c

+ 26 - 1
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 */

+ 161 - 0
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