From f3978e548747dfe988016ef7c99fe50975da90df Mon Sep 17 00:00:00 2001
From: Vincent Sanders <vincent.sanders@collabora.co.uk>
Date: Mon, 2 Sep 2013 16:44:57 +0100
Subject: [PATCH 108/196] vchiq: create_pagelist copes with vmalloc memory

Signed-off-by: Daniel Stone <daniels@collabora.com>
---
 .../interface/vchiq_arm/vchiq_2835_arm.c           | 83 ++++++++++++++--------
 1 file changed, 53 insertions(+), 30 deletions(-)

diff --git a/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c b/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c
index 2b5fa56..b3bdaa2 100644
--- a/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c
+++ b/drivers/misc/vc04_services/interface/vchiq_arm/vchiq_2835_arm.c
@@ -374,6 +374,7 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 	unsigned int num_pages, offset, i;
 	char *addr, *base_addr, *next_addr;
 	int run, addridx, actual_pages;
+        unsigned long *need_release;
 
 	offset = (unsigned int)buf & (PAGE_SIZE - 1);
 	num_pages = (count + offset + PAGE_SIZE - 1) / PAGE_SIZE;
@@ -384,9 +385,10 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 	** list
 	*/
 	pagelist = kmalloc(sizeof(PAGELIST_T) +
-		(num_pages * sizeof(unsigned long)) +
-		(num_pages * sizeof(pages[0])),
-		GFP_KERNEL);
+                           (num_pages * sizeof(unsigned long)) +
+                           sizeof(unsigned long) +
+                           (num_pages * sizeof(pages[0])),
+                           GFP_KERNEL);
 
 	vchiq_log_trace(vchiq_arm_log_level,
 		"create_pagelist - %x", (unsigned int)pagelist);
@@ -394,28 +396,44 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 		return -ENOMEM;
 
 	addrs = pagelist->addrs;
-	pages = (struct page **)(addrs + num_pages);
+        need_release = (unsigned long *)(addrs + num_pages);
+	pages = (struct page **)(addrs + num_pages + 1);
 
-	down_read(&task->mm->mmap_sem);
-	actual_pages = get_user_pages(task, task->mm,
-		(unsigned long)buf & ~(PAGE_SIZE - 1), num_pages,
-		(type == PAGELIST_READ) /*Write */ , 0 /*Force */ ,
-		pages, NULL /*vmas */);
-	up_read(&task->mm->mmap_sem);
-
-   if (actual_pages != num_pages)
-   {
-      /* This is probably due to the process being killed */
-      while (actual_pages > 0)
-      {
-         actual_pages--;
-         page_cache_release(pages[actual_pages]);
-      }
-      kfree(pagelist);
-      if (actual_pages == 0)
-         actual_pages = -ENOMEM;
-      return actual_pages;
-   }
+	if (is_vmalloc_addr(buf)) {
+		for (actual_pages = 0; actual_pages < num_pages; actual_pages++) {
+			pages[actual_pages] = vmalloc_to_page(buf + (actual_pages * PAGE_SIZE));
+		}
+                *need_release = 0; /* do not try and release vmalloc pages */
+	} else {
+		down_read(&task->mm->mmap_sem);
+		actual_pages = get_user_pages(task, task->mm,
+				          (unsigned long)buf & ~(PAGE_SIZE - 1),
+					  num_pages,
+					  (type == PAGELIST_READ) /*Write */ ,
+					  0 /*Force */ ,
+					  pages,
+					  NULL /*vmas */);
+		up_read(&task->mm->mmap_sem);
+
+		if (actual_pages != num_pages) {
+			vchiq_log_info(vchiq_arm_log_level,
+				       "create_pagelist - only %d/%d pages locked",
+				       actual_pages,
+				       num_pages);
+
+			/* This is probably due to the process being killed */
+			while (actual_pages > 0)
+			{
+				actual_pages--;
+				page_cache_release(pages[actual_pages]);
+			}
+			kfree(pagelist);
+			if (actual_pages == 0)
+				actual_pages = -ENOMEM;
+			return actual_pages;
+		}
+                *need_release = 1; /* release user pages */
+	}
 
 	pagelist->length = count;
 	pagelist->type = type;
@@ -482,6 +500,7 @@ create_pagelist(char __user *buf, size_t count, unsigned short type,
 static void
 free_pagelist(PAGELIST_T *pagelist, int actual)
 {
+        unsigned long *need_release;
 	struct page **pages;
 	unsigned int num_pages, i;
 
@@ -492,7 +511,8 @@ free_pagelist(PAGELIST_T *pagelist, int actual)
 		(pagelist->length + pagelist->offset + PAGE_SIZE - 1) /
 		PAGE_SIZE;
 
-	pages = (struct page **)(pagelist->addrs + num_pages);
+        need_release = (unsigned long *)(pagelist->addrs + num_pages);
+	pages = (struct page **)(pagelist->addrs + num_pages + 1);
 
 	/* Deal with any partial cache lines (fragments) */
 	if (pagelist->type >= PAGELIST_READ_WITH_FRAGMENTS) {
@@ -528,11 +548,14 @@ free_pagelist(PAGELIST_T *pagelist, int actual)
 		up(&g_free_fragments_sema);
 	}
 
-	for (i = 0; i < num_pages; i++) {
-		if (pagelist->type != PAGELIST_WRITE)
-			set_page_dirty(pages[i]);
-		page_cache_release(pages[i]);
-	}
+        if (*need_release) {
+		for (i = 0; i < num_pages; i++) {
+			if (pagelist->type != PAGELIST_WRITE)
+				set_page_dirty(pages[i]);
+
+			page_cache_release(pages[i]);
+		}
+        }
 
 	kfree(pagelist);
 }
-- 
1.9.1

