diff --git a/qdf/inc/qdf_mem.h b/qdf/inc/qdf_mem.h index 8541789c81..ccbd113aef 100644 --- a/qdf/inc/qdf_mem.h +++ b/qdf/inc/qdf_mem.h @@ -465,6 +465,32 @@ void qdf_mem_multi_pages_free(qdf_device_t osdev, int qdf_mem_multi_page_link(qdf_device_t osdev, struct qdf_mem_multi_page_t *pages, uint32_t elem_size, uint32_t elem_count, uint8_t cacheable); + +#ifdef WLAN_DEBUGFS + +/** + * qdf_mem_kmalloc_inc() - increment kmalloc allocated bytes count + * @size: number of bytes to increment by + * + * Return: None + */ +void qdf_mem_kmalloc_inc(qdf_size_t size); + +/** + * qdf_mem_kmalloc_dec() - decrement kmalloc allocated bytes count + * @size: number of bytes to decrement by + * + * Return: None + */ +void qdf_mem_kmalloc_dec(qdf_size_t size); + +#else + +static inline void qdf_mem_kmalloc_inc(qdf_size_t size) { } +static inline void qdf_mem_kmalloc_dec(qdf_size_t size) { } + +#endif /* WLAN_DEBUGFS */ + /** * qdf_mem_skb_inc() - increment total skb allocation size * @size: size to be added diff --git a/qdf/inc/qdf_talloc.h b/qdf/inc/qdf_talloc.h new file mode 100644 index 0000000000..003c3f354a --- /dev/null +++ b/qdf/inc/qdf_talloc.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2018 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. + */ + +/** + * DOC: qdf_talloc.h - Public APIs for t(ree) alloc(ate) memory management + * + * These APIs allocate memory like malloc, but track those allocations via a + * parent-child relationship, or tree. If the parent is freed while it still has + * children, a panic will be triggered. This effectively gives you the ability + * to limit the lifetime of an allocation by ensuring the child allocation + * lifetime will be strictly less than the parent allocation lifetime. + */ + +#ifndef __QDF_TALLOC_H +#define __QDF_TALLOC_H + +#include "i_qdf_talloc.h" +#include "qdf_status.h" + +/** + * qdf_talloc() - t(ree) alloc(ate) memory + * @parent: the parent memory of the new allocation + * @size: requested size of the newly allocated memory + * + * Return: pointer to the newly allocated memory + */ +#define qdf_talloc(parent, size) \ + qdf_talloc_fl(parent, size, __func__, __LINE__) + +/** + * qdf_talloc_type() - t(ree) alloc(ate) memory for a type + * @parent: the parent memory of the new allocation + * @cursor: pointer to the type of memory to allocate + * + * This API automatically determines the correct size needed or an allocation + * based on the type of @cursor. If you need to allocate an arbitrary number + * of bytes, use qdf_talloc() instead. + * + * Return: pointer to the newly allocated memory + */ +#define qdf_talloc_type(parent, cursor) \ + qdf_talloc(parent, sizeof(*(cursor))) + +/** + * qdf_talloc_fl() - t(ree) alloc(ate) memory with function and line info + * @parent: the parent memory of the new allocation + * @size: requested size of the newly allocated memory + * @func: name of the function requesting the allocation + * @line: line number of the call site in @func + * + * Return: pointer to the newly allocated memory + */ +#define qdf_talloc_fl(parent, size, func, line) \ + __qdf_talloc_fl(parent, size, func, line) + +/** + * qdf_tfree() - free memory allocated using the *_talloc() function family + * @inout_ptr: double point to memory to free, set to NULL + * + * Return: None + */ +#define qdf_tfree(ptr) \ + qdf_tfree_fl(ptr, __func__, __LINE__) + +/** + * qdf_tfree_fl() - free memory allocated using the *_talloc() function family + * with function and line info + * @ptr: pointer to memory to free + * @func: name of the function requesting the free + * @line: line number of the call site in @func + * + * Return: None + */ +#define qdf_tfree_fl(ptr, func, line) \ +do { \ + __qdf_tfree_fl(ptr, func, line); \ + ptr = (void *)1; \ +} while (false) + +/** + * qdf_talloc_assert_no_children() - assert @parent has not child allocations + * @parent: the parent memory ponter to check + * + * Return: None + */ +#define qdf_talloc_assert_no_children(parent) \ + qdf_talloc_assert_no_children_fl(parent, __func__, __LINE__) + +#ifdef WLAN_TALLOC_DEBUG + +/** + * qdf_talloc_feature_init() - initialize the QDF talloc feature + * + * Must be called before allocating memory via a qdf_talloc API. + * + * Return: None + */ +QDF_STATUS qdf_talloc_feature_init(void); + +/** + * qdf_talloc_feature_deinit() - deinitialize the QDF talloc feature + * + * Memory must not be allocated via a qdf_talloc API after this is called. This + * API asserts that the parent/child relationship table is empty in order to + * catch memory leaks. + * + * Return: None + */ +void qdf_talloc_feature_deinit(void); + +void *__qdf_talloc_fl(const void *parent, const size_t size, + const char *func, const uint16_t line); + +void __qdf_tfree_fl(void *ptr, const char *func, const uint16_t line); + +/** + * qdf_talloc_assert_no_children_fl() - assert @parent has not child allocations + * @parent: the parent memory ponter to check + * @func: name of the function requesting the assert + * @line: line number of the call site in @func + * + * Return: None + */ +void qdf_talloc_assert_no_children_fl(const void *parent, + const char *func, const uint16_t line); + +#else /* WLAN_TALLOC_DEBUG */ + +static inline QDF_STATUS qdf_talloc_feature_init(void) +{ + return QDF_STATUS_SUCCESS; +} + +static inline void qdf_talloc_feature_deinit(void) { } + +static inline void *__qdf_talloc_fl(const void *parent, const size_t size, + const char *func, const uint16_t line) +{ + return __zalloc_auto(size); +} + +static inline void +__qdf_tfree_fl(void *ptr, const char *func, const uint16_t line) +{ + __free(ptr); +} + +static inline void +qdf_talloc_assert_no_children_fl(const void *parent, + const char *func, const uint16_t line) { } + +#endif /* WLAN_TALLOC_DEBUG */ + +#endif /* __QDF_TALLOC_H */ diff --git a/qdf/linux/src/i_qdf_talloc.h b/qdf/linux/src/i_qdf_talloc.h new file mode 100644 index 0000000000..7965d93ee2 --- /dev/null +++ b/qdf/linux/src/i_qdf_talloc.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 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. + */ + +/** + * DOC: i_qdf_talloc.h + * + * Linux-specific definitions for use by QDF talloc APIs + */ + +#ifndef __I_QDF_TALLOC_H +#define __I_QDF_TALLOC_H + +#include "asm/page.h" +#include "linux/irqflags.h" +#include "linux/preempt.h" +#include "linux/slab.h" + +#define __can_sleep() \ + (!in_interrupt() && !irqs_disabled() && !in_atomic()) + +#define __zalloc_sleeps(size) kmalloc(size, GFP_KERNEL) +#define __zalloc_atomic(size) kmalloc(size, GFP_ATOMIC) +#define __zalloc_auto(size) \ + kmalloc(size, __can_sleep() ? GFP_KERNEL : GFP_ATOMIC) + +#define __free(ptr) kfree(ptr) + +#define __alloc_size(ptr) ksize(ptr) + +#define __page_size ((size_t)PAGE_SIZE) + +#endif /* __I_QDF_TALLOC_H */ + diff --git a/qdf/linux/src/qdf_mem.c b/qdf/linux/src/qdf_mem.c index 59b6ae5d88..fe1a8b22e0 100644 --- a/qdf/linux/src/qdf_mem.c +++ b/qdf/linux/src/qdf_mem.c @@ -30,6 +30,7 @@ #include #include "qdf_atomic.h" #include "qdf_str.h" +#include "qdf_talloc.h" #include #include #include @@ -272,12 +273,12 @@ static struct __qdf_mem_stat { qdf_atomic_t skb; } qdf_mem_stat; -static inline void qdf_mem_kmalloc_inc(qdf_size_t size) +void qdf_mem_kmalloc_inc(qdf_size_t size) { qdf_atomic_add(size, &qdf_mem_stat.kmalloc); } -static inline void qdf_mem_dma_inc(qdf_size_t size) +static void qdf_mem_dma_inc(qdf_size_t size) { qdf_atomic_add(size, &qdf_mem_stat.dma); } @@ -287,7 +288,7 @@ void qdf_mem_skb_inc(qdf_size_t size) qdf_atomic_add(size, &qdf_mem_stat.skb); } -static inline void qdf_mem_kmalloc_dec(qdf_size_t size) +void qdf_mem_kmalloc_dec(qdf_size_t size) { qdf_atomic_sub(size, &qdf_mem_stat.kmalloc); } @@ -648,12 +649,9 @@ static QDF_STATUS qdf_mem_debugfs_init(void) #else /* WLAN_DEBUGFS */ -static inline void qdf_mem_kmalloc_inc(qdf_size_t size) {} static inline void qdf_mem_dma_inc(qdf_size_t size) {} -static inline void qdf_mem_kmalloc_dec(qdf_size_t size) {} static inline void qdf_mem_dma_dec(qdf_size_t size) {} - static QDF_STATUS qdf_mem_debugfs_init(void) { return QDF_STATUS_E_NOSUPPORT; @@ -1093,6 +1091,8 @@ void qdf_mem_free_debug(void *ptr, const char *file, uint32_t line) if (qdf_unlikely((qdf_size_t)ptr <= sizeof(*header))) panic("Failed to free invalid memory location %pK", ptr); + qdf_talloc_assert_no_children_fl(ptr, file, line); + qdf_spin_lock_irqsave(&qdf_mem_list_lock); header = qdf_mem_get_header(ptr); error_bitmap = qdf_mem_header_validate(header, current_domain); @@ -1776,6 +1776,8 @@ void qdf_mem_free_consistent_debug(qdf_device_t osdev, void *dev, if (qdf_unlikely(!vaddr)) return; + qdf_talloc_assert_no_children_fl(vaddr, file, line); + qdf_spin_lock_irqsave(&qdf_mem_dma_list_lock); /* For DMA buffers we only add trailers, this function will retrieve * the header structure at the tail diff --git a/qdf/linux/src/qdf_module.c b/qdf/linux/src/qdf_module.c index d10c11ef49..92dff158c3 100644 --- a/qdf/linux/src/qdf_module.c +++ b/qdf/linux/src/qdf_module.c @@ -27,6 +27,7 @@ #include #include #include +#include MODULE_AUTHOR("Qualcomm Atheros Inc."); MODULE_DESCRIPTION("Qualcomm Atheros Device Framework Module"); @@ -47,10 +48,12 @@ qdf_mod_init(void) qdf_shared_print_ctrl_init(); qdf_debugfs_init(); qdf_mem_init(); + qdf_talloc_feature_init(); qdf_logging_init(); qdf_perfmod_init(); qdf_nbuf_mod_init(); qdf_event_list_init(); + return 0; } module_init(qdf_mod_init); @@ -67,6 +70,7 @@ qdf_mod_exit(void) qdf_nbuf_mod_exit(); qdf_perfmod_exit(); qdf_logging_exit(); + qdf_talloc_feature_deinit(); qdf_mem_exit(); qdf_debugfs_exit(); qdf_shared_print_ctrl_cleanup(); diff --git a/qdf/src/qdf_talloc.c b/qdf/src/qdf_talloc.c new file mode 100644 index 0000000000..22cd110623 --- /dev/null +++ b/qdf/src/qdf_talloc.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2018 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. + */ + +/** + * DOC: qdf_talloc.c + * + * OS-independent talloc implementation + */ + +#ifdef WLAN_TALLOC_DEBUG + +#include "i_qdf_talloc.h" +#include "qdf_hashtable.h" +#include "qdf_list.h" +#include "qdf_mc_timer.h" +#include "qdf_mem.h" +#include "qdf_module.h" +#include "qdf_status.h" +#include "qdf_str.h" +#include "qdf_talloc.h" + +#define QDF_TALLOC_MAX_BYTES __page_size +#define QDF_TALLOC_SLEEP_TIMEOUT_MS 300 +#define QDF_TALLOC_FUNC_NAME_SIZE 48 +#define QDF_TALLOC_HT_BITS 8 /* 256 buckets */ + +static void +__qdf_talloc_log_nomem(const size_t size, const char *func, const uint16_t line) +{ + qdf_nofl_info("Failed to alloc %zuB; via %s():%d", size, func, line); +} + +static void * +__qdf_zalloc_auto(const size_t size, const char *func, const uint16_t line) +{ + unsigned long start, duration; + void *ptr; + + start = qdf_mc_timer_get_system_time(); + ptr = __zalloc_auto(size); + duration = qdf_mc_timer_get_system_time() - start; + + if (duration > QDF_TALLOC_SLEEP_TIMEOUT_MS) + qdf_nofl_info("Alloc slept; %lums, %zuB; via %s():%d", + duration, size, func, line); + + if (!ptr) { + __qdf_talloc_log_nomem(size, func, line); + return NULL; + } + + qdf_mem_kmalloc_inc(__alloc_size(ptr)); + + return ptr; +} + +static void * +__qdf_zalloc_atomic(const size_t size, const char *func, const uint16_t line) +{ + void *ptr; + + ptr = __zalloc_atomic(size); + if (!ptr) { + __qdf_talloc_log_nomem(size, func, line); + return NULL; + } + + qdf_mem_kmalloc_inc(__alloc_size(ptr)); + + return ptr; +} + +static void __qdf_free(const void *ptr) +{ + qdf_mem_kmalloc_dec(__alloc_size(ptr)); + + __free(ptr); +} + +static qdf_ht_declare(__qdf_talloc_meta_ht, QDF_TALLOC_HT_BITS); +static qdf_spinlock_t __qdf_talloc_meta_lock; + +/** + * struct qdf_talloc_parent_meta - parent/children metadata for memory tracking + * @entry: entry for membership in the parent hashtable + * @children: list of associated children + */ +struct qdf_talloc_parent_meta { + struct qdf_ht_entry entry; + uintptr_t key; + qdf_list_t children; +}; + +static struct qdf_talloc_parent_meta * +qdf_talloc_parent_meta_alloc(const void *parent, + const char *func, const uint16_t line) +{ + struct qdf_talloc_parent_meta *pmeta; + + QDF_BUG(qdf_spin_is_locked(&__qdf_talloc_meta_lock)); + + pmeta = __qdf_zalloc_atomic(sizeof(*pmeta), func, line); + if (!pmeta) + return NULL; + + pmeta->key = (uintptr_t)parent; + qdf_list_create(&pmeta->children, 0); + qdf_ht_add(__qdf_talloc_meta_ht, &pmeta->entry, pmeta->key); + + return pmeta; +} + +static void qdf_talloc_parent_meta_free(struct qdf_talloc_parent_meta *pmeta) +{ + QDF_BUG(qdf_spin_is_locked(&__qdf_talloc_meta_lock)); + + qdf_ht_remove(&pmeta->entry); + qdf_list_destroy(&pmeta->children); + __free(pmeta); +} + +static struct qdf_talloc_parent_meta * +qdf_talloc_parent_meta_lookup(const void *parent) +{ + struct qdf_talloc_parent_meta *pmeta; + uintptr_t key = (uintptr_t)parent; + + QDF_BUG(qdf_spin_is_locked(&__qdf_talloc_meta_lock)); + + qdf_ht_get(__qdf_talloc_meta_ht, pmeta, entry, key, key); + + return pmeta; +} + +/** + * struct qdf_talloc_child_meta - talloc child debug information + * @parent: parent pointer used during allocation for leak tracking + * @node: list node for membership in @parent's children list + * @func: name of the function that requested the allocation + * @line: line number of the call site in @func + * @size: size of the allocation in bytes + */ +struct qdf_talloc_child_meta { + const void *parent; + qdf_list_node_t node; + char func[QDF_TALLOC_FUNC_NAME_SIZE]; + uint16_t line; + uint32_t size; + uint32_t guard; +}; + +/** + * struct qdf_talloc_header - talloc debug header information + * @meta: child allocation metadata + * @guard: a known value, used to detect out-of-bounds access + */ +struct qdf_talloc_header { + struct qdf_talloc_child_meta meta; + uint32_t guard; +}; + +/** + * struct qdf_talloc_trailer - talloc debug trailer information + * @guard: a known value, used to detect out-of-bounds access + */ +struct qdf_talloc_trailer { + uint32_t guard; +}; + +static uint32_t QDF_TALLOC_GUARD = 0xaabbeeff; + +#define QDF_TALLOC_DEBUG_SIZE \ + (sizeof(struct qdf_talloc_header) + sizeof(struct qdf_talloc_trailer)) + +static struct qdf_talloc_header *qdf_talloc_header(void *ptr) +{ + return (struct qdf_talloc_header *)ptr - 1; +} + +static void *qdf_talloc_ptr(struct qdf_talloc_header *header) +{ + return header + 1; +} + +static struct qdf_talloc_trailer * +qdf_talloc_trailer(struct qdf_talloc_header *header) +{ + void *ptr = qdf_talloc_ptr(header); + size_t size = header->meta.size; + + return (struct qdf_talloc_trailer *)((uint8_t *)ptr + size); +} + +static void qdf_talloc_meta_init(struct qdf_talloc_header *header, + const void *parent, const size_t size, + const char *func, const uint16_t line) +{ + struct qdf_talloc_trailer *trailer; + + /* copy the function name to support multi-*.ko configurations */ + qdf_str_lcopy(header->meta.func, func, sizeof(header->meta.func)); + header->meta.parent = parent; + header->meta.line = line; + header->meta.size = size; + header->guard = QDF_TALLOC_GUARD; + + trailer = qdf_talloc_trailer(header); + trailer->guard = QDF_TALLOC_GUARD; +} + +static bool qdf_talloc_meta_assert_valid(struct qdf_talloc_header *header, + const char *func, const uint16_t line) +{ + struct qdf_talloc_trailer *trailer = qdf_talloc_trailer(header); + bool is_valid = true; + + if (header->guard != QDF_TALLOC_GUARD) { + qdf_nofl_alert("Corrupted header guard 0x%x (expected 0x%x)", + header->guard, QDF_TALLOC_GUARD); + is_valid = false; + } + + if (header->meta.size > QDF_TALLOC_MAX_BYTES) { + qdf_nofl_alert("Corrupted allocation size %u (expected <= %zu)", + header->meta.size, QDF_TALLOC_MAX_BYTES); + is_valid = false; + } + + if (!qdf_list_node_in_any_list(&header->meta.node)) { + qdf_nofl_alert("Corrupted header node or double free"); + is_valid = false; + } + + if (trailer->guard != QDF_TALLOC_GUARD) { + qdf_nofl_alert("Corrupted trailer guard 0x%x (expected 0x%x)", + trailer->guard, QDF_TALLOC_GUARD); + is_valid = false; + } + + if (!is_valid) + QDF_DEBUG_PANIC("Fatal memory error detected @ %s():%d", + func, line); + + return is_valid; +} + +static void qdf_leaks_print_header(void) +{ + qdf_nofl_alert("-----------------------------------------------------"); + qdf_nofl_alert(" size function():line"); + qdf_nofl_alert("-----------------------------------------------------"); +} + +static uint32_t qdf_leaks_print(const struct qdf_talloc_parent_meta *pmeta) +{ + struct qdf_talloc_child_meta *cmeta; + uint32_t count = 0; + + qdf_list_for_each(&pmeta->children, cmeta, node) { + qdf_nofl_alert("%6uB @ %s():%u", + cmeta->size, cmeta->func, cmeta->line); + count++; + } + + return count; +} + +#define qdf_leaks_panic(count, func, line) \ + QDF_DEBUG_PANIC("%u fatal memory leaks detected @ %s():%u", \ + count, func, line) + +QDF_STATUS qdf_talloc_feature_init(void) +{ + qdf_spinlock_create(&__qdf_talloc_meta_lock); + qdf_ht_init(__qdf_talloc_meta_ht); + + return QDF_STATUS_SUCCESS; +} +qdf_export_symbol(qdf_talloc_feature_init); + +void qdf_talloc_feature_deinit(void) +{ + qdf_spin_lock_bh(&__qdf_talloc_meta_lock); + + if (!qdf_ht_empty(__qdf_talloc_meta_ht)) { + struct qdf_talloc_parent_meta *pmeta; + uint32_t count = 0; + int i; + + qdf_leaks_print_header(); + + qdf_ht_for_each(__qdf_talloc_meta_ht, i, pmeta, entry) + count += qdf_leaks_print(pmeta); + + qdf_leaks_panic(count, __func__, __LINE__); + } + + qdf_spin_unlock_bh(&__qdf_talloc_meta_lock); + + qdf_ht_deinit(__qdf_talloc_meta_ht); + qdf_spinlock_destroy(&__qdf_talloc_meta_lock); +} +qdf_export_symbol(qdf_talloc_feature_deinit); + +static QDF_STATUS qdf_talloc_meta_insert(struct qdf_talloc_header *header, + const char *func, const uint16_t line) +{ + struct qdf_talloc_child_meta *cmeta = &header->meta; + struct qdf_talloc_parent_meta *pmeta; + + QDF_BUG(qdf_spin_is_locked(&__qdf_talloc_meta_lock)); + + pmeta = qdf_talloc_parent_meta_lookup(cmeta->parent); + if (!pmeta) + pmeta = qdf_talloc_parent_meta_alloc(cmeta->parent, func, line); + if (!pmeta) + return QDF_STATUS_E_NOMEM; + + qdf_list_insert_back(&pmeta->children, &cmeta->node); + + return QDF_STATUS_SUCCESS; +} + +void *__qdf_talloc_fl(const void *parent, const size_t size, + const char *func, const uint16_t line) +{ + QDF_STATUS status; + struct qdf_talloc_header *header; + + QDF_BUG(parent); + if (!parent) + return NULL; + + QDF_BUG(size <= QDF_TALLOC_MAX_BYTES); + if (size > QDF_TALLOC_MAX_BYTES) + return NULL; + + header = __qdf_zalloc_auto(size + QDF_TALLOC_DEBUG_SIZE, func, line); + if (!header) + return NULL; + + qdf_talloc_meta_init(header, parent, size, func, line); + + qdf_spin_lock_bh(&__qdf_talloc_meta_lock); + status = qdf_talloc_meta_insert(header, func, line); + qdf_spin_unlock_bh(&__qdf_talloc_meta_lock); + + if (QDF_IS_STATUS_ERROR(status)) { + __qdf_free(header); + return NULL; + } + + return qdf_talloc_ptr(header); +} +qdf_export_symbol(__qdf_talloc_fl); + +static void +__qdf_talloc_assert_no_children(const void *parent, + const char *func, const uint16_t line) +{ + struct qdf_talloc_parent_meta *pmeta; + uint32_t count; + + QDF_BUG(qdf_spin_is_locked(&__qdf_talloc_meta_lock)); + + pmeta = qdf_talloc_parent_meta_lookup(parent); + if (!pmeta) + return; + + qdf_leaks_print_header(); + count = qdf_leaks_print(pmeta); + qdf_leaks_panic(count, func, line); +} + +static void qdf_talloc_meta_remove(struct qdf_talloc_header *header, + const char *func, const uint16_t line) +{ + struct qdf_talloc_child_meta *cmeta = &header->meta; + struct qdf_talloc_parent_meta *pmeta; + + QDF_BUG(qdf_spin_is_locked(&__qdf_talloc_meta_lock)); + + __qdf_talloc_assert_no_children(qdf_talloc_ptr(header), func, line); + + pmeta = qdf_talloc_parent_meta_lookup(cmeta->parent); + if (!pmeta) { + QDF_DEBUG_PANIC("double-free or free-no-allocate @ %s():%u", + func, line); + return; + } + + qdf_list_remove_node(&pmeta->children, &cmeta->node); + + if (qdf_list_empty(&pmeta->children)) + qdf_talloc_parent_meta_free(pmeta); +} + +void __qdf_tfree_fl(void *ptr, const char *func, const uint16_t line) +{ + struct qdf_talloc_header *header; + + QDF_BUG(ptr); + if (!ptr) + return; + + header = qdf_talloc_header(ptr); + qdf_talloc_meta_assert_valid(header, func, line); + + qdf_spin_lock_bh(&__qdf_talloc_meta_lock); + qdf_talloc_meta_remove(header, func, line); + qdf_spin_unlock_bh(&__qdf_talloc_meta_lock); + + __qdf_free(header); +} +qdf_export_symbol(__qdf_tfree_fl); + +void qdf_talloc_assert_no_children_fl(const void *parent, + const char *func, const uint16_t line) +{ + qdf_spin_lock_bh(&__qdf_talloc_meta_lock); + __qdf_talloc_assert_no_children(parent, func, line); + qdf_spin_unlock_bh(&__qdf_talloc_meta_lock); +} +qdf_export_symbol(qdf_talloc_assert_no_children_fl); + +#endif /* WLAN_TALLOC_DEBUG */ + diff --git a/qdf/test/qdf_talloc_test.c b/qdf/test/qdf_talloc_test.c new file mode 100644 index 0000000000..2ff368fe71 --- /dev/null +++ b/qdf/test/qdf_talloc_test.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 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_status.h" +#include "qdf_talloc.h" +#include "qdf_talloc_test.h" +#include "qdf_trace.h" + +static uint32_t qdf_talloc_test_alloc_free(void) +{ + uint32_t value; + uint32_t *root; + uint32_t *child; + + root = &value; + + child = qdf_talloc_type(root, child); + QDF_BUG(child); + + qdf_tfree(child); + + return 0; +} + +static uint32_t qdf_talloc_test_parent_child(void) +{ + uint32_t value; + uint32_t *root; + uint32_t *parent; + uint32_t *child; + + root = &value; + + parent = qdf_talloc_type(root, parent); + QDF_BUG(parent); + + child = qdf_talloc_type(parent, child); + QDF_BUG(child); + + qdf_tfree(child); + qdf_tfree(parent); + + return 0; +} + +uint32_t qdf_talloc_unit_test(void) +{ + uint32_t errors = 0; + + errors += qdf_talloc_test_alloc_free(); + errors += qdf_talloc_test_parent_child(); + + return errors; +} + diff --git a/qdf/test/qdf_talloc_test.h b/qdf/test/qdf_talloc_test.h new file mode 100644 index 0000000000..16b42c3372 --- /dev/null +++ b/qdf/test/qdf_talloc_test.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 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_TALLOC_TEST +#define __QDF_TALLOC_TEST + +#ifdef WLAN_TALLOC_TEST +/** + * qdf_talloc_unit_test() - run the qdf talloc unit test suite + * + * Return: number of failed test cases + */ +uint32_t qdf_talloc_unit_test(void); +#else +static inline uint32_t qdf_talloc_unit_test(void) +{ + return 0; +} +#endif /* WLAN_TALLOC_TEST */ + +#endif /* __QDF_TALLOC_TEST */ +