qcacmn: Extract memory debugging features from qdf_mem
When memory debugging is enabled, qdf_mem_malloc and qdf_mem_free do a number of memory corruption checks. Extract these checks into their own functions to allow other memory allocation/free logic to leverage the same logic. Change-Id: I07802093119c90b3d8c40a50d5b4bb152cb8243f CRs-Fixed: 2136659
This commit is contained in:
@@ -96,9 +96,36 @@ void qdf_mem_init(void);
|
|||||||
void qdf_mem_exit(void);
|
void qdf_mem_exit(void);
|
||||||
|
|
||||||
#ifdef MEMORY_DEBUG
|
#ifdef MEMORY_DEBUG
|
||||||
|
/**
|
||||||
|
* qdf_mem_malloc_debug() - debug version of QDF memory allocation API
|
||||||
|
* @size: Number of bytes of memory to allocate.
|
||||||
|
* @file: File name of the call site
|
||||||
|
* @line: Line number of the call site
|
||||||
|
*
|
||||||
|
* This function will dynamicallly allocate the specified number of bytes of
|
||||||
|
* memory and add it to the qdf tracking list to check for memory leaks and
|
||||||
|
* corruptions
|
||||||
|
*
|
||||||
|
* Return: A valid memory location on success, or NULL on failure
|
||||||
|
*/
|
||||||
|
void *qdf_mem_malloc_debug(size_t size, const char *file, uint32_t line);
|
||||||
|
|
||||||
#define qdf_mem_malloc(size) \
|
#define qdf_mem_malloc(size) \
|
||||||
qdf_mem_malloc_debug(size, __FILE__, __LINE__)
|
qdf_mem_malloc_debug(size, __FILE__, __LINE__)
|
||||||
void *qdf_mem_malloc_debug(size_t size, char *file_name, uint32_t line_num);
|
|
||||||
|
/**
|
||||||
|
* qdf_mem_free_debug() - debug version of qdf_mem_free
|
||||||
|
* @ptr: Pointer to the starting address of the memory to be freed.
|
||||||
|
*
|
||||||
|
* This function will free the memory pointed to by 'ptr'. It also checks for
|
||||||
|
* memory corruption, underrun, overrun, double free, domain mismatch, etc.
|
||||||
|
*
|
||||||
|
* Return: none
|
||||||
|
*/
|
||||||
|
void qdf_mem_free_debug(void *ptr, const char *file, uint32_t line);
|
||||||
|
|
||||||
|
#define qdf_mem_free(ptr) \
|
||||||
|
qdf_mem_free_debug(ptr, __FILE__, __LINE__)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* qdf_mem_check_for_leaks() - Assert that the current memory domain is empty
|
* qdf_mem_check_for_leaks() - Assert that the current memory domain is empty
|
||||||
@@ -133,20 +160,20 @@ void *qdf_mem_malloc_debug(size_t size, char *file_name, uint32_t line_num);
|
|||||||
void qdf_mem_check_for_leaks(void);
|
void qdf_mem_check_for_leaks(void);
|
||||||
#else
|
#else
|
||||||
void *qdf_mem_malloc(qdf_size_t size);
|
void *qdf_mem_malloc(qdf_size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* qdf_mem_free() - free QDF memory
|
||||||
|
* @ptr: Pointer to the starting address of the memory to be freed.
|
||||||
|
*
|
||||||
|
* Return: None
|
||||||
|
*/
|
||||||
|
void qdf_mem_free(void *ptr);
|
||||||
|
|
||||||
static inline void qdf_mem_check_for_leaks(void) { }
|
static inline void qdf_mem_check_for_leaks(void) { }
|
||||||
#endif /* MEMORY_DEBUG */
|
#endif /* MEMORY_DEBUG */
|
||||||
|
|
||||||
void *qdf_mem_alloc_outline(qdf_device_t osdev, qdf_size_t size);
|
void *qdf_mem_alloc_outline(qdf_device_t osdev, qdf_size_t size);
|
||||||
|
|
||||||
/**
|
|
||||||
* qdf_mem_free() - free QDF memory
|
|
||||||
* @ptr: Pointer to the starting address of the memory to be free'd.
|
|
||||||
* This function will free the memory pointed to by 'ptr'.
|
|
||||||
* Return:
|
|
||||||
* None
|
|
||||||
*/
|
|
||||||
void qdf_mem_free(void *ptr);
|
|
||||||
|
|
||||||
void qdf_mem_set(void *ptr, uint32_t num_bytes, uint32_t value);
|
void qdf_mem_set(void *ptr, uint32_t num_bytes, uint32_t value);
|
||||||
|
|
||||||
void qdf_mem_zero(void *ptr, uint32_t num_bytes);
|
void qdf_mem_zero(void *ptr, uint32_t num_bytes);
|
||||||
|
@@ -60,6 +60,10 @@
|
|||||||
#include "qdf_debug_domain.h"
|
#include "qdf_debug_domain.h"
|
||||||
#include <qdf_list.h>
|
#include <qdf_list.h>
|
||||||
|
|
||||||
|
/* Preprocessor Definitions and Constants */
|
||||||
|
#define QDF_MEM_MAX_MALLOC (1024 * 1024) /* 1MiB */
|
||||||
|
#define QDF_MEM_WARN_THRESHOLD 300 /* ms */
|
||||||
|
|
||||||
static qdf_list_t qdf_mem_domains[QDF_DEBUG_DOMAIN_COUNT];
|
static qdf_list_t qdf_mem_domains[QDF_DEBUG_DOMAIN_COUNT];
|
||||||
static qdf_spinlock_t qdf_mem_list_lock;
|
static qdf_spinlock_t qdf_mem_list_lock;
|
||||||
|
|
||||||
@@ -69,29 +73,175 @@ static inline qdf_list_t *qdf_mem_list_get(enum qdf_debug_domain domain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct s_qdf_mem_struct - memory object to dubug
|
* struct qdf_mem_header - memory object to dubug
|
||||||
* @node: node to the list
|
* @node: node to the list
|
||||||
* @filename: name of file
|
* @domain: the active memory domain at time of allocation
|
||||||
* @line_num: line number
|
* @freed: flag set during free, used to detect double frees
|
||||||
* @size: size of the file
|
* Use uint8_t so we can detect corruption
|
||||||
* @header: array that contains header
|
* @file: name of the file the allocation was made from
|
||||||
* @in_use: memory usage count
|
* @line: line number of the file the allocation was made from
|
||||||
|
* @size: size of the allocation in bytes
|
||||||
|
* @header: a known value, used to detect out-of-bounds access
|
||||||
*/
|
*/
|
||||||
struct s_qdf_mem_struct {
|
struct qdf_mem_header {
|
||||||
qdf_list_node_t node;
|
qdf_list_node_t node;
|
||||||
char *file_name;
|
enum qdf_debug_domain domain;
|
||||||
uint32_t line_num;
|
uint8_t freed;
|
||||||
|
const char *file;
|
||||||
|
uint32_t line;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint64_t header;
|
uint64_t header;
|
||||||
enum qdf_debug_domain domain;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint64_t WLAN_MEM_HEADER = 0x6162636465666768;
|
static uint64_t WLAN_MEM_HEADER = 0x6162636465666768;
|
||||||
static uint64_t WLAN_MEM_TAIL = 0x8081828384858687;
|
static uint64_t WLAN_MEM_TRAILER = 0x8081828384858687;
|
||||||
#endif /* MEMORY_DEBUG */
|
|
||||||
|
|
||||||
/* Preprocessor Definitions and Constants */
|
static inline struct qdf_mem_header *qdf_mem_get_header(void *ptr)
|
||||||
#define QDF_GET_MEMORY_TIME_THRESHOLD 300
|
{
|
||||||
|
return (struct qdf_mem_header *)ptr - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t *qdf_mem_get_trailer(struct qdf_mem_header *header)
|
||||||
|
{
|
||||||
|
return (uint64_t *)((void *)(header + 1) + header->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *qdf_mem_get_ptr(struct qdf_mem_header *header)
|
||||||
|
{
|
||||||
|
return (void *)(header + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* number of bytes needed for the qdf memory debug information */
|
||||||
|
#define QDF_MEM_DEBUG_SIZE \
|
||||||
|
(sizeof(struct qdf_mem_header) + sizeof(WLAN_MEM_TRAILER))
|
||||||
|
|
||||||
|
static void qdf_mem_header_init(struct qdf_mem_header *header, qdf_size_t size,
|
||||||
|
const char *file, uint32_t line)
|
||||||
|
{
|
||||||
|
QDF_BUG(header);
|
||||||
|
if (!header)
|
||||||
|
return;
|
||||||
|
|
||||||
|
header->domain = qdf_debug_domain_get();
|
||||||
|
header->freed = false;
|
||||||
|
header->file = file;
|
||||||
|
header->line = line;
|
||||||
|
header->size = size;
|
||||||
|
header->header = WLAN_MEM_HEADER;
|
||||||
|
*qdf_mem_get_trailer(header) = WLAN_MEM_TRAILER;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum qdf_mem_validation_bitmap {
|
||||||
|
QDF_MEM_BAD_HEADER = 1 << 0,
|
||||||
|
QDF_MEM_BAD_TRAILER = 1 << 1,
|
||||||
|
QDF_MEM_BAD_SIZE = 1 << 2,
|
||||||
|
QDF_MEM_DOUBLE_FREE = 1 << 3,
|
||||||
|
QDF_MEM_BAD_FREED = 1 << 4,
|
||||||
|
QDF_MEM_BAD_NODE = 1 << 5,
|
||||||
|
QDF_MEM_BAD_DOMAIN = 1 << 6,
|
||||||
|
QDF_MEM_WRONG_DOMAIN = 1 << 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* qdf_mem_validate_list_node() - validate that the node is in a list
|
||||||
|
* @qdf_node: node to check for being in a list
|
||||||
|
*
|
||||||
|
* Return: true if the node validly linked in an anchored doubly linked list
|
||||||
|
*/
|
||||||
|
static bool qdf_mem_validate_list_node(qdf_list_node_t *qdf_node)
|
||||||
|
{
|
||||||
|
struct list_head *node = qdf_node;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if the node is an empty list, it is not tied to an anchor node
|
||||||
|
* and must have been removed with list_del_init
|
||||||
|
*/
|
||||||
|
if (list_empty(node))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!node->prev || !node->next)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (node->prev->next != node || node->next->prev != node)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum qdf_mem_validation_bitmap
|
||||||
|
qdf_mem_header_validate(struct qdf_mem_header *header,
|
||||||
|
enum qdf_debug_domain domain)
|
||||||
|
{
|
||||||
|
enum qdf_mem_validation_bitmap error_bitmap = 0;
|
||||||
|
|
||||||
|
if (header->header != WLAN_MEM_HEADER)
|
||||||
|
error_bitmap |= QDF_MEM_BAD_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;
|
||||||
|
else if (header->freed)
|
||||||
|
error_bitmap |= QDF_MEM_BAD_FREED;
|
||||||
|
|
||||||
|
if (!qdf_mem_validate_list_node(&header->node))
|
||||||
|
error_bitmap |= QDF_MEM_BAD_NODE;
|
||||||
|
|
||||||
|
if (header->domain < QDF_DEBUG_DOMAIN_INIT ||
|
||||||
|
header->domain >= QDF_DEBUG_DOMAIN_COUNT)
|
||||||
|
error_bitmap |= QDF_MEM_BAD_DOMAIN;
|
||||||
|
else if (header->domain != domain)
|
||||||
|
error_bitmap |= QDF_MEM_WRONG_DOMAIN;
|
||||||
|
|
||||||
|
return error_bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
qdf_mem_header_assert_valid(struct qdf_mem_header *header,
|
||||||
|
enum qdf_debug_domain current_domain,
|
||||||
|
enum qdf_mem_validation_bitmap error_bitmap,
|
||||||
|
const char *file,
|
||||||
|
uint32_t line)
|
||||||
|
{
|
||||||
|
if (!error_bitmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_BAD_HEADER)
|
||||||
|
qdf_err("Corrupted memory header 0x%llx (expected 0x%llx)",
|
||||||
|
header->header, WLAN_MEM_HEADER);
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_BAD_SIZE)
|
||||||
|
qdf_err("Corrupted memory size %u (expected < %d)",
|
||||||
|
header->size, QDF_MEM_MAX_MALLOC);
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_BAD_TRAILER)
|
||||||
|
qdf_err("Corrupted memory trailer 0x%llx (expected 0x%llx)",
|
||||||
|
*qdf_mem_get_trailer(header), WLAN_MEM_TRAILER);
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_DOUBLE_FREE)
|
||||||
|
qdf_err("Memory has previously been freed");
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_BAD_FREED)
|
||||||
|
qdf_err("Corrupted memory freed flag 0x%x", header->freed);
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_BAD_NODE)
|
||||||
|
qdf_err("Corrupted memory header node or double free");
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_BAD_DOMAIN)
|
||||||
|
qdf_err("Corrupted memory domain 0x%x", header->domain);
|
||||||
|
|
||||||
|
if (error_bitmap & QDF_MEM_WRONG_DOMAIN)
|
||||||
|
qdf_err("Memory domain mismatch; found %s(%d), expected %s(%d)",
|
||||||
|
qdf_debug_domain_name(header->domain), header->domain,
|
||||||
|
qdf_debug_domain_name(current_domain), current_domain);
|
||||||
|
|
||||||
|
panic("A fatal memory error was detected @ %s:%d",
|
||||||
|
kbasename(file), line);
|
||||||
|
}
|
||||||
|
#endif /* MEMORY_DEBUG */
|
||||||
|
|
||||||
u_int8_t prealloc_disabled = 1;
|
u_int8_t prealloc_disabled = 1;
|
||||||
qdf_declare_param(prealloc_disabled, byte);
|
qdf_declare_param(prealloc_disabled, byte);
|
||||||
@@ -171,15 +321,15 @@ static int seq_printf_printer(void *priv, const char *fmt, ...)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* struct __qdf_mem_info - memory statistics
|
* struct __qdf_mem_info - memory statistics
|
||||||
* @file_name: the file which allocated memory
|
* @file: the file which allocated memory
|
||||||
* @line_num: the line at which allocation happened
|
* @line: the line at which allocation happened
|
||||||
* @size: the size of allocation
|
* @size: the size of allocation
|
||||||
* @count: how many allocations of same type
|
* @count: how many allocations of same type
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
struct __qdf_mem_info {
|
struct __qdf_mem_info {
|
||||||
char *file_name;
|
const char *file;
|
||||||
uint32_t line_num;
|
uint32_t line;
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
};
|
};
|
||||||
@@ -230,8 +380,8 @@ static void qdf_mem_meta_table_print(struct __qdf_mem_info *table,
|
|||||||
table[i].count,
|
table[i].count,
|
||||||
table[i].size,
|
table[i].size,
|
||||||
table[i].count * table[i].size,
|
table[i].count * table[i].size,
|
||||||
kbasename(table[i].file_name),
|
kbasename(table[i].file),
|
||||||
table[i].line_num);
|
table[i].line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,21 +393,21 @@ static void qdf_mem_meta_table_print(struct __qdf_mem_info *table,
|
|||||||
* Return: true if the table is full after inserting, false otherwise
|
* Return: true if the table is full after inserting, false otherwise
|
||||||
*/
|
*/
|
||||||
static bool qdf_mem_meta_table_insert(struct __qdf_mem_info *table,
|
static bool qdf_mem_meta_table_insert(struct __qdf_mem_info *table,
|
||||||
struct s_qdf_mem_struct *meta)
|
struct qdf_mem_header *meta)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < QDF_MEM_STAT_TABLE_SIZE; i++) {
|
for (i = 0; i < QDF_MEM_STAT_TABLE_SIZE; i++) {
|
||||||
if (!table[i].count) {
|
if (!table[i].count) {
|
||||||
table[i].file_name = meta->file_name;
|
table[i].file = meta->file;
|
||||||
table[i].line_num = meta->line_num;
|
table[i].line = meta->line;
|
||||||
table[i].size = meta->size;
|
table[i].size = meta->size;
|
||||||
table[i].count = 1;
|
table[i].count = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table[i].file_name == meta->file_name &&
|
if (table[i].file == meta->file &&
|
||||||
table[i].line_num == meta->line_num &&
|
table[i].line == meta->line &&
|
||||||
table[i].size == meta->size) {
|
table[i].size == meta->size) {
|
||||||
table[i].count++;
|
table[i].count++;
|
||||||
break;
|
break;
|
||||||
@@ -291,7 +441,7 @@ static void qdf_mem_domain_print(qdf_list_t *domain,
|
|||||||
qdf_spin_lock(&qdf_mem_list_lock);
|
qdf_spin_lock(&qdf_mem_list_lock);
|
||||||
status = qdf_list_peek_front(domain, &node);
|
status = qdf_list_peek_front(domain, &node);
|
||||||
while (QDF_IS_STATUS_SUCCESS(status)) {
|
while (QDF_IS_STATUS_SUCCESS(status)) {
|
||||||
struct s_qdf_mem_struct *meta = (struct s_qdf_mem_struct *)node;
|
struct qdf_mem_header *meta = (struct qdf_mem_header *)node;
|
||||||
bool is_full = qdf_mem_meta_table_insert(table, meta);
|
bool is_full = qdf_mem_meta_table_insert(table, meta);
|
||||||
|
|
||||||
qdf_spin_unlock(&qdf_mem_list_lock);
|
qdf_spin_unlock(&qdf_mem_list_lock);
|
||||||
@@ -735,16 +885,18 @@ EXPORT_SYMBOL(qdf_mem_zero_outline);
|
|||||||
*/
|
*/
|
||||||
static void *qdf_mem_prealloc_get(size_t size)
|
static void *qdf_mem_prealloc_get(size_t size)
|
||||||
{
|
{
|
||||||
void *mem;
|
void *ptr;
|
||||||
|
|
||||||
if (size <= WCNSS_PRE_ALLOC_GET_THRESHOLD)
|
if (size <= WCNSS_PRE_ALLOC_GET_THRESHOLD)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
mem = wcnss_prealloc_get(size);
|
ptr = wcnss_prealloc_get(size);
|
||||||
if (mem)
|
if (!ptr)
|
||||||
memset(mem, 0, size);
|
return NULL;
|
||||||
|
|
||||||
return mem;
|
memset(ptr, 0, size);
|
||||||
|
|
||||||
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool qdf_mem_prealloc_put(void *ptr)
|
static inline bool qdf_mem_prealloc_put(void *ptr)
|
||||||
@@ -763,6 +915,14 @@ static inline bool qdf_mem_prealloc_put(void *ptr)
|
|||||||
}
|
}
|
||||||
#endif /* CONFIG_WCNSS_MEM_PRE_ALLOC */
|
#endif /* CONFIG_WCNSS_MEM_PRE_ALLOC */
|
||||||
|
|
||||||
|
static int qdf_mem_malloc_flags(void)
|
||||||
|
{
|
||||||
|
if (in_interrupt() || irqs_disabled() || in_atomic())
|
||||||
|
return GFP_ATOMIC;
|
||||||
|
|
||||||
|
return GFP_KERNEL;
|
||||||
|
}
|
||||||
|
|
||||||
/* External Function implementation */
|
/* External Function implementation */
|
||||||
#ifdef MEMORY_DEBUG
|
#ifdef MEMORY_DEBUG
|
||||||
|
|
||||||
@@ -867,245 +1027,83 @@ static void qdf_mem_debug_exit(void)
|
|||||||
qdf_spinlock_destroy(&qdf_mem_list_lock);
|
qdf_spinlock_destroy(&qdf_mem_list_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void *qdf_mem_malloc_debug(size_t size, const char *file, uint32_t line)
|
||||||
* qdf_mem_malloc_debug() - debug version of QDF memory allocation API
|
|
||||||
* @size: Number of bytes of memory to allocate.
|
|
||||||
* @file_name: File name from which memory allocation is called
|
|
||||||
* @line_num: Line number from which memory allocation is called
|
|
||||||
*
|
|
||||||
* This function will dynamicallly allocate the specified number of bytes of
|
|
||||||
* memory and ad it in qdf tracking list to check against memory leaks and
|
|
||||||
* corruptions
|
|
||||||
*
|
|
||||||
* Return:
|
|
||||||
* Upon successful allocate, returns a non-NULL pointer to the allocated
|
|
||||||
* memory. If this function is unable to allocate the amount of memory
|
|
||||||
* specified (for any reason) it returns %NULL.
|
|
||||||
*/
|
|
||||||
void *qdf_mem_malloc_debug(size_t size, char *file_name, uint32_t line_num)
|
|
||||||
{
|
{
|
||||||
|
QDF_STATUS status;
|
||||||
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
|
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
|
||||||
qdf_list_t *mem_list = qdf_mem_list_get(current_domain);
|
qdf_list_t *mem_list = qdf_mem_list_get(current_domain);
|
||||||
struct s_qdf_mem_struct *mem_struct;
|
struct qdf_mem_header *header;
|
||||||
void *mem_ptr = NULL;
|
void *ptr;
|
||||||
uint32_t new_size;
|
unsigned long start, duration;
|
||||||
int flags = GFP_KERNEL;
|
|
||||||
unsigned long time_before_kzalloc;
|
|
||||||
|
|
||||||
if (size > (1024 * 1024) || size == 0) {
|
if (!size || size > QDF_MEM_MAX_MALLOC) {
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR,
|
qdf_err("Cannot malloc %zu bytes @ %s:%d", size, file, line);
|
||||||
"%s: called with invalid arg; passed in %zu !!!",
|
|
||||||
__func__, size);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
mem_ptr = qdf_mem_prealloc_get(size);
|
ptr = qdf_mem_prealloc_get(size);
|
||||||
if (mem_ptr)
|
if (ptr)
|
||||||
return mem_ptr;
|
return ptr;
|
||||||
|
|
||||||
if (in_interrupt() || irqs_disabled() || in_atomic())
|
start = qdf_mc_timer_get_system_time();
|
||||||
flags = GFP_ATOMIC;
|
header = kzalloc(size + QDF_MEM_DEBUG_SIZE, qdf_mem_malloc_flags());
|
||||||
|
duration = qdf_mc_timer_get_system_time() - start;
|
||||||
|
|
||||||
new_size = sizeof(*mem_struct) + size + sizeof(WLAN_MEM_TAIL);
|
if (duration > QDF_MEM_WARN_THRESHOLD)
|
||||||
time_before_kzalloc = qdf_mc_timer_get_system_time();
|
qdf_warn("Malloc slept; %lums, %zuB @ %s:%d",
|
||||||
mem_struct = kzalloc(new_size, flags);
|
duration, size, file, line);
|
||||||
/**
|
|
||||||
* If time taken by kmalloc is greater than
|
|
||||||
* QDF_GET_MEMORY_TIME_THRESHOLD msec
|
|
||||||
*/
|
|
||||||
if (qdf_mc_timer_get_system_time() - time_before_kzalloc >=
|
|
||||||
QDF_GET_MEMORY_TIME_THRESHOLD)
|
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR,
|
|
||||||
"%s: kzalloc took %lu msec for size %zu called from %p_s at line %d",
|
|
||||||
__func__,
|
|
||||||
qdf_mc_timer_get_system_time() - time_before_kzalloc,
|
|
||||||
size, (void *)_RET_IP_, line_num);
|
|
||||||
|
|
||||||
if (mem_struct) {
|
if (!header)
|
||||||
QDF_STATUS status;
|
return NULL;
|
||||||
uint64_t *trailer;
|
|
||||||
|
|
||||||
mem_ptr = (void *)(mem_struct + 1);
|
qdf_mem_header_init(header, size, file, line);
|
||||||
trailer = (uint64_t *)(mem_ptr + size);
|
ptr = qdf_mem_get_ptr(header);
|
||||||
|
|
||||||
mem_struct->file_name = file_name;
|
qdf_spin_lock_irqsave(&qdf_mem_list_lock);
|
||||||
mem_struct->line_num = line_num;
|
status = qdf_list_insert_front(mem_list, &header->node);
|
||||||
mem_struct->size = size;
|
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
||||||
mem_struct->domain = current_domain;
|
if (QDF_IS_STATUS_ERROR(status))
|
||||||
mem_struct->header = WLAN_MEM_HEADER;
|
qdf_err("Failed to insert memory header; status %d", status);
|
||||||
*trailer = WLAN_MEM_TAIL;
|
|
||||||
|
|
||||||
qdf_mem_kmalloc_inc(ksize(mem_struct));
|
qdf_mem_kmalloc_inc(size);
|
||||||
|
|
||||||
qdf_spin_lock_irqsave(&qdf_mem_list_lock);
|
return ptr;
|
||||||
status = qdf_list_insert_front(mem_list, &mem_struct->node);
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
if (QDF_IS_STATUS_ERROR(status))
|
|
||||||
qdf_err("Unable to insert into list status %d", status);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mem_ptr;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(qdf_mem_malloc_debug);
|
qdf_export_symbol(qdf_mem_malloc_debug);
|
||||||
|
|
||||||
/**
|
void qdf_mem_free_debug(void *ptr, const char *file, uint32_t line)
|
||||||
* qdf_mem_validate_node_for_free() - validate that the node is in a list
|
|
||||||
* @qdf_node: node to check for being in a list
|
|
||||||
*
|
|
||||||
* qdf_node should be a non null value.
|
|
||||||
*
|
|
||||||
* Return: true if the node validly linked in an anchored doubly linked list
|
|
||||||
*/
|
|
||||||
static bool qdf_mem_validate_node_for_free(qdf_list_node_t *qdf_node)
|
|
||||||
{
|
|
||||||
struct list_head *node = qdf_node;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* if the node is an empty list, it is not tied to an anchor node
|
|
||||||
* and must have been removed with list_del_init
|
|
||||||
*/
|
|
||||||
if (list_empty(node))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (node->prev == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (node->next == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (node->prev->next != node)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (node->next->prev != node)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* qdf_mem_free() - QDF memory free API
|
|
||||||
* @ptr: Pointer to the starting address of the memory to be free'd.
|
|
||||||
*
|
|
||||||
* This function will free the memory pointed to by 'ptr'. It also checks
|
|
||||||
* is memory is corrupted or getting double freed and panic.
|
|
||||||
*
|
|
||||||
* Return: none
|
|
||||||
*/
|
|
||||||
void qdf_mem_free(void *ptr)
|
|
||||||
{
|
{
|
||||||
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
|
enum qdf_debug_domain current_domain = qdf_debug_domain_get();
|
||||||
qdf_list_t *mem_list = qdf_mem_list_get(current_domain);
|
struct qdf_mem_header *header;
|
||||||
struct s_qdf_mem_struct *mem_struct;
|
enum qdf_mem_validation_bitmap error_bitmap;
|
||||||
uint64_t *trailer;
|
|
||||||
|
|
||||||
/* freeing a null pointer is valid */
|
/* freeing a null pointer is valid */
|
||||||
if (qdf_unlikely(ptr == NULL))
|
if (qdf_unlikely(!ptr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (qdf_mem_prealloc_put(ptr))
|
if (qdf_mem_prealloc_put(ptr))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mem_struct = ((struct s_qdf_mem_struct *)ptr) - 1;
|
if (qdf_unlikely((qdf_size_t)ptr <= sizeof(*header)))
|
||||||
|
panic("Failed to free invalid memory location %pK", ptr);
|
||||||
if (qdf_unlikely(mem_struct == NULL)) {
|
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
|
|
||||||
"%s: null mem_struct", __func__);
|
|
||||||
QDF_BUG(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
trailer = (uint64_t *)(ptr + mem_struct->size);
|
|
||||||
|
|
||||||
qdf_spin_lock_irqsave(&qdf_mem_list_lock);
|
qdf_spin_lock_irqsave(&qdf_mem_list_lock);
|
||||||
|
header = qdf_mem_get_header(ptr);
|
||||||
/*
|
error_bitmap = qdf_mem_header_validate(header, current_domain);
|
||||||
* invalid memory access when checking the header/tailer
|
if (!error_bitmap) {
|
||||||
* would be a use after free and would indicate a double free
|
header->freed = true;
|
||||||
* or invalid pointer passed.
|
list_del_init(&header->node);
|
||||||
*/
|
qdf_mem_list_get(header->domain)->count--;
|
||||||
if (mem_struct->header != WLAN_MEM_HEADER)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* invalid memory access while checking validate node
|
|
||||||
* would indicate corruption in the nodes pointed to
|
|
||||||
*/
|
|
||||||
if (!qdf_mem_validate_node_for_free(&mem_struct->node))
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* invalid memory access here is unlikely and would imply
|
|
||||||
* that the size value was corrupted/incorrect.
|
|
||||||
* It is unlikely that the above checks would pass in a
|
|
||||||
* double free case.
|
|
||||||
*/
|
|
||||||
if (*trailer != WLAN_MEM_TAIL)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* make sure the memory belongs to the current domain */
|
|
||||||
if (mem_struct->domain != current_domain) {
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
qdf_err("Memory domain mismatch; found %s, expected %s (%s:%u)",
|
|
||||||
qdf_debug_domain_name(mem_struct->domain),
|
|
||||||
qdf_debug_domain_name(current_domain),
|
|
||||||
kbasename(mem_struct->file_name),
|
|
||||||
mem_struct->line_num);
|
|
||||||
qdf_mem_leak_panic();
|
|
||||||
|
|
||||||
/* continue de-allocation if we didn't crash */
|
|
||||||
qdf_spin_lock_irqsave(&qdf_mem_list_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* make the node an empty list before doing the spin unlock
|
|
||||||
* The empty list check will guarantee that we avoid a race condition.
|
|
||||||
*/
|
|
||||||
list_del_init(&mem_struct->node);
|
|
||||||
mem_list->count--;
|
|
||||||
qdf_mem_kmalloc_dec(ksize(mem_struct));
|
|
||||||
kfree(mem_struct);
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
||||||
|
|
||||||
return;
|
qdf_mem_header_assert_valid(header, current_domain, error_bitmap,
|
||||||
|
file, line);
|
||||||
|
|
||||||
error:
|
qdf_mem_kmalloc_dec(header->size);
|
||||||
if (!qdf_list_has_node(mem_list, &mem_struct->node)) {
|
kfree(header);
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
|
|
||||||
"%s: Unallocated memory (double free?)",
|
|
||||||
__func__);
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
QDF_BUG(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mem_struct->header != WLAN_MEM_HEADER) {
|
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
|
|
||||||
"Memory Header is corrupted.");
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
QDF_BUG(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!qdf_mem_validate_node_for_free(&mem_struct->node)) {
|
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
|
|
||||||
"Memory_struct is corrupted.");
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
QDF_BUG(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*trailer != WLAN_MEM_TAIL) {
|
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
|
|
||||||
"Memory Trailer is corrupted. mem_info: Filename %s, line_num %d",
|
|
||||||
mem_struct->file_name, (int)mem_struct->line_num);
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
QDF_BUG(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_FATAL,
|
|
||||||
"%s unexpected error", __func__);
|
|
||||||
qdf_spin_unlock_irqrestore(&qdf_mem_list_lock);
|
|
||||||
QDF_BUG(0);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(qdf_mem_free);
|
qdf_export_symbol(qdf_mem_free_debug);
|
||||||
|
|
||||||
void qdf_mem_check_for_leaks(void)
|
void qdf_mem_check_for_leaks(void)
|
||||||
{
|
{
|
||||||
@@ -1139,23 +1137,19 @@ static void qdf_mem_debug_exit(void) {}
|
|||||||
*/
|
*/
|
||||||
void *qdf_mem_malloc(size_t size)
|
void *qdf_mem_malloc(size_t size)
|
||||||
{
|
{
|
||||||
int flags = GFP_KERNEL;
|
void *ptr;
|
||||||
void *mem;
|
|
||||||
|
|
||||||
mem = qdf_mem_prealloc_get(size);
|
ptr = qdf_mem_prealloc_get(size);
|
||||||
if (mem)
|
if (ptr)
|
||||||
return mem;
|
return ptr;
|
||||||
|
|
||||||
if (in_interrupt() || irqs_disabled() || in_atomic())
|
ptr = kzalloc(size, qdf_mem_malloc_flags());
|
||||||
flags = GFP_ATOMIC;
|
if (!ptr)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
mem = kzalloc(size, flags);
|
qdf_mem_kmalloc_inc(ksize(ptr));
|
||||||
|
|
||||||
if (mem)
|
|
||||||
qdf_mem_kmalloc_inc(ksize(mem));
|
|
||||||
|
|
||||||
return mem;
|
|
||||||
|
|
||||||
|
return ptr;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(qdf_mem_malloc);
|
EXPORT_SYMBOL(qdf_mem_malloc);
|
||||||
|
|
||||||
@@ -1530,50 +1524,44 @@ void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size,
|
|||||||
void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size,
|
void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size,
|
||||||
qdf_dma_addr_t *phy_addr)
|
qdf_dma_addr_t *phy_addr)
|
||||||
{
|
{
|
||||||
int flags = GFP_KERNEL;
|
void *vaddr = NULL;
|
||||||
void *alloc_mem = NULL;
|
int i;
|
||||||
int alloc_try_high_mem = 0;
|
|
||||||
|
|
||||||
if (in_interrupt() || irqs_disabled() || in_atomic())
|
|
||||||
flags = GFP_ATOMIC;
|
|
||||||
|
|
||||||
*phy_addr = 0;
|
*phy_addr = 0;
|
||||||
|
|
||||||
while (alloc_try_high_mem++ < QDF_MEM_ALLOC_X86_MAX_RETRIES) {
|
for (i = 0; i < QDF_MEM_ALLOC_X86_MAX_RETRIES; i++) {
|
||||||
alloc_mem = dma_alloc_coherent(dev, size, phy_addr, flags);
|
vaddr = dma_alloc_coherent(dev, size, phy_addr,
|
||||||
|
qdf_mem_malloc_flags());
|
||||||
|
|
||||||
if (alloc_mem == NULL) {
|
if (!vaddr) {
|
||||||
qdf_print("%s failed , size: %zu!\n", __func__, size);
|
qdf_print("%s failed , size: %zu!\n", __func__, size);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*phy_addr < QCA8074_RAM_BASE) {
|
if (*phy_addr >= QCA8074_RAM_BASE)
|
||||||
dma_free_coherent(dev, size, alloc_mem, *phy_addr);
|
return vaddr;
|
||||||
alloc_mem = NULL;
|
|
||||||
} else
|
dma_free_coherent(dev, size, vaddr, *phy_addr);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return alloc_mem;
|
return NULL;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size,
|
void *qdf_mem_alloc_consistent(qdf_device_t osdev, void *dev, qdf_size_t size,
|
||||||
qdf_dma_addr_t *phy_addr)
|
qdf_dma_addr_t *phy_addr)
|
||||||
{
|
{
|
||||||
int flags = GFP_KERNEL;
|
void *ptr;
|
||||||
void *alloc_mem = NULL;
|
|
||||||
|
|
||||||
if (in_interrupt() || irqs_disabled() || in_atomic())
|
ptr = dma_alloc_coherent(dev, size, phy_addr, qdf_mem_malloc_flags());
|
||||||
flags = GFP_ATOMIC;
|
if (!ptr) {
|
||||||
|
qdf_warn("Warning: unable to alloc consistent memory of size %zu!\n",
|
||||||
|
size);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
alloc_mem = dma_alloc_coherent(dev, size, phy_addr, flags);
|
qdf_mem_dma_inc(size);
|
||||||
if (alloc_mem == NULL)
|
|
||||||
qdf_print("%s Warning: unable to alloc consistent memory of size %zu!\n",
|
|
||||||
__func__, size);
|
|
||||||
else
|
|
||||||
qdf_mem_dma_inc(size);
|
|
||||||
|
|
||||||
return alloc_mem;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Reference in New Issue
Block a user