Forráskód Böngészése

qcacmn: Add qdf_tracker

A common problem in any C code base is the proper management of various
resources, such as memory allocations, timers, works, locks, etc. To
that end, WLAN has added various one-off leak tracking features for
different resources over the years, to great benefit. However, this
approach is tedious, and prone to copy/paste and fix propagation issues,
making maintaining these various tracking features onerous.

Add a generic leak tracking data structure to WLAN for general use,
which implements these tracking logics. This will allow the various
resource tracking features to leverage this shared implementation for
ease of maintenance. Build on the hash table implementation added as
part of I7f5fc0c59ed220bde43044e7013b3c8573f1bf39.

Change-Id: I3247ace583108139dfb699ca077db3bdf5cfeace
CRs-Fixed: 2422958
Dustin Brown 6 éve
szülő
commit
9c62f7d939

+ 130 - 0
qdf/inc/qdf_tracker.h

@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __QDF_TRACKER_H
+#define __QDF_TRACKER_H
+
+#include "qdf_lock.h"
+#include "qdf_ptr_hash.h"
+#include "qdf_status.h"
+#include "qdf_types.h"
+
+#define QDF_TRACKER_FUNC_SIZE 48
+
+/**
+ * struct qdf_tracker - a generic type for tracking resources
+ * @leak_title: the string title to use when logging leaks
+ * @track_title: the string title to use when logging double tracking issues
+ * @untrack_title: the string title to use when logging double untracking issues
+ * @lock: lock for simultaneous access to @ht
+ * @ht: the hashtable used for storing tracking information
+ */
+struct qdf_tracker {
+	const char *leak_title;
+	const char *track_title;
+	const char *untrack_title;
+	struct qdf_spinlock lock;
+	struct qdf_ptr_hash *ht;
+};
+
+/**
+ * qdf_tracker_declare() - statically declare a qdf_tacker instance
+ * @name: C identifier to use for the new qdf_tracker
+ * @bits: the number of bits to use for hashing the resource pointers
+ * @leak_title: the string title to use when logging leaks
+ * @track_title: the string title to use when logging double tracking issues
+ * @untrack_title: the string title to use when logging double untracking issues
+ */
+#define qdf_tracker_declare(name, bits, _leak_title, \
+			    _track_title, _untrack_title) \
+qdf_ptr_hash_declare(name ## _ht, bits); \
+struct qdf_tracker name = { \
+	.leak_title = _leak_title, \
+	.track_title = _track_title, \
+	.untrack_title = _untrack_title, \
+	.ht = qdf_ptr_hash_ptr(name ## _ht), \
+}
+
+/**
+ * qdf_tracker_init() - initialize a qdf_tracker
+ * @tracker: the qdf_tracker to initialize
+ *
+ * Return: None
+ */
+void qdf_tracker_init(struct qdf_tracker *tracker);
+
+/**
+ * qdf_tracker_deinit() - de-initialize a qdf_tracker
+ * @tracker: the qdf_tracker to de-initialize
+ *
+ * Return: None
+ */
+void qdf_tracker_deinit(struct qdf_tracker *tracker);
+
+/**
+ * qdf_tracker_track() - track a resource with @tracker
+ * @tracker: the qdf_tracker to track with
+ * @ptr: an opaque pointer to the resource to track
+ * @func: name of the caller function operating on @ptr
+ * @line: line number of the call site operating on @ptr
+ *
+ * Return: QDF_STATUS
+ */
+qdf_must_check QDF_STATUS
+qdf_tracker_track(struct qdf_tracker *tracker, void *ptr,
+		  const char *func, uint32_t line);
+
+/**
+ * qdf_tracker_untrack() - untrack a resource with @tracker
+ * @tracker: the qdf_tracker used to track @ptr
+ * @ptr: an opaque pointer to the resource to untrack
+ * @func: name of the caller function operating on @ptr
+ * @line: line number of the call site operating on @ptr
+ *
+ * Return: None
+ */
+void qdf_tracker_untrack(struct qdf_tracker *tracker, void *ptr,
+			 const char *func, uint32_t line);
+
+/**
+ * qdf_tracker_check_for_leaks() - assert @tracker has no tracked resources
+ *	for the current debug domain
+ * @tracker: the qdf_tracker to check
+ *
+ * Return: None
+ */
+void qdf_tracker_check_for_leaks(struct qdf_tracker *tracker);
+
+/**
+ * qdf_tracker_lookup() - query tracking information for @ptr
+ * @tracker: the qdf_tracker to check
+ * @ptr: the opaque pointer of the resource to lookup
+ * @out_func: function name provided when @ptr was tracked, populated on success
+ * @out_line: line number provided when @ptr was tracked, populated on success
+ *
+ * Note: @out_func is assumed to be sizeof(QDF_TRACKER_FUNC_SIZE).
+ *
+ * Return: true if @tracker is tracking @ptr
+ */
+qdf_must_check bool
+qdf_tracker_lookup(struct qdf_tracker *tracker, void *ptr,
+		   char (*out_func)[QDF_TRACKER_FUNC_SIZE],
+		   uint32_t *out_line);
+
+#endif /* __QDF_TRACKER_H */
+

+ 2 - 2
qdf/inc/qdf_types.h

@@ -619,7 +619,7 @@ enum QDF_GLOBAL_MODE {
  * implemented in qdf_trace.c
  */
 void __printf(3, 4) qdf_trace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
-		   char *str_format, ...);
+				  const char *str_format, ...);
 /**
  * qdf_vtrace_msg() - the va_list version of qdf_trace_msg
  * @module: the calling module's Id
@@ -630,7 +630,7 @@ void __printf(3, 4) qdf_trace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
  * Return: None
  */
 void qdf_vtrace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
-		    char *str_format, va_list val);
+		    const char *str_format, va_list val);
 
 #ifdef CONFIG_MCL
 

