devcoredump: add scatterlist support

Add scatterlist support (dev_coredumpsg) to allow drivers to avoid
vmalloc() like dev_coredumpm(), while also avoiding the module
reference that the latter function requires.

This internally uses dev_coredumpm() with function inside the
devcoredump module, requiring removing the const
(which touches the driver using it.)

Signed-off-by: Aviya Erenfeld <aviya.erenfeld@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
diff --git a/include/linux/devcoredump.h b/include/linux/devcoredump.h
index c0a360e..269521f 100644
--- a/include/linux/devcoredump.h
+++ b/include/linux/devcoredump.h
@@ -1,3 +1,22 @@
+/*
+ * This file is provided under the GPLv2 license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2015 Intel Deutschland GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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.
+ *
+ * The full GNU General Public License is included in this distribution
+ * in the file called COPYING.
+ */
 #ifndef __DEVCOREDUMP_H
 #define __DEVCOREDUMP_H
 
@@ -5,17 +24,62 @@
 #include <linux/module.h>
 #include <linux/vmalloc.h>
 
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+/*
+ * _devcd_free_sgtable - free all the memory of the given scatterlist table
+ * (i.e. both pages and scatterlist instances)
+ * NOTE: if two tables allocated and chained using the sg_chain function then
+ * this function should be called only once on the first table
+ * @table: pointer to sg_table to free
+ */
+static inline void _devcd_free_sgtable(struct scatterlist *table)
+{
+	int i;
+	struct page *page;
+	struct scatterlist *iter;
+	struct scatterlist *delete_iter;
+
+	/* free pages */
+	iter = table;
+	for_each_sg(table, iter, sg_nents(table), i) {
+		page = sg_page(iter);
+		if (page)
+			__free_page(page);
+	}
+
+	/* then free all chained tables */
+	iter = table;
+	delete_iter = table;	/* always points on a head of a table */
+	while (!sg_is_last(iter)) {
+		iter++;
+		if (sg_is_chain(iter)) {
+			iter = sg_chain_ptr(iter);
+			kfree(delete_iter);
+			delete_iter = iter;
+		}
+	}
+
+	/* free the last table */
+	kfree(delete_iter);
+}
+
+
 #ifdef CONFIG_DEV_COREDUMP
-void dev_coredumpv(struct device *dev, const void *data, size_t datalen,
+void dev_coredumpv(struct device *dev, void *data, size_t datalen,
 		   gfp_t gfp);
 
 void dev_coredumpm(struct device *dev, struct module *owner,
-		   const void *data, size_t datalen, gfp_t gfp,
+		   void *data, size_t datalen, gfp_t gfp,
 		   ssize_t (*read)(char *buffer, loff_t offset, size_t count,
-				   const void *data, size_t datalen),
-		   void (*free)(const void *data));
+				   void *data, size_t datalen),
+		   void (*free)(void *data));
+
+void dev_coredumpsg(struct device *dev, struct scatterlist *table,
+		    size_t datalen, gfp_t gfp);
 #else
-static inline void dev_coredumpv(struct device *dev, const void *data,
+static inline void dev_coredumpv(struct device *dev, void *data,
 				 size_t datalen, gfp_t gfp)
 {
 	vfree(data);
@@ -23,13 +87,19 @@
 
 static inline void
 dev_coredumpm(struct device *dev, struct module *owner,
-	      const void *data, size_t datalen, gfp_t gfp,
+	      void *data, size_t datalen, gfp_t gfp,
 	      ssize_t (*read)(char *buffer, loff_t offset, size_t count,
-			      const void *data, size_t datalen),
-	      void (*free)(const void *data))
+			      void *data, size_t datalen),
+	      void (*free)(void *data))
 {
 	free(data);
 }
+
+static inline void dev_coredumpsg(struct device *dev, struct scatterlist *table,
+				  size_t datalen, gfp_t gfp)
+{
+	_devcd_free_sgtable(table);
+}
 #endif /* CONFIG_DEV_COREDUMP */
 
 #endif /* __DEVCOREDUMP_H */