diff options
author | Kabilan Kannan <kabilank@codeaurora.org> | 2017-12-05 13:13:58 -0800 |
---|---|---|
committer | snandini <snandini@codeaurora.org> | 2018-02-09 12:18:40 -0800 |
commit | 56800ad9a51776d97f5d50f827f205a1c0f5bd79 (patch) | |
tree | f464348cc98267d7c9e971e5f1b1230dd504c2a6 /qdf | |
parent | f01a81b79fa75b101f599c6e478d181753db135e (diff) | |
download | qca-wfi-host-cmn-56800ad9a51776d97f5d50f827f205a1c0f5bd79.tar.gz |
qcacmn: Add debug support for DMA memory allocations
Add debug support to detect memory leaks and corruption
in DMA memory operations.
Change-Id: I1478973828ddc147367c4a579c97c3840c106f44
CRs-Fixed: 2155603
Diffstat (limited to 'qdf')
-rw-r--r-- | qdf/inc/qdf_mem.h | 58 | ||||
-rw-r--r-- | qdf/linux/src/qdf_mem.c | 307 |
2 files changed, 247 insertions, 118 deletions
diff --git a/qdf/inc/qdf_mem.h b/qdf/inc/qdf_mem.h index 04b9bd344..c0cbfeb60 100644 --- a/qdf/inc/qdf_mem.h +++ b/qdf/inc/qdf_mem.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2018 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -158,6 +158,47 @@ void qdf_mem_free_debug(void *ptr, const char *file, uint32_t line); * Return: None */ void qdf_mem_check_for_leaks(void); + +/** + * qdf_mem_alloc_consistent_debug() - 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_debug(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_debug(osdev, dev, size, paddr, \ + __FILE__, __LINE__) + +/** + * qdf_mem_free_consistent_debug() - 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_debug(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); + +#define qdf_mem_free_consistent(osdev, dev, size, vaddr, paddr, memctx) \ + qdf_mem_free_consistent_debug(osdev, dev, size, vaddr, paddr, memctx, \ + __FILE__, __LINE__) #else void *qdf_mem_malloc(qdf_size_t size); @@ -170,6 +211,14 @@ void *qdf_mem_malloc(qdf_size_t size); void qdf_mem_free(void *ptr); static inline void qdf_mem_check_for_leaks(void) { } + +void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr); + +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); + #endif /* MEMORY_DEBUG */ void *qdf_mem_alloc_outline(qdf_device_t osdev, qdf_size_t size); @@ -184,13 +233,6 @@ 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); - -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); - void qdf_mem_zero_outline(void *buf, qdf_size_t size); void qdf_ether_addr_copy(void *dst_addr, const void *src_addr); diff --git a/qdf/linux/src/qdf_mem.c b/qdf/linux/src/qdf_mem.c index a5185fedc..ac5b1f567 100644 --- a/qdf/linux/src/qdf_mem.c +++ b/qdf/linux/src/qdf_mem.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017 The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2018 The Linux Foundation. All rights reserved. * * Previously licensed under the ISC license by Qualcomm Atheros, Inc. * @@ -67,11 +67,19 @@ static qdf_list_t qdf_mem_domains[QDF_DEBUG_DOMAIN_COUNT]; static qdf_spinlock_t qdf_mem_list_lock; +static qdf_list_t qdf_mem_dma_domains[QDF_DEBUG_DOMAIN_COUNT]; +static qdf_spinlock_t qdf_mem_dma_list_lock; + static inline qdf_list_t *qdf_mem_list_get(enum qdf_debug_domain domain) { return &qdf_mem_domains[domain]; } +static inline qdf_list_t *qdf_mem_dma_list(enum qdf_debug_domain domain) +{ + return &qdf_mem_dma_domains[domain]; +} + /** * struct qdf_mem_header - memory object to dubug * @node: node to the list @@ -101,6 +109,12 @@ static inline struct qdf_mem_header *qdf_mem_get_header(void *ptr) return (struct qdf_mem_header *)ptr - 1; } +static inline struct qdf_mem_header *qdf_mem_dma_get_header(void *ptr, + qdf_size_t size) +{ + return (struct qdf_mem_header *) ((uint8_t *) ptr + size); +} + static inline uint64_t *qdf_mem_get_trailer(struct qdf_mem_header *header) { return (uint64_t *)((void *)(header + 1) + header->size); @@ -115,6 +129,18 @@ static inline void *qdf_mem_get_ptr(struct qdf_mem_header *header) #define QDF_MEM_DEBUG_SIZE \ (sizeof(struct qdf_mem_header) + sizeof(WLAN_MEM_TRAILER)) +/* number of bytes needed for the qdf dma memory debug information */ +#define QDF_DMA_MEM_DEBUG_SIZE \ + (sizeof(struct qdf_mem_header)) + +static void qdf_mem_trailer_init(struct qdf_mem_header *header) +{ + QDF_BUG(header); + if (!header) + return; + *qdf_mem_get_trailer(header) = WLAN_MEM_TRAILER; +} + static void qdf_mem_header_init(struct qdf_mem_header *header, qdf_size_t size, const char *file, uint32_t line) { @@ -128,7 +154,6 @@ static void qdf_mem_header_init(struct qdf_mem_header *header, qdf_size_t size, header->line = line; header->size = size; header->header = WLAN_MEM_HEADER; - *qdf_mem_get_trailer(header) = WLAN_MEM_TRAILER; } enum qdf_mem_validation_bitmap { @@ -169,6 +194,16 @@ static bool qdf_mem_validate_list_node(qdf_list_node_t *qdf_node) } static enum qdf_mem_validation_bitmap +qdf_mem_trailer_validate(struct qdf_mem_header *header) +{ + enum qdf_mem_validation_bitmap error_bitmap = 0; + + if (*qdf_mem_get_trailer(header) != WLAN_MEM_TRAILER) + error_bitmap |= QDF_MEM_BAD_TRAILER; + return error_bitmap; +} + +static enum qdf_mem_validation_bitmap qdf_mem_header_validate(struct qdf_mem_header *header, enum qdf_debug_domain domain) { @@ -179,8 +214,6 @@ qdf_mem_header_validate(struct qdf_mem_header *header, if (header->size > QDF_MEM_MAX_MALLOC) error_bitmap |= QDF_MEM_BAD_SIZE; - else if (*qdf_mem_get_trailer(header) != WLAN_MEM_TRAILER) - error_bitmap |= QDF_MEM_BAD_TRAILER; if (header->freed == true) error_bitmap |= QDF_MEM_DOUBLE_FREE; @@ -939,74 +972,41 @@ static void qdf_mem_debug_init(void) for (i = 0; i < QDF_DEBUG_DOMAIN_COUNT; ++i) qdf_list_create(&qdf_mem_domains[i], 60000); qdf_spinlock_create(&qdf_mem_list_lock); - qdf_net_buf_debug_init(); - return; -} -#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_DEBUG_DOMAIN_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_debug_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_debug_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_DEBUG_DOMAIN_COUNT; ++i) { - qdf_list_t *domain = qdf_mem_list_get(i); - - if (qdf_list_empty(domain)) - continue; - - leaks_detected = true; - - qdf_err("\nMemory leaks detected in the %s (Id %d) domain!\n\n", - qdf_debug_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_DEBUG_DOMAIN_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_DEBUG_DOMAIN_COUNT; ++i) - qdf_mem_free_leaked_memory(qdf_mem_list_get(i)); - } + if (leak_count) + panic("%u fatal memory leaks detected!", leak_count); } /** @@ -1018,13 +1018,21 @@ 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_DEBUG_DOMAIN_COUNT; ++i) qdf_list_destroy(qdf_mem_list_get(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_DEBUG_DOMAIN_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) @@ -1053,10 +1061,13 @@ 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); + qdf_mem_trailer_init(header); ptr = qdf_mem_get_ptr(header); qdf_spin_lock_irqsave(&qdf_mem_list_lock); @@ -1090,6 +1101,8 @@ void qdf_mem_free_debug(void *ptr, const char *file, uint32_t line) qdf_spin_lock_irqsave(&qdf_mem_list_lock); header = qdf_mem_get_header(ptr); error_bitmap = qdf_mem_header_validate(header, current_domain); + error_bitmap |= qdf_mem_trailer_validate(header); + if (!error_bitmap) { header->freed = true; list_del_init(&header->node); @@ -1109,13 +1122,14 @@ void qdf_mem_check_for_leaks(void) { enum qdf_debug_domain current_domain = qdf_debug_domain_get(); qdf_list_t *mem_list = qdf_mem_list_get(current_domain); + qdf_list_t *dma_list = qdf_mem_dma_list(current_domain); + uint32_t leaks_count = 0; - if (!qdf_list_empty(mem_list)) { - qdf_err("Memory leaks detected in %s domain!", - qdf_debug_domain_name(current_domain)); - qdf_mem_domain_print(mem_list, qdf_err_printer, NULL); - qdf_mem_leak_panic(); - } + leaks_count += qdf_mem_domain_check_for_leaks(current_domain, mem_list); + leaks_count += qdf_mem_domain_check_for_leaks(current_domain, dma_list); + + if (leaks_count) + panic("%u fatal memory leaks detected!", leaks_count); } #else @@ -1494,7 +1508,7 @@ EXPORT_SYMBOL(qdf_mem_move); #if defined(A_SIMOS_DEVHOST) || defined(HIF_SDIO) || defined(HIF_USB) /** - * qdf_mem_alloc_consistent() - allocates consistent qdf memory + * qdf_mem_dma_alloc() - allocates memory for dma * @osdev: OS device handle * @dev: Pointer to device handle * @size: Size to be allocated @@ -1502,8 +1516,9 @@ EXPORT_SYMBOL(qdf_mem_move); * * 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 *phy_addr) { void *vaddr; @@ -1513,16 +1528,14 @@ void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, * of different size" warning on some platforms */ BUILD_BUG_ON(sizeof(*phy_addr) < sizeof(vaddr)); - if (vaddr) - qdf_mem_dma_inc(ksize(vaddr)); return vaddr; } #elif defined(QCA_WIFI_QCA8074) && defined(BUILD_X86) #define QCA8074_RAM_BASE 0x50000000 #define QDF_MEM_ALLOC_X86_MAX_RETRIES 10 -void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, - qdf_dma_addr_t *phy_addr) +void *qdf_mem_dma_alloc(qdf_device_t osdev, void *dev, qdf_size_t size, + qdf_dma_addr_t *phy_addr) { void *vaddr = NULL; int i; @@ -1546,60 +1559,134 @@ void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size, return NULL; } + #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 - 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 defined(A_SIMOS_DEVHOST) || defined(HIF_SDIO) || defined(HIF_USB) +inline void +qdf_mem_dma_free(void *dev, qdf_size_t size, void *vaddr, qdf_dma_addr_t paddr) +{ + qdf_mem_free(vaddr); +} +#else + +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, paddr); +} +#endif + +#ifdef MEMORY_DEBUG +void *qdf_mem_alloc_consistent_debug(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr, + const char *file, uint32_t line) +{ + QDF_STATUS status; + enum qdf_debug_domain current_domain = qdf_debug_domain_get(); + qdf_list_t *mem_list = qdf_mem_dma_list(current_domain); + struct qdf_mem_header *header; + void *vaddr; + + if (!size || size > QDF_MEM_MAX_MALLOC) { + qdf_err("Cannot malloc %zu bytes @ %s:%d", size, file, line); + return NULL; + } + + vaddr = qdf_mem_dma_alloc(osdev, dev, size + QDF_DMA_MEM_DEBUG_SIZE, + paddr); + + if (!vaddr) { + qdf_warn("Failed to malloc %zuB @ %s:%d", size, file, line); return NULL; } + header = qdf_mem_dma_get_header(vaddr, size); + /* For DMA buffers we only add trailers, this function will init + * the header structure at the tail + * Prefix the header into DMA buffer causes SMMU faults, so + * do not prefix header into the DMA buffers + */ + qdf_mem_header_init(header, size, file, line); + + 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; } +qdf_export_symbol(qdf_mem_alloc_consistent_debug); -#endif -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) +void qdf_mem_free_consistent_debug(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(ksize(vaddr)); - qdf_mem_free(vaddr); - return; + enum qdf_debug_domain domain = qdf_debug_domain_get(); + struct qdf_mem_header *header; + enum qdf_mem_validation_bitmap error_bitmap; + + /* freeing a null pointer is valid */ + if (qdf_unlikely(!vaddr)) + return; + + 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 + * Prefix the header into DMA buffer causes SMMU faults, so + * do not prefix header into the DMA buffers + */ + header = qdf_mem_dma_get_header(vaddr, size); + 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_DMA_MEM_DEBUG_SIZE, vaddr, paddr); } +qdf_export_symbol(qdf_mem_free_consistent_debug); #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) + +void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, + qdf_size_t size, qdf_dma_addr_t *paddr) +{ + void *vaddr = qdf_mem_dma_alloc(osdev, dev, size, paddr); + + if (vaddr) + qdf_mem_dma_inc(size); + + return vaddr; +} +qdf_export_symbol(qdf_mem_alloc_consistent); + +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) { - dma_free_coherent(dev, size, vaddr, phy_addr); qdf_mem_dma_dec(size); + qdf_mem_dma_free(dev, size, vaddr, paddr); } +qdf_export_symbol(qdf_mem_free_consistent); -#endif -EXPORT_SYMBOL(qdf_mem_free_consistent); +#endif /* MEMORY_DEBUG */ /** * qdf_mem_dma_sync_single_for_device() - assign memory to device |