18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * linux/arch/arm/lib/uaccess_with_memcpy.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Written by: Lennert Buytenhek and Nicolas Pitre 68c2ecf20Sopenharmony_ci * Copyright (C) 2009 Marvell Semiconductor 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/ctype.h> 118c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 128c2ecf20Sopenharmony_ci#include <linux/rwsem.h> 138c2ecf20Sopenharmony_ci#include <linux/mm.h> 148c2ecf20Sopenharmony_ci#include <linux/sched.h> 158c2ecf20Sopenharmony_ci#include <linux/hardirq.h> /* for in_atomic() */ 168c2ecf20Sopenharmony_ci#include <linux/gfp.h> 178c2ecf20Sopenharmony_ci#include <linux/highmem.h> 188c2ecf20Sopenharmony_ci#include <linux/hugetlb.h> 198c2ecf20Sopenharmony_ci#include <asm/current.h> 208c2ecf20Sopenharmony_ci#include <asm/page.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic int 238c2ecf20Sopenharmony_cipin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci unsigned long addr = (unsigned long)_addr; 268c2ecf20Sopenharmony_ci pgd_t *pgd; 278c2ecf20Sopenharmony_ci p4d_t *p4d; 288c2ecf20Sopenharmony_ci pmd_t *pmd; 298c2ecf20Sopenharmony_ci pte_t *pte; 308c2ecf20Sopenharmony_ci pud_t *pud; 318c2ecf20Sopenharmony_ci spinlock_t *ptl; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci pgd = pgd_offset(current->mm, addr); 348c2ecf20Sopenharmony_ci if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd))) 358c2ecf20Sopenharmony_ci return 0; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci p4d = p4d_offset(pgd, addr); 388c2ecf20Sopenharmony_ci if (unlikely(p4d_none(*p4d) || p4d_bad(*p4d))) 398c2ecf20Sopenharmony_ci return 0; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci pud = pud_offset(p4d, addr); 428c2ecf20Sopenharmony_ci if (unlikely(pud_none(*pud) || pud_bad(*pud))) 438c2ecf20Sopenharmony_ci return 0; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci pmd = pmd_offset(pud, addr); 468c2ecf20Sopenharmony_ci if (unlikely(pmd_none(*pmd))) 478c2ecf20Sopenharmony_ci return 0; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* 508c2ecf20Sopenharmony_ci * A pmd can be bad if it refers to a HugeTLB or THP page. 518c2ecf20Sopenharmony_ci * 528c2ecf20Sopenharmony_ci * Both THP and HugeTLB pages have the same pmd layout 538c2ecf20Sopenharmony_ci * and should not be manipulated by the pte functions. 548c2ecf20Sopenharmony_ci * 558c2ecf20Sopenharmony_ci * Lock the page table for the destination and check 568c2ecf20Sopenharmony_ci * to see that it's still huge and whether or not we will 578c2ecf20Sopenharmony_ci * need to fault on write. 588c2ecf20Sopenharmony_ci */ 598c2ecf20Sopenharmony_ci if (unlikely(pmd_thp_or_huge(*pmd))) { 608c2ecf20Sopenharmony_ci ptl = ¤t->mm->page_table_lock; 618c2ecf20Sopenharmony_ci spin_lock(ptl); 628c2ecf20Sopenharmony_ci if (unlikely(!pmd_thp_or_huge(*pmd) 638c2ecf20Sopenharmony_ci || pmd_hugewillfault(*pmd))) { 648c2ecf20Sopenharmony_ci spin_unlock(ptl); 658c2ecf20Sopenharmony_ci return 0; 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci *ptep = NULL; 698c2ecf20Sopenharmony_ci *ptlp = ptl; 708c2ecf20Sopenharmony_ci return 1; 718c2ecf20Sopenharmony_ci } 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (unlikely(pmd_bad(*pmd))) 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl); 778c2ecf20Sopenharmony_ci if (unlikely(!pte_present(*pte) || !pte_young(*pte) || 788c2ecf20Sopenharmony_ci !pte_write(*pte) || !pte_dirty(*pte))) { 798c2ecf20Sopenharmony_ci pte_unmap_unlock(pte, ptl); 808c2ecf20Sopenharmony_ci return 0; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci *ptep = pte; 848c2ecf20Sopenharmony_ci *ptlp = ptl; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci return 1; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic unsigned long noinline 908c2ecf20Sopenharmony_ci__copy_to_user_memcpy(void __user *to, const void *from, unsigned long n) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci unsigned long ua_flags; 938c2ecf20Sopenharmony_ci int atomic; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (uaccess_kernel()) { 968c2ecf20Sopenharmony_ci memcpy((void *)to, from, n); 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci /* the mmap semaphore is taken only if not in an atomic context */ 1018c2ecf20Sopenharmony_ci atomic = faulthandler_disabled(); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci if (!atomic) 1048c2ecf20Sopenharmony_ci mmap_read_lock(current->mm); 1058c2ecf20Sopenharmony_ci while (n) { 1068c2ecf20Sopenharmony_ci pte_t *pte; 1078c2ecf20Sopenharmony_ci spinlock_t *ptl; 1088c2ecf20Sopenharmony_ci int tocopy; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci while (!pin_page_for_write(to, &pte, &ptl)) { 1118c2ecf20Sopenharmony_ci if (!atomic) 1128c2ecf20Sopenharmony_ci mmap_read_unlock(current->mm); 1138c2ecf20Sopenharmony_ci if (__put_user(0, (char __user *)to)) 1148c2ecf20Sopenharmony_ci goto out; 1158c2ecf20Sopenharmony_ci if (!atomic) 1168c2ecf20Sopenharmony_ci mmap_read_lock(current->mm); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1; 1208c2ecf20Sopenharmony_ci if (tocopy > n) 1218c2ecf20Sopenharmony_ci tocopy = n; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci ua_flags = uaccess_save_and_enable(); 1248c2ecf20Sopenharmony_ci memcpy((void *)to, from, tocopy); 1258c2ecf20Sopenharmony_ci uaccess_restore(ua_flags); 1268c2ecf20Sopenharmony_ci to += tocopy; 1278c2ecf20Sopenharmony_ci from += tocopy; 1288c2ecf20Sopenharmony_ci n -= tocopy; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (pte) 1318c2ecf20Sopenharmony_ci pte_unmap_unlock(pte, ptl); 1328c2ecf20Sopenharmony_ci else 1338c2ecf20Sopenharmony_ci spin_unlock(ptl); 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci if (!atomic) 1368c2ecf20Sopenharmony_ci mmap_read_unlock(current->mm); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ciout: 1398c2ecf20Sopenharmony_ci return n; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ciunsigned long 1438c2ecf20Sopenharmony_ciarm_copy_to_user(void __user *to, const void *from, unsigned long n) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci /* 1468c2ecf20Sopenharmony_ci * This test is stubbed out of the main function above to keep 1478c2ecf20Sopenharmony_ci * the overhead for small copies low by avoiding a large 1488c2ecf20Sopenharmony_ci * register dump on the stack just to reload them right away. 1498c2ecf20Sopenharmony_ci * With frame pointer disabled, tail call optimization kicks in 1508c2ecf20Sopenharmony_ci * as well making this test almost invisible. 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_ci if (n < 64) { 1538c2ecf20Sopenharmony_ci unsigned long ua_flags = uaccess_save_and_enable(); 1548c2ecf20Sopenharmony_ci n = __copy_to_user_std(to, from, n); 1558c2ecf20Sopenharmony_ci uaccess_restore(ua_flags); 1568c2ecf20Sopenharmony_ci } else { 1578c2ecf20Sopenharmony_ci n = __copy_to_user_memcpy(uaccess_mask_range_ptr(to, n), 1588c2ecf20Sopenharmony_ci from, n); 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci return n; 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic unsigned long noinline 1648c2ecf20Sopenharmony_ci__clear_user_memset(void __user *addr, unsigned long n) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci unsigned long ua_flags; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (uaccess_kernel()) { 1698c2ecf20Sopenharmony_ci memset((void *)addr, 0, n); 1708c2ecf20Sopenharmony_ci return 0; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci mmap_read_lock(current->mm); 1748c2ecf20Sopenharmony_ci while (n) { 1758c2ecf20Sopenharmony_ci pte_t *pte; 1768c2ecf20Sopenharmony_ci spinlock_t *ptl; 1778c2ecf20Sopenharmony_ci int tocopy; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci while (!pin_page_for_write(addr, &pte, &ptl)) { 1808c2ecf20Sopenharmony_ci mmap_read_unlock(current->mm); 1818c2ecf20Sopenharmony_ci if (__put_user(0, (char __user *)addr)) 1828c2ecf20Sopenharmony_ci goto out; 1838c2ecf20Sopenharmony_ci mmap_read_lock(current->mm); 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1; 1878c2ecf20Sopenharmony_ci if (tocopy > n) 1888c2ecf20Sopenharmony_ci tocopy = n; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci ua_flags = uaccess_save_and_enable(); 1918c2ecf20Sopenharmony_ci memset((void *)addr, 0, tocopy); 1928c2ecf20Sopenharmony_ci uaccess_restore(ua_flags); 1938c2ecf20Sopenharmony_ci addr += tocopy; 1948c2ecf20Sopenharmony_ci n -= tocopy; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci if (pte) 1978c2ecf20Sopenharmony_ci pte_unmap_unlock(pte, ptl); 1988c2ecf20Sopenharmony_ci else 1998c2ecf20Sopenharmony_ci spin_unlock(ptl); 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci mmap_read_unlock(current->mm); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ciout: 2048c2ecf20Sopenharmony_ci return n; 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ciunsigned long arm_clear_user(void __user *addr, unsigned long n) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci /* See rational for this in __copy_to_user() above. */ 2108c2ecf20Sopenharmony_ci if (n < 64) { 2118c2ecf20Sopenharmony_ci unsigned long ua_flags = uaccess_save_and_enable(); 2128c2ecf20Sopenharmony_ci n = __clear_user_std(addr, n); 2138c2ecf20Sopenharmony_ci uaccess_restore(ua_flags); 2148c2ecf20Sopenharmony_ci } else { 2158c2ecf20Sopenharmony_ci n = __clear_user_memset(addr, n); 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci return n; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci#if 0 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci/* 2238c2ecf20Sopenharmony_ci * This code is disabled by default, but kept around in case the chosen 2248c2ecf20Sopenharmony_ci * thresholds need to be revalidated. Some overhead (small but still) 2258c2ecf20Sopenharmony_ci * would be implied by a runtime determined variable threshold, and 2268c2ecf20Sopenharmony_ci * so far the measurement on concerned targets didn't show a worthwhile 2278c2ecf20Sopenharmony_ci * variation. 2288c2ecf20Sopenharmony_ci * 2298c2ecf20Sopenharmony_ci * Note that a fairly precise sched_clock() implementation is needed 2308c2ecf20Sopenharmony_ci * for results to make some sense. 2318c2ecf20Sopenharmony_ci */ 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic int __init test_size_treshold(void) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci struct page *src_page, *dst_page; 2388c2ecf20Sopenharmony_ci void *user_ptr, *kernel_ptr; 2398c2ecf20Sopenharmony_ci unsigned long long t0, t1, t2; 2408c2ecf20Sopenharmony_ci int size, ret; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci ret = -ENOMEM; 2438c2ecf20Sopenharmony_ci src_page = alloc_page(GFP_KERNEL); 2448c2ecf20Sopenharmony_ci if (!src_page) 2458c2ecf20Sopenharmony_ci goto no_src; 2468c2ecf20Sopenharmony_ci dst_page = alloc_page(GFP_KERNEL); 2478c2ecf20Sopenharmony_ci if (!dst_page) 2488c2ecf20Sopenharmony_ci goto no_dst; 2498c2ecf20Sopenharmony_ci kernel_ptr = page_address(src_page); 2508c2ecf20Sopenharmony_ci user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__P010)); 2518c2ecf20Sopenharmony_ci if (!user_ptr) 2528c2ecf20Sopenharmony_ci goto no_vmap; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* warm up the src page dcache */ 2558c2ecf20Sopenharmony_ci ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci for (size = PAGE_SIZE; size >= 4; size /= 2) { 2588c2ecf20Sopenharmony_ci t0 = sched_clock(); 2598c2ecf20Sopenharmony_ci ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size); 2608c2ecf20Sopenharmony_ci t1 = sched_clock(); 2618c2ecf20Sopenharmony_ci ret |= __copy_to_user_std(user_ptr, kernel_ptr, size); 2628c2ecf20Sopenharmony_ci t2 = sched_clock(); 2638c2ecf20Sopenharmony_ci printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1); 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci for (size = PAGE_SIZE; size >= 4; size /= 2) { 2678c2ecf20Sopenharmony_ci t0 = sched_clock(); 2688c2ecf20Sopenharmony_ci ret |= __clear_user_memset(user_ptr, size); 2698c2ecf20Sopenharmony_ci t1 = sched_clock(); 2708c2ecf20Sopenharmony_ci ret |= __clear_user_std(user_ptr, size); 2718c2ecf20Sopenharmony_ci t2 = sched_clock(); 2728c2ecf20Sopenharmony_ci printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1); 2738c2ecf20Sopenharmony_ci } 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci if (ret) 2768c2ecf20Sopenharmony_ci ret = -EFAULT; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci vunmap(user_ptr); 2798c2ecf20Sopenharmony_cino_vmap: 2808c2ecf20Sopenharmony_ci put_page(dst_page); 2818c2ecf20Sopenharmony_cino_dst: 2828c2ecf20Sopenharmony_ci put_page(src_page); 2838c2ecf20Sopenharmony_cino_src: 2848c2ecf20Sopenharmony_ci return ret; 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cisubsys_initcall(test_size_treshold); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci#endif 290