diff options
| author | Dustin Brown <dustinb@codeaurora.org> | 2017-11-03 13:13:30 -0700 |
|---|---|---|
| committer | Dustin Brown <dustinb@codeaurora.org> | 2017-11-06 11:11:37 -0800 |
| commit | 3944d26497665cf0ae9f720d2d8ecf133e91a59c (patch) | |
| tree | 262d763c91614fe9b941bab684960f90e0a87e44 | |
| parent | 2906fc343c1beda4c4aaa49ccc9cdc4a677b0d0d (diff) | |
qcacmn: Add memory debug support for DMA allocations
In an effort to catch and prevent more memory leak and corruption
issues, extend the existing memory debugging support to include DMA
coherent memory allocations.
Change-Id: I25c60a1c5d264e7ea08ed2286fd4b3c3134e9f81
CRs-Fixed: 2138125
| -rw-r--r-- | qdf/inc/qdf_mem.h | 42 | ||||
| -rw-r--r-- | qdf/linux/src/qdf_mem.c | 286 |
2 files changed, 207 insertions, 121 deletions
diff --git a/qdf/inc/qdf_mem.h b/qdf/inc/qdf_mem.h index e05af7634bce..859a37fc36b3 100644 --- a/qdf/inc/qdf_mem.h +++ b/qdf/inc/qdf_mem.h @@ -211,12 +211,44 @@ void qdf_mem_move(void *dst_addr, const void *src_addr, uint32_t num_bytes); void qdf_mem_free_outline(void *buf); -void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, - qdf_dma_addr_t *paddr); +/** + * __qdf_mem_alloc_consistent() - allocates consistent qdf memory + * @osdev: OS device handle + * @dev: Pointer to device handle + * @size: Size to be allocated + * @paddr: Physical address + * @file: file name of the call site + * @line: line numbe rof the call site + * + * Return: pointer of allocated memory or null if memory alloc fails + */ +void *__qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr, + const char *file, uint32_t line); + +#define qdf_mem_alloc_consistent(osdev, dev, size, paddr) \ + __qdf_mem_alloc_consistent(osdev, dev, size, paddr, __FILE__, __LINE__) + +/** + * __qdf_mem_free_consistent() - free consistent qdf memory + * @osdev: OS device handle + * @size: Size to be allocated + * @vaddr: virtual address + * @paddr: Physical address + * @memctx: Pointer to DMA context + * @file: file name of the call site + * @line: line numbe rof the call site + * + * Return: none + */ +void __qdf_mem_free_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, void *vaddr, + qdf_dma_addr_t paddr, qdf_dma_context_t memctx, + const char *file, uint32_t line); -void qdf_mem_free_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, - void *vaddr, qdf_dma_addr_t paddr, - qdf_dma_context_t memctx); +#define qdf_mem_free_consistent(osdev, dev, size, vaddr, paddr, memctx) \ + __qdf_mem_free_consistent(osdev, dev, size, vaddr, paddr, memctx, \ + __FILE__, __LINE__) void qdf_mem_zero_outline(void *buf, qdf_size_t size); diff --git a/qdf/linux/src/qdf_mem.c b/qdf/linux/src/qdf_mem.c index f4d59a9c15de..1604d8af3cd7 100644 --- a/qdf/linux/src/qdf_mem.c +++ b/qdf/linux/src/qdf_mem.c @@ -98,6 +98,9 @@ const uint8_t *qdf_mem_domain_name(enum qdf_mem_domain domain) static qdf_list_t qdf_mem_domains[QDF_MEM_DOMAIN_MAX_COUNT]; static qdf_spinlock_t qdf_mem_list_lock; +static qdf_list_t qdf_mem_dma_domains[QDF_MEM_DOMAIN_MAX_COUNT]; +static qdf_spinlock_t qdf_mem_dma_list_lock; + static uint64_t WLAN_MEM_HEADER = 0x6162636465666768; static uint64_t WLAN_MEM_TRAILER = 0x8081828384858687; @@ -109,11 +112,24 @@ static inline qdf_list_t *qdf_mem_list(enum qdf_mem_domain domain) return &qdf_mem_domains[domain]; } +static inline qdf_list_t *qdf_mem_dma_list(enum qdf_mem_domain domain) +{ + if (domain < QDF_MEM_DOMAIN_INIT || domain >= QDF_MEM_DOMAIN_MAX_COUNT) + return NULL; + + return &qdf_mem_dma_domains[domain]; +} + static inline qdf_list_t *qdf_mem_active_list(void) { return &qdf_mem_domains[qdf_mem_get_domain()]; } +static inline qdf_list_t *qdf_mem_dma_active_list(void) +{ + return &qdf_mem_dma_domains[qdf_mem_get_domain()]; +} + /** * struct qdf_mem_header - memory object to dubug * @node: node to the list @@ -976,78 +992,46 @@ static void qdf_mem_debug_init(void) { int i; - /* Initalizing the list with maximum size of 60000 */ qdf_mem_current_domain = QDF_MEM_DOMAIN_INIT; + + /* mem */ for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) qdf_list_create(&qdf_mem_domains[i], 60000); qdf_spinlock_create(&qdf_mem_list_lock); - qdf_net_buf_debug_init(); -} -#ifdef CONFIG_HALT_KMEMLEAK -/* - * There are two scenarios for handling memory leaks. We want to either: - * 1) Crash and not release memory for offline debugging (internal testing) - * 2) Clean up any leaks and continue (production devices) - */ + /* dma */ + for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) + qdf_list_create(&qdf_mem_dma_domains[i], 0); + qdf_spinlock_create(&qdf_mem_dma_list_lock); -static inline void qdf_mem_leak_panic(void) -{ - QDF_BUG(0); + /* skb */ + qdf_net_buf_debug_init(); } -static inline void qdf_mem_free_leaked_memory(qdf_list_t *domain) { } -#else -static inline void qdf_mem_leak_panic(void) { } - -static void qdf_mem_free_leaked_memory(qdf_list_t *domain) +static uint32_t +qdf_mem_domain_check_for_leaks(enum qdf_mem_domain domain, qdf_list_t *mem_list) { - QDF_STATUS status; - qdf_list_node_t *node; + if (qdf_list_empty(mem_list)) + return 0; - qdf_spin_lock(&qdf_mem_list_lock); - status = qdf_list_remove_front(domain, &node); - while (QDF_IS_STATUS_SUCCESS(status)) { - kfree(node); - status = qdf_list_remove_front(domain, &node); - } - qdf_spin_unlock(&qdf_mem_list_lock); + qdf_err("Memory leaks detected in %s domain!", + qdf_mem_domain_name(domain)); + qdf_mem_domain_print(mem_list, qdf_err_printer, NULL); + + return mem_list->count; } -#endif -/** - * qdf_mem_debug_clean() - display memory leak debug info and free leaked - * pointers - * - * Return: none - */ -static void qdf_mem_debug_clean(void) +static void qdf_mem_domain_set_check_for_leaks(qdf_list_t *domains) { - bool leaks_detected = false; + uint32_t leak_count = 0; int i; /* detect and print leaks */ - for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) { - qdf_list_t *domain = &qdf_mem_domains[i]; - - if (qdf_list_empty(domain)) - continue; - - leaks_detected = true; - - qdf_err("\nMemory leaks detected in the %s (Id %d) domain!\n\n", - qdf_mem_domain_name(i), i); - qdf_mem_domain_print(domain, qdf_err_printer, NULL); - } - - if (leaks_detected) { - /* panic, if enabled */ - qdf_mem_leak_panic(); + for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) + leak_count += qdf_mem_domain_check_for_leaks(i, domains + i); - /* if we didn't crash, release the leaked memory */ - for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) - qdf_mem_free_leaked_memory(&qdf_mem_domains[i]); - } + if (leak_count) + panic("%u fatal memory leaks detected!", leak_count); } /** @@ -1059,13 +1043,20 @@ static void qdf_mem_debug_exit(void) { int i; + /* skb */ qdf_net_buf_debug_exit(); - qdf_mem_debug_clean(); + /* mem */ + qdf_mem_domain_set_check_for_leaks(qdf_mem_domains); for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) qdf_list_destroy(&qdf_mem_domains[i]); - qdf_spinlock_destroy(&qdf_mem_list_lock); + + /* dma */ + qdf_mem_domain_set_check_for_leaks(qdf_mem_dma_domains); + for (i = 0; i < QDF_MEM_DOMAIN_MAX_COUNT; ++i) + qdf_list_destroy(&qdf_mem_dma_domains[i]); + qdf_spinlock_destroy(&qdf_mem_dma_list_lock); } void *qdf_mem_malloc_debug(size_t size, const char *file, uint32_t line) @@ -1093,8 +1084,10 @@ void *qdf_mem_malloc_debug(size_t size, const char *file, uint32_t line) qdf_warn("Malloc slept; %lums, %zuB @ %s:%d", duration, size, file, line); - if (!header) + if (!header) { + qdf_warn("Failed to malloc %zuB @ %s:%d", size, file, line); return NULL; + } qdf_mem_header_init(header, size, file, line); ptr = qdf_mem_get_ptr(header); @@ -1157,14 +1150,14 @@ void qdf_mem_check_for_leaks(void) { enum qdf_mem_domain domain = qdf_mem_current_domain; qdf_list_t *mem_list = &qdf_mem_domains[domain]; + qdf_list_t *dma_list = &qdf_mem_dma_domains[domain]; + uint32_t leaks_count = 0; - if (qdf_list_empty(mem_list)) - return; + leaks_count += qdf_mem_domain_check_for_leaks(domain, mem_list); + leaks_count += qdf_mem_domain_check_for_leaks(domain, dma_list); - qdf_err("Memory leaks detected in %s domain!", - qdf_mem_domain_name(domain)); - qdf_mem_domain_print(mem_list, qdf_err_printer, NULL); - qdf_mem_leak_panic(); + if (leaks_count) + panic("%u fatal memory leaks detected!", leaks_count); } #else static void qdf_mem_debug_init(void) {} @@ -1479,85 +1472,146 @@ void qdf_mem_move(void *dst_addr, const void *src_addr, uint32_t num_bytes) qdf_export_symbol(qdf_mem_move); #if defined(A_SIMOS_DEVHOST) || defined(HIF_SDIO) || defined(HIF_USB) -/** - * qdf_mem_alloc_consistent() - allocates consistent qdf memory - * @osdev: OS device handle - * @dev: Pointer to device handle - * @size: Size to be allocated - * @phy_addr: Physical address - * - * Return: pointer of allocated memory or null if memory alloc fails - */ -void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, - qdf_dma_addr_t *phy_addr) +static inline void *qdf_mem_dma_alloc(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr) { void *vaddr; - vaddr = qdf_mem_malloc(size); - *phy_addr = ((uintptr_t) vaddr); + vaddr = kzmalloc(size, qdf_mem_malloc_flags()); + *paddr = (uintptr_t)vaddr; /* using this type conversion to suppress "cast from pointer to integer * of different size" warning on some platforms */ - BUILD_BUG_ON(sizeof(*phy_addr) < sizeof(vaddr)); - if (vaddr) - qdf_mem_dma_inc(ksize(vaddr)); + BUILD_BUG_ON(sizeof(*paddr) < sizeof(vaddr)); + return vaddr; } - #else -void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, - qdf_dma_addr_t *phy_addr) +static inline void *qdf_mem_dma_alloc(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr) { - void *ptr; + return dma_alloc_coherent(dev, size, paddr, qdf_mem_malloc_flags()); +} +#endif + +#ifdef MEMORY_DEBUG +void *__qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr, + const char *file, uint32_t line) +{ + QDF_STATUS status; + qdf_list_t *mem_list = qdf_mem_dma_active_list(); + struct qdf_mem_header *header; + void *vaddr; + unsigned long start, duration; + + if (!size || size > QDF_MEM_MAX_MALLOC) { + qdf_err("Cannot malloc %zu bytes @ %s:%d", size, file, line); + return NULL; + } + + start = qdf_mc_timer_get_system_time(); + header = qdf_mem_dma_alloc(osdev, dev, size + QDF_MEM_DEBUG_SIZE, + paddr); + duration = qdf_mc_timer_get_system_time() - start; + + if (duration > QDF_MEM_WARN_THRESHOLD) + qdf_warn("Malloc slept; %lums, %zuB @ %s:%d", + duration, size, file, line); - ptr = dma_alloc_coherent(dev, size, phy_addr, qdf_mem_malloc_flags()); - if (!ptr) { - qdf_warn("Warning: unable to alloc consistent memory of size %zu!\n", - size); + if (!header) { + qdf_warn("Failed to malloc %zuB @ %s:%d", size, file, line); return NULL; } + qdf_mem_header_init(header, size, file, line); + vaddr = qdf_mem_get_ptr(header); + *paddr += sizeof(*header); + + qdf_spin_lock_irqsave(&qdf_mem_dma_list_lock); + status = qdf_list_insert_front(mem_list, &header->node); + qdf_spin_unlock_irqrestore(&qdf_mem_dma_list_lock); + if (QDF_IS_STATUS_ERROR(status)) + qdf_err("Failed to insert memory header; status %d", status); + qdf_mem_dma_inc(size); - return ptr; + return vaddr; } +#else +void *__qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr, + const char *file, uint32_t line) +{ + void *vaddr = qdf_mem_dma_alloc(osdev, dev, size, paddr); -#endif -qdf_export_symbol(qdf_mem_alloc_consistent); + if (vaddr) + qdf_mem_dma_inc(size); + + return vaddr; +} +#endif /* MEMORY_DEBUG */ +qdf_export_symbol(__qdf_mem_alloc_consistent); #if defined(A_SIMOS_DEVHOST) || defined(HIF_SDIO) || defined(HIF_USB) -/** - * qdf_mem_free_consistent() - free consistent qdf memory - * @osdev: OS device handle - * @size: Size to be allocated - * @vaddr: virtual address - * @phy_addr: Physical address - * @mctx: Pointer to DMA context - * - * Return: none - */ -inline void qdf_mem_free_consistent(qdf_device_t osdev, void *dev, - qdf_size_t size, void *vaddr, - qdf_dma_addr_t phy_addr, - qdf_dma_context_t memctx) +inline void +qdf_mem_dma_free(void *dev, qdf_size_t size, void *vaddr, qdf_dma_addr_t paddr) { - qdf_mem_dma_dec(ksize(vaddr)); - qdf_mem_free(vaddr); - return; + kfree(vaddr); } #else -inline void qdf_mem_free_consistent(qdf_device_t osdev, void *dev, - qdf_size_t size, void *vaddr, - qdf_dma_addr_t phy_addr, - qdf_dma_context_t memctx) +inline void +qdf_mem_dma_free(void *dev, qdf_size_t size, void *vaddr, qdf_dma_addr_t paddr) { - dma_free_coherent(dev, size, vaddr, phy_addr); - qdf_mem_dma_dec(size); + dma_free_coherent(dev, size, vaddr, paddr); } - #endif -qdf_export_symbol(qdf_mem_free_consistent); + +#ifdef MEMORY_DEBUG +void __qdf_mem_free_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, void *vaddr, + qdf_dma_addr_t paddr, qdf_dma_context_t memctx, + const char *file, uint32_t line) +{ + enum qdf_mem_domain domain = qdf_mem_get_domain(); + struct qdf_mem_header *header; + enum qdf_mem_validation_bitmap error_bitmap; + + /* freeing a null pointer is valid */ + if (qdf_unlikely(!vaddr)) + return; + + if (qdf_unlikely((qdf_size_t)vaddr <= sizeof(*header))) + panic("Failed to free invalid memory location %pK", vaddr); + + qdf_spin_lock_irqsave(&qdf_mem_dma_list_lock); + header = qdf_mem_get_header(vaddr); + error_bitmap = qdf_mem_header_validate(header, domain); + if (!error_bitmap) { + header->freed = true; + list_del_init(&header->node); + qdf_mem_dma_list(header->domain)->count--; + } + qdf_spin_unlock_irqrestore(&qdf_mem_dma_list_lock); + + qdf_mem_header_assert_valid(header, domain, error_bitmap, file, line); + + qdf_mem_dma_dec(header->size); + qdf_mem_dma_free(dev, size + QDF_MEM_DEBUG_SIZE, header, + paddr - sizeof(*header)); +} +#else +void __qdf_mem_free_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, void *vaddr, + qdf_dma_addr_t paddr, qdf_dma_context_t memctx, + const char *file, uint32_t line) +{ + qdf_mem_dma_dec(size); + qdf_mem_dma_free(dev, size, vaddr, paddr); +} +#endif /* MEMORY_DEBUG */ +qdf_export_symbol(__qdf_mem_free_consistent); /** * qdf_mem_dma_sync_single_for_device() - assign memory to device |
