Merge branch 'stable-3.2' into pandora-3.2
[pandora-kernel.git] / arch / arm / lib / uaccess_with_memcpy.c
1 /*
2  *  linux/arch/arm/lib/uaccess_with_memcpy.c
3  *
4  *  Written by: Lennert Buytenhek and Nicolas Pitre
5  *  Copyright (C) 2009 Marvell Semiconductor
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  */
11
12 #include <linux/kernel.h>
13 #include <linux/ctype.h>
14 #include <linux/uaccess.h>
15 #include <linux/rwsem.h>
16 #include <linux/mm.h>
17 #include <linux/sched.h>
18 #include <linux/hardirq.h> /* for in_atomic() */
19 #include <linux/gfp.h>
20 #include <linux/highmem.h>
21 #include <asm/current.h>
22 #include <asm/page.h>
23
24 static int
25 pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
26 {
27         unsigned long addr = (unsigned long)_addr;
28         pgd_t *pgd;
29         pmd_t *pmd;
30         pte_t *pte;
31         pud_t *pud;
32         spinlock_t *ptl;
33
34         pgd = pgd_offset(current->mm, addr);
35         if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
36                 return 0;
37
38         pud = pud_offset(pgd, addr);
39         if (unlikely(pud_none(*pud) || pud_bad(*pud)))
40                 return 0;
41
42         pmd = pmd_offset(pud, addr);
43         if (unlikely(pmd_none(*pmd)))
44                 return 0;
45
46         /*
47          * A pmd can be bad if it refers to a HugeTLB or THP page.
48          *
49          * Both THP and HugeTLB pages have the same pmd layout
50          * and should not be manipulated by the pte functions.
51          * 
52          * Lock the page table for the destination and check
53          * to see that it's still huge and whether or not we will
54          * need to fault on write, or if we have a splitting THP.
55          */
56         if (unlikely(pmd_thp_or_huge(*pmd))) {
57                 ptl = &current->mm->page_table_lock;
58                 spin_lock(ptl);
59                 if (unlikely( !pmd_thp_or_huge(*pmd)
60                         || pmd_hugewillfault(*pmd)
61                         || pmd_trans_splitting(*pmd))) {
62                         spin_unlock(ptl);
63                         return 0;
64                 }
65
66                 *ptep = NULL;
67                 *ptlp = ptl;
68                 return 1;
69         }
70
71         if (unlikely(pmd_bad(*pmd)))
72                 return 0;
73
74         pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
75         if (unlikely(!pte_present(*pte) || !pte_young(*pte) ||
76             !pte_write(*pte) || !pte_dirty(*pte))) {
77                 pte_unmap_unlock(pte, ptl);
78                 return 0;
79         }
80
81         *ptep = pte;
82         *ptlp = ptl;
83
84         return 1;
85 }
86
87 static unsigned long noinline
88 __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
89 {
90         int atomic;
91
92         if (unlikely(segment_eq(get_fs(), KERNEL_DS))) {
93                 memcpy((void *)to, from, n);
94                 return 0;
95         }
96
97         /* the mmap semaphore is taken only if not in an atomic context */
98         atomic = in_atomic();
99
100         if (!atomic)
101                 down_read(&current->mm->mmap_sem);
102         while (n) {
103                 pte_t *pte;
104                 spinlock_t *ptl;
105                 int tocopy;
106
107                 while (!pin_page_for_write(to, &pte, &ptl)) {
108                         if (!atomic)
109                                 up_read(&current->mm->mmap_sem);
110                         if (__put_user(0, (char __user *)to))
111                                 goto out;
112                         if (!atomic)
113                                 down_read(&current->mm->mmap_sem);
114                 }
115
116                 tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1;
117                 if (tocopy > n)
118                         tocopy = n;
119
120                 memcpy((void *)to, from, tocopy);
121                 to += tocopy;
122                 from += tocopy;
123                 n -= tocopy;
124
125                 if (pte)
126                         pte_unmap_unlock(pte, ptl);
127                 else
128                         spin_unlock(ptl);
129         }
130         if (!atomic)
131                 up_read(&current->mm->mmap_sem);
132
133 out:
134         return n;
135 }
136
137 unsigned long
138 __copy_to_user(void __user *to, const void *from, unsigned long n)
139 {
140         /*
141          * This test is stubbed out of the main function above to keep
142          * the overhead for small copies low by avoiding a large
143          * register dump on the stack just to reload them right away.
144          * With frame pointer disabled, tail call optimization kicks in
145          * as well making this test almost invisible.
146          */
147         if (n < 64)
148                 return __copy_to_user_std(to, from, n);
149         return __copy_to_user_memcpy(to, from, n);
150 }
151         
152 static unsigned long noinline
153 __clear_user_memset(void __user *addr, unsigned long n)
154 {
155         if (unlikely(segment_eq(get_fs(), KERNEL_DS))) {
156                 memset((void *)addr, 0, n);
157                 return 0;
158         }
159
160         down_read(&current->mm->mmap_sem);
161         while (n) {
162                 pte_t *pte;
163                 spinlock_t *ptl;
164                 int tocopy;
165
166                 while (!pin_page_for_write(addr, &pte, &ptl)) {
167                         up_read(&current->mm->mmap_sem);
168                         if (__put_user(0, (char __user *)addr))
169                                 goto out;
170                         down_read(&current->mm->mmap_sem);
171                 }
172
173                 tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1;
174                 if (tocopy > n)
175                         tocopy = n;
176
177                 memset((void *)addr, 0, tocopy);
178                 addr += tocopy;
179                 n -= tocopy;
180
181                 if (pte)
182                         pte_unmap_unlock(pte, ptl);
183                 else
184                         spin_unlock(ptl);
185         }
186         up_read(&current->mm->mmap_sem);
187
188 out:
189         return n;
190 }
191
192 unsigned long __clear_user(void __user *addr, unsigned long n)
193 {
194         /* See rational for this in __copy_to_user() above. */
195         if (n < 64)
196                 return __clear_user_std(addr, n);
197         return __clear_user_memset(addr, n);
198 }
199
200 #if 0
201
202 /*
203  * This code is disabled by default, but kept around in case the chosen
204  * thresholds need to be revalidated.  Some overhead (small but still)
205  * would be implied by a runtime determined variable threshold, and
206  * so far the measurement on concerned targets didn't show a worthwhile
207  * variation.
208  *
209  * Note that a fairly precise sched_clock() implementation is needed
210  * for results to make some sense.
211  */
212
213 #include <linux/vmalloc.h>
214
215 static int __init test_size_treshold(void)
216 {
217         struct page *src_page, *dst_page;
218         void *user_ptr, *kernel_ptr;
219         unsigned long long t0, t1, t2;
220         int size, ret;
221
222         ret = -ENOMEM;
223         src_page = alloc_page(GFP_KERNEL);
224         if (!src_page)
225                 goto no_src;
226         dst_page = alloc_page(GFP_KERNEL);
227         if (!dst_page)
228                 goto no_dst;
229         kernel_ptr = page_address(src_page);
230         user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__P010));
231         if (!user_ptr)
232                 goto no_vmap;
233
234         /* warm up the src page dcache */
235         ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE);
236
237         for (size = PAGE_SIZE; size >= 4; size /= 2) {
238                 t0 = sched_clock();
239                 ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size);
240                 t1 = sched_clock();
241                 ret |= __copy_to_user_std(user_ptr, kernel_ptr, size);
242                 t2 = sched_clock();
243                 printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
244         }
245
246         for (size = PAGE_SIZE; size >= 4; size /= 2) {
247                 t0 = sched_clock();
248                 ret |= __clear_user_memset(user_ptr, size);
249                 t1 = sched_clock();
250                 ret |= __clear_user_std(user_ptr, size);
251                 t2 = sched_clock();
252                 printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
253         }
254
255         if (ret)
256                 ret = -EFAULT;
257
258         vunmap(user_ptr);
259 no_vmap:
260         put_page(dst_page);
261 no_dst:
262         put_page(src_page);
263 no_src:
264         return ret;
265 }
266
267 subsys_initcall(test_size_treshold);
268
269 #endif