From f3978e548747dfe988016ef7c99fe50975da90df Mon Sep 17 00:00:00 2001 From: Vincent Sanders 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 --- .../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