Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * linux/arch/arm/lib/uaccess_with_memcpy.c
4 : : *
5 : : * Written by: Lennert Buytenhek and Nicolas Pitre
6 : : * Copyright (C) 2009 Marvell Semiconductor
7 : : */
8 : :
9 : : #include <linux/kernel.h>
10 : : #include <linux/ctype.h>
11 : : #include <linux/uaccess.h>
12 : : #include <linux/rwsem.h>
13 : : #include <linux/mm.h>
14 : : #include <linux/sched.h>
15 : : #include <linux/hardirq.h> /* for in_atomic() */
16 : : #include <linux/gfp.h>
17 : : #include <linux/highmem.h>
18 : : #include <linux/hugetlb.h>
19 : : #include <asm/current.h>
20 : : #include <asm/page.h>
21 : :
22 : : #ifndef COPY_FROM_USER_THRESHOLD
23 : : #define COPY_FROM_USER_THRESHOLD 64
24 : : #endif
25 : :
26 : : #ifndef COPY_TO_USER_THRESHOLD
27 : : #define COPY_TO_USER_THRESHOLD 64
28 : : #endif
29 : :
30 : : static int
31 : 62129275 : pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
32 : : {
33 : 62129275 : unsigned long addr = (unsigned long)_addr;
34 : : pgd_t *pgd;
35 : : pmd_t *pmd;
36 : : pte_t *pte;
37 : : pud_t *pud;
38 : : spinlock_t *ptl;
39 : :
40 : 62129275 : pgd = pgd_offset(current->mm, addr);
41 : : if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
42 : : return 0;
43 : :
44 : : pud = pud_offset(pgd, addr);
45 : : if (unlikely(pud_none(*pud) || pud_bad(*pud)))
46 : : return 0;
47 : :
48 : : pmd = pmd_offset(pud, addr);
49 [ + + ]: 62129275 : if (unlikely(pmd_none(*pmd)))
50 : : return 0;
51 : :
52 : : /*
53 : : * A pmd can be bad if it refers to a HugeTLB or THP page.
54 : : *
55 : : * Both THP and HugeTLB pages have the same pmd layout
56 : : * and should not be manipulated by the pte functions.
57 : : *
58 : : * Lock the page table for the destination and check
59 : : * to see that it's still huge and whether or not we will
60 : : * need to fault on write.
61 : : */
62 : : if (unlikely(pmd_thp_or_huge(*pmd))) {
63 : : ptl = ¤t->mm->page_table_lock;
64 : : spin_lock(ptl);
65 : : if (unlikely(!pmd_thp_or_huge(*pmd)
66 : : || pmd_hugewillfault(*pmd))) {
67 : : spin_unlock(ptl);
68 : : return 0;
69 : : }
70 : :
71 : : *ptep = NULL;
72 : : *ptlp = ptl;
73 : : return 1;
74 : : }
75 : :
76 [ + + ]: 60512385 : if (unlikely(pmd_bad(*pmd)))
77 : : return 0;
78 : :
79 : 60511558 : pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
80 [ + + ]: 60528744 : if (unlikely(!pte_present(*pte) || !pte_young(*pte) ||
81 : : !pte_write(*pte) || !pte_dirty(*pte))) {
82 : : pte_unmap_unlock(pte, ptl);
83 : 1145281 : return 0;
84 : : }
85 : :
86 : 59383462 : *ptep = pte;
87 : 59383462 : *ptlp = ptl;
88 : :
89 : 59383462 : return 1;
90 : : }
91 : :
92 : : static int
93 : 0 : pin_page_for_read(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
94 : : {
95 : 0 : unsigned long addr = (unsigned long)_addr;
96 : : pgd_t *pgd;
97 : : pmd_t *pmd;
98 : : pte_t *pte;
99 : : pud_t *pud;
100 : : spinlock_t *ptl;
101 : :
102 : 0 : pgd = pgd_offset(current->mm, addr);
103 : : if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
104 : : {
105 : : return 0;
106 : : }
107 : : pud = pud_offset(pgd, addr);
108 : : if (unlikely(pud_none(*pud) || pud_bad(*pud)))
109 : : {
110 : : return 0;
111 : : }
112 : :
113 : : pmd = pmd_offset(pud, addr);
114 [ # # # # ]: 0 : if (unlikely(pmd_none(*pmd) || pmd_bad(*pmd)))
115 : : return 0;
116 : :
117 : 0 : pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
118 [ # # ]: 0 : if (unlikely(!pte_present(*pte) || !pte_young(*pte))) {
119 : : pte_unmap_unlock(pte, ptl);
120 : 0 : return 0;
121 : : }
122 : :
123 : 0 : *ptep = pte;
124 : 0 : *ptlp = ptl;
125 : :
126 : 0 : return 1;
127 : : }
128 : :
129 : : unsigned long noinline
130 : 48202838 : __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
131 : : {
132 : : unsigned long ua_flags;
133 : : int atomic;
134 : :
135 [ - + ]: 48202838 : if (uaccess_kernel()) {
136 : 0 : memcpy((void *)to, from, n);
137 : 0 : return 0;
138 : : }
139 : :
140 : : /* the mmap semaphore is taken only if not in an atomic context */
141 [ + + - + ]: 96426674 : atomic = faulthandler_disabled();
142 : :
143 [ + + ]: 48241625 : if (!atomic)
144 : 48190721 : down_read(¤t->mm->mmap_sem);
145 [ + + ]: 105954003 : while (n) {
146 : : pte_t *pte;
147 : : spinlock_t *ptl;
148 : : int tocopy;
149 : :
150 [ + + ]: 58861606 : while (!pin_page_for_write(to, &pte, &ptl)) {
151 [ + - ]: 1145282 : if (!atomic)
152 : 1145282 : up_read(¤t->mm->mmap_sem);
153 [ + - ]: 1145282 : if (__put_user(0, (char __user *)to))
154 : : goto out;
155 [ + - ]: 1145279 : if (!atomic)
156 : 1145281 : down_read(¤t->mm->mmap_sem);
157 : : }
158 : :
159 : 57730844 : tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1;
160 [ + + ]: 57730844 : if (tocopy > n)
161 : 48085602 : tocopy = n;
162 : :
163 : : ua_flags = uaccess_save_and_enable();
164 : 57730844 : memcpy((void *)to, from, tocopy);
165 : : uaccess_restore(ua_flags);
166 : 57730844 : to += tocopy;
167 : 57730844 : from += tocopy;
168 : 57730844 : n -= tocopy;
169 : :
170 [ + - ]: 57730844 : if (pte)
171 : 57730844 : pte_unmap_unlock(pte, ptl);
172 : : else
173 : 0 : spin_unlock(ptl);
174 : : }
175 [ + + ]: 48233167 : if (!atomic)
176 : 48203869 : up_read(¤t->mm->mmap_sem);
177 : :
178 : : out:
179 : 48220955 : return n;
180 : : }
181 : :
182 : : unsigned long noinline
183 : 0 : __copy_from_user_memcpy(void *to, const void __user *from, unsigned long n)
184 : : {
185 : : unsigned long ua_flags;
186 : : int atomic;
187 : :
188 [ # # ]: 0 : if (unlikely(segment_eq(get_fs(), KERNEL_DS))) {
189 : 0 : memcpy(to, (const void *)from, n);
190 : 0 : return 0;
191 : : }
192 : :
193 : : /* the mmap semaphore is taken only if not in an atomic context */
194 : 0 : atomic = in_atomic();
195 : :
196 [ # # ]: 0 : if (!atomic)
197 : 0 : down_read(¤t->mm->mmap_sem);
198 [ # # ]: 0 : while (n) {
199 : : pte_t *pte;
200 : : spinlock_t *ptl;
201 : : int tocopy;
202 : :
203 [ # # ]: 0 : while (!pin_page_for_read(from, &pte, &ptl)) {
204 : : char temp;
205 [ # # ]: 0 : if (!atomic)
206 : 0 : up_read(¤t->mm->mmap_sem);
207 [ # # ]: 0 : if (__get_user(temp, (char __user *)from))
208 : : goto out;
209 [ # # ]: 0 : if (!atomic)
210 : 0 : down_read(¤t->mm->mmap_sem);
211 : : }
212 : :
213 : 0 : tocopy = (~(unsigned long)from & ~PAGE_MASK) + 1;
214 [ # # ]: 0 : if (tocopy > n)
215 : 0 : tocopy = n;
216 : :
217 : : ua_flags = uaccess_save_and_enable();
218 : 0 : memcpy(to, (const void *)from, tocopy);
219 : : uaccess_restore(ua_flags);
220 : 0 : to += tocopy;
221 : 0 : from += tocopy;
222 : 0 : n -= tocopy;
223 : :
224 : 0 : pte_unmap_unlock(pte, ptl);
225 : : }
226 [ # # ]: 0 : if (!atomic)
227 : 0 : up_read(¤t->mm->mmap_sem);
228 : :
229 : : out:
230 : 0 : return n;
231 : : }
232 : :
233 : : unsigned long
234 : 131992825 : arm_copy_to_user(void __user *to, const void *from, unsigned long n)
235 : : {
236 : : /*
237 : : * This test is stubbed out of the main function above to keep
238 : : * the overhead for small copies low by avoiding a large
239 : : * register dump on the stack just to reload them right away.
240 : : * With frame pointer disabled, tail call optimization kicks in
241 : : * as well making this test almost invisible.
242 : : */
243 [ + + ]: 131992825 : if (n < COPY_TO_USER_THRESHOLD) {
244 : : unsigned long ua_flags = uaccess_save_and_enable();
245 : 83780043 : n = __copy_to_user_std(to, from, n);
246 : : uaccess_restore(ua_flags);
247 : : } else {
248 : 48231871 : n = __copy_to_user_memcpy(uaccess_mask_range_ptr(to, n),
249 : : from, n);
250 : : }
251 : 132008500 : return n;
252 : : }
253 : :
254 : : unsigned long __must_check
255 : 40424238 : arm_copy_from_user(void *to, const void __user *from, unsigned long n)
256 : : {
257 : : #ifdef CONFIG_BCM2835_FAST_MEMCPY
258 : : /*
259 : : * This test is stubbed out of the main function above to keep
260 : : * the overhead for small copies low by avoiding a large
261 : : * register dump on the stack just to reload them right away.
262 : : * With frame pointer disabled, tail call optimization kicks in
263 : : * as well making this test almost invisible.
264 : : */
265 : : if (n < COPY_TO_USER_THRESHOLD) {
266 : : unsigned long ua_flags = uaccess_save_and_enable();
267 : : n = __copy_from_user_std(to, from, n);
268 : : uaccess_restore(ua_flags);
269 : : } else {
270 : : n = __copy_from_user_memcpy(to, from, n);
271 : : }
272 : : #else
273 : : unsigned long ua_flags = uaccess_save_and_enable();
274 : 40424238 : n = __copy_from_user_std(to, from, n);
275 : : uaccess_restore(ua_flags);
276 : : #endif
277 : 40425182 : return n;
278 : : }
279 : :
280 : : static unsigned long noinline
281 : 1649691 : __clear_user_memset(void __user *addr, unsigned long n)
282 : : {
283 : : unsigned long ua_flags;
284 : :
285 [ - + ]: 1649691 : if (uaccess_kernel()) {
286 : 0 : memset((void *)addr, 0, n);
287 : 0 : return 0;
288 : : }
289 : :
290 : 1649691 : down_read(¤t->mm->mmap_sem);
291 [ + + ]: 1649828 : while (n) {
292 : : pte_t *pte;
293 : : spinlock_t *ptl;
294 : : int tocopy;
295 : :
296 [ + + ]: 3291052 : while (!pin_page_for_write(addr, &pte, &ptl)) {
297 : 1641187 : up_read(¤t->mm->mmap_sem);
298 [ + - ]: 1641351 : if (__put_user(0, (char __user *)addr))
299 : : goto out;
300 : 1641348 : down_read(¤t->mm->mmap_sem);
301 : : }
302 : :
303 : 1649403 : tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1;
304 [ + + ]: 1649403 : if (tocopy > n)
305 : 8471 : tocopy = n;
306 : :
307 : : ua_flags = uaccess_save_and_enable();
308 : 1649403 : memset((void *)addr, 0, tocopy);
309 : : uaccess_restore(ua_flags);
310 : 1649403 : addr += tocopy;
311 : 1649403 : n -= tocopy;
312 : :
313 [ + - ]: 1649403 : if (pte)
314 : 1649403 : pte_unmap_unlock(pte, ptl);
315 : : else
316 : 0 : spin_unlock(ptl);
317 : : }
318 : 1649453 : up_read(¤t->mm->mmap_sem);
319 : :
320 : : out:
321 : 1649830 : return n;
322 : : }
323 : :
324 : 1655710 : unsigned long arm_clear_user(void __user *addr, unsigned long n)
325 : : {
326 : : /* See rational for this in __copy_to_user() above. */
327 [ + + ]: 1655710 : if (n < 64) {
328 : : unsigned long ua_flags = uaccess_save_and_enable();
329 : 5929 : n = __clear_user_std(addr, n);
330 : : uaccess_restore(ua_flags);
331 : : } else {
332 : 1649781 : n = __clear_user_memset(addr, n);
333 : : }
334 : 1655735 : return n;
335 : : }
336 : :
337 : : #if 0
338 : :
339 : : /*
340 : : * This code is disabled by default, but kept around in case the chosen
341 : : * thresholds need to be revalidated. Some overhead (small but still)
342 : : * would be implied by a runtime determined variable threshold, and
343 : : * so far the measurement on concerned targets didn't show a worthwhile
344 : : * variation.
345 : : *
346 : : * Note that a fairly precise sched_clock() implementation is needed
347 : : * for results to make some sense.
348 : : */
349 : :
350 : : #include <linux/vmalloc.h>
351 : :
352 : : static int __init test_size_treshold(void)
353 : : {
354 : : struct page *src_page, *dst_page;
355 : : void *user_ptr, *kernel_ptr;
356 : : unsigned long long t0, t1, t2;
357 : : int size, ret;
358 : :
359 : : ret = -ENOMEM;
360 : : src_page = alloc_page(GFP_KERNEL);
361 : : if (!src_page)
362 : : goto no_src;
363 : : dst_page = alloc_page(GFP_KERNEL);
364 : : if (!dst_page)
365 : : goto no_dst;
366 : : kernel_ptr = page_address(src_page);
367 : : user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__P010));
368 : : if (!user_ptr)
369 : : goto no_vmap;
370 : :
371 : : /* warm up the src page dcache */
372 : : ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE);
373 : :
374 : : for (size = PAGE_SIZE; size >= 4; size /= 2) {
375 : : t0 = sched_clock();
376 : : ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size);
377 : : t1 = sched_clock();
378 : : ret |= __copy_to_user_std(user_ptr, kernel_ptr, size);
379 : : t2 = sched_clock();
380 : : printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
381 : : }
382 : :
383 : : for (size = PAGE_SIZE; size >= 4; size /= 2) {
384 : : t0 = sched_clock();
385 : : ret |= __clear_user_memset(user_ptr, size);
386 : : t1 = sched_clock();
387 : : ret |= __clear_user_std(user_ptr, size);
388 : : t2 = sched_clock();
389 : : printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
390 : : }
391 : :
392 : : if (ret)
393 : : ret = -EFAULT;
394 : :
395 : : vunmap(user_ptr);
396 : : no_vmap:
397 : : put_page(dst_page);
398 : : no_dst:
399 : : put_page(src_page);
400 : : no_src:
401 : : return ret;
402 : : }
403 : :
404 : : subsys_initcall(test_size_treshold);
405 : :
406 : : #endif
|