diff --git a/qdf/inc/qdf_tracker.h b/qdf/inc/qdf_tracker.h new file mode 100644 index 000000000000..04dee0e7b246 --- /dev/null +++ b/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 */ + diff --git a/qdf/inc/qdf_types.h b/qdf/inc/qdf_types.h index 80b9656243c0..289285d18761 100644 --- a/qdf/inc/qdf_types.h +++ b/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 diff --git a/qdf/linux/src/i_qdf_trace.h b/qdf/linux/src/i_qdf_trace.h index a4330f7b33ec..7602c762a67e 100644 --- a/qdf/linux/src/i_qdf_trace.h +++ b/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, ...) { } diff --git a/qdf/linux/src/qdf_trace.c b/qdf/linux/src/qdf_trace.c index 0fe8f685138f..ba1497ff5977 100644 --- a/qdf/linux/src/qdf_trace.c +++ b/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); } diff --git a/qdf/src/qdf_tracker.c b/qdf/src/qdf_tracker.c new file mode 100644 index 000000000000..7fe001114326 --- /dev/null +++ b/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; +} + diff --git a/qdf/test/qdf_tracker_test.c b/qdf/test/qdf_tracker_test.c new file mode 100644 index 000000000000..b1bd7a8968b8 --- /dev/null +++ b/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; +} + diff --git a/qdf/test/qdf_tracker_test.h b/qdf/test/qdf_tracker_test.h new file mode 100644 index 000000000000..8500f5110439 --- /dev/null +++ b/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 */ +