summaryrefslogtreecommitdiff
path: root/qdf
diff options
context:
space:
mode:
authorKabilan Kannan <kabilank@codeaurora.org>2017-12-05 13:13:58 -0800
committersnandini <snandini@codeaurora.org>2018-02-09 12:18:40 -0800
commit56800ad9a51776d97f5d50f827f205a1c0f5bd79 (patch)
treef464348cc98267d7c9e971e5f1b1230dd504c2a6 /qdf
parentf01a81b79fa75b101f599c6e478d181753db135e (diff)
downloadqca-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.h58
-rw-r--r--qdf/linux/src/qdf_mem.c307
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