162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * linux/arch/arm/lib/uaccess_with_memcpy.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Written by: Lennert Buytenhek and Nicolas Pitre 662306a36Sopenharmony_ci * Copyright (C) 2009 Marvell Semiconductor 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/ctype.h> 1162306a36Sopenharmony_ci#include <linux/uaccess.h> 1262306a36Sopenharmony_ci#include <linux/rwsem.h> 1362306a36Sopenharmony_ci#include <linux/mm.h> 1462306a36Sopenharmony_ci#include <linux/sched.h> 1562306a36Sopenharmony_ci#include <linux/hardirq.h> /* for in_atomic() */ 1662306a36Sopenharmony_ci#include <linux/gfp.h> 1762306a36Sopenharmony_ci#include <linux/highmem.h> 1862306a36Sopenharmony_ci#include <linux/hugetlb.h> 1962306a36Sopenharmony_ci#include <asm/current.h> 2062306a36Sopenharmony_ci#include <asm/page.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic int 2362306a36Sopenharmony_cipin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci unsigned long addr = (unsigned long)_addr; 2662306a36Sopenharmony_ci pgd_t *pgd; 2762306a36Sopenharmony_ci p4d_t *p4d; 2862306a36Sopenharmony_ci pmd_t *pmd; 2962306a36Sopenharmony_ci pte_t *pte; 3062306a36Sopenharmony_ci pud_t *pud; 3162306a36Sopenharmony_ci spinlock_t *ptl; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci pgd = pgd_offset(current->mm, addr); 3462306a36Sopenharmony_ci if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd))) 3562306a36Sopenharmony_ci return 0; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci p4d = p4d_offset(pgd, addr); 3862306a36Sopenharmony_ci if (unlikely(p4d_none(*p4d) || p4d_bad(*p4d))) 3962306a36Sopenharmony_ci return 0; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci pud = pud_offset(p4d, addr); 4262306a36Sopenharmony_ci if (unlikely(pud_none(*pud) || pud_bad(*pud))) 4362306a36Sopenharmony_ci return 0; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci pmd = pmd_offset(pud, addr); 4662306a36Sopenharmony_ci if (unlikely(pmd_none(*pmd))) 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* 5062306a36Sopenharmony_ci * A pmd can be bad if it refers to a HugeTLB or THP page. 5162306a36Sopenharmony_ci * 5262306a36Sopenharmony_ci * Both THP and HugeTLB pages have the same pmd layout 5362306a36Sopenharmony_ci * and should not be manipulated by the pte functions. 5462306a36Sopenharmony_ci * 5562306a36Sopenharmony_ci * Lock the page table for the destination and check 5662306a36Sopenharmony_ci * to see that it's still huge and whether or not we will 5762306a36Sopenharmony_ci * need to fault on write. 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_ci if (unlikely(pmd_thp_or_huge(*pmd))) { 6062306a36Sopenharmony_ci ptl = ¤t->mm->page_table_lock; 6162306a36Sopenharmony_ci spin_lock(ptl); 6262306a36Sopenharmony_ci if (unlikely(!pmd_thp_or_huge(*pmd) 6362306a36Sopenharmony_ci || pmd_hugewillfault(*pmd))) { 6462306a36Sopenharmony_ci spin_unlock(ptl); 6562306a36Sopenharmony_ci return 0; 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci *ptep = NULL; 6962306a36Sopenharmony_ci *ptlp = ptl; 7062306a36Sopenharmony_ci return 1; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci if (unlikely(pmd_bad(*pmd))) 7462306a36Sopenharmony_ci return 0; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl); 7762306a36Sopenharmony_ci if (unlikely(!pte)) 7862306a36Sopenharmony_ci return 0; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (unlikely(!pte_present(*pte) || !pte_young(*pte) || 8162306a36Sopenharmony_ci !pte_write(*pte) || !pte_dirty(*pte))) { 8262306a36Sopenharmony_ci pte_unmap_unlock(pte, ptl); 8362306a36Sopenharmony_ci return 0; 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci *ptep = pte; 8762306a36Sopenharmony_ci *ptlp = ptl; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return 1; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic unsigned long noinline 9362306a36Sopenharmony_ci__copy_to_user_memcpy(void __user *to, const void *from, unsigned long n) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci unsigned long ua_flags; 9662306a36Sopenharmony_ci int atomic; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* the mmap semaphore is taken only if not in an atomic context */ 9962306a36Sopenharmony_ci atomic = faulthandler_disabled(); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (!atomic) 10262306a36Sopenharmony_ci mmap_read_lock(current->mm); 10362306a36Sopenharmony_ci while (n) { 10462306a36Sopenharmony_ci pte_t *pte; 10562306a36Sopenharmony_ci spinlock_t *ptl; 10662306a36Sopenharmony_ci int tocopy; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci while (!pin_page_for_write(to, &pte, &ptl)) { 10962306a36Sopenharmony_ci if (!atomic) 11062306a36Sopenharmony_ci mmap_read_unlock(current->mm); 11162306a36Sopenharmony_ci if (__put_user(0, (char __user *)to)) 11262306a36Sopenharmony_ci goto out; 11362306a36Sopenharmony_ci if (!atomic) 11462306a36Sopenharmony_ci mmap_read_lock(current->mm); 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1; 11862306a36Sopenharmony_ci if (tocopy > n) 11962306a36Sopenharmony_ci tocopy = n; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci ua_flags = uaccess_save_and_enable(); 12262306a36Sopenharmony_ci __memcpy((void *)to, from, tocopy); 12362306a36Sopenharmony_ci uaccess_restore(ua_flags); 12462306a36Sopenharmony_ci to += tocopy; 12562306a36Sopenharmony_ci from += tocopy; 12662306a36Sopenharmony_ci n -= tocopy; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (pte) 12962306a36Sopenharmony_ci pte_unmap_unlock(pte, ptl); 13062306a36Sopenharmony_ci else 13162306a36Sopenharmony_ci spin_unlock(ptl); 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci if (!atomic) 13462306a36Sopenharmony_ci mmap_read_unlock(current->mm); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ciout: 13762306a36Sopenharmony_ci return n; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ciunsigned long 14162306a36Sopenharmony_ciarm_copy_to_user(void __user *to, const void *from, unsigned long n) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci /* 14462306a36Sopenharmony_ci * This test is stubbed out of the main function above to keep 14562306a36Sopenharmony_ci * the overhead for small copies low by avoiding a large 14662306a36Sopenharmony_ci * register dump on the stack just to reload them right away. 14762306a36Sopenharmony_ci * With frame pointer disabled, tail call optimization kicks in 14862306a36Sopenharmony_ci * as well making this test almost invisible. 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_ci if (n < 64) { 15162306a36Sopenharmony_ci unsigned long ua_flags = uaccess_save_and_enable(); 15262306a36Sopenharmony_ci n = __copy_to_user_std(to, from, n); 15362306a36Sopenharmony_ci uaccess_restore(ua_flags); 15462306a36Sopenharmony_ci } else { 15562306a36Sopenharmony_ci n = __copy_to_user_memcpy(uaccess_mask_range_ptr(to, n), 15662306a36Sopenharmony_ci from, n); 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci return n; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic unsigned long noinline 16262306a36Sopenharmony_ci__clear_user_memset(void __user *addr, unsigned long n) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci unsigned long ua_flags; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci mmap_read_lock(current->mm); 16762306a36Sopenharmony_ci while (n) { 16862306a36Sopenharmony_ci pte_t *pte; 16962306a36Sopenharmony_ci spinlock_t *ptl; 17062306a36Sopenharmony_ci int tocopy; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci while (!pin_page_for_write(addr, &pte, &ptl)) { 17362306a36Sopenharmony_ci mmap_read_unlock(current->mm); 17462306a36Sopenharmony_ci if (__put_user(0, (char __user *)addr)) 17562306a36Sopenharmony_ci goto out; 17662306a36Sopenharmony_ci mmap_read_lock(current->mm); 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1; 18062306a36Sopenharmony_ci if (tocopy > n) 18162306a36Sopenharmony_ci tocopy = n; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci ua_flags = uaccess_save_and_enable(); 18462306a36Sopenharmony_ci __memset((void *)addr, 0, tocopy); 18562306a36Sopenharmony_ci uaccess_restore(ua_flags); 18662306a36Sopenharmony_ci addr += tocopy; 18762306a36Sopenharmony_ci n -= tocopy; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (pte) 19062306a36Sopenharmony_ci pte_unmap_unlock(pte, ptl); 19162306a36Sopenharmony_ci else 19262306a36Sopenharmony_ci spin_unlock(ptl); 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci mmap_read_unlock(current->mm); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ciout: 19762306a36Sopenharmony_ci return n; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ciunsigned long arm_clear_user(void __user *addr, unsigned long n) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci /* See rational for this in __copy_to_user() above. */ 20362306a36Sopenharmony_ci if (n < 64) { 20462306a36Sopenharmony_ci unsigned long ua_flags = uaccess_save_and_enable(); 20562306a36Sopenharmony_ci n = __clear_user_std(addr, n); 20662306a36Sopenharmony_ci uaccess_restore(ua_flags); 20762306a36Sopenharmony_ci } else { 20862306a36Sopenharmony_ci n = __clear_user_memset(addr, n); 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci return n; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci#if 0 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci/* 21662306a36Sopenharmony_ci * This code is disabled by default, but kept around in case the chosen 21762306a36Sopenharmony_ci * thresholds need to be revalidated. Some overhead (small but still) 21862306a36Sopenharmony_ci * would be implied by a runtime determined variable threshold, and 21962306a36Sopenharmony_ci * so far the measurement on concerned targets didn't show a worthwhile 22062306a36Sopenharmony_ci * variation. 22162306a36Sopenharmony_ci * 22262306a36Sopenharmony_ci * Note that a fairly precise sched_clock() implementation is needed 22362306a36Sopenharmony_ci * for results to make some sense. 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci#include <linux/vmalloc.h> 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic int __init test_size_treshold(void) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct page *src_page, *dst_page; 23162306a36Sopenharmony_ci void *user_ptr, *kernel_ptr; 23262306a36Sopenharmony_ci unsigned long long t0, t1, t2; 23362306a36Sopenharmony_ci int size, ret; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci ret = -ENOMEM; 23662306a36Sopenharmony_ci src_page = alloc_page(GFP_KERNEL); 23762306a36Sopenharmony_ci if (!src_page) 23862306a36Sopenharmony_ci goto no_src; 23962306a36Sopenharmony_ci dst_page = alloc_page(GFP_KERNEL); 24062306a36Sopenharmony_ci if (!dst_page) 24162306a36Sopenharmony_ci goto no_dst; 24262306a36Sopenharmony_ci kernel_ptr = page_address(src_page); 24362306a36Sopenharmony_ci user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__PAGE_COPY)); 24462306a36Sopenharmony_ci if (!user_ptr) 24562306a36Sopenharmony_ci goto no_vmap; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci /* warm up the src page dcache */ 24862306a36Sopenharmony_ci ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci for (size = PAGE_SIZE; size >= 4; size /= 2) { 25162306a36Sopenharmony_ci t0 = sched_clock(); 25262306a36Sopenharmony_ci ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size); 25362306a36Sopenharmony_ci t1 = sched_clock(); 25462306a36Sopenharmony_ci ret |= __copy_to_user_std(user_ptr, kernel_ptr, size); 25562306a36Sopenharmony_ci t2 = sched_clock(); 25662306a36Sopenharmony_ci printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1); 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci for (size = PAGE_SIZE; size >= 4; size /= 2) { 26062306a36Sopenharmony_ci t0 = sched_clock(); 26162306a36Sopenharmony_ci ret |= __clear_user_memset(user_ptr, size); 26262306a36Sopenharmony_ci t1 = sched_clock(); 26362306a36Sopenharmony_ci ret |= __clear_user_std(user_ptr, size); 26462306a36Sopenharmony_ci t2 = sched_clock(); 26562306a36Sopenharmony_ci printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1); 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (ret) 26962306a36Sopenharmony_ci ret = -EFAULT; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci vunmap(user_ptr); 27262306a36Sopenharmony_cino_vmap: 27362306a36Sopenharmony_ci put_page(dst_page); 27462306a36Sopenharmony_cino_dst: 27562306a36Sopenharmony_ci put_page(src_page); 27662306a36Sopenharmony_cino_src: 27762306a36Sopenharmony_ci return ret; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cisubsys_initcall(test_size_treshold); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci#endif 283