wcnss: Pre-alloc memory for WLAN driver

WLAN driver will use this pre-allocated memory (when available) for
large memory allocations; this will prevent WLAN driver load
failures because of the un-availability of the large size slabs
during module load.

Change-Id: I8a8139bdf343ddc871036f6d5c6ab90993816de0
Signed-off-by: Sameer Thalappil <sameert@codeaurora.org>
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index 9b8ab39..5278324 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -309,6 +309,12 @@
 	---help---
 	  Pronto Support for the Qualcomm WCNSS triple play connectivity subsystem
 
+config WCNSS_MEM_PRE_ALLOC
+	tristate "WCNSS pre-alloc memory support"
+	depends on WCNSS_CORE
+	---help---
+	  Pre-allocate memory for the WLAN driver module
+
 source "drivers/net/wireless/ath/Kconfig"
 source "drivers/net/wireless/b43/Kconfig"
 source "drivers/net/wireless/b43legacy/Kconfig"
diff --git a/drivers/net/wireless/wcnss/Makefile b/drivers/net/wireless/wcnss/Makefile
index 11fa673..613d477 100644
--- a/drivers/net/wireless/wcnss/Makefile
+++ b/drivers/net/wireless/wcnss/Makefile
@@ -4,3 +4,4 @@
 wcnsscore-objs += wcnss_wlan.o qcomwlan_secif.o wcnss_vreg.o
 
 obj-$(CONFIG_WCNSS_CORE) += wcnsscore.o
+obj-$(CONFIG_WCNSS_MEM_PRE_ALLOC) += wcnss_prealloc.o
diff --git a/drivers/net/wireless/wcnss/wcnss_prealloc.c b/drivers/net/wireless/wcnss/wcnss_prealloc.c
new file mode 100644
index 0000000..7d10657
--- /dev/null
+++ b/drivers/net/wireless/wcnss/wcnss_prealloc.c
@@ -0,0 +1,103 @@
+/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/wcnss_wlan.h>
+
+static DEFINE_MUTEX(alloc_lock);
+
+struct wcnss_prealloc {
+	int occupied;
+	unsigned int size;
+	void *ptr;
+};
+
+/* pre-alloced mem for WLAN driver */
+static struct wcnss_prealloc wcnss_allocs[] = {
+	{0, 8  * 1024, NULL},
+	{0, 8  * 1024, NULL},
+	{0, 8  * 1024, NULL},
+	{0, 8  * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 32 * 1024, NULL},
+	{0, 64 * 1024, NULL},
+	{0, 64 * 1024, NULL},
+};
+
+int wcnss_prealloc_init(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) {
+		wcnss_allocs[i].occupied = 0;
+		wcnss_allocs[i].ptr = kmalloc(wcnss_allocs[i].size, GFP_KERNEL);
+		if (wcnss_allocs[i].ptr == NULL)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void wcnss_prealloc_deinit(void)
+{
+	int i = 0;
+
+	for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++)
+		kfree(wcnss_allocs[i].ptr);
+}
+
+void *wcnss_prealloc_get(unsigned int size)
+{
+	int i = 0;
+
+	mutex_lock(&alloc_lock);
+	for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) {
+		if (wcnss_allocs[i].occupied)
+			continue;
+
+		if (wcnss_allocs[i].size > size) {
+			/* we found the slot */
+			wcnss_allocs[i].occupied = 1;
+			mutex_unlock(&alloc_lock);
+			return wcnss_allocs[i].ptr;
+		}
+	}
+	pr_err("wcnss: %s: prealloc not available\n", __func__);
+	mutex_unlock(&alloc_lock);
+
+	return NULL;
+}
+EXPORT_SYMBOL(wcnss_prealloc_get);
+
+int wcnss_prealloc_put(void *ptr)
+{
+	int i = 0;
+
+	mutex_lock(&alloc_lock);
+	for (i = 0; i < ARRAY_SIZE(wcnss_allocs); i++) {
+		if (wcnss_allocs[i].ptr == ptr) {
+			wcnss_allocs[i].occupied = 0;
+			mutex_unlock(&alloc_lock);
+			return 1;
+		}
+	}
+	mutex_unlock(&alloc_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(wcnss_prealloc_put);
diff --git a/drivers/net/wireless/wcnss/wcnss_prealloc.h b/drivers/net/wireless/wcnss/wcnss_prealloc.h
new file mode 100644
index 0000000..73ae6c6
--- /dev/null
+++ b/drivers/net/wireless/wcnss/wcnss_prealloc.h
@@ -0,0 +1,19 @@
+/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _WCNSS_PRE_ALLOC_H_
+#define _WCNSS_PRE_ALLOC_H_
+
+int wcnss_prealloc_init(void);
+void wcnss_prealloc_deinit(void);
+
+#endif/* _WCNSS_PRE_ALLOC_H_ */
diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c
index e83b195..ac0a2fd 100644
--- a/drivers/net/wireless/wcnss/wcnss_wlan.c
+++ b/drivers/net/wireless/wcnss/wcnss_wlan.c
@@ -27,6 +27,9 @@
 #include <linux/of_gpio.h>
 #include <mach/peripheral-loader.h>
 #include <mach/msm_smd.h>
+#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
+#include "wcnss_prealloc.h"
+#endif
 
 #define DEVICE "wcnss_wlan"
 #define VERSION "1.01"
@@ -805,11 +808,19 @@
 
 static int __init wcnss_wlan_init(void)
 {
+	int ret = 0;
+
 	platform_driver_register(&wcnss_wlan_driver);
 	platform_driver_register(&wcnss_wlan_ctrl_driver);
 	platform_driver_register(&wcnss_ctrl_driver);
 
-	return 0;
+#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
+	ret = wcnss_prealloc_init();
+	if (ret < 0)
+		pr_err("wcnss: pre-allocation failed\n");
+#endif
+
+	return ret;
 }
 
 static void __exit wcnss_wlan_exit(void)
@@ -826,6 +837,9 @@
 	platform_driver_unregister(&wcnss_ctrl_driver);
 	platform_driver_unregister(&wcnss_wlan_ctrl_driver);
 	platform_driver_unregister(&wcnss_wlan_driver);
+#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
+	wcnss_prealloc_deinit();
+#endif
 }
 
 module_init(wcnss_wlan_init);