qcacmn: Add nbuf map/unmap tracking

When MEMORY_DEBUG is enabled, track calls to nbuf map/unmap to detect
the following usage problems:

	* double map
	* double unmap
	* unmap without map
	* never unmap

Change-Id: I7326bb628d4cf0be9004398fd396907b1a06bf22
CRs-Fixed: 2155599
This commit is contained in:
Dustin Brown 2017-12-07 14:18:47 -08:00 • committed by snandini
commit 4082a0951f
2 changed files with 325 additions and 17 deletions

View file

@ -522,6 +522,96 @@ qdf_nbuf_set_send_complete_flag(qdf_nbuf_t buf, bool flag)
__qdf_nbuf_set_send_complete_flag(buf, flag);
}
#ifdef MEMORY_DEBUG
/**
* qdf_nbuf_map_check_for_leaks() - check for nbut map leaks
*
* Check for net buffers that have been mapped, but never unmapped.
*
* Returns: None
*/
void qdf_nbuf_map_check_for_leaks(void);
QDF_STATUS qdf_nbuf_map_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line);
#define qdf_nbuf_map(osdev, buf, dir) \
qdf_nbuf_map_debug(osdev, buf, dir, __FILE__, __LINE__)
void qdf_nbuf_unmap_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line);
#define qdf_nbuf_unmap(osdev, buf, dir) \
qdf_nbuf_unmap_debug(osdev, buf, dir, __FILE__, __LINE__)
QDF_STATUS qdf_nbuf_map_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line);
#define qdf_nbuf_map_single(osdev, buf, dir) \
qdf_nbuf_map_single_debug(osdev, buf, dir, __FILE__, __LINE__)
void qdf_nbuf_unmap_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line);
#define qdf_nbuf_unmap_single(osdev, buf, dir) \
qdf_nbuf_unmap_single_debug(osdev, buf, dir, __FILE__, __LINE__)
QDF_STATUS qdf_nbuf_map_nbytes_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line);
#define qdf_nbuf_map_nbytes(osdev, buf, dir, nbytes) \
qdf_nbuf_map_nbytes_debug(osdev, buf, dir, nbytes, __FILE__, __LINE__)
void qdf_nbuf_unmap_nbytes_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line);
#define qdf_nbuf_unmap_nbytes(osdev, buf, dir, nbytes) \
qdf_nbuf_unmap_nbytes_debug(osdev, buf, dir, nbytes, __FILE__, __LINE__)
QDF_STATUS qdf_nbuf_map_nbytes_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line);
#define qdf_nbuf_map_nbytes_single(osdev, buf, dir, nbytes) \
qdf_nbuf_map_nbytes_single_debug(osdev, buf, dir, nbytes, \
__FILE__, __LINE__)
void qdf_nbuf_unmap_nbytes_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line);
#define qdf_nbuf_unmap_nbytes_single(osdev, buf, dir, nbytes) \
qdf_nbuf_unmap_nbytes_single_debug(osdev, buf, dir, nbytes, \
__FILE__, __LINE__)
#else /* MEMORY_DEBUG */
static inline QDF_STATUS
qdf_nbuf_map(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
@ -534,6 +624,18 @@ qdf_nbuf_unmap(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
__qdf_nbuf_unmap(osdev, buf, dir);
}
static inline QDF_STATUS
qdf_nbuf_map_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
return __qdf_nbuf_map_single(osdev, buf, dir);
}
static inline void
qdf_nbuf_unmap_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
__qdf_nbuf_unmap_single(osdev, buf, dir);
}
static inline QDF_STATUS
qdf_nbuf_map_nbytes(qdf_device_t osdev, qdf_nbuf_t buf,
qdf_dma_dir_t dir, int nbytes)
@ -548,18 +650,6 @@ qdf_nbuf_unmap_nbytes(qdf_device_t osdev,
__qdf_nbuf_unmap_nbytes(osdev, buf, dir, nbytes);
}
static inline void
qdf_nbuf_sync_for_cpu(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
__qdf_nbuf_sync_for_cpu(osdev, buf, dir);
}
static inline QDF_STATUS
qdf_nbuf_map_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
return __qdf_nbuf_map_single(osdev, buf, dir);
}
static inline QDF_STATUS
qdf_nbuf_map_nbytes_single(
qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir, int nbytes)
@ -567,18 +657,19 @@ qdf_nbuf_map_nbytes_single(
return __qdf_nbuf_map_nbytes_single(osdev, buf, dir, nbytes);
}
static inline void
qdf_nbuf_unmap_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
__qdf_nbuf_unmap_single(osdev, buf, dir);
}
static inline void
qdf_nbuf_unmap_nbytes_single(
qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir, int nbytes)
{
return __qdf_nbuf_unmap_nbytes_single(osdev, buf, dir, nbytes);
}
#endif /* MEMORY_DEBUG */
static inline void
qdf_nbuf_sync_for_cpu(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
{
__qdf_nbuf_sync_for_cpu(osdev, buf, dir);
}
static inline int qdf_nbuf_get_num_frags(qdf_nbuf_t buf)
{

View file

@ -30,6 +30,7 @@
* QCA driver framework(QDF) network buffer management APIs
*/
#include <linux/hashtable.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/skbuff.h>
@ -468,6 +469,220 @@ void __qdf_nbuf_free(struct sk_buff *skb)
EXPORT_SYMBOL(__qdf_nbuf_free);
#ifdef MEMORY_DEBUG
#define QDF_NBUF_MAP_HT_BITS 10 /* 1024 buckets */
static DECLARE_HASHTABLE(qdf_nbuf_map_ht, QDF_NBUF_MAP_HT_BITS);
static qdf_spinlock_t qdf_nbuf_map_lock;
struct qdf_nbuf_map_metadata {
struct hlist_node node;
qdf_nbuf_t nbuf;
const char *file;
uint32_t line;
};
static void qdf_nbuf_map_tracking_init(void)
{
hash_init(qdf_nbuf_map_ht);
qdf_spinlock_create(&qdf_nbuf_map_lock);
}
void qdf_nbuf_map_check_for_leaks(void)
{
struct qdf_nbuf_map_metadata *meta;
int bucket;
uint32_t count = 0;
bool is_empty;
qdf_spin_lock_irqsave(&qdf_nbuf_map_lock);
is_empty = hash_empty(qdf_nbuf_map_ht);
qdf_spin_unlock_irqrestore(&qdf_nbuf_map_lock);
if (is_empty)
return;
qdf_err("Nbuf map without unmap events detected!");
qdf_err("------------------------------------------------------------");
qdf_spin_lock_irqsave(&qdf_nbuf_map_lock);
hash_for_each(qdf_nbuf_map_ht, bucket, meta, node) {
qdf_spin_unlock_irqrestore(&qdf_nbuf_map_lock);
count++;
qdf_err("0x%pk @ %s:%u", meta->nbuf, kbasename(meta->file),
meta->line);
qdf_spin_lock_irqsave(&qdf_nbuf_map_lock);
}
qdf_spin_unlock_irqrestore(&qdf_nbuf_map_lock);
panic("%u fatal nbuf map without unmap events detected!", count);
}
static void qdf_nbuf_map_tracking_deinit(void)
{
qdf_nbuf_map_check_for_leaks();
qdf_spinlock_destroy(&qdf_nbuf_map_lock);
}
static struct qdf_nbuf_map_metadata *qdf_nbuf_meta_get(qdf_nbuf_t nbuf)
{
struct qdf_nbuf_map_metadata *meta;
hash_for_each_possible(qdf_nbuf_map_ht, meta, node, (size_t)nbuf) {
if (meta->nbuf == nbuf)
return meta;
}
return NULL;
}
static void
qdf_nbuf_track_map(qdf_nbuf_t nbuf, const char *file, uint32_t line)
{
struct qdf_nbuf_map_metadata *meta;
QDF_BUG(nbuf);
if (!nbuf) {
qdf_err("Cannot map null nbuf");
return;
}
qdf_spin_lock_irqsave(&qdf_nbuf_map_lock);
meta = qdf_nbuf_meta_get(nbuf);
qdf_spin_unlock_irqrestore(&qdf_nbuf_map_lock);
if (meta)
panic("Double nbuf map detected @ %s:%u",
kbasename(file), line);
meta = qdf_mem_malloc(sizeof(*meta));
if (!meta) {
qdf_err("Failed to allocate nbuf map tracking metadata");
return;
}
meta->nbuf = nbuf;
meta->file = file;
meta->line = line;
qdf_spin_lock_irqsave(&qdf_nbuf_map_lock);
hash_add(qdf_nbuf_map_ht, &meta->node, (size_t)nbuf);
qdf_spin_unlock_irqrestore(&qdf_nbuf_map_lock);
}
static void
qdf_nbuf_untrack_map(qdf_nbuf_t nbuf, const char *file, uint32_t line)
{
struct qdf_nbuf_map_metadata *meta;
QDF_BUG(nbuf);
if (!nbuf) {
qdf_err("Cannot unmap null nbuf");
return;
}
qdf_spin_lock_irqsave(&qdf_nbuf_map_lock);
meta = qdf_nbuf_meta_get(nbuf);
if (!meta)
panic("Double nbuf unmap or unmap without map detected @%s:%u",
kbasename(file), line);
hash_del(&meta->node);
qdf_spin_unlock_irqrestore(&qdf_nbuf_map_lock);
qdf_mem_free(meta);
}
QDF_STATUS qdf_nbuf_map_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line)
{
qdf_nbuf_track_map(buf, file, line);
return __qdf_nbuf_map(osdev, buf, dir);
}
void qdf_nbuf_unmap_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line)
{
qdf_nbuf_untrack_map(buf, file, line);
__qdf_nbuf_unmap_single(osdev, buf, dir);
}
QDF_STATUS qdf_nbuf_map_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line)
{
qdf_nbuf_track_map(buf, file, line);
return __qdf_nbuf_map_single(osdev, buf, dir);
}
void qdf_nbuf_unmap_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
const char *file,
uint32_t line)
{
qdf_nbuf_untrack_map(buf, file, line);
__qdf_nbuf_unmap_single(osdev, buf, dir);
}
QDF_STATUS qdf_nbuf_map_nbytes_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line)
{
qdf_nbuf_track_map(buf, file, line);
return __qdf_nbuf_map_nbytes(osdev, buf, dir, nbytes);
}
void qdf_nbuf_unmap_nbytes_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line)
{
qdf_nbuf_untrack_map(buf, file, line);
__qdf_nbuf_unmap_nbytes(osdev, buf, dir, nbytes);
}
QDF_STATUS qdf_nbuf_map_nbytes_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line)
{
qdf_nbuf_track_map(buf, file, line);
return __qdf_nbuf_map_nbytes_single(osdev, buf, dir, nbytes);
}
void qdf_nbuf_unmap_nbytes_single_debug(qdf_device_t osdev,
qdf_nbuf_t buf,
qdf_dma_dir_t dir,
int nbytes,
const char *file,
uint32_t line)
{
qdf_nbuf_untrack_map(buf, file, line);
__qdf_nbuf_unmap_nbytes_single(osdev, buf, dir, nbytes);
}
#endif /* MEMORY_DEBUG */
/**
* __qdf_nbuf_map() - map a buffer to local bus address space
* @osdev: OS device
@ -1647,6 +1862,7 @@ void qdf_net_buf_debug_init(void)
{
uint32_t i;
qdf_nbuf_map_tracking_init();
qdf_nbuf_track_memory_manager_create();
for (i = 0; i < QDF_NET_BUF_TRACK_MAX_SIZE; i++) {
@ -1689,6 +1905,7 @@ void qdf_net_buf_debug_exit(void)
}
qdf_nbuf_track_memory_manager_destroy();
qdf_nbuf_map_tracking_deinit();
#ifdef CONFIG_HALT_KMEMLEAK
if (count) {