+ 1 - 1
qdf/linux/src/i_qdf_trace.h

@@ -227,7 +227,7 @@ static inline void __qdf_trace_noop(QDF_MODULE_ID module, char *format, ...) { }
 /* This code will be used for compilation if tracing is to be compiled out */
 /* of the code so these functions/macros are 'do nothing' */
 static inline void qdf_trace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
-		   char *str_format, ...)
+				 const char *str_format, ...)
 {
 }
 

+ 2 - 2
qdf/linux/src/qdf_trace.c

@@ -320,7 +320,7 @@ qdf_export_symbol(qdf_snprintf);
  * Return: None
  */
 void qdf_trace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
-		   char *str_format, ...)
+		   const char *str_format, ...)
 {
 	va_list val;
 
@@ -331,7 +331,7 @@ void qdf_trace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
 qdf_export_symbol(qdf_trace_msg);
 
 void qdf_vtrace_msg(QDF_MODULE_ID module, QDF_TRACE_LEVEL level,
-		    char *str_format, va_list val)
+		    const char *str_format, va_list val)
 {
 	qdf_trace_msg_cmn(qdf_pidx, module, level, str_format, val);
 }

+ 191 - 0
qdf/src/qdf_tracker.c

@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "qdf_debug_domain.h"
+#include "qdf_lock.h"
+#include "qdf_mem.h"
+#include "qdf_module.h"
+#include "qdf_ptr_hash.h"
+#include "qdf_status.h"
+#include "qdf_str.h"
+#include "qdf_tracker.h"
+#include "qdf_types.h"
+
+struct qdf_tracker_node {
+	struct qdf_ptr_hash_entry entry;
+	enum qdf_debug_domain domain;
+	char func[QDF_TRACKER_FUNC_SIZE];
+	uint32_t line;
+};
+
+void qdf_tracker_init(struct qdf_tracker *tracker)
+{
+	qdf_spinlock_create(&tracker->lock);
+	qdf_ptr_hash_init(tracker->ht);
+}
+qdf_export_symbol(qdf_tracker_init);
+
+void qdf_tracker_deinit(struct qdf_tracker *tracker)
+{
+	qdf_tracker_check_for_leaks(tracker);
+
+	qdf_spin_lock_bh(&tracker->lock);
+	QDF_BUG(qdf_ptr_hash_empty(tracker->ht));
+	qdf_spin_unlock_bh(&tracker->lock);
+
+	qdf_ptr_hash_deinit(tracker->ht);
+	qdf_spinlock_destroy(&tracker->lock);
+}
+qdf_export_symbol(qdf_tracker_deinit);
+
+static inline void qdf_tracker_print_break(void)
+{
+	qdf_nofl_alert("-----------------------------------------------------");
+}
+
+static uint32_t qdf_tracker_leaks_print(struct qdf_tracker *tracker,
+					enum qdf_debug_domain domain)
+{
+	struct qdf_ptr_hash_bucket *bucket;
+	struct qdf_tracker_node *node;
+	bool print_header = true;
+	uint32_t count = 0;
+
+	QDF_BUG(qdf_spin_is_locked(&tracker->lock));
+
+	qdf_ptr_hash_for_each(tracker->ht, bucket, node, entry) {
+		if (node->domain != domain)
+			continue;
+
+		if (print_header) {
+			print_header = false;
+			qdf_nofl_alert("%s detected in %s domain!",
+				       tracker->leak_title,
+				       qdf_debug_domain_name(domain));
+			qdf_tracker_print_break();
+		}
+
+		count++;
+		qdf_nofl_alert("0x%zx @ %s:%u", node->entry.key,
+			       node->func, node->line);
+	}
+
+	if (count)
+		qdf_tracker_print_break();
+
+	return count;
+}
+
+void qdf_tracker_check_for_leaks(struct qdf_tracker *tracker)
+{
+	enum qdf_debug_domain domain = qdf_debug_domain_get();
+	uint32_t leaks;
+
+	qdf_spin_lock_bh(&tracker->lock);
+	leaks = qdf_tracker_leaks_print(tracker, domain);
+	if (leaks)
+		QDF_DEBUG_PANIC("%u fatal %s detected in %s domain!",
+				leaks, tracker->leak_title,
+				qdf_debug_domain_name(domain));
+	qdf_spin_unlock_bh(&tracker->lock);
+}
+qdf_export_symbol(qdf_tracker_check_for_leaks);
+
+QDF_STATUS qdf_tracker_track(struct qdf_tracker *tracker, void *ptr,
+			     const char *func, uint32_t line)
+{
+	struct qdf_tracker_node *node;
+
+	QDF_BUG(ptr);
+	if (!ptr)
+		return QDF_STATUS_E_INVAL;
+
+	qdf_spin_lock_bh(&tracker->lock);
+	node = qdf_ptr_hash_get(tracker->ht, ptr, node, entry);
+	if (node)
+		QDF_DEBUG_PANIC("Double %s (via %s:%u); last %s from %s:%u",
+				tracker->track_title, func, line,
+				tracker->track_title, node->func, node->line);
+	qdf_spin_unlock_bh(&tracker->lock);
+
+	if (node)
+		return QDF_STATUS_E_ALREADY;
+
+	node = qdf_mem_malloc(sizeof(*node));
+	if (!node)
+		return QDF_STATUS_E_NOMEM;
+
+	node->domain = qdf_debug_domain_get();
+	qdf_str_lcopy(node->func, func, QDF_TRACKER_FUNC_SIZE);
+	node->line = line;
+
+	qdf_spin_lock_bh(&tracker->lock);
+	qdf_ptr_hash_add(tracker->ht, ptr, node, entry);
+	qdf_spin_unlock_bh(&tracker->lock);
+
+	return QDF_STATUS_SUCCESS;
+}
+qdf_export_symbol(qdf_tracker_track);
+
+void qdf_tracker_untrack(struct qdf_tracker *tracker, void *ptr,
+			 const char *func, uint32_t line)
+{
+	enum qdf_debug_domain domain = qdf_debug_domain_get();
+	struct qdf_tracker_node *node;
+
+	QDF_BUG(ptr);
+	if (!ptr)
+		return;
+
+	qdf_spin_lock_bh(&tracker->lock);
+	node = qdf_ptr_hash_remove(tracker->ht, ptr, node, entry);
+	if (!node)
+		QDF_DEBUG_PANIC("Double %s (via %s:%u)",
+				tracker->untrack_title, func, line);
+	else if (node->domain != domain)
+		QDF_DEBUG_PANIC("%s domain mismatch; tracked:%s, %s:%u; untracked:%s , %s:%u",
+				tracker->untrack_title,
+				qdf_debug_domain_name(node->domain),
+				node->func, node->line,
+				qdf_debug_domain_name(domain),
+				func, line);
+	qdf_spin_unlock_bh(&tracker->lock);
+
+	if (node)
+		qdf_mem_free(node);
+}
+qdf_export_symbol(qdf_tracker_untrack);
+
+bool qdf_tracker_lookup(struct qdf_tracker *tracker, void *ptr,
+			char (*out_func)[QDF_TRACKER_FUNC_SIZE],
+			uint32_t *out_line)
+{
+	struct qdf_tracker_node *node;
+
+	qdf_spin_lock_bh(&tracker->lock);
+	node = qdf_ptr_hash_get(tracker->ht, ptr, node, entry);
+	if (node) {
+		qdf_str_lcopy((char *)out_func, node->func,
+			      QDF_TRACKER_FUNC_SIZE);
+		*out_line = node->line;
+	}
+	qdf_spin_unlock_bh(&tracker->lock);
+
+	return !!node;
+}
+

