Browse Source

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
Dustin Brown 7 years ago
parent
commit
4082a0951f
2 changed files with 325 additions and 17 deletions
  1. 108 17
      qdf/inc/qdf_nbuf.h
  2. 217 0
      qdf/linux/src/qdf_nbuf.c

+ 108 - 17
qdf/inc/qdf_nbuf.h

@@ -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)
 {
@@ -535,29 +625,29 @@ qdf_nbuf_unmap(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
 }
 
 static inline QDF_STATUS
-qdf_nbuf_map_nbytes(qdf_device_t osdev, qdf_nbuf_t buf,
-		qdf_dma_dir_t dir, int nbytes)
+qdf_nbuf_map_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
 {
-	return __qdf_nbuf_map_nbytes(osdev, buf, dir, nbytes);
+	return __qdf_nbuf_map_single(osdev, buf, dir);
 }
 
 static inline void
-qdf_nbuf_unmap_nbytes(qdf_device_t osdev,
-	qdf_nbuf_t buf, qdf_dma_dir_t dir, int nbytes)
+qdf_nbuf_unmap_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
 {
-	__qdf_nbuf_unmap_nbytes(osdev, buf, dir, nbytes);
+	__qdf_nbuf_unmap_single(osdev, buf, dir);
 }
 
-static inline void
-qdf_nbuf_sync_for_cpu(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
+static inline QDF_STATUS
+qdf_nbuf_map_nbytes(qdf_device_t osdev, qdf_nbuf_t buf,
+		qdf_dma_dir_t dir, int nbytes)
 {
-	__qdf_nbuf_sync_for_cpu(osdev, buf, dir);
+	return __qdf_nbuf_map_nbytes(osdev, buf, dir, nbytes);
 }
 
-static inline QDF_STATUS
-qdf_nbuf_map_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
+static inline void
+qdf_nbuf_unmap_nbytes(qdf_device_t osdev,
+	qdf_nbuf_t buf, qdf_dma_dir_t dir, int nbytes)
 {
-	return __qdf_nbuf_map_single(osdev, buf, dir);
+	__qdf_nbuf_unmap_nbytes(osdev, buf, dir, nbytes);
 }
 
 static inline QDF_STATUS
@@ -568,16 +658,17 @@ qdf_nbuf_map_nbytes_single(
 }
 
 static inline void
-qdf_nbuf_unmap_single(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
+qdf_nbuf_unmap_nbytes_single(
+	qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir, int nbytes)
 {
-	__qdf_nbuf_unmap_single(osdev, buf, dir);
+	return __qdf_nbuf_unmap_nbytes_single(osdev, buf, dir, nbytes);
 }
+#endif /* MEMORY_DEBUG */
 
 static inline void
-qdf_nbuf_unmap_nbytes_single(
-	qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir, int nbytes)
+qdf_nbuf_sync_for_cpu(qdf_device_t osdev, qdf_nbuf_t buf, qdf_dma_dir_t dir)
 {
-	return __qdf_nbuf_unmap_nbytes_single(osdev, buf, dir, nbytes);
+	__qdf_nbuf_sync_for_cpu(osdev, buf, dir);
 }
 
 static inline int qdf_nbuf_get_num_frags(qdf_nbuf_t buf)

+ 217 - 0
qdf/linux/src/qdf_nbuf.c

@@ -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) {