+ 95 - 0
qdf/test/qdf_tracker_test.c

@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "qdf_tracker.h"
+#include "qdf_tracker_test.h"
+#include "qdf_trace.h"
+#include "qdf_types.h"
+
+#define qdf_ut_tracker_bits 4 /* 16 buckets */
+#define qdf_ut_tracker_item_count 3
+#define qdf_ut_tracker_declare(name) \
+	qdf_tracker_declare(name, qdf_ut_tracker_bits, "unit-test leak", \
+			    "unit-test alloc", "unit-test free")
+
+static uint32_t qdf_tracker_test_empty(void)
+{
+	qdf_ut_tracker_declare(tracker);
+	char func[QDF_TRACKER_FUNC_SIZE];
+	uint32_t line;
+
+	/* a new tracker should ... */
+	qdf_tracker_init(&tracker);
+
+	/* ... be empty */
+	qdf_tracker_check_for_leaks(&tracker);
+
+	/* ... not contain an arbitrary pointer */
+	QDF_BUG(!qdf_tracker_lookup(&tracker, &tracker, &func, &line));
+
+	qdf_tracker_deinit(&tracker);
+
+	return 0;
+}
+
+static uint32_t qdf_tracker_test_add_remove(void)
+{
+	qdf_ut_tracker_declare(tracker);
+	bool items[qdf_ut_tracker_item_count];
+	QDF_STATUS status;
+	int i;
+
+	qdf_tracker_init(&tracker);
+
+	/* an empty tracker should track items */
+	for (i = 0; i < qdf_ut_tracker_item_count; i++) {
+		status = qdf_tracker_track(&tracker, items + i,
+					   __func__, __LINE__);
+		items[i] = QDF_IS_STATUS_SUCCESS(status);
+	}
+
+	/* a non-empty tracker should find previously added items */
+	for (i = 0; i < qdf_ut_tracker_item_count; i++) {
+		char func[QDF_TRACKER_FUNC_SIZE];
+		uint32_t line;
+
+		if (!items[i])
+			continue;
+
+		QDF_BUG(qdf_tracker_lookup(&tracker, items + i, &func, &line));
+		qdf_tracker_untrack(&tracker, items + i, __func__, __LINE__);
+	}
+
+	/* a tracker should be empty after all items are untracked*/
+	qdf_tracker_check_for_leaks(&tracker);
+
+	qdf_tracker_deinit(&tracker);
+
+	return 0;
+}
+
+uint32_t qdf_tracker_unit_test(void)
+{
+	uint32_t errors = 0;
+
+	errors += qdf_tracker_test_empty();
+	errors += qdf_tracker_test_add_remove();
+
+	return errors;
+}
+

+ 37 - 0
qdf/test/qdf_tracker_test.h

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __QDF_TRACKER_TEST
+#define __QDF_TRACKER_TEST
+
+#ifdef WLAN_TRACKER_TEST
+/**
+ * qdf_tracker_unit_test() - run the qdf tracker unit test suite
+ *
+ * Return: number of failed test cases
+ */
+uint32_t qdf_tracker_unit_test(void);
+#else
+static inline uint32_t qdf_tracker_unit_test(void)
+{
+	return 0;
+}
+#endif /* WLAN_TRACKER_TEST */
+
+#endif /* __QDF_TRACKER_TEST */
+